Compare commits
245 Commits
1.1.4.7
...
1.1.5-pre7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
935c53e452 | ||
|
|
dd0ccb327b | ||
|
|
919134759b | ||
|
|
c1d42b498a | ||
|
|
a7e67796f1 | ||
|
|
6692c9e851 | ||
|
|
ace949aaa0 | ||
|
|
fbd9687432 | ||
|
|
460a8262c1 | ||
|
|
c8de503fae | ||
|
|
a60cd51ff4 | ||
|
|
aad980ce23 | ||
|
|
e7cda7b9fa | ||
|
|
1d368b7a8b | ||
|
|
725d7055bf | ||
|
|
1fb798db4e | ||
|
|
8e1d5e0dd5 | ||
|
|
2d9a1310b9 | ||
|
|
588ec7babd | ||
|
|
2be13e7283 | ||
|
|
d5d95671ff | ||
|
|
a0eccda6ff | ||
|
|
ec82c86210 | ||
|
|
de03bef226 | ||
|
|
0f8166620e | ||
|
|
76c2de4394 | ||
|
|
0d38ded981 | ||
|
|
646888c06f | ||
|
|
332f6f1bb4 | ||
|
|
aaab5371b2 | ||
|
|
ad931d7ea2 | ||
|
|
377e430d74 | ||
|
|
a797467606 | ||
|
|
5ee83d902d | ||
|
|
27ae296b28 | ||
|
|
e589f27195 | ||
|
|
c89d6a5a59 | ||
|
|
861365930d | ||
|
|
0d4d92a202 | ||
|
|
4c6ad0e385 | ||
|
|
ad45e995e2 | ||
|
|
50a035a479 | ||
|
|
c0dbd6cbb2 | ||
|
|
686af4a330 | ||
|
|
46aad06e34 | ||
|
|
3921b2304d | ||
|
|
bca5b0419c | ||
|
|
9754b061dd | ||
|
|
407b31c5c1 | ||
|
|
37b1228552 | ||
|
|
0acd9ca767 | ||
|
|
8f3c9f029c | ||
|
|
9310732343 | ||
|
|
e767e506f3 | ||
|
|
ef3a612338 | ||
|
|
d66a42a0aa | ||
|
|
0f06de0047 | ||
|
|
963181fef2 | ||
|
|
ffd4f9ee73 | ||
|
|
976622df89 | ||
|
|
13c220338c | ||
|
|
1291dc77c8 | ||
|
|
08e5477e74 | ||
|
|
c4c6a2243e | ||
|
|
58791e3e91 | ||
|
|
d5bb4bc149 | ||
|
|
3d1199363b | ||
|
|
f225fa33e1 | ||
|
|
e85c8b3dde | ||
|
|
737be8dcac | ||
|
|
77dd939172 | ||
|
|
0a5965a423 | ||
|
|
a53be6814c | ||
|
|
415b8e9da3 | ||
|
|
f034c24d13 | ||
|
|
1ac93d6269 | ||
|
|
906c8f7999 | ||
|
|
c904a5ded8 | ||
|
|
0c9486f6b4 | ||
|
|
576740a502 | ||
|
|
b3f9f43b57 | ||
|
|
e7424bcc66 | ||
|
|
209ec70ea9 | ||
|
|
3b4e251034 | ||
|
|
86beb879a2 | ||
|
|
321d434141 | ||
|
|
b9d17e27b1 | ||
|
|
2f6f6da6c0 | ||
|
|
c3d3fa67f7 | ||
|
|
032dfd69be | ||
|
|
e9dc154642 | ||
|
|
b43840b636 | ||
|
|
1a9d8e35ba | ||
|
|
ccb61415f5 | ||
|
|
08944241bb | ||
|
|
63030147ea | ||
|
|
8ff71c44ca | ||
|
|
4eaf16f500 | ||
|
|
1a9c8a62f2 | ||
|
|
4256c2b023 | ||
|
|
bbcf0dec1b | ||
|
|
da52cac2c6 | ||
|
|
e8a32a6149 | ||
|
|
a71a7b66f8 | ||
|
|
9808f50816 | ||
|
|
cf86bb7e13 | ||
|
|
ff065254ae | ||
|
|
39b4c1a59b | ||
|
|
28f10e0a4b | ||
|
|
12c0ed5baf | ||
|
|
23272d285b | ||
|
|
67b4ed65ab | ||
|
|
7524b3d168 | ||
|
|
340a933e70 | ||
|
|
488ca29fc1 | ||
|
|
cc00b2cc39 | ||
|
|
287cea4d6c | ||
|
|
39e556891a | ||
|
|
0ae4157384 | ||
|
|
6e1ceb1277 | ||
|
|
71a170deb5 | ||
|
|
9482a706da | ||
|
|
0804484a49 | ||
|
|
cdb9bb3dbc | ||
|
|
6ca0de96f4 | ||
|
|
d908f58528 | ||
|
|
1368733a24 | ||
|
|
32e71dbf65 | ||
|
|
c9ce1af2c6 | ||
|
|
416f9e6a8d | ||
|
|
3ae3955f53 | ||
|
|
464f008023 | ||
|
|
52498b3e34 | ||
|
|
57c57b02a5 | ||
|
|
b8c6868043 | ||
|
|
8200fbf512 | ||
|
|
8650c96b7b | ||
|
|
15fe7787ba | ||
|
|
d83076cb07 | ||
|
|
8b3b4c28a5 | ||
|
|
740c001e2f | ||
|
|
096b057f81 | ||
|
|
a161fa5e58 | ||
|
|
bebf34db23 | ||
|
|
b95061434a | ||
|
|
f2a05bb970 | ||
|
|
6c361a047b | ||
|
|
3fb9e22378 | ||
|
|
b2fb4c9afe | ||
|
|
0862c0fc87 | ||
|
|
77ec78e3fe | ||
|
|
fb59c208e3 | ||
|
|
112a06f92a | ||
|
|
c10c4a6f89 | ||
|
|
669c807b23 | ||
|
|
c9de79532a | ||
|
|
32ce2b87db | ||
|
|
4cfcf18bc9 | ||
|
|
14ae61f891 | ||
|
|
a2d5ecc51e | ||
|
|
84f972a3ab | ||
|
|
5249ceccdb | ||
|
|
5035495043 | ||
|
|
25483d71e9 | ||
|
|
c3fa976b26 | ||
|
|
43beb518f4 | ||
|
|
11edabb890 | ||
|
|
019cd9fda0 | ||
|
|
9d747c8e2c | ||
|
|
4cf1c25b36 | ||
|
|
6c6ed46aea | ||
|
|
e1473a453e | ||
|
|
9f6ef0281a | ||
|
|
84d5a24bc3 | ||
|
|
ed8c39aa76 | ||
|
|
23d235b8f4 | ||
|
|
8bea09b78a | ||
|
|
897fda875a | ||
|
|
510bfe01be | ||
|
|
f6ca007815 | ||
|
|
35b34cb2d4 | ||
|
|
5197cca69c | ||
|
|
e5f0742bf6 | ||
|
|
88d207cc24 | ||
|
|
931fcb6f8f | ||
|
|
e4a960ecf9 | ||
|
|
e44419e088 | ||
|
|
16f577f3fd | ||
|
|
a65edab7d1 | ||
|
|
c0bbf8400a | ||
|
|
1dc2da68ac | ||
|
|
3d49529272 | ||
|
|
41768656b4 | ||
|
|
c7e7b3f9c5 | ||
|
|
e0b0a98f0f | ||
|
|
ca0eb1716f | ||
|
|
06d8296939 | ||
|
|
322885f284 | ||
|
|
4553b86cb4 | ||
|
|
904756b6ea | ||
|
|
2bfa1bb6c2 | ||
|
|
8439a3d85c | ||
|
|
454d6b9de1 | ||
|
|
44c7c44a27 | ||
|
|
40e5e2f372 | ||
|
|
138739781c | ||
|
|
355d897ef0 | ||
|
|
a06aef2b25 | ||
|
|
6ef9a24ed1 | ||
|
|
4df2bb0073 | ||
|
|
f93753ccfd | ||
|
|
52373dc540 | ||
|
|
203a997583 | ||
|
|
b22a406471 | ||
|
|
a441759eb6 | ||
|
|
9057401b16 | ||
|
|
6d0017c256 | ||
|
|
12b27b1d8d | ||
|
|
884bb53d6f | ||
|
|
aa356b5376 | ||
|
|
2aa9b46433 | ||
|
|
42f5a42dd9 | ||
|
|
74f0fb471c | ||
|
|
c31e772a63 | ||
|
|
32f6d97256 | ||
|
|
a28db0dd98 | ||
|
|
aba9493ae0 | ||
|
|
4973176868 | ||
|
|
a000e2262c | ||
|
|
a5715868b3 | ||
|
|
a928e48159 | ||
|
|
16c152d306 | ||
|
|
5747dee03d | ||
|
|
06c545acd4 | ||
|
|
54c3c314e1 | ||
|
|
11c4fae547 | ||
|
|
8f87d248a1 | ||
|
|
ec37af5900 | ||
|
|
1b213793d4 | ||
|
|
aaa8998cb1 | ||
|
|
94760a4136 | ||
|
|
bdbd6cd377 | ||
|
|
d69d81912d | ||
|
|
198a38b103 | ||
|
|
750e67d835 |
16
.github/ISSUE_TEMPLATE/bug-反馈.yml
vendored
@@ -9,10 +9,24 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 检查清单
|
label: 检查清单
|
||||||
options:
|
options:
|
||||||
- label: 之前没有人提交过类似或相同的 bug report。
|
- label: 搜索了 [历史 issue](https://github.com/bggRGjQaUbCoE/PiliPlus/issues?q=is%3Aissue) ,并未发现相同问题
|
||||||
required: true
|
required: true
|
||||||
- label: 正在使用最新版本。
|
- label: 正在使用最新版本。
|
||||||
required: true
|
required: true
|
||||||
|
- label: 已排除网络问题
|
||||||
|
required: true
|
||||||
|
- label: 已排除账号问题
|
||||||
|
required: true
|
||||||
|
- label: 已排除设置问题
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: assign
|
||||||
|
attributes:
|
||||||
|
label: Assign
|
||||||
|
options:
|
||||||
|
- label: self-assign
|
||||||
|
required: false
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
12
.github/ISSUE_TEMPLATE/功能请求.yml
vendored
@@ -9,10 +9,20 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: 检查清单
|
label: 检查清单
|
||||||
options:
|
options:
|
||||||
- label: 之前没有人提交过类似或相同的功能请求。
|
- label: 搜索了 [历史 issue](https://github.com/bggRGjQaUbCoE/PiliPlus/issues?q=is%3Aissue) ,并未发现相同功能请求
|
||||||
required: true
|
required: true
|
||||||
- label: 正在使用最新版本。
|
- label: 正在使用最新版本。
|
||||||
required: true
|
required: true
|
||||||
|
- label: 设置中未搜索到该功能
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: assign
|
||||||
|
attributes:
|
||||||
|
label: Assign
|
||||||
|
options:
|
||||||
|
- label: self-assign
|
||||||
|
required: false
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: desc
|
id: desc
|
||||||
|
|||||||
92
.github/workflows/android.yml
vendored
@@ -1,92 +0,0 @@
|
|||||||
name: Android Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- ready_for_review
|
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
android:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 代码迁出
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: 构建Java环境
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: "zulu"
|
|
||||||
java-version: "17"
|
|
||||||
|
|
||||||
- name: 检查缓存
|
|
||||||
uses: actions/cache@v4
|
|
||||||
id: cache-flutter
|
|
||||||
with:
|
|
||||||
path: /root/flutter-sdk # Flutter SDK 的路径
|
|
||||||
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
|
|
||||||
|
|
||||||
- name: 安装Flutter
|
|
||||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version-file: pubspec.yaml
|
|
||||||
|
|
||||||
- name: apply bottom sheet patch
|
|
||||||
working-directory: ${{ env.FLUTTER_ROOT }}
|
|
||||||
run: |
|
|
||||||
git apply $GITHUB_WORKSPACE/lib/scripts/bottom_sheet_patch.diff
|
|
||||||
git config --global user.name "github-actions[bot]"
|
|
||||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
|
||||||
git add .
|
|
||||||
git commit -m "bottom sheet patch"
|
|
||||||
|
|
||||||
- name: 下载项目依赖
|
|
||||||
run: flutter pub get
|
|
||||||
|
|
||||||
- name: Write key
|
|
||||||
if: github.event_name != 'pull_request'
|
|
||||||
run: |
|
|
||||||
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
|
|
||||||
echo "${{ secrets.SIGN_KEYSTORE_BASE64 }}" | base64 --decode > android/app/key.jks
|
|
||||||
echo storeFile='key.jks' >> android/key.properties
|
|
||||||
echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' >> android/key.properties
|
|
||||||
echo keyAlias='${{ secrets.KEY_ALIAS }}' >> android/key.properties
|
|
||||||
echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> android/key.properties
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: flutter build apk
|
|
||||||
run: |
|
|
||||||
chmod +x lib/scripts/build.dart
|
|
||||||
dart lib/scripts/build.dart "true"
|
|
||||||
flutter build apk --release --split-per-abi
|
|
||||||
|
|
||||||
- name: 上传
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: app-arm64-v8a
|
|
||||||
path: |
|
|
||||||
build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
|
|
||||||
|
|
||||||
- name: 上传
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: app-armeabi-v7a
|
|
||||||
path: |
|
|
||||||
build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
|
|
||||||
|
|
||||||
- name: 上传
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: app-x86_64
|
|
||||||
path: |
|
|
||||||
build/app/outputs/flutter-apk/app-x86_64-release.apk
|
|
||||||
168
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
- ready_for_review
|
||||||
|
paths-ignore:
|
||||||
|
- "**.md"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
build_android:
|
||||||
|
description: "Build Android"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
build_ios:
|
||||||
|
description: "Build iOS"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
build_mac:
|
||||||
|
description: "Build Mac"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
build_win_x64:
|
||||||
|
description: "Build Win-x64"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
build_linux_x64:
|
||||||
|
description: "Build Linux-x64"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
tag:
|
||||||
|
description: "tag"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
android:
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_android == 'true' }}
|
||||||
|
name: Release Android
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions: write-all
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 代码迁出
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 构建Java环境
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: "zulu"
|
||||||
|
java-version: "17"
|
||||||
|
cache: "gradle"
|
||||||
|
cache-dependency-path: |
|
||||||
|
android/*.gradle*
|
||||||
|
android/**/gradle-wrapper.properties
|
||||||
|
|
||||||
|
- name: 安装Flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
id: flutter-action
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
flutter-version-file: pubspec.yaml
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: apply bottom sheet patch
|
||||||
|
working-directory: ${{ env.FLUTTER_ROOT }}
|
||||||
|
run: git apply $GITHUB_WORKSPACE/lib/scripts/bottom_sheet_patch.diff
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Write key
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
|
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
|
||||||
|
echo "${{ secrets.SIGN_KEYSTORE_BASE64 }}" | base64 --decode > android/app/key.jks
|
||||||
|
echo storeFile='key.jks' >> android/key.properties
|
||||||
|
echo storePassword='${{ secrets.KEYSTORE_PASSWORD }}' >> android/key.properties
|
||||||
|
echo keyAlias='${{ secrets.KEY_ALIAS }}' >> android/key.properties
|
||||||
|
echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> android/key.properties
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Set and Extract version
|
||||||
|
shell: pwsh
|
||||||
|
run: lib/scripts/build.ps1 android
|
||||||
|
|
||||||
|
- name: flutter build apk
|
||||||
|
run: flutter build apk --release --split-per-abi --dart-define-from-file=pili_release.json --pub
|
||||||
|
|
||||||
|
- name: rename
|
||||||
|
run: |
|
||||||
|
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
|
||||||
|
abi=$(echo "$file" | sed -E 's|.*app-(.*)-release\.apk|\1|')
|
||||||
|
mv "$file" "PiliPlus_android_${{ env.version }}_${abi}.apk"
|
||||||
|
done
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag != '' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.inputs.tag }}
|
||||||
|
name: ${{ github.event.inputs.tag }}
|
||||||
|
files: |
|
||||||
|
PiliPlus_android_*.apk
|
||||||
|
|
||||||
|
- name: 上传
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Android_arm64-v8a
|
||||||
|
path: |
|
||||||
|
PiliPlus_android_*_arm64-v8a.apk
|
||||||
|
|
||||||
|
- name: 上传
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Android_armeabi-v7a
|
||||||
|
path: |
|
||||||
|
PiliPlus_android_*_armeabi-v7a.apk
|
||||||
|
|
||||||
|
- name: 上传
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Android_x86_64
|
||||||
|
path: |
|
||||||
|
PiliPlus_android_*_x86_64.apk
|
||||||
|
|
||||||
|
ios:
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_ios == 'true' }}
|
||||||
|
uses: ./.github/workflows/ios.yml
|
||||||
|
permissions: write-all
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
||||||
|
|
||||||
|
mac:
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_mac == 'true' }}
|
||||||
|
uses: ./.github/workflows/mac.yml
|
||||||
|
permissions: write-all
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
||||||
|
|
||||||
|
win_x64:
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_win_x64 == 'true' }}
|
||||||
|
uses: ./.github/workflows/win_x64.yml
|
||||||
|
permissions: write-all
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
||||||
|
|
||||||
|
linux_x64:
|
||||||
|
if: ${{ github.event_name == 'pull_request' || github.event.inputs.build_linux_x64 == 'true' }}
|
||||||
|
uses: ./.github/workflows/linux_x64.yml
|
||||||
|
permissions: write-all
|
||||||
|
with:
|
||||||
|
tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
|
||||||
43
.github/workflows/ios.yml
vendored
@@ -1,19 +1,14 @@
|
|||||||
name: Build for iOS
|
name: Build for iOS
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_call:
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- ready_for_review
|
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
inputs:
|
||||||
branch:
|
tag:
|
||||||
|
description: "tag"
|
||||||
required: false
|
required: false
|
||||||
default: "main"
|
default: ""
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-macos-app:
|
build-macos-app:
|
||||||
@@ -21,9 +16,8 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup flutter
|
- name: Setup flutter
|
||||||
@@ -32,16 +26,27 @@ jobs:
|
|||||||
channel: stable
|
channel: stable
|
||||||
flutter-version-file: pubspec.yaml
|
flutter-version-file: pubspec.yaml
|
||||||
|
|
||||||
|
- name: Set and Extract version
|
||||||
|
shell: pwsh
|
||||||
|
run: lib/scripts/build.ps1
|
||||||
|
|
||||||
- name: Build iOS
|
- name: Build iOS
|
||||||
run: |
|
run: |
|
||||||
chmod +x lib/scripts/build.dart
|
flutter build ios --release --no-codesign --dart-define-from-file=pili_release.json
|
||||||
dart lib/scripts/build.dart
|
|
||||||
flutter build ios --release --no-codesign
|
|
||||||
ln -sf ./build/ios/iphoneos Payload
|
ln -sf ./build/ios/iphoneos Payload
|
||||||
zip -r9 ios-release-no-sign.ipa Payload/runner.app
|
zip -r9 PiliPlus_ios_${{env.version}}.ipa Payload/runner.app
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.inputs.tag }}
|
||||||
|
name: ${{ github.event.inputs.tag }}
|
||||||
|
files: |
|
||||||
|
PiliPlus_ios_*.ipa
|
||||||
|
|
||||||
- name: Upload ios release
|
- name: Upload ios release
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ios-release
|
name: iOS-release
|
||||||
path: ios-release-no-sign.ipa
|
path: PiliPlus_ios_*.ipa
|
||||||
|
|||||||
78
.github/workflows/linux.yml
vendored
@@ -1,78 +0,0 @@
|
|||||||
name: Build for Linux
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- ready_for_review
|
|
||||||
paths-ignore:
|
|
||||||
- "**.md"
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
required: false
|
|
||||||
default: "main"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-linux-app:
|
|
||||||
name: Release Linux
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y clang cmake libgtk-3-dev ninja-build libayatana-appindicator3-dev unzip webkit2gtk-4.1 libasound2-dev
|
|
||||||
sudo apt-get install -y gcc g++ autoconf automake debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev \
|
|
||||||
libarchive-dev libbluray-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev libdrm-dev \
|
|
||||||
libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \
|
|
||||||
libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \
|
|
||||||
libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \
|
|
||||||
libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \
|
|
||||||
libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \
|
|
||||||
libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \
|
|
||||||
libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev \
|
|
||||||
libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \
|
|
||||||
linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \
|
|
||||||
x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \
|
|
||||||
unixodbc-dev libpq-dev libxxhash-dev libaom-dev \
|
|
||||||
libgtk-3-0 libblkid1 liblzma5 libmpv-dev
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Setup flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version-file: pubspec.yaml
|
|
||||||
|
|
||||||
- name: Get Flutter dependencies
|
|
||||||
run: flutter pub get
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set and Extract version
|
|
||||||
run: |
|
|
||||||
dart lib/scripts/build.dart
|
|
||||||
VERSION=$(cat pubspec.yaml | grep 'version:' | sed 's/version: //g' | tr -d '[:space:]')
|
|
||||||
echo "version=$VERSION" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
#TODO: deb and rpm packages need to be build
|
|
||||||
- name: Build Linux
|
|
||||||
run: flutter build linux --release -v
|
|
||||||
|
|
||||||
- name: Package linux build output
|
|
||||||
run: tar -zcvf PiliPlus_linux_${{ env.version }}_amd64.tar.gz -C build/linux/x64/release/bundle .
|
|
||||||
|
|
||||||
- name: Upload linux outputs
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: linux_outputs
|
|
||||||
path: |
|
|
||||||
PiliPlus_linux_*.tar.gz
|
|
||||||
260
.github/workflows/linux_x64.yml
vendored
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
name: Build for Linux x64
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "tag"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-linux-app:
|
||||||
|
name: Release Linux x64
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang cmake libgtk-3-dev ninja-build libayatana-appindicator3-dev unzip webkit2gtk-4.1 libasound2-dev rpm patchelf
|
||||||
|
sudo apt-get install -y gcc g++ autoconf automake debhelper glslang-dev ladspa-sdk xutils-dev libasound2-dev \
|
||||||
|
libarchive-dev libbluray-dev libbs2b-dev libcaca-dev libcdio-paranoia-dev libdrm-dev \
|
||||||
|
libdav1d-dev libdvdnav-dev libegl1-mesa-dev libepoxy-dev libfontconfig-dev libfreetype6-dev \
|
||||||
|
libfribidi-dev libgl1-mesa-dev libgbm-dev libgme-dev libgsm1-dev libharfbuzz-dev libjpeg-dev \
|
||||||
|
libbrotli-dev liblcms2-dev libmodplug-dev libmp3lame-dev libopenal-dev \
|
||||||
|
libopus-dev libopencore-amrnb-dev libopencore-amrwb-dev libpulse-dev librtmp-dev \
|
||||||
|
libsdl2-dev libsixel-dev libssh-dev libsoxr-dev libspeex-dev libtool \
|
||||||
|
libv4l-dev libva-dev libvdpau-dev libvorbis-dev libvo-amrwbenc-dev \
|
||||||
|
libunwind-dev libvpx-dev libwayland-dev libx11-dev libxext-dev \
|
||||||
|
libxkbcommon-dev libxrandr-dev libxss-dev libxv-dev libxvidcore-dev \
|
||||||
|
linux-libc-dev nasm ninja-build pkg-config python3 python3-docutils wayland-protocols \
|
||||||
|
x11proto-core-dev zlib1g-dev libfdk-aac-dev libtheora-dev libwebp-dev \
|
||||||
|
unixodbc-dev libpq-dev libxxhash-dev libaom-dev \
|
||||||
|
libgtk-3-0 libblkid1 liblzma5 libmpv-dev
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Setup flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
flutter-version-file: pubspec.yaml
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Set and Extract version
|
||||||
|
shell: pwsh
|
||||||
|
run: lib/scripts/build.ps1
|
||||||
|
|
||||||
|
#TODO: deb and rpm packages need to be build
|
||||||
|
- name: Build Linux
|
||||||
|
run: flutter build linux --release -v --pub --dart-define-from-file=pili_release.json
|
||||||
|
|
||||||
|
- name: Package .tar.gz
|
||||||
|
run: tar -zcvf PiliPlus_linux_${{ env.version }}_amd64.tar.gz -C build/linux/x64/release/bundle .
|
||||||
|
|
||||||
|
- name: Packege deb
|
||||||
|
run: |
|
||||||
|
printf "建立构建目录...\n"
|
||||||
|
mkdir "PiliPlus_linux_${{ env.version }}_amd64"
|
||||||
|
pushd "PiliPlus_linux_${{ env.version }}_amd64"
|
||||||
|
mkdir -p opt/PiliPlus
|
||||||
|
mkdir -p usr/share/applications
|
||||||
|
mkdir -p usr/share/icons/hicolor/512x512/apps
|
||||||
|
|
||||||
|
printf "复制文件...\n"
|
||||||
|
cp -r ../build/linux/x64/release/bundle/* opt/PiliPlus
|
||||||
|
cp -r ../assets/linux/DEBIAN .
|
||||||
|
cp ../assets/linux/piliplus.desktop usr/share/applications
|
||||||
|
cp ../assets/images/logo/logo.png usr/share/icons/hicolor/512x512/apps/piliplus.png
|
||||||
|
|
||||||
|
printf "修改控制文件...\n"
|
||||||
|
# 替换版本号
|
||||||
|
sed -i "2s/version_need_change/${{ env.version }}/g" DEBIAN/control
|
||||||
|
# 计算安装大小并替换
|
||||||
|
SIZE_KB=$(du -s -b --apparent-size . | awk '{print int($1)}')
|
||||||
|
SIZE_KB=$(($SIZE_KB - $(du -s -b --apparent-size DEBIAN | awk '{print int($1)}')))
|
||||||
|
SIZE_KB=$(echo $SIZE_KB | awk '{print int($1/1024 + 0.999)}')
|
||||||
|
printf "\t安装大小: %s KB\n" "$SIZE_KB"
|
||||||
|
sed -i "9s/size_need_change/${SIZE_KB}/g" DEBIAN/control
|
||||||
|
|
||||||
|
printf "生成并写入 md5sums ...\n"
|
||||||
|
md5sum opt/PiliPlus/piliplus >> DEBIAN/md5sums
|
||||||
|
md5sum opt/PiliPlus/lib/* >> DEBIAN/md5sums
|
||||||
|
md5sum opt/PiliPlus/data/icudtl.dat >> DEBIAN/md5sums
|
||||||
|
|
||||||
|
printf "设置权限...\n"
|
||||||
|
chmod 0644 DEBIAN/control
|
||||||
|
chmod 0644 DEBIAN/md5sums
|
||||||
|
chmod 0755 DEBIAN/postinst
|
||||||
|
chmod 0755 DEBIAN/postrm
|
||||||
|
chmod 0755 DEBIAN/prerm
|
||||||
|
|
||||||
|
printf "打包 deb 文件...\n"
|
||||||
|
popd
|
||||||
|
dpkg-deb --build --verbose --root-owner-group "PiliPlus_linux_${{ env.version }}_amd64"
|
||||||
|
printf "完成: PiliPlus_linux_%s_amd64.deb\n" "${{ env.version }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Packege rpm
|
||||||
|
run: |
|
||||||
|
printf "建立 RPM 构建目录...\n"
|
||||||
|
RPM_BUILD_ROOT="$PWD/rpm_build"
|
||||||
|
mkdir -p "$RPM_BUILD_ROOT/BUILD" "$RPM_BUILD_ROOT/RPMS" "$RPM_BUILD_ROOT/SOURCES" "$RPM_BUILD_ROOT/SPECS" "$RPM_BUILD_ROOT/SRPMS"
|
||||||
|
|
||||||
|
printf "准备源码归档(仅包含运行时与元数据)...\n"
|
||||||
|
DATE="$(date '+%a %b %d %Y')"
|
||||||
|
SRC_DIR="$PWD/piliplus-${{ env.version }}"
|
||||||
|
mkdir -p "$SRC_DIR/bundle" "$SRC_DIR/assets"
|
||||||
|
cp -r build/linux/x64/release/bundle/* "$SRC_DIR/bundle/"
|
||||||
|
cp assets/linux/piliplus.desktop "$SRC_DIR/assets/piliplus.desktop"
|
||||||
|
cp assets/images/logo/logo.png "$SRC_DIR/assets/piliplus.png"
|
||||||
|
tar -zcvf "$RPM_BUILD_ROOT/SOURCES/piliplus-${{ env.version }}.tar.gz" -C "$PWD" "piliplus-${{ env.version }}"
|
||||||
|
|
||||||
|
printf "生成 spec 文件...\n"
|
||||||
|
cat > "$RPM_BUILD_ROOT/SPECS/piliplus.spec" <<EOF
|
||||||
|
Name: piliplus
|
||||||
|
Version: ${{ env.version }}
|
||||||
|
Release: 1%{?dist}
|
||||||
|
Summary: PiliPlus Linux Version
|
||||||
|
License: GPL-3.0
|
||||||
|
Source0: piliplus-${{ env.version }}.tar.gz
|
||||||
|
Requires: desktop-file-utils, hicolor-icon-theme
|
||||||
|
|
||||||
|
%description
|
||||||
|
使用 Flutter 开发的 BiliBili 第三方客户端
|
||||||
|
|
||||||
|
%prep
|
||||||
|
%setup -q -n piliplus-${{ env.version }}
|
||||||
|
|
||||||
|
%build
|
||||||
|
|
||||||
|
%install
|
||||||
|
mkdir -p %{buildroot}/opt/PiliPlus
|
||||||
|
cp -r bundle/* %{buildroot}/opt/PiliPlus/
|
||||||
|
|
||||||
|
# 二进制权限与命令行入口
|
||||||
|
chmod 755 %{buildroot}/opt/PiliPlus/piliplus
|
||||||
|
mkdir -p %{buildroot}/usr/bin
|
||||||
|
ln -sf /opt/PiliPlus/piliplus %{buildroot}/usr/bin/piliplus
|
||||||
|
|
||||||
|
# 桌面集成
|
||||||
|
mkdir -p %{buildroot}/usr/share/applications
|
||||||
|
install -m 644 assets/piliplus.desktop %{buildroot}/usr/share/applications/piliplus.desktop
|
||||||
|
|
||||||
|
mkdir -p %{buildroot}/usr/share/icons/hicolor/512x512/apps
|
||||||
|
install -m 644 assets/piliplus.png %{buildroot}/usr/share/icons/hicolor/512x512/apps/piliplus.png
|
||||||
|
|
||||||
|
%post
|
||||||
|
update-desktop-database -q || true
|
||||||
|
gtk-update-icon-cache -q -t -f %{_datadir}/icons/hicolor || true
|
||||||
|
|
||||||
|
%postun
|
||||||
|
update-desktop-database -q || true
|
||||||
|
gtk-update-icon-cache -q -t -f %{_datadir}/icons/hicolor || true
|
||||||
|
|
||||||
|
%files
|
||||||
|
/opt/PiliPlus
|
||||||
|
/usr/bin/piliplus
|
||||||
|
/usr/share/applications/piliplus.desktop
|
||||||
|
/usr/share/icons/hicolor/512x512/apps/piliplus.png
|
||||||
|
|
||||||
|
%changelog
|
||||||
|
* DATE - ${{ env.version }}-1
|
||||||
|
- Initial RPM release
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sed -i "s/DATE/${DATE}/g" "$RPM_BUILD_ROOT/SPECS/piliplus.spec"
|
||||||
|
|
||||||
|
printf "构建 RPM 包...\n"
|
||||||
|
rpmbuild --define "_topdir $RPM_BUILD_ROOT" -bb "$RPM_BUILD_ROOT/SPECS/piliplus.spec"
|
||||||
|
|
||||||
|
printf "移动生成的 RPM...\n"
|
||||||
|
find "$RPM_BUILD_ROOT/RPMS" -name "*.rpm" -exec mv {} "PiliPlus_linux_${{ env.version }}_amd64.rpm" \;
|
||||||
|
|
||||||
|
printf "完成: PiliPlus_linux_%s_amd64.rpm\n" "${{ env.version }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Package AppImage
|
||||||
|
run: |
|
||||||
|
printf "下载 appimagetool...\n"
|
||||||
|
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
|
||||||
|
chmod +x appimagetool-x86_64.AppImage
|
||||||
|
|
||||||
|
printf "建立 AppDir 目录结构...\n"
|
||||||
|
APPDIR="PiliPlus.AppDir"
|
||||||
|
mkdir -p "$APPDIR/usr/bin"
|
||||||
|
mkdir -p "$APPDIR/usr/lib"
|
||||||
|
mkdir -p "$APPDIR/usr/share/applications"
|
||||||
|
mkdir -p "$APPDIR/usr/share/icons/hicolor/512x512/apps"
|
||||||
|
|
||||||
|
printf "复制应用文件...\n"
|
||||||
|
cp -r build/linux/x64/release/bundle/* "$APPDIR/usr/bin/"
|
||||||
|
|
||||||
|
printf "复制桌面文件和图标...\n"
|
||||||
|
cp assets/linux/piliplus.desktop "$APPDIR/piliplus.desktop"
|
||||||
|
cp assets/linux/piliplus.desktop "$APPDIR/usr/share/applications/piliplus.desktop"
|
||||||
|
cp assets/images/logo/logo.png "$APPDIR/piliplus.png"
|
||||||
|
cp assets/images/logo/logo.png "$APPDIR/usr/share/icons/hicolor/512x512/apps/piliplus.png"
|
||||||
|
|
||||||
|
printf "创建 AppRun 启动脚本...\n"
|
||||||
|
cat > "$APPDIR/AppRun" <<'APPRUN_EOF'
|
||||||
|
#!/bin/bash
|
||||||
|
SELF=$(readlink -f "$0")
|
||||||
|
HERE=${SELF%/*}
|
||||||
|
export PATH="${HERE}/usr/bin:${PATH}"
|
||||||
|
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"
|
||||||
|
exec "${HERE}/usr/bin/piliplus" "$@"
|
||||||
|
APPRUN_EOF
|
||||||
|
chmod +x "$APPDIR/AppRun"
|
||||||
|
|
||||||
|
printf "修改桌面文件中的 Exec 路径...\n"
|
||||||
|
sed -i 's|Exec=piliplus|Exec=piliplus|g' "$APPDIR/piliplus.desktop"
|
||||||
|
sed -i 's|Icon=piliplus|Icon=piliplus|g' "$APPDIR/piliplus.desktop"
|
||||||
|
|
||||||
|
printf "打包 AppImage...\n"
|
||||||
|
ARCH=x86_64 ./appimagetool-x86_64.AppImage "$APPDIR" "PiliPlus_linux_${{ env.version }}_amd64.AppImage"
|
||||||
|
|
||||||
|
printf "完成: PiliPlus_linux_%s_amd64.AppImage\n" "${{ env.version }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.inputs.tag }}
|
||||||
|
name: ${{ github.event.inputs.tag }}
|
||||||
|
files: |
|
||||||
|
PiliPlus_linux_*.tar.gz
|
||||||
|
PiliPlus_linux_*.deb
|
||||||
|
PiliPlus_linux_*.rpm
|
||||||
|
PiliPlus_linux_*.AppImage
|
||||||
|
|
||||||
|
- name: Upload linux targz package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Linux_targz_amd64_packege
|
||||||
|
path: PiliPlus_linux_*.tar.gz
|
||||||
|
|
||||||
|
- name: Upload linux deb package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Linux_deb_amd64_package
|
||||||
|
path: PiliPlus_linux_*.deb
|
||||||
|
|
||||||
|
- name: Upload linux rpm package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Linux_rpm_amd64_package
|
||||||
|
path: PiliPlus_linux_*.rpm
|
||||||
|
|
||||||
|
- name: Upload linux AppImage package
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Linux_AppImage_amd64_package
|
||||||
|
path: PiliPlus_linux_*.AppImage
|
||||||
43
.github/workflows/mac.yml
vendored
@@ -1,19 +1,14 @@
|
|||||||
name: Build for Mac
|
name: Build for Mac
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_call:
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- ready_for_review
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
inputs:
|
||||||
branch:
|
tag:
|
||||||
|
description: "tag"
|
||||||
required: false
|
required: false
|
||||||
default: 'main'
|
default: ""
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-mac-app:
|
build-mac-app:
|
||||||
@@ -21,9 +16,8 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup flutter
|
- name: Setup flutter
|
||||||
@@ -32,12 +26,12 @@ jobs:
|
|||||||
channel: stable
|
channel: stable
|
||||||
flutter-version-file: pubspec.yaml
|
flutter-version-file: pubspec.yaml
|
||||||
|
|
||||||
|
- name: Set and Extract version
|
||||||
|
shell: pwsh
|
||||||
|
run: lib/scripts/build.ps1
|
||||||
|
|
||||||
- name: Build Mac
|
- name: Build Mac
|
||||||
run: |
|
run: flutter build macos --release --dart-define-from-file=pili_release.json
|
||||||
dart lib/scripts/build.dart
|
|
||||||
VERSION=$(cat pubspec.yaml | grep 'version:' | sed 's/version: //g' | tr -d '[:space:]')
|
|
||||||
echo "version=$VERSION" >> $GITHUB_ENV
|
|
||||||
flutter build macos --release
|
|
||||||
|
|
||||||
- name: Prepare Upload
|
- name: Prepare Upload
|
||||||
run: |
|
run: |
|
||||||
@@ -48,8 +42,17 @@ jobs:
|
|||||||
- name: Rename DMG
|
- name: Rename DMG
|
||||||
run: mv PiliPlus*.dmg PiliPlus_macos_${{ env.version }}.dmg
|
run: mv PiliPlus*.dmg PiliPlus_macos_${{ env.version }}.dmg
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.inputs.tag }}
|
||||||
|
name: ${{ github.event.inputs.tag }}
|
||||||
|
files: |
|
||||||
|
PiliPlus_macos_*.dmg
|
||||||
|
|
||||||
- name: Upload macos release
|
- name: Upload macos release
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: macos-release
|
name: macOS-release
|
||||||
path: PiliPlus*.dmg
|
path: PiliPlus_macos_*.dmg
|
||||||
|
|||||||
49
.github/workflows/win.yml
vendored
@@ -1,49 +0,0 @@
|
|||||||
name: Build for Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- synchronize
|
|
||||||
- reopened
|
|
||||||
- ready_for_review
|
|
||||||
paths-ignore:
|
|
||||||
- '**.md'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
required: false
|
|
||||||
default: 'main'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows-app:
|
|
||||||
name: Release Windows
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version-file: pubspec.yaml
|
|
||||||
|
|
||||||
- name: Build Windows
|
|
||||||
run: |
|
|
||||||
dart lib/scripts/build.dart
|
|
||||||
flutter build windows --release
|
|
||||||
|
|
||||||
- name: Prepare Upload
|
|
||||||
run: |
|
|
||||||
mkdir -p Release/PiliPlus-Win
|
|
||||||
mv build/windows/x64/runner/Release/* Release/PiliPlus-Win/
|
|
||||||
|
|
||||||
- name: Upload windows release
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: windows-release
|
|
||||||
path: Release
|
|
||||||
80
.github/workflows/win_x64.yml
vendored
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
name: Build for Windows x64
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: "tag"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
type: string
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows-app:
|
||||||
|
name: Release Windows x64
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: stable
|
||||||
|
flutter-version-file: pubspec.yaml
|
||||||
|
|
||||||
|
- name: Add fastforge and Inno Setup
|
||||||
|
run: |
|
||||||
|
dart pub global activate fastforge
|
||||||
|
choco install innosetup
|
||||||
|
|
||||||
|
- name: Add Chinese language file for Inno Setup
|
||||||
|
run: |
|
||||||
|
Copy-Item "windows/packaging/exe/ChineseSimplified.isl" "C:\Program Files (x86)\Inno Setup 6\Languages\ChineseSimplified.isl"
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Set and Extract version
|
||||||
|
shell: pwsh
|
||||||
|
run: lib/scripts/build.ps1
|
||||||
|
|
||||||
|
- name: Build Windows
|
||||||
|
run: |
|
||||||
|
fastforge package --platform windows --targets exe --flutter-build-args="dart-define-from-file=pili_release.json"
|
||||||
|
|
||||||
|
- name: Prepare Upload
|
||||||
|
run: |
|
||||||
|
mkdir -p Release/PiliPlus-Win
|
||||||
|
mkdir -p PiliPlus-Win-Setup
|
||||||
|
mv build/windows/x64/runner/Release/* Release/PiliPlus-Win/
|
||||||
|
mv dist/**/*.exe PiliPlus-Win-Setup/PiliPlus_windows_${{env.version}}_x64_setup.exe
|
||||||
|
|
||||||
|
- name: Compress
|
||||||
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
|
run: |
|
||||||
|
Compress-Archive -Path "Release/PiliPlus-Win" -DestinationPath "PiliPlus_windows_${{env.version}}_x64.zip"
|
||||||
|
shell: pwsh
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
if: ${{ github.event.inputs.tag != '' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ github.event.inputs.tag }}
|
||||||
|
name: ${{ github.event.inputs.tag }}
|
||||||
|
files: |
|
||||||
|
PiliPlus_windows_*.zip
|
||||||
|
PiliPlus-Win-Setup/PiliPlus_windows_*.exe
|
||||||
|
|
||||||
|
- name: Upload windows file release
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Windows-file-x64-release
|
||||||
|
path: Release
|
||||||
|
|
||||||
|
- name: Upload windows setup release
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Windows-setup-x64-release
|
||||||
|
path: PiliPlus-Win-Setup
|
||||||
15
.gitignore
vendored
@@ -19,7 +19,7 @@ migrate_working_dir/
|
|||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Flutter repo-specific
|
# Flutter repo-specific
|
||||||
/bin/cache/
|
/bin/cache/
|
||||||
@@ -134,7 +134,16 @@ app.*.symbols
|
|||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
!/dev/ci/**/Gemfile.lock
|
!/dev/ci/**/Gemfile.lock
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
!.vscode/launch.json
|
||||||
/lib/build_config.dart
|
!.vscode/tasks.json
|
||||||
|
|
||||||
devtools_options.yaml
|
devtools_options.yaml
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
||||||
|
|
||||||
|
pili_release.json
|
||||||
|
|
||||||
|
dist
|
||||||
|
|
||||||
|
test.dart
|
||||||
8
.vscode/launch.json
vendored
@@ -1,22 +1,22 @@
|
|||||||
{
|
{
|
||||||
// 使用 IntelliSense 了解相关属性。
|
// 使用 IntelliSense 了解相关属性。
|
||||||
// 悬停以查看现有属性的描述。
|
// 悬停以查看现有属性的描述。
|
||||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "piliplus",
|
"name": "PiliPlus",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart"
|
"type": "dart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "piliplus (profile mode)",
|
"name": "PiliPlus (profile mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "profile"
|
"flutterMode": "profile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "piliplus (release mode)",
|
"name": "PiliPlus (release mode)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"flutterMode": "release"
|
"flutterMode": "release"
|
||||||
|
|||||||
10
README.md
@@ -43,8 +43,14 @@
|
|||||||
|
|
||||||
## feat
|
## feat
|
||||||
|
|
||||||
|
- [x] DLNA 投屏
|
||||||
|
- [x] 离线缓存/播放
|
||||||
|
- [x] 移动端支持点击弹幕悬停,点赞、复制、举报 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||||
|
- [x] 播放音频
|
||||||
|
- [x] 跳过番剧片头/片尾
|
||||||
|
- [x] 安卓端 `loudnorm` 适配 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||||
- [x] Win/Mac 支持极验、短信登录 by [@My-Responsitories](https://github.com/My-Responsitories)
|
- [x] Win/Mac 支持极验、短信登录 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||||
- [x] 视频截取 GIF by [@My-Responsitories](https://github.com/My-Responsitories)
|
- [x] 视频截取动图 by [@My-Responsitories](https://github.com/My-Responsitories)
|
||||||
- [x] AI 原声翻译
|
- [x] AI 原声翻译
|
||||||
- [x] SuperChat
|
- [x] SuperChat
|
||||||
- [x] 播放课堂视频
|
- [x] 播放课堂视频
|
||||||
@@ -147,7 +153,7 @@
|
|||||||
- [x] 粉丝、关注用户、拉黑用户查看
|
- [x] 粉丝、关注用户、拉黑用户查看
|
||||||
- [x] 用户主页查看
|
- [x] 用户主页查看
|
||||||
- [x] 关注/取关用户
|
- [x] 关注/取关用户
|
||||||
- [ ] 离线缓存
|
- [x] 离线缓存
|
||||||
- [x] 稍后再看
|
- [x] 稍后再看
|
||||||
- [x] 观看记录
|
- [x] 观看记录
|
||||||
- [x] 我的收藏
|
- [x] 我的收藏
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ analyzer:
|
|||||||
exclude:
|
exclude:
|
||||||
- lib/grpc/bilibili/**
|
- lib/grpc/bilibili/**
|
||||||
- lib/grpc/google/**
|
- lib/grpc/google/**
|
||||||
|
# - lib/common/widgets/flutter/**
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
trailing_commas: preserve
|
trailing_commas: preserve
|
||||||
|
|||||||
@@ -210,4 +210,5 @@
|
|||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.piliplus
|
package com.example.piliplus
|
||||||
|
|
||||||
|
import android.app.PictureInPictureParams
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -42,7 +43,10 @@ class MainActivity : AudioServiceActivity() {
|
|||||||
val cookies = call.argument<List<String>>("cookies") ?: emptyList<String>()
|
val cookies = call.argument<List<String>>("cookies") ?: emptyList<String>()
|
||||||
|
|
||||||
val intent = Intent().apply {
|
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("action", action)
|
||||||
putExtra("oid", oid.toLong())
|
putExtra("oid", oid.toLong())
|
||||||
putExtra("type", type)
|
putExtra("type", type)
|
||||||
@@ -51,23 +55,27 @@ class MainActivity : AudioServiceActivity() {
|
|||||||
putExtra("parent", parent.toLong())
|
putExtra("parent", parent.toLong())
|
||||||
putExtra("ctime", ctime.toLong())
|
putExtra("ctime", ctime.toLong())
|
||||||
putExtra("comment_text", commentText)
|
putExtra("comment_text", commentText)
|
||||||
if(pictures != null)
|
if (pictures != null)
|
||||||
putExtra("pictures", pictures)
|
putExtra("pictures", pictures)
|
||||||
putExtra("source_id", sourceId)
|
putExtra("source_id", sourceId)
|
||||||
putExtra("uid", uid.toLong())
|
putExtra("uid", uid.toLong())
|
||||||
putStringArrayListExtra("cookies", ArrayList(cookies))
|
putStringArrayListExtra("cookies", ArrayList(cookies))
|
||||||
}
|
}
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (_: Exception) {}
|
} catch (_: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"linkVerifySettings" -> {
|
"linkVerifySettings" -> {
|
||||||
val uri = ("package:" + context.packageName).toUri()
|
val uri = ("package:" + context.packageName).toUri()
|
||||||
try {
|
try {
|
||||||
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri)
|
Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri)
|
||||||
} else {
|
} else {
|
||||||
Intent("android.intent.action.MAIN", uri).setClassName("com.android.settings",
|
Intent("android.intent.action.MAIN", uri).setClassName(
|
||||||
"com.android.settings.applications.InstalledAppOpenByDefaultActivity")
|
"com.android.settings",
|
||||||
|
"com.android.settings.applications.InstalledAppOpenByDefaultActivity"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
@@ -75,33 +83,56 @@ class MainActivity : AudioServiceActivity() {
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"music" -> {
|
"music" -> {
|
||||||
val title = call.argument<String>("title")
|
val title = call.argument<String>("title")
|
||||||
val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH).apply {
|
val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH).apply {
|
||||||
putExtra(SearchManager.QUERY, title)
|
putExtra(SearchManager.QUERY, title)
|
||||||
putExtra(MediaStore.EXTRA_MEDIA_TITLE, title)
|
putExtra(MediaStore.EXTRA_MEDIA_TITLE, title)
|
||||||
call.argument<String?>("artist")?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) }
|
call.argument<String?>("artist")
|
||||||
call.argument<String?>("album")?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) }
|
?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) }
|
||||||
|
call.argument<String?>("album")
|
||||||
|
?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) }
|
||||||
|
|
||||||
addCategory(Intent.CATEGORY_DEFAULT)
|
addCategory(Intent.CATEGORY_DEFAULT)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
|
if (packageManager.resolveActivity(
|
||||||
|
intent,
|
||||||
|
PackageManager.MATCH_DEFAULT_ONLY
|
||||||
|
) != null
|
||||||
|
) {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
result.success(true)
|
result.success(true)
|
||||||
return@setMethodCallHandler
|
return@setMethodCallHandler
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {}
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
intent.action = MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH
|
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)
|
startActivity(intent)
|
||||||
result.success(true)
|
result.success(true)
|
||||||
return@setMethodCallHandler
|
return@setMethodCallHandler
|
||||||
}
|
}
|
||||||
} catch (_: Throwable) {}
|
} catch (_: Throwable) {
|
||||||
|
}
|
||||||
result.success(false)
|
result.success(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"setPipAutoEnterEnabled" -> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
val params = PictureInPictureParams.Builder()
|
||||||
|
.setAutoEnterEnabled(call.argument<Boolean>("autoEnable") ?: false)
|
||||||
|
.build()
|
||||||
|
setPictureInPictureParams(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,6 +155,7 @@ class MainActivity : AudioServiceActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
stopService(Intent(this, com.ryanheise.audioservice.AudioService::class.java))
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
android.os.Process.killProcess(android.os.Process.myPid())
|
android.os.Process.killProcess(android.os.Process.myPid())
|
||||||
exitProcess(0)
|
exitProcess(0)
|
||||||
@@ -134,7 +166,10 @@ class MainActivity : AudioServiceActivity() {
|
|||||||
methodChannel.invokeMethod("onUserLeaveHint", null)
|
methodChannel.invokeMethod("onUserLeaveHint", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
|
override fun onPictureInPictureModeChanged(
|
||||||
|
isInPictureInPictureMode: Boolean,
|
||||||
|
newConfig: Configuration?
|
||||||
|
) {
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||||
MethodChannel(
|
MethodChannel(
|
||||||
flutterEngine!!.dartExecutor.binaryMessenger,
|
flutterEngine!!.dartExecutor.binaryMessenger,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ pluginManagement {
|
|||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.12.1" apply false
|
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")
|
include(":app")
|
||||||
|
|||||||
16
assets/linux/DEBIAN/control
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Package: PiliPlus
|
||||||
|
Version: version_need_change
|
||||||
|
Maintainer: gh-MzA4Nzk <githubaccount2333@proton.me>
|
||||||
|
Original-Maintainer: bggRGjQaUbCoE <githubaccount56556@proton.me>
|
||||||
|
Section: x11
|
||||||
|
Priority: optional
|
||||||
|
Architecture: amd64
|
||||||
|
Essential: no
|
||||||
|
Installed-Size: size_need_change
|
||||||
|
Description: third-party Bilibili client developed in Flutter
|
||||||
|
Homepage: https://github.com/bggRGjQaUbCoE/PiliPlus
|
||||||
|
Depends: libgtk-3-0t64,
|
||||||
|
libmpv2,
|
||||||
|
gir1.2-ayatanaappindicator3-0.1,
|
||||||
|
libayatana-appindicator3-1
|
||||||
|
|
||||||
21
assets/linux/DEBIAN/postinst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
ln -sf /opt/PiliPlus/piliplus /usr/bin/piliplus
|
||||||
|
chmod +x /usr/bin/piliplus
|
||||||
|
|
||||||
|
if [ $1 == "config" ] && [ -x /usr/binupdate-mime-database ]; then
|
||||||
|
echo "updating mime database..."
|
||||||
|
update-mime-database /usr/share/mime || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $1 == "config" ] && [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||||
|
echo "updating icon cache..."
|
||||||
|
gtk-update-icon-cache -q -f -t /usr/share/icons/hicolor || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $1 == "config" ] && [ -x /usr/bin/update-desktop-database ]; then
|
||||||
|
echo "updating desktop database..."
|
||||||
|
update-desktop-database -q /usr/share/applications || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
26
assets/linux/DEBIAN/postrm
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
rm /usr/bin/piliplus
|
||||||
|
if [ "$1" = "remove" ] || [ "$1" = "purge" ]; then
|
||||||
|
if [ -x /usr/bin/update-desktop-database ]; then
|
||||||
|
echo "updating desktop database..."
|
||||||
|
update-desktop-database -q /usr/share/applications || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x /usr/bin/gtk-update-icon-cache ]; then
|
||||||
|
echo "updating icon cache..."
|
||||||
|
gtk-update-icon-cache -q -t /usr/share/icons/hicolor || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -x /usr/bin/update-mime-database ]; then
|
||||||
|
echo "updating mime database..."
|
||||||
|
update-mime-database /usr/share/mime || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $1 = "purge" ]; then
|
||||||
|
echo "Removing user data..."
|
||||||
|
rm -rf /home/*/.local/share/com.example.PiliPlus || true
|
||||||
|
rm -rf /root/.local/share/com.example.PiliPlus || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
8
assets/linux/DEBIAN/prerm
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if [ "$1" = "remove" ] || [ "$1" = "deconfigure" ]; then
|
||||||
|
echo "Stopping PiliPlus if running..."
|
||||||
|
pkill -x piliplus || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
9
assets/linux/piliplus.desktop
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=PiliPlus
|
||||||
|
Comment=A third-party Bilibili Client developed in Flutter
|
||||||
|
Comment[zh_CN]=使用 Flutter 开发的 BiliBili 第三方客户端
|
||||||
|
Exec=piliplus
|
||||||
|
Icon=piliplus
|
||||||
|
Terminal=false
|
||||||
|
Categories=Video;AudioVideo;Player;
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
## 1.0.0
|
|
||||||
|
|
||||||
### 初始版本
|
|
||||||
+ 直播、推荐、动态功能
|
|
||||||
+ 投稿、番剧播放功能
|
|
||||||
+ 播放器手势支持
|
|
||||||
+ 画质、音质、解码格式支持
|
|
||||||
+ 点赞、投币、收藏功能
|
|
||||||
+ 关注/取关、用户主页功能
|
|
||||||
+ 评论功能
|
|
||||||
+ 历史记录、稍后再看功能
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
## 1.0.1
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 升级播放器依赖
|
|
||||||
+ android平台 AV1格式视频支持
|
|
||||||
+ 视频全屏功能
|
|
||||||
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
## 1.0.10
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 长按倍速抬起后未恢复默认倍速
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
## 1.0.11
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 适配了原生媒体通知栏 @Daydreamer-riri
|
|
||||||
+ 视频主题图标 @Daydreamer-riri
|
|
||||||
+ 关闭软件后自动画中画播放
|
|
||||||
+ UP主分组管理
|
|
||||||
+ md2样式底栏
|
|
||||||
+
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 历史记录记忆播放
|
|
||||||
+ 部分类型视频连播
|
|
||||||
+ 播放速度选择框不支持返回手势
|
|
||||||
+ 播放速度选择框不支持返回手势
|
|
||||||
+ 视频播放速度总是显示1.0X
|
|
||||||
+ 评论页面计数错误
|
|
||||||
+ 退出视频还有声音
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 视频加载速度
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
## 1.0.12
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ iOS端视频播放时没有声音
|
|
||||||
+ 超过6分钟弹幕不显示
|
|
||||||
+ 视频详情页网络异常
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
## 1.0.13
|
|
||||||
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 视频详情页稍后再看
|
|
||||||
+ 发送弹幕 感谢@orz12
|
|
||||||
+ 消息展示
|
|
||||||
+ up主页显示获赞数
|
|
||||||
+ up主页显示合集
|
|
||||||
+ 视频详情页「ai总结」增加开关
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 首页推荐问题(需要重新登录)
|
|
||||||
+ 长按倍速逻辑
|
|
||||||
+ 视频详情页网络异常
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 设置面板样式 感谢@GuMengYu @KoolShow
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
## 1.0.14
|
|
||||||
|
|
||||||
圣诞节快乐~ 🎉
|
|
||||||
|
|
||||||
大部分内容由@orz12提供,感谢👏
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 全屏弹幕消失
|
|
||||||
+ iOS全屏/退出全屏视频暂停
|
|
||||||
+ 个人主页关注状态
|
|
||||||
+ 视频合集向下滑动UI问题
|
|
||||||
+ 媒体库滑动底栏不隐藏
|
|
||||||
+ 个人主页动态加载问题 * 2
|
|
||||||
+ 未登录状态访问个人主页异常
|
|
||||||
+ 视频搜索标题特殊字符转义
|
|
||||||
+ iOS闪退
|
|
||||||
+ 消息页面夜间模式异常
|
|
||||||
+ 消息页面含有撤回消息时异常
|
|
||||||
+ 弹幕速度
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 全屏播放方案优化
|
|
||||||
+ 弹幕加载逻辑优化
|
|
||||||
+ 点赞、投币逻辑优化
|
|
||||||
+ 进度条及播放时间渲染优化
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
## 1.0.15
|
|
||||||
|
|
||||||
元旦快乐~ 🎉
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
+ 转发动态评论展示
|
|
||||||
+ 推荐、最热、收藏视频增肌日期显示
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 全屏播放相关问题
|
|
||||||
+ 评论区@用户展示问题
|
|
||||||
+ 登录状态闪退问题
|
|
||||||
+ pip意外触发问题
|
|
||||||
+ 动态页tab切换样式问题
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 首页默认使用web端推荐
|
|
||||||
+ 取消iOS路由切换效果
|
|
||||||
+ 视频分享中添加Up主
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
## 1.0.16
|
|
||||||
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
+ toast 背景支持透明度调节
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ web端推荐未展示【已关注】
|
|
||||||
+ up主动态页异常
|
|
||||||
+ 未打开自动播放时,视频详情页异常
|
|
||||||
+ 视频暂停状态取消自动ip
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
## 1.0.17
|
|
||||||
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
+ 视频全屏时隐藏进度条
|
|
||||||
+ 动态内容增加投稿跳转
|
|
||||||
+ 未开启自动播放时点击封面播放
|
|
||||||
+ 弹幕发送标识
|
|
||||||
+ 定时关闭
|
|
||||||
+ 推荐视频卡片拉黑up功能
|
|
||||||
+ 首页tabbar编辑排序
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 连续跳转搜索页未刷新
|
|
||||||
+ 搜索结果为空时页面异常
|
|
||||||
+ 评论区链接解析
|
|
||||||
+ 视频全屏状态栏背景色
|
|
||||||
+ 私信对话气泡位置
|
|
||||||
+ 设置up关注分组样式
|
|
||||||
+ 每次推荐请求数据相同
|
|
||||||
+ iOS代理网络异常
|
|
||||||
+ 双击切换播放状态无声
|
|
||||||
+ 设置自定义倍速白屏
|
|
||||||
+ 免登录查看1080p
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 首页web端推荐观看数展示
|
|
||||||
+ 首页web端推荐接口更新
|
|
||||||
+ 首页样式
|
|
||||||
+ 搜索页跳转
|
|
||||||
+ 弹幕资源优化
|
|
||||||
+ 图片渲染占用内存优化(部分)
|
|
||||||
+ 两次返回退出应用
|
|
||||||
+ schame 补充
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
## 1.0.18
|
|
||||||
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
## 1.0.19
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 视频404、评论加载错误
|
|
||||||
+ bvav转换
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 视频详情页内存占用
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
## 1.0.2
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 自动检查更新
|
|
||||||
+ 封面图片保存
|
|
||||||
+ 动态跳转番剧
|
|
||||||
+ 历史记录番剧记忆播放
|
|
||||||
+ 一键清空稍后再看
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 切换分P cid未切换
|
|
||||||
+ cookie存储问题
|
|
||||||
+ 登录/退出登录问题
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 页面空/异常状态样式
|
|
||||||
+ 退出登录提示
|
|
||||||
+ 请求节流
|
|
||||||
+ 全屏播放
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
## 1.0.3
|
|
||||||
|
|
||||||
建议卸载1.0.2版本,重新安装
|
|
||||||
### 新功能
|
|
||||||
+ 底部播放进度条设置
|
|
||||||
+ 复制图片链接
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 用户数据格式修改
|
|
||||||
+ video Fit
|
|
||||||
+ 没有audio 资源的视频异常
|
|
||||||
+ 评论区域图片无法点击
|
|
||||||
+ 视频进度条拖动问题
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 页面空/异常状态样式
|
|
||||||
+ 部分页面样式
|
|
||||||
+ 图片预览页面样式
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
## 1.0.4
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 热搜刷新
|
|
||||||
+ 视频搜索排序、筛选
|
|
||||||
+ app字体大小自定义
|
|
||||||
+ app主题色自定义
|
|
||||||
+ 「课堂」类动态渲染
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 搜索词联想richText渲染异常
|
|
||||||
+ 部分动态点赞异常
|
|
||||||
+ 默认视频解码格式
|
|
||||||
+ 搜索页面返回搜索词未清空
|
|
||||||
+ 动态详情评论加载异常
|
|
||||||
+ 动态页面下拉刷新数据异常
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 一些样式修改
|
|
||||||
+ 取消热搜词缓存
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
## 1.0.5
|
|
||||||
|
|
||||||
主要是bug修复跟一部分小功能,弹幕功能需要下一版。
|
|
||||||
问题反馈请前往QQ频道或提交issues。
|
|
||||||
感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 高帧率支持
|
|
||||||
+ 默认评论排序设置
|
|
||||||
+ 默认动态类别设置
|
|
||||||
+ 动态合集查看
|
|
||||||
+ 同时观看人数
|
|
||||||
+ iOS路由切换效果
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 收藏夹翻页
|
|
||||||
+ 首页搜索框频繁点击消失
|
|
||||||
+ 评论排序切换空白
|
|
||||||
+ 快速返回首页
|
|
||||||
+ 重复进入个人中心页面数据未刷新
|
|
||||||
+ 动态goods数据异常
|
|
||||||
+ 大会员切换番剧
|
|
||||||
+ 高画质codes匹配
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 倍速选择
|
|
||||||
+ 播放器亮度记忆
|
|
||||||
+ 下载对应版本apk
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
## 1.0.6
|
|
||||||
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 首页单列布局
|
|
||||||
+ 首页推荐展示播放量、弹幕数
|
|
||||||
+ 简单弹幕功能实现(持续开发中...)
|
|
||||||
+ 评论区搜索关键词开关 issues#46
|
|
||||||
+ 热搜榜隐藏功能 issues#35
|
|
||||||
+ 自动全屏 issues#37
|
|
||||||
+ 快速收藏功能
|
|
||||||
+ 双击快进/快退开关
|
|
||||||
+ 评论链接跳转视频
|
|
||||||
+ 支持移除单个稍后再看
|
|
||||||
+ app scheme外链跳转
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 杜比、无损音频切换
|
|
||||||
+ 收藏夹展示 issues#42
|
|
||||||
+ 搜索建议次 issues#47
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 倍速选择优化
|
|
||||||
+ 导航条沉浸
|
|
||||||
+ 取消Hero动画
|
|
||||||
+ 视频锁定逻辑
|
|
||||||
+ 登录逻辑优化
|
|
||||||
+ 图片预览样式
|
|
||||||
+ +评论区用户点击范围
|
|
||||||
+ 关注、粉丝页面优化
|
|
||||||
+ 关闭自动播放时播放器初始化逻辑
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
## 1.0.7
|
|
||||||
|
|
||||||
默认倍速、直播弹幕、专栏等功能开发中
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 弹幕设置、屏蔽功能
|
|
||||||
+ 不是很完美的后台播放功能
|
|
||||||
+ 不是很完美的画中画(pip)功能(Android端)
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 动态页面加载异常
|
|
||||||
+ 网络异常时页面空白
|
|
||||||
+ 竖屏全屏状态栏问题
|
|
||||||
+ iOS端代理请求异常
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 图片预览
|
|
||||||
+ 全屏播放时自动旋转
|
|
||||||
+ 转发内容增加视频标题
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
## 1.0.8
|
|
||||||
|
|
||||||
直播弹幕、循环播放等功能开发中
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 用户拉黑功能
|
|
||||||
+ gif图片保存
|
|
||||||
+ 删除已看历史记录
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 弹幕数量较少
|
|
||||||
+ 弹幕屏蔽设置自动记忆
|
|
||||||
+ 动态页面渲染
|
|
||||||
+ 用户主页数据错乱
|
|
||||||
+ 大家都在搜空白
|
|
||||||
+ 默认自动全屏,顶部操作栏丢失
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 全屏状态栏区域显示优化
|
|
||||||
+ 图片保存至PiliPala文件夹
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
## 1.0.9
|
|
||||||
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 自定义倍速、默认倍速
|
|
||||||
+ 历史记录搜索
|
|
||||||
+ 收藏夹搜索
|
|
||||||
+ 历史记录多选删除
|
|
||||||
+ 视频循环播放
|
|
||||||
+ 免登录看1080P
|
|
||||||
+ 评论区视频链接跳转
|
|
||||||
+ up主分组
|
|
||||||
+ up主投稿搜索
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 搜索视频标题乱码
|
|
||||||
+ 屏幕帧率
|
|
||||||
+ 动态页面渲染
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 快进手势
|
|
||||||
+ 视频简介链接匹配
|
|
||||||
+ 视频全屏时安全区域
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
1
distribute_options.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
output: dist/
|
||||||
@@ -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
|
|
||||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1 +0,0 @@
|
|||||||
A third-party Bilibili client developed in Flutter
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
PiliPlus
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
PiliPlus 是使用 Flutter 开发的 BiliBili 第三方客户端,
|
|
||||||
是由PiliPalaX仓库fork并进行了差异化开发的版本
|
|
||||||
|
|
||||||
主要功能:
|
|
||||||
|
|
||||||
* 推荐视频列表
|
|
||||||
* 最热视频列表
|
|
||||||
* 热门直播
|
|
||||||
* 番剧列表
|
|
||||||
* 屏蔽黑名单内用户视频
|
|
||||||
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 526 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
@@ -1 +0,0 @@
|
|||||||
使用 Flutter 开发的 BiliBili 第三方客户端
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
PiliPlus
|
|
||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>11.0</string>
|
<string>13.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
211
ios/Podfile.lock
@@ -1,83 +1,135 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- appscheme (1.0.4):
|
- app_links (7.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- audio_service (0.0.1):
|
- audio_service (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- audio_session (0.0.1):
|
- audio_session (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- auto_orientation (0.0.1):
|
- auto_orientation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- chat_bottom_container (0.0.1):
|
||||||
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- 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 (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_mailer (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- flutter_native_splash (2.4.3):
|
||||||
|
- Flutter
|
||||||
- flutter_volume_controller (0.0.1):
|
- flutter_volume_controller (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast
|
- gt3_flutter_plugin (0.0.9):
|
||||||
- FMDB (2.7.5):
|
|
||||||
- FMDB/standard (= 2.7.5)
|
|
||||||
- FMDB/standard (2.7.5)
|
|
||||||
- gt3_flutter_plugin (0.0.8):
|
|
||||||
- Flutter
|
- Flutter
|
||||||
- GT3Captcha-iOS
|
- GT3Captcha-iOS
|
||||||
- GT3Captcha-iOS (0.15.8.3)
|
- 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):
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_native_event_loop (1.0.0):
|
- media_kit_native_event_loop (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_video (0.0.1):
|
- media_kit_video (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- OrderedSet (6.0.3)
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- permission_handler_apple (9.1.1):
|
- permission_handler_apple (9.3.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
|
||||||
- saver_gallery (0.0.1):
|
- saver_gallery (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- SDWebImage (5.21.3):
|
||||||
|
- SDWebImage/Core (= 5.21.3)
|
||||||
|
- SDWebImage/Core (5.21.3)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- sqflite (0.0.3):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FlutterMacOS
|
||||||
- status_bar_control (3.2.1):
|
- sqflite_darwin (0.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- system_proxy (0.0.1):
|
- FlutterMacOS
|
||||||
- Flutter
|
- SwiftyGif (5.4.5)
|
||||||
- Toast (4.1.0)
|
- TOCropViewController (2.8.0)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- webview_cookie_manager (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- webview_flutter_wkwebview (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
|
||||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/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`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/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`)
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/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_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_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
@@ -87,45 +139,58 @@ DEPENDENCIES:
|
|||||||
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- status_bar_control (from `.symlinks/plugins/status_bar_control/ios`)
|
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- system_proxy (from `.symlinks/plugins/system_proxy/ios`)
|
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- 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`)
|
- 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:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- FMDB
|
- DKImagePickerController
|
||||||
|
- DKPhotoGallery
|
||||||
- GT3Captcha-iOS
|
- GT3Captcha-iOS
|
||||||
- ReachabilitySwift
|
- OrderedSet
|
||||||
- Toast
|
- SDWebImage
|
||||||
|
- SwiftyGif
|
||||||
|
- TOCropViewController
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
appscheme:
|
app_links:
|
||||||
:path: ".symlinks/plugins/appscheme/ios"
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
audio_service:
|
audio_service:
|
||||||
:path: ".symlinks/plugins/audio_service/ios"
|
:path: ".symlinks/plugins/audio_service/darwin"
|
||||||
audio_session:
|
audio_session:
|
||||||
:path: ".symlinks/plugins/audio_session/ios"
|
:path: ".symlinks/plugins/audio_session/ios"
|
||||||
auto_orientation:
|
auto_orientation:
|
||||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
:path: ".symlinks/plugins/auto_orientation/ios"
|
||||||
|
chat_bottom_container:
|
||||||
|
:path: ".symlinks/plugins/chat_bottom_container/ios"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
|
file_picker:
|
||||||
|
:path: ".symlinks/plugins/file_picker/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_inappwebview_ios:
|
||||||
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
flutter_mailer:
|
flutter_mailer:
|
||||||
:path: ".symlinks/plugins/flutter_mailer/ios"
|
:path: ".symlinks/plugins/flutter_mailer/ios"
|
||||||
|
flutter_native_splash:
|
||||||
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_volume_controller:
|
flutter_volume_controller:
|
||||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
gt3_flutter_plugin:
|
gt3_flutter_plugin:
|
||||||
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
: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:
|
media_kit_libs_ios_video:
|
||||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
@@ -144,57 +209,55 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
sqflite:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
status_bar_control:
|
sqflite_darwin:
|
||||||
:path: ".symlinks/plugins/status_bar_control/ios"
|
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||||
system_proxy:
|
|
||||||
:path: ".symlinks/plugins/system_proxy/ios"
|
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
volume_controller:
|
|
||||||
:path: ".symlinks/plugins/volume_controller/ios"
|
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
: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:
|
SPEC CHECKSUMS:
|
||||||
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
app_links: 6d01271b3907b0ee7325c5297c75d697c4226c4d
|
||||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
|
||||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
|
||||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
chat_bottom_container: d8b077152c91b0ab90001e900748ea50353a5520
|
||||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||||
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
|
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||||
|
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||||
|
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||||
|
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
||||||
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
|
||||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||||
|
image_cropper: b8ef14d3fcff4040b0f9da2ca28d98219a5cba0e
|
||||||
|
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||||
|
live_photo_maker: 7d57bfc70a120b4673c10871f354f4b1b6fde5fd
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
saver_gallery: 76172dc4bf6b40e66d694948ada9ff402304dd87
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 6a6f7794b67f07c4f1e24f6374b2d8ad367ffb39
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||||
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||||
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
|
||||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
|
||||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
PODFILE CHECKSUM: f62db4fb414ebdecb264109948f76dfef35fdc3d
|
||||||
|
|
||||||
COCOAPODS: 1.14.3
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@@ -48,6 +48,7 @@
|
|||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
@main
|
@main
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||||
override func application(
|
override func application(
|
||||||
_ application: UIApplication,
|
_ application: UIApplication,
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
|
||||||
application.applicationSupportsShakeToEdit = false // Disable shake to undo
|
application.applicationSupportsShakeToEdit = false // Disable shake to undo
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||||
|
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,27 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<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>
|
<key>FlutterDeepLinkingEnabled</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
|||||||
16
lib/build_config.dart
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class BuildConfig {
|
||||||
|
static const int versionCode = int.fromEnvironment(
|
||||||
|
'pili.code',
|
||||||
|
defaultValue: 1,
|
||||||
|
);
|
||||||
|
static const String versionName = String.fromEnvironment(
|
||||||
|
'pili.name',
|
||||||
|
defaultValue: 'SNAPSHOT',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const int buildTime = int.fromEnvironment('pili.time');
|
||||||
|
static const String commitHash = String.fromEnvironment(
|
||||||
|
'pili.hash',
|
||||||
|
defaultValue: 'N/A',
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:PiliPlus/http/constants.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class StyleString {
|
class StyleString {
|
||||||
@@ -41,9 +40,7 @@ class Constants {
|
|||||||
'{"appId":1,"platform":3,"version":"8.43.0","abtest":""}';
|
'{"appId":1,"platform":3,"version":"8.43.0","abtest":""}';
|
||||||
|
|
||||||
static const baseHeaders = {
|
static const baseHeaders = {
|
||||||
'connection': 'keep-alive',
|
// 'referer': HttpString.baseUrl,
|
||||||
'accept-encoding': 'br,gzip',
|
|
||||||
'referer': HttpString.baseUrl,
|
|
||||||
'env': 'prod',
|
'env': 'prod',
|
||||||
'app-key': 'android64',
|
'app-key': 'android64',
|
||||||
'x-bili-aurora-zone': 'sh001',
|
'x-bili-aurora-zone': 'sh001',
|
||||||
|
|||||||
@@ -1,50 +1,35 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Widget iconButton({
|
Widget iconButton({
|
||||||
required BuildContext context,
|
BuildContext? context,
|
||||||
String? tooltip,
|
String? tooltip,
|
||||||
required IconData icon,
|
required Widget icon,
|
||||||
required VoidCallback? onPressed,
|
required VoidCallback? onPressed,
|
||||||
double size = 36,
|
double size = 36,
|
||||||
double? iconSize,
|
double? iconSize,
|
||||||
Color? bgColor,
|
Color? bgColor,
|
||||||
Color? iconColor,
|
Color? iconColor,
|
||||||
}) {
|
}) {
|
||||||
late final theme = Theme.of(context);
|
Color? backgroundColor = bgColor;
|
||||||
|
Color? foregroundColor = iconColor;
|
||||||
|
if (context != null) {
|
||||||
|
final colorScheme = ColorScheme.of(context);
|
||||||
|
backgroundColor = colorScheme.secondaryContainer;
|
||||||
|
foregroundColor = colorScheme.onSecondaryContainer;
|
||||||
|
}
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
|
icon: icon,
|
||||||
tooltip: tooltip,
|
tooltip: tooltip,
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
icon: Icon(
|
|
||||||
icon,
|
|
||||||
size: iconSize ?? size / 2,
|
|
||||||
color: iconColor ?? theme.colorScheme.onSecondaryContainer,
|
|
||||||
),
|
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
backgroundColor: bgColor ?? theme.colorScheme.secondaryContainer,
|
iconSize: iconSize ?? size / 2,
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
foregroundColor: foregroundColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget mediumButton({
|
|
||||||
String? tooltip,
|
|
||||||
IconData? icon,
|
|
||||||
VoidCallback? onPressed,
|
|
||||||
}) {
|
|
||||||
return SizedBox(
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: tooltip,
|
|
||||||
icon: Icon(icon),
|
|
||||||
style: const ButtonStyle(
|
|
||||||
padding: WidgetStatePropertyAll(EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
onPressed: onPressed,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
34
lib/common/widgets/button/more_btn.dart
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Widget moreTextButton({
|
||||||
|
String text = '查看更多',
|
||||||
|
required VoidCallback onTap,
|
||||||
|
EdgeInsets? padding,
|
||||||
|
Color? color,
|
||||||
|
}) {
|
||||||
|
Widget child = Text.rich(
|
||||||
|
style: TextStyle(color: color, height: 1),
|
||||||
|
strutStyle: const StrutStyle(leading: 0, height: 1),
|
||||||
|
TextSpan(
|
||||||
|
children: [
|
||||||
|
TextSpan(text: text),
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Icon(
|
||||||
|
size: 22,
|
||||||
|
color: color,
|
||||||
|
Icons.keyboard_arrow_right,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (padding != null) {
|
||||||
|
child = Padding(padding: padding, child: child);
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: onTap,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,19 +10,24 @@ class CustomIcons {
|
|||||||
static const IconData dyn = _CustomIconData(0xe804);
|
static const IconData dyn = _CustomIconData(0xe804);
|
||||||
static const IconData fav = _CustomIconData(0xe805);
|
static const IconData fav = _CustomIconData(0xe805);
|
||||||
static const IconData live_reserve = _CustomIconData(0xe806);
|
static const IconData live_reserve = _CustomIconData(0xe806);
|
||||||
static const IconData share = _CustomIconData(0xe807);
|
static const IconData player_dm_tip_back = _CustomIconData(0xe807);
|
||||||
static const IconData share_line = _CustomIconData(0xe808);
|
static const IconData player_dm_tip_copy = _CustomIconData(0xe808);
|
||||||
static const IconData share_node = _CustomIconData(0xe809);
|
static const IconData player_dm_tip_like = _CustomIconData(0xe809);
|
||||||
static const IconData star_favorite_line = _CustomIconData(0xe80a);
|
static const IconData player_dm_tip_like_solid = _CustomIconData(0xe80a);
|
||||||
static const IconData star_favorite_solid = _CustomIconData(0xe80b);
|
static const IconData player_dm_tip_recall = _CustomIconData(0xe80b);
|
||||||
static const IconData thumbs_down = _CustomIconData(0xe80c);
|
static const IconData share = _CustomIconData(0xe80c);
|
||||||
static const IconData thumbs_down_outline = _CustomIconData(0xe80d);
|
static const IconData share_line = _CustomIconData(0xe80d);
|
||||||
static const IconData thumbs_up = _CustomIconData(0xe80e);
|
static const IconData share_node = _CustomIconData(0xe80e);
|
||||||
static const IconData thumbs_up_fill = _CustomIconData(0xe80f);
|
static const IconData star_favorite_line = _CustomIconData(0xe80f);
|
||||||
static const IconData thumbs_up_line = _CustomIconData(0xe810);
|
static const IconData star_favorite_solid = _CustomIconData(0xe810);
|
||||||
static const IconData thumbs_up_outline = _CustomIconData(0xe811);
|
static const IconData thumbs_down = _CustomIconData(0xe811);
|
||||||
static const IconData topic_tag = _CustomIconData(0xe812);
|
static const IconData thumbs_down_outline = _CustomIconData(0xe812);
|
||||||
static const IconData watch_later = _CustomIconData(0xe813);
|
static const IconData thumbs_up = _CustomIconData(0xe813);
|
||||||
|
static const IconData thumbs_up_fill = _CustomIconData(0xe814);
|
||||||
|
static const IconData thumbs_up_line = _CustomIconData(0xe815);
|
||||||
|
static const IconData thumbs_up_outline = _CustomIconData(0xe816);
|
||||||
|
static const IconData topic_tag = _CustomIconData(0xe817);
|
||||||
|
static const IconData watch_later = _CustomIconData(0xe818);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CustomIconData extends IconData {
|
class _CustomIconData extends IconData {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
class CustomSliverPersistentHeaderDelegate
|
class CustomSliverPersistentHeaderDelegate
|
||||||
extends SliverPersistentHeaderDelegate {
|
extends SliverPersistentHeaderDelegate {
|
||||||
CustomSliverPersistentHeaderDelegate({
|
const CustomSliverPersistentHeaderDelegate({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.bgColor,
|
required this.bgColor,
|
||||||
double extent = 45,
|
double extent = 45,
|
||||||
|
|||||||
@@ -1,41 +1,46 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
Future<void> showConfirmDialog({
|
Future<bool> showConfirmDialog({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required String title,
|
required String title,
|
||||||
dynamic content,
|
Object? content,
|
||||||
required VoidCallback onConfirm,
|
@Deprecated('use `bool result = await showConfirmDialog()` instead')
|
||||||
}) {
|
VoidCallback? onConfirm,
|
||||||
return showDialog(
|
}) async {
|
||||||
context: context,
|
assert(content is String? || content is Widget);
|
||||||
builder: (context) {
|
return await showDialog<bool>(
|
||||||
return AlertDialog(
|
context: context,
|
||||||
title: Text(title),
|
builder: (context) {
|
||||||
content: content is String
|
return AlertDialog(
|
||||||
? Text(content)
|
title: Text(title),
|
||||||
: content is Widget
|
content: content is String
|
||||||
? content
|
? Text(content)
|
||||||
: null,
|
: content is Widget
|
||||||
actions: [
|
? content
|
||||||
TextButton(
|
: null,
|
||||||
onPressed: Get.back,
|
actions: [
|
||||||
child: Text(
|
TextButton(
|
||||||
'取消',
|
onPressed: Get.back,
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
child: Text(
|
||||||
),
|
'取消',
|
||||||
),
|
style: TextStyle(
|
||||||
TextButton(
|
color: Theme.of(context).colorScheme.outline,
|
||||||
onPressed: () {
|
),
|
||||||
Get.back();
|
),
|
||||||
onConfirm();
|
),
|
||||||
},
|
TextButton(
|
||||||
child: const Text('确认'),
|
onPressed: () {
|
||||||
),
|
Get.back(result: true);
|
||||||
],
|
onConfirm?.call();
|
||||||
);
|
},
|
||||||
},
|
child: const Text('确认'),
|
||||||
);
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void showPgcFollowDialog({
|
void showPgcFollowDialog({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
import 'package:PiliPlus/common/widgets/radio_widget.dart';
|
||||||
import 'package:PiliPlus/utils/extension.dart';
|
import 'package:PiliPlus/utils/extension.dart';
|
||||||
|
import 'package:PiliPlus/utils/utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@@ -124,11 +125,12 @@ Future<void> autoWrapReportDialog(
|
|||||||
Get.back();
|
Get.back();
|
||||||
SmartDialog.showToast('举报成功');
|
SmartDialog.showToast('举报成功');
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(data['message']);
|
SmartDialog.showToast(data['message'].toString());
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, s) {
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
SmartDialog.showToast('提交失败:$e');
|
SmartDialog.showToast('提交失败:$e');
|
||||||
|
Utils.reportError(e, s);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('确定'),
|
child: const Text('确定'),
|
||||||
@@ -231,4 +233,34 @@ class ReportOptions {
|
|||||||
0: '其他',
|
0: '其他',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static Map<String, Map<int, String>> get danmakuReport => const {
|
||||||
|
'': {
|
||||||
|
1: '违法违禁',
|
||||||
|
2: '色情低俗',
|
||||||
|
3: '赌博诈骗',
|
||||||
|
4: '人身攻击',
|
||||||
|
5: '侵犯隐私',
|
||||||
|
6: '垃圾广告',
|
||||||
|
7: '引战',
|
||||||
|
8: '剧透',
|
||||||
|
9: '恶意刷屏',
|
||||||
|
10: '视频无关',
|
||||||
|
12: '青少年不良信息',
|
||||||
|
13: '违法信息外链',
|
||||||
|
0: '其它', // 11
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static Map<String, Map<int, String>> get liveDanmakuReport => const {
|
||||||
|
'': {
|
||||||
|
1: '违法违规',
|
||||||
|
2: '低俗色情',
|
||||||
|
3: '垃圾广告',
|
||||||
|
4: '辱骂引战',
|
||||||
|
5: '政治敏感',
|
||||||
|
6: '青少年不良信息',
|
||||||
|
7: '其他', // avoid show form
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ library;
|
|||||||
|
|
||||||
import 'dart:math' as math;
|
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/foundation.dart';
|
||||||
import 'package:flutter/material.dart' hide InkWell;
|
import 'package:flutter/material.dart' hide InkWell;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@@ -21,9 +21,6 @@ import 'package:flutter/gestures.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
|
||||||
// Examples can assume:
|
|
||||||
// late BuildContext context;
|
|
||||||
|
|
||||||
abstract class _ParentInkResponseState {
|
abstract class _ParentInkResponseState {
|
||||||
void markChildInkResponsePressed(
|
void markChildInkResponsePressed(
|
||||||
_ParentInkResponseState childState,
|
_ParentInkResponseState childState,
|
||||||
@@ -144,6 +141,7 @@ class InkResponse extends StatelessWidget {
|
|||||||
this.onTapCancel,
|
this.onTapCancel,
|
||||||
this.onDoubleTap,
|
this.onDoubleTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
this.onLongPressUp,
|
||||||
this.onSecondaryTap,
|
this.onSecondaryTap,
|
||||||
this.onSecondaryTapUp,
|
this.onSecondaryTapUp,
|
||||||
this.onSecondaryTapDown,
|
this.onSecondaryTapDown,
|
||||||
@@ -197,6 +195,19 @@ class InkResponse extends StatelessWidget {
|
|||||||
/// Called when the user long-presses on this part of the material.
|
/// Called when the user long-presses on this part of the material.
|
||||||
final GestureLongPressCallback? onLongPress;
|
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.
|
/// Called when the user taps this part of the material with a secondary button.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@@ -237,7 +248,7 @@ class InkResponse extends StatelessWidget {
|
|||||||
/// become highlighted and false if this part of the material has stopped
|
/// become highlighted and false if this part of the material has stopped
|
||||||
/// being highlighted.
|
/// 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
|
/// gesture is ongoing, then [onTapCancel] will be fired and
|
||||||
/// [onHighlightChanged] will be fired with the value false _during the
|
/// [onHighlightChanged] will be fired with the value false _during the
|
||||||
/// build_. This means, for instance, that in that scenario [State.setState]
|
/// build_. This means, for instance, that in that scenario [State.setState]
|
||||||
@@ -493,6 +504,7 @@ class InkResponse extends StatelessWidget {
|
|||||||
onTapCancel: onTapCancel,
|
onTapCancel: onTapCancel,
|
||||||
onDoubleTap: onDoubleTap,
|
onDoubleTap: onDoubleTap,
|
||||||
onLongPress: onLongPress,
|
onLongPress: onLongPress,
|
||||||
|
onLongPressUp: onLongPressUp,
|
||||||
onSecondaryTap: onSecondaryTap,
|
onSecondaryTap: onSecondaryTap,
|
||||||
onSecondaryTapUp: onSecondaryTapUp,
|
onSecondaryTapUp: onSecondaryTapUp,
|
||||||
onSecondaryTapDown: onSecondaryTapDown,
|
onSecondaryTapDown: onSecondaryTapDown,
|
||||||
@@ -550,6 +562,7 @@ class _InkResponseStateWidget extends StatefulWidget {
|
|||||||
this.onTapCancel,
|
this.onTapCancel,
|
||||||
this.onDoubleTap,
|
this.onDoubleTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
this.onLongPressUp,
|
||||||
this.onSecondaryTap,
|
this.onSecondaryTap,
|
||||||
this.onSecondaryTapUp,
|
this.onSecondaryTapUp,
|
||||||
this.onSecondaryTapDown,
|
this.onSecondaryTapDown,
|
||||||
@@ -588,6 +601,7 @@ class _InkResponseStateWidget extends StatefulWidget {
|
|||||||
final GestureTapCallback? onTapCancel;
|
final GestureTapCallback? onTapCancel;
|
||||||
final GestureTapCallback? onDoubleTap;
|
final GestureTapCallback? onDoubleTap;
|
||||||
final GestureLongPressCallback? onLongPress;
|
final GestureLongPressCallback? onLongPress;
|
||||||
|
final GestureLongPressUpCallback? onLongPressUp;
|
||||||
final GestureTapCallback? onSecondaryTap;
|
final GestureTapCallback? onSecondaryTap;
|
||||||
final GestureTapUpCallback? onSecondaryTapUp;
|
final GestureTapUpCallback? onSecondaryTapUp;
|
||||||
final GestureTapDownCallback? onSecondaryTapDown;
|
final GestureTapDownCallback? onSecondaryTapDown;
|
||||||
@@ -628,6 +642,7 @@ class _InkResponseStateWidget extends StatefulWidget {
|
|||||||
if (onTap != null) 'tap',
|
if (onTap != null) 'tap',
|
||||||
if (onDoubleTap != null) 'double tap',
|
if (onDoubleTap != null) 'double tap',
|
||||||
if (onLongPress != null) 'long press',
|
if (onLongPress != null) 'long press',
|
||||||
|
if (onLongPressUp != null) 'long press up',
|
||||||
if (onTapDown != null) 'tap down',
|
if (onTapDown != null) 'tap down',
|
||||||
if (onTapUp != null) 'tap up',
|
if (onTapUp != null) 'tap up',
|
||||||
if (onTapCancel != null) 'tap cancel',
|
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() {
|
void handleSecondaryTap() {
|
||||||
_currentSplash?.confirm();
|
_currentSplash?.confirm();
|
||||||
_currentSplash = null;
|
_currentSplash = null;
|
||||||
@@ -1140,6 +1161,7 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
|||||||
return widget.onTap != null ||
|
return widget.onTap != null ||
|
||||||
widget.onDoubleTap != null ||
|
widget.onDoubleTap != null ||
|
||||||
widget.onLongPress != null ||
|
widget.onLongPress != null ||
|
||||||
|
widget.onLongPressUp != null ||
|
||||||
widget.onTapUp != null ||
|
widget.onTapUp != null ||
|
||||||
widget.onTapDown != null;
|
widget.onTapDown != null;
|
||||||
}
|
}
|
||||||
@@ -1276,6 +1298,9 @@ class _InkResponseState extends State<_InkResponseStateWidget>
|
|||||||
onLongPress: widget.onLongPress != null
|
onLongPress: widget.onLongPress != null
|
||||||
? handleLongPress
|
? handleLongPress
|
||||||
: null,
|
: null,
|
||||||
|
onLongPressUp: widget.onLongPressUp != null
|
||||||
|
? handleLongPressUp
|
||||||
|
: null,
|
||||||
onSecondaryTapDown: _secondaryEnabled
|
onSecondaryTapDown: _secondaryEnabled
|
||||||
? handleSecondaryTapDown
|
? handleSecondaryTapDown
|
||||||
: null,
|
: null,
|
||||||
@@ -1387,6 +1412,7 @@ class InkWell extends InkResponse {
|
|||||||
super.onTap,
|
super.onTap,
|
||||||
super.onDoubleTap,
|
super.onDoubleTap,
|
||||||
super.onLongPress,
|
super.onLongPress,
|
||||||
|
super.onLongPressUp,
|
||||||
super.onTapDown,
|
super.onTapDown,
|
||||||
super.onTapUp,
|
super.onTapUp,
|
||||||
super.onTapCancel,
|
super.onTapCancel,
|
||||||
@@ -12,7 +12,7 @@ library;
|
|||||||
|
|
||||||
import 'dart:ui' show lerpDouble;
|
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/foundation.dart';
|
||||||
import 'package:flutter/material.dart' hide InkWell, ButtonStyleButton;
|
import 'package:flutter/material.dart' hide InkWell, ButtonStyleButton;
|
||||||
|
|
||||||
@@ -316,6 +316,7 @@ class ListTile extends StatelessWidget {
|
|||||||
/// Requires one of its ancestors to be a [Material] widget.
|
/// Requires one of its ancestors to be a [Material] widget.
|
||||||
const ListTile({
|
const ListTile({
|
||||||
super.key,
|
super.key,
|
||||||
|
this.safeArea = false,
|
||||||
this.leading,
|
this.leading,
|
||||||
this.title,
|
this.title,
|
||||||
this.subtitle,
|
this.subtitle,
|
||||||
@@ -335,6 +336,7 @@ class ListTile extends StatelessWidget {
|
|||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
this.onSecondaryTap,
|
||||||
this.onFocusChange,
|
this.onFocusChange,
|
||||||
this.mouseCursor,
|
this.mouseCursor,
|
||||||
this.selected = false,
|
this.selected = false,
|
||||||
@@ -355,6 +357,8 @@ class ListTile extends StatelessWidget {
|
|||||||
this.statesController,
|
this.statesController,
|
||||||
}) : assert(isThreeLine != true || subtitle != null);
|
}) : assert(isThreeLine != true || subtitle != null);
|
||||||
|
|
||||||
|
final bool safeArea;
|
||||||
|
|
||||||
/// A widget to display before the title.
|
/// A widget to display before the title.
|
||||||
///
|
///
|
||||||
/// Typically an [Icon] or a [CircleAvatar] widget.
|
/// Typically an [Icon] or a [CircleAvatar] widget.
|
||||||
@@ -563,6 +567,8 @@ class ListTile extends StatelessWidget {
|
|||||||
/// Inoperative if [enabled] is false.
|
/// Inoperative if [enabled] is false.
|
||||||
final GestureLongPressCallback? onLongPress;
|
final GestureLongPressCallback? onLongPress;
|
||||||
|
|
||||||
|
final GestureTapCallback? onSecondaryTap;
|
||||||
|
|
||||||
/// {@macro flutter.material.inkwell.onFocusChange}
|
/// {@macro flutter.material.inkwell.onFocusChange}
|
||||||
final ValueChanged<bool>? onFocusChange;
|
final ValueChanged<bool>? onFocusChange;
|
||||||
|
|
||||||
@@ -908,10 +914,7 @@ class ListTile extends StatelessWidget {
|
|||||||
WidgetState.disabled,
|
WidgetState.disabled,
|
||||||
};
|
};
|
||||||
final MouseCursor effectiveMouseCursor =
|
final MouseCursor effectiveMouseCursor =
|
||||||
WidgetStateProperty.resolveAs<MouseCursor?>(
|
WidgetStateProperty.resolveAs<MouseCursor?>(mouseCursor, mouseStates) ??
|
||||||
mouseCursor,
|
|
||||||
mouseStates,
|
|
||||||
) ??
|
|
||||||
tileTheme.mouseCursor?.resolve(mouseStates) ??
|
tileTheme.mouseCursor?.resolve(mouseStates) ??
|
||||||
WidgetStateMouseCursor.clickable.resolve(mouseStates);
|
WidgetStateMouseCursor.clickable.resolve(mouseStates);
|
||||||
|
|
||||||
@@ -922,10 +925,64 @@ class ListTile extends StatelessWidget {
|
|||||||
? ListTileTitleAlignment.threeLine
|
? ListTileTitleAlignment.threeLine
|
||||||
: ListTileTitleAlignment.titleHeight);
|
: ListTileTitleAlignment.titleHeight);
|
||||||
|
|
||||||
|
Widget child = IconTheme.merge(
|
||||||
|
data: iconThemeData,
|
||||||
|
child: IconButtonTheme(
|
||||||
|
data: iconButtonThemeData,
|
||||||
|
child: _ListTile(
|
||||||
|
leading: leadingIcon,
|
||||||
|
title: titleText,
|
||||||
|
subtitle: subtitleText,
|
||||||
|
trailing: trailingIcon,
|
||||||
|
isDense: _isDenseLayout(theme, tileTheme),
|
||||||
|
visualDensity:
|
||||||
|
visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
|
||||||
|
isThreeLine:
|
||||||
|
isThreeLine ??
|
||||||
|
tileTheme.isThreeLine ??
|
||||||
|
theme.listTileTheme.isThreeLine ??
|
||||||
|
false,
|
||||||
|
textDirection: textDirection,
|
||||||
|
titleBaselineType:
|
||||||
|
titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!,
|
||||||
|
subtitleBaselineType:
|
||||||
|
subtitleStyle?.textBaseline ??
|
||||||
|
defaults.subtitleTextStyle!.textBaseline!,
|
||||||
|
horizontalTitleGap:
|
||||||
|
horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
|
||||||
|
minVerticalPadding:
|
||||||
|
minVerticalPadding ??
|
||||||
|
tileTheme.minVerticalPadding ??
|
||||||
|
defaults.minVerticalPadding!,
|
||||||
|
minLeadingWidth:
|
||||||
|
minLeadingWidth ??
|
||||||
|
tileTheme.minLeadingWidth ??
|
||||||
|
defaults.minLeadingWidth!,
|
||||||
|
minTileHeight: minTileHeight ?? tileTheme.minTileHeight,
|
||||||
|
titleAlignment: effectiveTitleAlignment,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (safeArea) {
|
||||||
|
child = SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: false,
|
||||||
|
minimum: resolvedContentPadding,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child = Padding(
|
||||||
|
padding: resolvedContentPadding,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
customBorder: shape ?? tileTheme.shape,
|
customBorder: shape ?? tileTheme.shape,
|
||||||
onTap: enabled ? onTap : null,
|
onTap: enabled ? onTap : null,
|
||||||
onLongPress: enabled ? onLongPress : null,
|
onLongPress: enabled ? onLongPress : null,
|
||||||
|
onSecondaryTap: enabled ? onSecondaryTap : null,
|
||||||
onFocusChange: onFocusChange,
|
onFocusChange: onFocusChange,
|
||||||
mouseCursor: effectiveMouseCursor,
|
mouseCursor: effectiveMouseCursor,
|
||||||
canRequestFocus: enabled,
|
canRequestFocus: enabled,
|
||||||
@@ -947,50 +1004,7 @@ class ListTile extends StatelessWidget {
|
|||||||
shape: shape ?? tileTheme.shape ?? const Border(),
|
shape: shape ?? tileTheme.shape ?? const Border(),
|
||||||
color: _tileBackgroundColor(theme, tileTheme, defaults),
|
color: _tileBackgroundColor(theme, tileTheme, defaults),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: child,
|
||||||
padding: resolvedContentPadding,
|
|
||||||
child: IconTheme.merge(
|
|
||||||
data: iconThemeData,
|
|
||||||
child: IconButtonTheme(
|
|
||||||
data: iconButtonThemeData,
|
|
||||||
child: _ListTile(
|
|
||||||
leading: leadingIcon,
|
|
||||||
title: titleText,
|
|
||||||
subtitle: subtitleText,
|
|
||||||
trailing: trailingIcon,
|
|
||||||
isDense: _isDenseLayout(theme, tileTheme),
|
|
||||||
visualDensity:
|
|
||||||
visualDensity ??
|
|
||||||
tileTheme.visualDensity ??
|
|
||||||
theme.visualDensity,
|
|
||||||
isThreeLine:
|
|
||||||
isThreeLine ??
|
|
||||||
tileTheme.isThreeLine ??
|
|
||||||
theme.listTileTheme.isThreeLine ??
|
|
||||||
false,
|
|
||||||
textDirection: textDirection,
|
|
||||||
titleBaselineType:
|
|
||||||
titleStyle.textBaseline ??
|
|
||||||
defaults.titleTextStyle!.textBaseline!,
|
|
||||||
subtitleBaselineType:
|
|
||||||
subtitleStyle?.textBaseline ??
|
|
||||||
defaults.subtitleTextStyle!.textBaseline!,
|
|
||||||
horizontalTitleGap:
|
|
||||||
horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
|
|
||||||
minVerticalPadding:
|
|
||||||
minVerticalPadding ??
|
|
||||||
tileTheme.minVerticalPadding ??
|
|
||||||
defaults.minVerticalPadding!,
|
|
||||||
minLeadingWidth:
|
|
||||||
minLeadingWidth ??
|
|
||||||
tileTheme.minLeadingWidth ??
|
|
||||||
defaults.minLeadingWidth!,
|
|
||||||
minTileHeight: minTileHeight ?? tileTheme.minTileHeight,
|
|
||||||
titleAlignment: effectiveTitleAlignment,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1313,12 +1327,7 @@ class _RenderListTile extends RenderBox
|
|||||||
@override
|
@override
|
||||||
Iterable<RenderBox> get children {
|
Iterable<RenderBox> get children {
|
||||||
final RenderBox? title = childForSlot(_ListTileSlot.title);
|
final RenderBox? title = childForSlot(_ListTileSlot.title);
|
||||||
return <RenderBox>[
|
return <RenderBox>[?leading, ?title, ?subtitle, ?trailing];
|
||||||
?leading,
|
|
||||||
?title,
|
|
||||||
?subtitle,
|
|
||||||
?trailing,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isDense => _isDense;
|
bool get isDense => _isDense;
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
/// @docImport 'text.dart';
|
/// @docImport 'text.dart';
|
||||||
library;
|
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/gestures.dart' show DragStartBehavior;
|
||||||
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
import 'package:flutter/material.dart' hide Scrollable, ScrollableState;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@@ -19,13 +19,14 @@ library;
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:PiliPlus/common/widgets/flutter/page/scrollable_helpers.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.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/rendering.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
export 'package:flutter/physics.dart' show Tolerance;
|
export 'package:flutter/physics.dart' show Tolerance;
|
||||||
|
|
||||||
@@ -664,10 +665,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
vsync: this,
|
vsync: this,
|
||||||
reverseDuration: const Duration(milliseconds: 500),
|
reverseDuration: const Duration(milliseconds: 500),
|
||||||
);
|
);
|
||||||
_anim = Tween<Offset>(
|
|
||||||
begin: Offset.zero,
|
|
||||||
end: const Offset(0, 1),
|
|
||||||
).animate(_animController);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@protected
|
@protected
|
||||||
@@ -786,7 +783,6 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
bool? _isSliding;
|
bool? _isSliding;
|
||||||
|
|
||||||
late AnimationController _animController;
|
late AnimationController _animController;
|
||||||
late Animation<Offset> _anim;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@protected
|
@protected
|
||||||
@@ -938,7 +934,7 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
if (_animController.value * _maxWidth +
|
if (_animController.value * _maxWidth +
|
||||||
(_isRTL ? (_maxWidth - dx) : dx) >=
|
(_isRTL ? (_maxWidth - dx) : dx) >=
|
||||||
100) {
|
100) {
|
||||||
Get.back();
|
Navigator.pop(context);
|
||||||
} else {
|
} else {
|
||||||
_animController.reverse();
|
_animController.reverse();
|
||||||
}
|
}
|
||||||
@@ -1185,8 +1181,15 @@ class CustomScrollableState extends State<CustomScrollable>
|
|||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
_maxWidth = constraints.maxWidth;
|
_maxWidth = constraints.maxWidth;
|
||||||
return SlideTransition(
|
return AnimatedBuilder(
|
||||||
position: _anim,
|
animation: _animController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Align(
|
||||||
|
alignment: AlignmentDirectional.topStart,
|
||||||
|
heightFactor: 1 - _animController.value,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
child: Material(
|
child: Material(
|
||||||
color: widget.bgColor,
|
color: widget.bgColor,
|
||||||
child: widget.header != null
|
child: widget.header != null
|
||||||
@@ -1935,7 +1938,7 @@ class _RenderScrollSemantics extends RenderProxyBox {
|
|||||||
if (child.isTagged(RenderViewport.excludeFromScrolling)) {
|
if (child.isTagged(RenderViewport.excludeFromScrolling)) {
|
||||||
excluded.add(child);
|
excluded.add(child);
|
||||||
} else {
|
} else {
|
||||||
if (!child.hasFlag(SemanticsFlag.isHidden)) {
|
if (!child.flagsCollection.isHidden) {
|
||||||
firstVisibleIndex ??= child.indexInParent;
|
firstVisibleIndex ??= child.indexInParent;
|
||||||
}
|
}
|
||||||
included.add(child);
|
included.add(child);
|
||||||
@@ -1980,222 +1983,3 @@ class _RestorableScrollOffset extends RestorableValue<double?> {
|
|||||||
@override
|
@override
|
||||||
bool get enabled => value != null;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
210
lib/common/widgets/flutter/page/scrollable_helpers.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:ui' show SemanticsRole;
|
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/foundation.dart' show clampDouble;
|
||||||
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
||||||
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
import 'package:flutter/material.dart' hide TabBarView, PageView;
|
||||||
@@ -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:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
@@ -6,25 +15,10 @@ import 'package:flutter/cupertino.dart';
|
|||||||
import 'package:flutter/foundation.dart' show clampDouble;
|
import 'package:flutter/foundation.dart' show clampDouble;
|
||||||
import 'package:flutter/material.dart' hide RefreshIndicator;
|
import 'package:flutter/material.dart' hide RefreshIndicator;
|
||||||
|
|
||||||
Widget refreshIndicator({
|
double displacement = Pref.refreshDisplacement;
|
||||||
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.
|
|
||||||
|
|
||||||
// The over-scroll distance that moves the indicator to its maximum
|
// The over-scroll distance that moves the indicator to its maximum
|
||||||
// displacement, as a percentage of the scrollable's container extent.
|
// displacement, as a percentage of the scrollable's container extent.
|
||||||
|
|
||||||
double displacement = Pref.refreshDisplacement;
|
|
||||||
double kDragContainerExtentPercentage = Pref.refreshDragPercentage;
|
double kDragContainerExtentPercentage = Pref.refreshDragPercentage;
|
||||||
|
|
||||||
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
|
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
|
||||||
@@ -403,15 +397,15 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
|||||||
_effectiveValueColor =
|
_effectiveValueColor =
|
||||||
widget.color ?? Theme.of(context).colorScheme.primary;
|
widget.color ?? Theme.of(context).colorScheme.primary;
|
||||||
final Color color = _effectiveValueColor;
|
final Color color = _effectiveValueColor;
|
||||||
if (color.alpha == 0x00) {
|
if (color.a == 0) {
|
||||||
// Set an always stopped animation instead of a driven tween.
|
// Set an always stopped animation instead of a driven tween.
|
||||||
_valueColor = AlwaysStoppedAnimation<Color>(color);
|
_valueColor = AlwaysStoppedAnimation<Color>(color);
|
||||||
} else {
|
} else {
|
||||||
// Respect the alpha of the given color.
|
// Respect the alpha of the given color.
|
||||||
_valueColor = _positionController.drive(
|
_valueColor = _positionController.drive(
|
||||||
ColorTween(
|
ColorTween(
|
||||||
begin: color.withAlpha(0),
|
begin: color.withValues(alpha: 0),
|
||||||
end: color.withAlpha(color.alpha),
|
end: color,
|
||||||
).chain(
|
).chain(
|
||||||
CurveTween(curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)),
|
CurveTween(curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit)),
|
||||||
),
|
),
|
||||||
@@ -555,7 +549,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
|||||||
1.0,
|
1.0,
|
||||||
); // This triggers various rebuilds.
|
); // This triggers various rebuilds.
|
||||||
if (_status == RefreshIndicatorStatus.drag &&
|
if (_status == RefreshIndicatorStatus.drag &&
|
||||||
_valueColor.value!.alpha == _effectiveValueColor.alpha) {
|
_valueColor.value!.a == _effectiveValueColor.a) {
|
||||||
_status = RefreshIndicatorStatus.armed;
|
_status = RefreshIndicatorStatus.armed;
|
||||||
widget.onStatusChange?.call(_status);
|
widget.onStatusChange?.call(_status);
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
/// @docImport 'editable.dart';
|
/// @docImport 'editable.dart';
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui'
|
import 'dart:ui'
|
||||||
as ui
|
as ui
|
||||||
@@ -46,6 +45,15 @@ typedef _TextBoundaryAtPositionInText =
|
|||||||
|
|
||||||
const String _kEllipsis = '\u2026';
|
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.
|
/// A render object that displays a paragraph of text.
|
||||||
class RenderParagraph extends RenderBox
|
class RenderParagraph extends RenderBox
|
||||||
with
|
with
|
||||||
@@ -68,7 +76,7 @@ class RenderParagraph extends RenderBox
|
|||||||
'This feature was deprecated after v3.12.0-2.0.pre.',
|
'This feature was deprecated after v3.12.0-2.0.pre.',
|
||||||
)
|
)
|
||||||
double textScaleFactor = 1.0,
|
double textScaleFactor = 1.0,
|
||||||
TextScaler textScaler = TextScaler.noScaling,
|
TextScaler textScaler = const _UnspecifiedTextScaler(),
|
||||||
int? maxLines,
|
int? maxLines,
|
||||||
Locale? locale,
|
Locale? locale,
|
||||||
StrutStyle? strutStyle,
|
StrutStyle? strutStyle,
|
||||||
@@ -78,18 +86,16 @@ class RenderParagraph extends RenderBox
|
|||||||
Color? selectionColor,
|
Color? selectionColor,
|
||||||
SelectionRegistrar? registrar,
|
SelectionRegistrar? registrar,
|
||||||
required Color primary,
|
required Color primary,
|
||||||
|
VoidCallback? onShowMore,
|
||||||
}) : assert(text.debugAssertIsValid()),
|
}) : assert(text.debugAssertIsValid()),
|
||||||
|
assert(maxLines == null || maxLines > 0),
|
||||||
assert(
|
assert(
|
||||||
maxLines == null ||
|
identical(textScaler, const _UnspecifiedTextScaler()) ||
|
||||||
(maxLines > 0 &&
|
textScaleFactor == 1.0,
|
||||||
overflow != TextOverflow.ellipsis &&
|
|
||||||
overflow != TextOverflow.fade),
|
|
||||||
),
|
|
||||||
assert(
|
|
||||||
identical(textScaler, TextScaler.noScaling) || textScaleFactor == 1.0,
|
|
||||||
'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
|
'textScaleFactor is deprecated and cannot be specified when textScaler is specified.',
|
||||||
),
|
),
|
||||||
_primary = primary,
|
_primary = primary,
|
||||||
|
_onShowMore = onShowMore,
|
||||||
_softWrap = softWrap,
|
_softWrap = softWrap,
|
||||||
_overflow = overflow,
|
_overflow = overflow,
|
||||||
_selectionColor = selectionColor,
|
_selectionColor = selectionColor,
|
||||||
@@ -97,7 +103,7 @@ class RenderParagraph extends RenderBox
|
|||||||
text: text,
|
text: text,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
textScaler: textScaler == TextScaler.noScaling
|
textScaler: textScaler == const _UnspecifiedTextScaler()
|
||||||
? TextScaler.linear(textScaleFactor)
|
? TextScaler.linear(textScaleFactor)
|
||||||
: textScaler,
|
: textScaler,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
@@ -144,7 +150,22 @@ class RenderParagraph extends RenderBox
|
|||||||
|
|
||||||
/// The text to display.
|
/// The text to display.
|
||||||
InlineSpan get text => _textPainter.text!;
|
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)) {
|
switch (_textPainter.text!.compareTo(value)) {
|
||||||
case RenderComparison.identical:
|
case RenderComparison.identical:
|
||||||
return;
|
return;
|
||||||
@@ -294,6 +315,8 @@ class RenderParagraph extends RenderBox
|
|||||||
_disposeSelectableFragments();
|
_disposeSelectableFragments();
|
||||||
_textPainter.dispose();
|
_textPainter.dispose();
|
||||||
_textIntrinsicsCache?.dispose();
|
_textIntrinsicsCache?.dispose();
|
||||||
|
_tapGestureRecognizer?.dispose();
|
||||||
|
_tapGestureRecognizer = null;
|
||||||
_morePainter?.dispose();
|
_morePainter?.dispose();
|
||||||
_morePainter = null;
|
_morePainter = null;
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@@ -553,6 +576,18 @@ class RenderParagraph extends RenderBox
|
|||||||
@override
|
@override
|
||||||
@protected
|
@protected
|
||||||
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
||||||
|
if (_tapGestureRecognizer != null) {
|
||||||
|
if (_morePainter case final textPainter?) {
|
||||||
|
late final height = _textPainter.height;
|
||||||
|
if (position.dx < textPainter.width &&
|
||||||
|
position.dy > height &&
|
||||||
|
position.dy < height + textPainter.height) {
|
||||||
|
result.add(HitTestEntry(textPainter.text as TextSpan));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final GlyphInfo? glyph = _textPainter.getClosestGlyphForOffset(position);
|
final GlyphInfo? glyph = _textPainter.getClosestGlyphForOffset(position);
|
||||||
// The hit-test can't fall through the horizontal gaps between visually
|
// The hit-test can't fall through the horizontal gaps between visually
|
||||||
// adjacent characters on the same line, even with a large letter-spacing or
|
// adjacent characters on the same line, even with a large letter-spacing or
|
||||||
@@ -673,16 +708,20 @@ class RenderParagraph extends RenderBox
|
|||||||
|
|
||||||
Color _primary;
|
Color _primary;
|
||||||
|
|
||||||
set primary(Color primary) {
|
VoidCallback? _onShowMore;
|
||||||
if (_primary != primary) {
|
set onShowMore(VoidCallback? onShowMore) {
|
||||||
_primary = primary;
|
if (_onShowMore != onShowMore) {
|
||||||
_morePainter?.text = _moreTextSpan;
|
_onShowMore = onShowMore;
|
||||||
|
_tapGestureRecognizer?.onTap = onShowMore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSpan get _moreTextSpan => TextSpan(
|
TapGestureRecognizer? _tapGestureRecognizer;
|
||||||
style: text.style!.copyWith(color: _primary),
|
|
||||||
|
TextSpan _moreTextSpan([TextStyle? style]) => TextSpan(
|
||||||
|
style: (style ?? text.style!).copyWith(color: _primary),
|
||||||
text: '查看更多',
|
text: '查看更多',
|
||||||
|
recognizer: _tapGestureRecognizer,
|
||||||
);
|
);
|
||||||
TextPainter? _morePainter;
|
TextPainter? _morePainter;
|
||||||
|
|
||||||
@@ -709,8 +748,11 @@ class RenderParagraph extends RenderBox
|
|||||||
size.height < textSize.height || _textPainter.didExceedMaxLines;
|
size.height < textSize.height || _textPainter.didExceedMaxLines;
|
||||||
|
|
||||||
if (didOverflowHeight) {
|
if (didOverflowHeight) {
|
||||||
|
if (_onShowMore != null) {
|
||||||
|
_tapGestureRecognizer ??= TapGestureRecognizer()..onTap = _onShowMore;
|
||||||
|
}
|
||||||
_morePainter ??= TextPainter(
|
_morePainter ??= TextPainter(
|
||||||
text: _moreTextSpan,
|
text: _moreTextSpan(),
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
textScaler: textScaler,
|
textScaler: textScaler,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
@@ -812,6 +854,11 @@ class RenderParagraph extends RenderBox
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(() {
|
||||||
|
_textPainter.debugPaintTextLayoutBoxes = debugPaintTextLayoutBoxes;
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
_textPainter.paint(context.canvas, offset);
|
_textPainter.paint(context.canvas, offset);
|
||||||
|
|
||||||
paintInlineChildren(context, offset);
|
paintInlineChildren(context, offset);
|
||||||
@@ -983,8 +1030,9 @@ class RenderParagraph extends RenderBox
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (needsAssembleSemanticsNode) {
|
if (needsAssembleSemanticsNode) {
|
||||||
config.explicitChildNodes = true;
|
config
|
||||||
config.isSemanticBoundary = true;
|
..explicitChildNodes = true
|
||||||
|
..isSemanticBoundary = true;
|
||||||
} else if (needsChildConfigurationsDelegate) {
|
} else if (needsChildConfigurationsDelegate) {
|
||||||
config.childConfigurationsDelegate =
|
config.childConfigurationsDelegate =
|
||||||
_childSemanticsConfigurationsDelegate;
|
_childSemanticsConfigurationsDelegate;
|
||||||
@@ -1013,8 +1061,9 @@ class RenderParagraph extends RenderBox
|
|||||||
AttributedString(buffer.toString(), attributes: attributes),
|
AttributedString(buffer.toString(), attributes: attributes),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
config.attributedLabel = _cachedAttributedLabels![0];
|
config
|
||||||
config.textDirection = textDirection;
|
..attributedLabel = _cachedAttributedLabels![0]
|
||||||
|
..textDirection = textDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,7 +1145,7 @@ class RenderParagraph extends RenderBox
|
|||||||
// can be re-used when [assembleSemanticsNode] is called again. This ensures
|
// can be re-used when [assembleSemanticsNode] is called again. This ensures
|
||||||
// stable ids for the [SemanticsNode]s of [TextSpan]s across
|
// stable ids for the [SemanticsNode]s of [TextSpan]s across
|
||||||
// [assembleSemanticsNode] invocations.
|
// [assembleSemanticsNode] invocations.
|
||||||
LinkedHashMap<Key, SemanticsNode>? _cachedChildNodes;
|
Map<Key, SemanticsNode>? _cachedChildNodes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void assembleSemanticsNode(
|
void assembleSemanticsNode(
|
||||||
@@ -1113,8 +1162,7 @@ class RenderParagraph extends RenderBox
|
|||||||
int placeholderIndex = 0;
|
int placeholderIndex = 0;
|
||||||
int childIndex = 0;
|
int childIndex = 0;
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
final LinkedHashMap<Key, SemanticsNode> newChildCache =
|
final Map<Key, SemanticsNode> newChildCache = <Key, SemanticsNode>{};
|
||||||
LinkedHashMap<Key, SemanticsNode>();
|
|
||||||
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
|
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
|
||||||
for (final InlineSpanSemanticsInformation info
|
for (final InlineSpanSemanticsInformation info
|
||||||
in _cachedCombinedSemanticsInfos!) {
|
in _cachedCombinedSemanticsInfos!) {
|
||||||
@@ -1184,8 +1232,9 @@ class RenderParagraph extends RenderBox
|
|||||||
onDoubleTap: final VoidCallback? handler,
|
onDoubleTap: final VoidCallback? handler,
|
||||||
):
|
):
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
configuration.onTap = handler;
|
configuration
|
||||||
configuration.isLink = true;
|
..onTap = handler
|
||||||
|
..isLink = true;
|
||||||
}
|
}
|
||||||
case LongPressGestureRecognizer(
|
case LongPressGestureRecognizer(
|
||||||
onLongPress: final GestureLongPressCallback? onLongPress,
|
onLongPress: final GestureLongPressCallback? onLongPress,
|
||||||
@@ -1255,29 +1304,30 @@ class RenderParagraph extends RenderBox
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(EnumProperty<TextAlign>('textAlign', textAlign));
|
properties
|
||||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
|
..add(EnumProperty<TextAlign>('textAlign', textAlign))
|
||||||
properties.add(
|
..add(EnumProperty<TextDirection>('textDirection', textDirection))
|
||||||
FlagProperty(
|
..add(
|
||||||
'softWrap',
|
FlagProperty(
|
||||||
value: softWrap,
|
'softWrap',
|
||||||
ifTrue: 'wrapping at box width',
|
value: softWrap,
|
||||||
ifFalse: 'no wrapping except at line break characters',
|
ifTrue: 'wrapping at box width',
|
||||||
showName: true,
|
ifFalse: 'no wrapping except at line break characters',
|
||||||
),
|
showName: true,
|
||||||
);
|
),
|
||||||
properties.add(EnumProperty<TextOverflow>('overflow', overflow));
|
)
|
||||||
properties.add(
|
..add(EnumProperty<TextOverflow>('overflow', overflow))
|
||||||
DiagnosticsProperty<TextScaler>(
|
..add(
|
||||||
'textScaler',
|
DiagnosticsProperty<TextScaler>(
|
||||||
textScaler,
|
'textScaler',
|
||||||
defaultValue: TextScaler.noScaling,
|
textScaler,
|
||||||
),
|
defaultValue: TextScaler.noScaling,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
..add(
|
||||||
);
|
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
||||||
properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
|
)
|
||||||
|
..add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1738,8 +1788,7 @@ class _SelectableFragment
|
|||||||
final TextPosition? existingSelectionEnd = _textSelectionEnd;
|
final TextPosition? existingSelectionEnd = _textSelectionEnd;
|
||||||
|
|
||||||
_setSelectionPosition(null, isEnd: isEnd);
|
_setSelectionPosition(null, isEnd: isEnd);
|
||||||
final Matrix4 transform = paragraph.getTransformTo(null);
|
final Matrix4 transform = paragraph.getTransformTo(null)..invert();
|
||||||
transform.invert();
|
|
||||||
final Offset localPosition = MatrixUtils.transformPoint(
|
final Offset localPosition = MatrixUtils.transformPoint(
|
||||||
transform,
|
transform,
|
||||||
globalPosition,
|
globalPosition,
|
||||||
@@ -1812,8 +1861,7 @@ class _SelectableFragment
|
|||||||
required bool isEnd,
|
required bool isEnd,
|
||||||
}) {
|
}) {
|
||||||
_setSelectionPosition(null, isEnd: isEnd);
|
_setSelectionPosition(null, isEnd: isEnd);
|
||||||
final Matrix4 transform = paragraph.getTransformTo(null);
|
final Matrix4 transform = paragraph.getTransformTo(null)..invert();
|
||||||
transform.invert();
|
|
||||||
final Offset localPosition = MatrixUtils.transformPoint(
|
final Offset localPosition = MatrixUtils.transformPoint(
|
||||||
transform,
|
transform,
|
||||||
globalPosition,
|
globalPosition,
|
||||||
@@ -2318,8 +2366,8 @@ class _SelectableFragment
|
|||||||
existingSelectionEnd,
|
existingSelectionEnd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final Matrix4 originTransform = originParagraph.getTransformTo(null);
|
final Matrix4 originTransform = originParagraph.getTransformTo(null)
|
||||||
originTransform.invert();
|
..invert();
|
||||||
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
|
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
|
||||||
originTransform,
|
originTransform,
|
||||||
globalPosition,
|
globalPosition,
|
||||||
@@ -2623,8 +2671,8 @@ class _SelectableFragment
|
|||||||
existingSelectionEnd,
|
existingSelectionEnd,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final Matrix4 originTransform = originParagraph.getTransformTo(null);
|
final Matrix4 originTransform = originParagraph.getTransformTo(null)
|
||||||
originTransform.invert();
|
..invert();
|
||||||
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
|
final Offset originParagraphLocalPosition = MatrixUtils.transformPoint(
|
||||||
originTransform,
|
originTransform,
|
||||||
globalPosition,
|
globalPosition,
|
||||||
@@ -3086,8 +3134,7 @@ class _SelectableFragment
|
|||||||
RenderObject? current = paragraph;
|
RenderObject? current = paragraph;
|
||||||
while (current != null) {
|
while (current != null) {
|
||||||
if (current is RenderParagraph) {
|
if (current is RenderParagraph) {
|
||||||
final Matrix4 currentTransform = current.getTransformTo(null);
|
final Matrix4 currentTransform = current.getTransformTo(null)..invert();
|
||||||
currentTransform.invert();
|
|
||||||
final Offset currentParagraphLocalPosition = MatrixUtils.transformPoint(
|
final Offset currentParagraphLocalPosition = MatrixUtils.transformPoint(
|
||||||
currentTransform,
|
currentTransform,
|
||||||
globalPosition,
|
globalPosition,
|
||||||
@@ -3779,13 +3826,14 @@ class _SelectableFragment
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(
|
properties
|
||||||
DiagnosticsProperty<String>(
|
..add(
|
||||||
'textInsideRange',
|
DiagnosticsProperty<String>(
|
||||||
range.textInside(fullText),
|
'textInsideRange',
|
||||||
),
|
range.textInside(fullText),
|
||||||
);
|
),
|
||||||
properties.add(DiagnosticsProperty<TextRange>('range', range));
|
)
|
||||||
properties.add(DiagnosticsProperty<String>('fullText', fullText));
|
..add(DiagnosticsProperty<TextRange>('range', range))
|
||||||
|
..add(DiagnosticsProperty<String>('fullText', fullText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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/material.dart';
|
||||||
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
||||||
|
|
||||||
@@ -114,6 +118,8 @@ class RichText extends MultiChildRenderObjectWidget {
|
|||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
this.selectionRegistrar,
|
this.selectionRegistrar,
|
||||||
this.selectionColor,
|
this.selectionColor,
|
||||||
|
this.onShowMore,
|
||||||
|
required this.primary,
|
||||||
}) : assert(maxLines == null || maxLines > 0),
|
}) : assert(maxLines == null || maxLines > 0),
|
||||||
assert(selectionRegistrar == null || selectionColor != null),
|
assert(selectionRegistrar == null || selectionColor != null),
|
||||||
assert(
|
assert(
|
||||||
@@ -228,6 +234,10 @@ class RichText extends MultiChildRenderObjectWidget {
|
|||||||
/// widgets.
|
/// widgets.
|
||||||
final Color? selectionColor;
|
final Color? selectionColor;
|
||||||
|
|
||||||
|
final Color primary;
|
||||||
|
|
||||||
|
final VoidCallback? onShowMore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RenderParagraph createRenderObject(BuildContext context) {
|
RenderParagraph createRenderObject(BuildContext context) {
|
||||||
assert(textDirection != null || debugCheckHasDirectionality(context));
|
assert(textDirection != null || debugCheckHasDirectionality(context));
|
||||||
@@ -245,7 +255,8 @@ class RichText extends MultiChildRenderObjectWidget {
|
|||||||
locale: locale ?? Localizations.maybeLocaleOf(context),
|
locale: locale ?? Localizations.maybeLocaleOf(context),
|
||||||
registrar: selectionRegistrar,
|
registrar: selectionRegistrar,
|
||||||
selectionColor: selectionColor,
|
selectionColor: selectionColor,
|
||||||
primary: Theme.of(context).colorScheme.primary,
|
primary: primary,
|
||||||
|
onShowMore: onShowMore,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +264,7 @@ class RichText extends MultiChildRenderObjectWidget {
|
|||||||
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
|
void updateRenderObject(BuildContext context, RenderParagraph renderObject) {
|
||||||
assert(textDirection != null || debugCheckHasDirectionality(context));
|
assert(textDirection != null || debugCheckHasDirectionality(context));
|
||||||
renderObject
|
renderObject
|
||||||
..text = text
|
..text = (text: text, primary: primary)
|
||||||
..textAlign = textAlign
|
..textAlign = textAlign
|
||||||
..textDirection = textDirection ?? Directionality.of(context)
|
..textDirection = textDirection ?? Directionality.of(context)
|
||||||
..softWrap = softWrap
|
..softWrap = softWrap
|
||||||
@@ -266,74 +277,75 @@ class RichText extends MultiChildRenderObjectWidget {
|
|||||||
..locale = locale ?? Localizations.maybeLocaleOf(context)
|
..locale = locale ?? Localizations.maybeLocaleOf(context)
|
||||||
..registrar = selectionRegistrar
|
..registrar = selectionRegistrar
|
||||||
..selectionColor = selectionColor
|
..selectionColor = selectionColor
|
||||||
..primary = Theme.of(context).colorScheme.primary;
|
..onShowMore = onShowMore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(
|
properties
|
||||||
EnumProperty<TextAlign>(
|
..add(
|
||||||
'textAlign',
|
EnumProperty<TextAlign>(
|
||||||
textAlign,
|
'textAlign',
|
||||||
defaultValue: TextAlign.start,
|
textAlign,
|
||||||
),
|
defaultValue: TextAlign.start,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
EnumProperty<TextDirection>(
|
..add(
|
||||||
'textDirection',
|
EnumProperty<TextDirection>(
|
||||||
textDirection,
|
'textDirection',
|
||||||
defaultValue: null,
|
textDirection,
|
||||||
),
|
defaultValue: null,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
FlagProperty(
|
..add(
|
||||||
'softWrap',
|
FlagProperty(
|
||||||
value: softWrap,
|
'softWrap',
|
||||||
ifTrue: 'wrapping at box width',
|
value: softWrap,
|
||||||
ifFalse: 'no wrapping except at line break characters',
|
ifTrue: 'wrapping at box width',
|
||||||
showName: true,
|
ifFalse: 'no wrapping except at line break characters',
|
||||||
),
|
showName: true,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
EnumProperty<TextOverflow>(
|
..add(
|
||||||
'overflow',
|
EnumProperty<TextOverflow>(
|
||||||
overflow,
|
'overflow',
|
||||||
defaultValue: TextOverflow.clip,
|
overflow,
|
||||||
),
|
defaultValue: TextOverflow.clip,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
DiagnosticsProperty<TextScaler>(
|
..add(
|
||||||
'textScaler',
|
DiagnosticsProperty<TextScaler>(
|
||||||
textScaler,
|
'textScaler',
|
||||||
defaultValue: TextScaler.noScaling,
|
textScaler,
|
||||||
),
|
defaultValue: TextScaler.noScaling,
|
||||||
);
|
),
|
||||||
properties.add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
|
)
|
||||||
properties.add(
|
..add(IntProperty('maxLines', maxLines, ifNull: 'unlimited'))
|
||||||
EnumProperty<TextWidthBasis>(
|
..add(
|
||||||
'textWidthBasis',
|
EnumProperty<TextWidthBasis>(
|
||||||
textWidthBasis,
|
'textWidthBasis',
|
||||||
defaultValue: TextWidthBasis.parent,
|
textWidthBasis,
|
||||||
),
|
defaultValue: TextWidthBasis.parent,
|
||||||
);
|
),
|
||||||
properties.add(StringProperty('text', text.toPlainText()));
|
)
|
||||||
properties.add(
|
..add(StringProperty('text', text.toPlainText()))
|
||||||
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
..add(
|
||||||
);
|
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
||||||
properties.add(
|
)
|
||||||
DiagnosticsProperty<StrutStyle>(
|
..add(
|
||||||
'strutStyle',
|
DiagnosticsProperty<StrutStyle>(
|
||||||
strutStyle,
|
'strutStyle',
|
||||||
defaultValue: null,
|
strutStyle,
|
||||||
),
|
defaultValue: null,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
DiagnosticsProperty<TextHeightBehavior>(
|
..add(
|
||||||
'textHeightBehavior',
|
DiagnosticsProperty<TextHeightBehavior>(
|
||||||
textHeightBehavior,
|
'textHeightBehavior',
|
||||||
defaultValue: null,
|
textHeightBehavior,
|
||||||
),
|
defaultValue: null,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,8 +17,8 @@ library;
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui show TextHeightBehavior;
|
import 'dart:ui' as ui show TextHeightBehavior;
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/text/paragraph.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart';
|
||||||
import 'package:PiliPlus/common/widgets/text/rich_text.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text/rich_text.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart' hide RichText;
|
import 'package:flutter/material.dart' hide RichText;
|
||||||
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
import 'package:flutter/rendering.dart' hide RenderParagraph;
|
||||||
@@ -47,7 +47,11 @@ import 'package:flutter/rendering.dart' hide RenderParagraph;
|
|||||||
/// Container(
|
/// Container(
|
||||||
/// width: 100,
|
/// width: 100,
|
||||||
/// decoration: BoxDecoration(border: Border.all()),
|
/// 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}
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
@@ -60,10 +64,11 @@ import 'package:flutter/rendering.dart' hide RenderParagraph;
|
|||||||
/// 
|
/// 
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// Text(
|
/// const Text(
|
||||||
|
/// 'Hello, how are you?',
|
||||||
/// overflow: TextOverflow.fade,
|
/// overflow: TextOverflow.fade,
|
||||||
/// maxLines: 1,
|
/// maxLines: 1,
|
||||||
/// 'Hello $_name, how are you?')
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Here soft wrapping is enabled and the [Text] widget tries to wrap the words
|
/// 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;
|
|||||||
/// 
|
/// 
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
/// Text(
|
/// const Text(
|
||||||
|
/// 'Hello, how are you?',
|
||||||
/// overflow: TextOverflow.fade,
|
/// overflow: TextOverflow.fade,
|
||||||
/// softWrap: false,
|
/// softWrap: false,
|
||||||
/// 'Hello $_name, how are you?')
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Here soft wrapping is disabled with `softWrap: false` and the [Text] widget
|
/// Here soft wrapping is disabled with `softWrap: false` and the [Text] widget
|
||||||
@@ -174,6 +180,8 @@ class Text extends StatelessWidget {
|
|||||||
this.textWidthBasis,
|
this.textWidthBasis,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
this.selectionColor,
|
this.selectionColor,
|
||||||
|
this.onShowMore,
|
||||||
|
required this.primary,
|
||||||
}) : textSpan = null,
|
}) : textSpan = null,
|
||||||
assert(
|
assert(
|
||||||
textScaler == null || textScaleFactor == null,
|
textScaler == null || textScaleFactor == null,
|
||||||
@@ -211,6 +219,8 @@ class Text extends StatelessWidget {
|
|||||||
this.textWidthBasis,
|
this.textWidthBasis,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
this.selectionColor,
|
this.selectionColor,
|
||||||
|
this.onShowMore,
|
||||||
|
required this.primary,
|
||||||
}) : data = null,
|
}) : data = null,
|
||||||
assert(
|
assert(
|
||||||
textScaler == null || textScaleFactor == null,
|
textScaler == null || textScaleFactor == null,
|
||||||
@@ -349,6 +359,10 @@ class Text extends StatelessWidget {
|
|||||||
/// (semi-transparent grey).
|
/// (semi-transparent grey).
|
||||||
final Color? selectionColor;
|
final Color? selectionColor;
|
||||||
|
|
||||||
|
final Color primary;
|
||||||
|
|
||||||
|
final VoidCallback? onShowMore;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
||||||
@@ -402,8 +416,10 @@ class Text extends StatelessWidget {
|
|||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: effectiveTextStyle,
|
style: effectiveTextStyle,
|
||||||
text: data,
|
text: data,
|
||||||
|
locale: locale,
|
||||||
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
||||||
),
|
),
|
||||||
|
primary: primary,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -433,8 +449,11 @@ class Text extends StatelessWidget {
|
|||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: effectiveTextStyle,
|
style: effectiveTextStyle,
|
||||||
text: data,
|
text: data,
|
||||||
|
locale: locale,
|
||||||
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
children: textSpan != null ? <InlineSpan>[textSpan!] : null,
|
||||||
),
|
),
|
||||||
|
onShowMore: onShowMore,
|
||||||
|
primary: primary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (semanticsLabel != null || semanticsIdentifier != null) {
|
if (semanticsLabel != null || semanticsIdentifier != null) {
|
||||||
@@ -532,6 +551,7 @@ class _SelectableTextContainer extends StatefulWidget {
|
|||||||
required this.textWidthBasis,
|
required this.textWidthBasis,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
required this.selectionColor,
|
required this.selectionColor,
|
||||||
|
required this.primary,
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextSpan text;
|
final TextSpan text;
|
||||||
@@ -546,6 +566,7 @@ class _SelectableTextContainer extends StatefulWidget {
|
|||||||
final TextWidthBasis textWidthBasis;
|
final TextWidthBasis textWidthBasis;
|
||||||
final ui.TextHeightBehavior? textHeightBehavior;
|
final ui.TextHeightBehavior? textHeightBehavior;
|
||||||
final Color selectionColor;
|
final Color selectionColor;
|
||||||
|
final Color primary;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_SelectableTextContainer> createState() =>
|
State<_SelectableTextContainer> createState() =>
|
||||||
@@ -588,6 +609,7 @@ class _SelectableTextContainerState extends State<_SelectableTextContainer> {
|
|||||||
textHeightBehavior: widget.textHeightBehavior,
|
textHeightBehavior: widget.textHeightBehavior,
|
||||||
selectionColor: widget.selectionColor,
|
selectionColor: widget.selectionColor,
|
||||||
text: widget.text,
|
text: widget.text,
|
||||||
|
primary: widget.primary,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -608,6 +630,7 @@ class _RichText extends StatelessWidget {
|
|||||||
required this.textWidthBasis,
|
required this.textWidthBasis,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
required this.selectionColor,
|
required this.selectionColor,
|
||||||
|
required this.primary,
|
||||||
});
|
});
|
||||||
|
|
||||||
final GlobalKey? textKey;
|
final GlobalKey? textKey;
|
||||||
@@ -623,6 +646,7 @@ class _RichText extends StatelessWidget {
|
|||||||
final TextWidthBasis textWidthBasis;
|
final TextWidthBasis textWidthBasis;
|
||||||
final ui.TextHeightBehavior? textHeightBehavior;
|
final ui.TextHeightBehavior? textHeightBehavior;
|
||||||
final Color selectionColor;
|
final Color selectionColor;
|
||||||
|
final Color primary;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -642,6 +666,7 @@ class _RichText extends StatelessWidget {
|
|||||||
selectionRegistrar: registrar,
|
selectionRegistrar: registrar,
|
||||||
selectionColor: selectionColor,
|
selectionColor: selectionColor,
|
||||||
text: text,
|
text: text,
|
||||||
|
primary: primary,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1113,7 @@ class _SelectableTextContainerDelegate
|
|||||||
bool forwardSelection =
|
bool forwardSelection =
|
||||||
currentSelectionEndIndex >= currentSelectionStartIndex;
|
currentSelectionEndIndex >= currentSelectionStartIndex;
|
||||||
if (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.
|
// Use the range from the selectable within the selection as the source of truth for selection direction.
|
||||||
final SelectedContentRange rangeAtSelectableInSelection =
|
final SelectedContentRange rangeAtSelectableInSelection =
|
||||||
selectables[currentSelectionStartIndex].getSelection()!;
|
selectables[currentSelectionStartIndex].getSelection()!;
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
/// @docImport 'text_field.dart';
|
/// @docImport 'text_field.dart';
|
||||||
library;
|
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/cupertino.dart' hide EditableText, EditableTextState;
|
||||||
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
|
import 'package:flutter/material.dart' hide EditableText, EditableTextState;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@@ -311,8 +311,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// If there aren't any buttons to build, build an empty toolbar.
|
// If there aren't any buttons to build, build an empty toolbar.
|
||||||
if ((children != null && children!.isEmpty) ||
|
if ((children ?? buttonItems)?.isEmpty ?? true) {
|
||||||
(buttonItems != null && buttonItems!.isEmpty)) {
|
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
/// @docImport 'package:flutter/material.dart';
|
/// @docImport 'package:flutter/material.dart';
|
||||||
library;
|
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/cupertino.dart' hide EditableText, EditableTextState;
|
||||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
|
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@@ -209,7 +209,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// If there aren't any buttons to build, build an empty toolbar.
|
// 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();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
/// @docImport 'package:flutter/material.dart';
|
/// @docImport 'package:flutter/material.dart';
|
||||||
library;
|
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/cupertino.dart' hide EditableText, EditableTextState;
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart'
|
import 'package:flutter/services.dart'
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
/// @docImport 'package:flutter/cupertino.dart';
|
/// @docImport 'package:flutter/cupertino.dart';
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'dart:collection';
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui'
|
import 'dart:ui'
|
||||||
as ui
|
as ui
|
||||||
@@ -16,10 +15,10 @@ import 'dart:ui'
|
|||||||
SemanticsInputType,
|
SemanticsInputType,
|
||||||
TextBox;
|
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/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
@@ -153,7 +152,10 @@ class VerticalCaretMovementRun implements Iterator<TextPosition> {
|
|||||||
final TextPosition closestPosition = _editable._textPainter
|
final TextPosition closestPosition = _editable._textPainter
|
||||||
.getPositionForOffset(newOffset);
|
.getPositionForOffset(newOffset);
|
||||||
final MapEntry<Offset, TextPosition> position =
|
final MapEntry<Offset, TextPosition> position =
|
||||||
MapEntry<Offset, TextPosition>(newOffset, closestPosition);
|
MapEntry<Offset, TextPosition>(
|
||||||
|
newOffset,
|
||||||
|
closestPosition,
|
||||||
|
);
|
||||||
_positionCache[lineNumber] = position;
|
_positionCache[lineNumber] = position;
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
@@ -294,8 +296,8 @@ class RenderEditable extends RenderBox
|
|||||||
bool paintCursorAboveText = false,
|
bool paintCursorAboveText = false,
|
||||||
Offset cursorOffset = Offset.zero,
|
Offset cursorOffset = Offset.zero,
|
||||||
double devicePixelRatio = 1.0,
|
double devicePixelRatio = 1.0,
|
||||||
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.tight,
|
ui.BoxHeightStyle selectionHeightStyle = ui.BoxHeightStyle.max,
|
||||||
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.tight,
|
ui.BoxWidthStyle selectionWidthStyle = ui.BoxWidthStyle.max,
|
||||||
bool? enableInteractiveSelection,
|
bool? enableInteractiveSelection,
|
||||||
this.floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
|
this.floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
|
||||||
TextRange? promptRectRange,
|
TextRange? promptRectRange,
|
||||||
@@ -1301,7 +1303,7 @@ class RenderEditable extends RenderBox
|
|||||||
// can be re-used when [assembleSemanticsNode] is called again. This ensures
|
// can be re-used when [assembleSemanticsNode] is called again. This ensures
|
||||||
// stable ids for the [SemanticsNode]s of [TextSpan]s across
|
// stable ids for the [SemanticsNode]s of [TextSpan]s across
|
||||||
// [assembleSemanticsNode] invocations.
|
// [assembleSemanticsNode] invocations.
|
||||||
LinkedHashMap<Key, SemanticsNode>? _cachedChildNodes;
|
Map<Key, SemanticsNode>? _cachedChildNodes;
|
||||||
|
|
||||||
/// Returns a list of rects that bound the given selection, and the text
|
/// Returns a list of rects that bound the given selection, and the text
|
||||||
/// direction. The text direction is used by the engine to calculate
|
/// direction. The text direction is used by the engine to calculate
|
||||||
@@ -1311,7 +1313,11 @@ class RenderEditable extends RenderBox
|
|||||||
List<TextBox> getBoxesForSelection(TextSelection selection) {
|
List<TextBox> getBoxesForSelection(TextSelection selection) {
|
||||||
_computeTextMetricsIfNeeded();
|
_computeTextMetricsIfNeeded();
|
||||||
return _textPainter
|
return _textPainter
|
||||||
.getBoxesForSelection(selection)
|
.getBoxesForSelection(
|
||||||
|
selection,
|
||||||
|
boxHeightStyle: selectionHeightStyle,
|
||||||
|
boxWidthStyle: selectionWidthStyle,
|
||||||
|
)
|
||||||
.map(
|
.map(
|
||||||
(TextBox textBox) => TextBox.fromLTRBD(
|
(TextBox textBox) => TextBox.fromLTRBD(
|
||||||
textBox.left + _paintOffset.dx,
|
textBox.left + _paintOffset.dx,
|
||||||
@@ -1381,6 +1387,7 @@ class RenderEditable extends RenderBox
|
|||||||
..isMultiline = _isMultiline
|
..isMultiline = _isMultiline
|
||||||
..textDirection = textDirection
|
..textDirection = textDirection
|
||||||
..isFocused = hasFocus
|
..isFocused = hasFocus
|
||||||
|
..isFocusable = true
|
||||||
..isTextField = true
|
..isTextField = true
|
||||||
..isReadOnly = readOnly
|
..isReadOnly = readOnly
|
||||||
// This is the default for customer that uses RenderEditable directly.
|
// This is the default for customer that uses RenderEditable directly.
|
||||||
@@ -1437,8 +1444,7 @@ class RenderEditable extends RenderBox
|
|||||||
int placeholderIndex = 0;
|
int placeholderIndex = 0;
|
||||||
int childIndex = 0;
|
int childIndex = 0;
|
||||||
RenderBox? child = firstChild;
|
RenderBox? child = firstChild;
|
||||||
final LinkedHashMap<Key, SemanticsNode> newChildCache =
|
final Map<Key, SemanticsNode> newChildCache = <Key, SemanticsNode>{};
|
||||||
LinkedHashMap<Key, SemanticsNode>();
|
|
||||||
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
|
_cachedCombinedSemanticsInfos ??= combineSemanticsInfo(_semanticsInfo!);
|
||||||
for (final InlineSpanSemanticsInformation info
|
for (final InlineSpanSemanticsInformation info
|
||||||
in _cachedCombinedSemanticsInfos!) {
|
in _cachedCombinedSemanticsInfos!) {
|
||||||
@@ -1507,8 +1513,9 @@ class RenderEditable extends RenderBox
|
|||||||
onDoubleTap: final VoidCallback? handler,
|
onDoubleTap: final VoidCallback? handler,
|
||||||
):
|
):
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
configuration.onTap = handler;
|
configuration
|
||||||
configuration.isLink = true;
|
..onTap = handler
|
||||||
|
..isLink = true;
|
||||||
}
|
}
|
||||||
case LongPressGestureRecognizer(
|
case LongPressGestureRecognizer(
|
||||||
onLongPress: final GestureLongPressCallback? onLongPress,
|
onLongPress: final GestureLongPressCallback? onLongPress,
|
||||||
@@ -2203,17 +2210,14 @@ class RenderEditable extends RenderBox
|
|||||||
Offset? to,
|
Offset? to,
|
||||||
required SelectionChangedCause cause,
|
required SelectionChangedCause cause,
|
||||||
}) {
|
}) {
|
||||||
final localFrom = globalToLocal(from);
|
|
||||||
_computeTextMetricsIfNeeded();
|
_computeTextMetricsIfNeeded();
|
||||||
|
final localFrom = globalToLocal(from);
|
||||||
final TextPosition fromPosition = _textPainter.getPositionForOffset(
|
final TextPosition fromPosition = _textPainter.getPositionForOffset(
|
||||||
localFrom - _paintOffset,
|
localFrom - _paintOffset,
|
||||||
);
|
);
|
||||||
|
|
||||||
final TextPosition? toPosition = to == null
|
final TextPosition? toPosition = to == null
|
||||||
? null
|
? null
|
||||||
: _textPainter.getPositionForOffset(
|
: _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset);
|
||||||
globalToLocal(to) - _paintOffset,
|
|
||||||
);
|
|
||||||
|
|
||||||
int baseOffset = fromPosition.offset;
|
int baseOffset = fromPosition.offset;
|
||||||
int extentOffset = toPosition?.offset ?? fromPosition.offset;
|
int extentOffset = toPosition?.offset ?? fromPosition.offset;
|
||||||
@@ -2265,7 +2269,6 @@ class RenderEditable extends RenderBox
|
|||||||
/// beginning and end of a word respectively.
|
/// beginning and end of a word respectively.
|
||||||
///
|
///
|
||||||
/// {@macro flutter.rendering.RenderEditable.selectPosition}
|
/// {@macro flutter.rendering.RenderEditable.selectPosition}
|
||||||
|
|
||||||
void selectWordsInRange({
|
void selectWordsInRange({
|
||||||
required Offset from,
|
required Offset from,
|
||||||
Offset? to,
|
Offset? to,
|
||||||
@@ -2278,9 +2281,7 @@ class RenderEditable extends RenderBox
|
|||||||
final TextSelection fromWord = getWordAtOffset(fromPosition);
|
final TextSelection fromWord = getWordAtOffset(fromPosition);
|
||||||
final TextPosition toPosition = to == null
|
final TextPosition toPosition = to == null
|
||||||
? fromPosition
|
? fromPosition
|
||||||
: _textPainter.getPositionForOffset(
|
: _textPainter.getPositionForOffset(globalToLocal(to) - _paintOffset);
|
||||||
globalToLocal(to) - _paintOffset,
|
|
||||||
);
|
|
||||||
final TextSelection toWord = toPosition == fromPosition
|
final TextSelection toWord = toPosition == fromPosition
|
||||||
? fromWord
|
? fromWord
|
||||||
: getWordAtOffset(toPosition);
|
: getWordAtOffset(toPosition);
|
||||||
@@ -2526,9 +2527,7 @@ class RenderEditable extends RenderBox
|
|||||||
..layout(minWidth: minWidth, maxWidth: maxWidth);
|
..layout(minWidth: minWidth, maxWidth: maxWidth);
|
||||||
final double width = forceLine
|
final double width = forceLine
|
||||||
? constraints.maxWidth
|
? constraints.maxWidth
|
||||||
: constraints.constrainWidth(
|
: constraints.constrainWidth(_textIntrinsics.size.width + _caretMargin);
|
||||||
_textIntrinsics.size.width + _caretMargin,
|
|
||||||
);
|
|
||||||
return Size(
|
return Size(
|
||||||
width,
|
width,
|
||||||
constraints.constrainHeight(_preferredHeight(constraints.maxWidth)),
|
constraints.constrainHeight(_preferredHeight(constraints.maxWidth)),
|
||||||
@@ -2603,8 +2602,9 @@ class RenderEditable extends RenderBox
|
|||||||
_backgroundRenderObject?.layout(painterConstraints);
|
_backgroundRenderObject?.layout(painterConstraints);
|
||||||
|
|
||||||
_maxScrollExtent = _getMaxScrollExtent(contentSize);
|
_maxScrollExtent = _getMaxScrollExtent(contentSize);
|
||||||
offset.applyViewportDimension(_viewportExtent);
|
offset
|
||||||
offset.applyContentDimensions(0.0, _maxScrollExtent);
|
..applyViewportDimension(_viewportExtent)
|
||||||
|
..applyContentDimensions(0.0, _maxScrollExtent);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The relative origin in relation to the distance the user has theoretically
|
// The relative origin in relation to the distance the user has theoretically
|
||||||
@@ -2942,28 +2942,29 @@ class RenderEditable extends RenderBox
|
|||||||
@override
|
@override
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
super.debugFillProperties(properties);
|
super.debugFillProperties(properties);
|
||||||
properties.add(ColorProperty('cursorColor', cursorColor));
|
properties
|
||||||
properties.add(
|
..add(ColorProperty('cursorColor', cursorColor))
|
||||||
DiagnosticsProperty<ValueNotifier<bool>>('showCursor', showCursor),
|
..add(
|
||||||
);
|
DiagnosticsProperty<ValueNotifier<bool>>('showCursor', showCursor),
|
||||||
properties.add(IntProperty('maxLines', maxLines));
|
)
|
||||||
properties.add(IntProperty('minLines', minLines));
|
..add(IntProperty('maxLines', maxLines))
|
||||||
properties.add(
|
..add(IntProperty('minLines', minLines))
|
||||||
DiagnosticsProperty<bool>('expands', expands, defaultValue: false),
|
..add(
|
||||||
);
|
DiagnosticsProperty<bool>('expands', expands, defaultValue: false),
|
||||||
properties.add(ColorProperty('selectionColor', selectionColor));
|
)
|
||||||
properties.add(
|
..add(ColorProperty('selectionColor', selectionColor))
|
||||||
DiagnosticsProperty<TextScaler>(
|
..add(
|
||||||
'textScaler',
|
DiagnosticsProperty<TextScaler>(
|
||||||
textScaler,
|
'textScaler',
|
||||||
defaultValue: TextScaler.noScaling,
|
textScaler,
|
||||||
),
|
defaultValue: TextScaler.noScaling,
|
||||||
);
|
),
|
||||||
properties.add(
|
)
|
||||||
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
..add(
|
||||||
);
|
DiagnosticsProperty<Locale>('locale', locale, defaultValue: null),
|
||||||
properties.add(DiagnosticsProperty<TextSelection>('selection', selection));
|
)
|
||||||
properties.add(DiagnosticsProperty<ViewportOffset>('offset', offset));
|
..add(DiagnosticsProperty<TextSelection>('selection', selection))
|
||||||
|
..add(DiagnosticsProperty<ViewportOffset>('offset', offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -3154,11 +3155,13 @@ class _TextHighlightPainter extends RenderEditablePainter {
|
|||||||
|
|
||||||
highlightPaint.color = color;
|
highlightPaint.color = color;
|
||||||
final TextPainter textPainter = renderEditable._textPainter;
|
final TextPainter textPainter = renderEditable._textPainter;
|
||||||
final List<TextBox> boxes = textPainter.getBoxesForSelection(
|
final Set<TextBox> boxes = textPainter
|
||||||
TextSelection(baseOffset: range.start, extentOffset: range.end),
|
.getBoxesForSelection(
|
||||||
boxHeightStyle: selectionHeightStyle,
|
TextSelection(baseOffset: range.start, extentOffset: range.end),
|
||||||
boxWidthStyle: selectionWidthStyle,
|
boxHeightStyle: selectionHeightStyle,
|
||||||
);
|
boxWidthStyle: selectionWidthStyle,
|
||||||
|
)
|
||||||
|
.toSet();
|
||||||
|
|
||||||
for (final TextBox box in boxes) {
|
for (final TextBox box in boxes) {
|
||||||
canvas.drawRect(
|
canvas.drawRect(
|
||||||
@@ -3215,7 +3218,7 @@ class _CaretPainter extends RenderEditablePainter {
|
|||||||
Color? get caretColor => _caretColor;
|
Color? get caretColor => _caretColor;
|
||||||
Color? _caretColor;
|
Color? _caretColor;
|
||||||
set caretColor(Color? value) {
|
set caretColor(Color? value) {
|
||||||
if (caretColor?.value == value?.value) {
|
if (caretColor?.toARGB32() == value?.toARGB32()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3246,7 +3249,7 @@ class _CaretPainter extends RenderEditablePainter {
|
|||||||
Color? get backgroundCursorColor => _backgroundCursorColor;
|
Color? get backgroundCursorColor => _backgroundCursorColor;
|
||||||
Color? _backgroundCursorColor;
|
Color? _backgroundCursorColor;
|
||||||
set backgroundCursorColor(Color? value) {
|
set backgroundCursorColor(Color? value) {
|
||||||
if (backgroundCursorColor?.value == value?.value) {
|
if (backgroundCursorColor?.toARGB32() == value?.toARGB32()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3318,7 +3321,7 @@ class _CaretPainter extends RenderEditablePainter {
|
|||||||
paintRegularCursor(canvas, renderEditable, caretColor, caretTextPosition);
|
paintRegularCursor(canvas, renderEditable, caretColor, caretTextPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Color? floatingCursorColor = this.caretColor?.withOpacity(0.75);
|
final Color? floatingCursorColor = this.caretColor?.withValues(alpha: 0.75);
|
||||||
// Floating Cursor.
|
// Floating Cursor.
|
||||||
if (floatingCursorRect == null ||
|
if (floatingCursorRect == null ||
|
||||||
floatingCursorColor == null ||
|
floatingCursorColor == null ||
|
||||||
@@ -20,44 +20,25 @@ import 'dart:async';
|
|||||||
import 'dart:io' show Platform;
|
import 'dart:io' show Platform;
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:ui' as ui hide TextStyle;
|
import 'dart:ui' as ui hide TextStyle;
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:PiliPlus/common/widgets/text_field/controller.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart';
|
||||||
import 'package:PiliPlus/common/widgets/text_field/editable.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text_field/editable.dart';
|
||||||
import 'package:PiliPlus/common/widgets/text_field/spell_check.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text_field/spell_check.dart';
|
||||||
import 'package:PiliPlus/common/widgets/text_field/text_selection.dart';
|
import 'package:PiliPlus/common/widgets/flutter/text_field/text_selection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart'
|
import 'package:flutter/material.dart'
|
||||||
hide
|
hide
|
||||||
|
EditableText,
|
||||||
|
EditableTextState,
|
||||||
SpellCheckConfiguration,
|
SpellCheckConfiguration,
|
||||||
buildTextSpanWithSpellCheckSuggestions,
|
TextSelectionGestureDetectorBuilder,
|
||||||
TextSelectionOverlay,
|
TextSelectionOverlay;
|
||||||
TextSelectionGestureDetectorBuilder;
|
|
||||||
import 'package:flutter/rendering.dart'
|
import 'package:flutter/rendering.dart'
|
||||||
hide RenderEditable, VerticalCaretMovementRun;
|
hide RenderEditable, VerticalCaretMovementRun;
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.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
|
/// Signature for a widget builder that builds a context menu for the given
|
||||||
/// [EditableTextState].
|
/// [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.
|
// A time-value pair that represents a key frame in an animation.
|
||||||
class _KeyFrame {
|
class _KeyFrame {
|
||||||
const _KeyFrame(this.time, this.value);
|
const _KeyFrame(this.time, this.value);
|
||||||
@@ -506,7 +439,7 @@ class EditableText extends StatefulWidget {
|
|||||||
this.readOnly = false,
|
this.readOnly = false,
|
||||||
this.obscuringCharacter = '•',
|
this.obscuringCharacter = '•',
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.autocorrect = true,
|
bool? autocorrect,
|
||||||
SmartDashesType? smartDashesType,
|
SmartDashesType? smartDashesType,
|
||||||
SmartQuotesType? smartQuotesType,
|
SmartQuotesType? smartQuotesType,
|
||||||
this.enableSuggestions = true,
|
this.enableSuggestions = true,
|
||||||
@@ -556,12 +489,13 @@ class EditableText extends StatefulWidget {
|
|||||||
this.cursorOpacityAnimates = false,
|
this.cursorOpacityAnimates = false,
|
||||||
this.cursorOffset,
|
this.cursorOffset,
|
||||||
this.paintCursorAboveText = false,
|
this.paintCursorAboveText = false,
|
||||||
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
|
ui.BoxHeightStyle? selectionHeightStyle,
|
||||||
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
|
ui.BoxWidthStyle? selectionWidthStyle,
|
||||||
this.scrollPadding = const EdgeInsets.all(20.0),
|
this.scrollPadding = const EdgeInsets.all(20.0),
|
||||||
this.keyboardAppearance = Brightness.light,
|
this.keyboardAppearance = Brightness.light,
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
this.dragStartBehavior = DragStartBehavior.start,
|
||||||
bool? enableInteractiveSelection,
|
bool? enableInteractiveSelection,
|
||||||
|
bool? selectAllOnFocus,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.scrollPhysics,
|
this.scrollPhysics,
|
||||||
this.autocorrectionTextRectColor,
|
this.autocorrectionTextRectColor,
|
||||||
@@ -586,7 +520,10 @@ class EditableText extends StatefulWidget {
|
|||||||
this.contextMenuBuilder,
|
this.contextMenuBuilder,
|
||||||
this.spellCheckConfiguration,
|
this.spellCheckConfiguration,
|
||||||
this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
|
this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
|
||||||
|
this.hintLocales,
|
||||||
}) : assert(obscuringCharacter.length == 1),
|
}) : assert(obscuringCharacter.length == 1),
|
||||||
|
autocorrect =
|
||||||
|
autocorrect ?? _inferAutocorrect(autofillHints: autofillHints),
|
||||||
smartDashesType =
|
smartDashesType =
|
||||||
smartDashesType ??
|
smartDashesType ??
|
||||||
(obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
|
(obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
|
||||||
@@ -608,6 +545,7 @@ class EditableText extends StatefulWidget {
|
|||||||
),
|
),
|
||||||
enableInteractiveSelection =
|
enableInteractiveSelection =
|
||||||
enableInteractiveSelection ?? (!readOnly || !obscureText),
|
enableInteractiveSelection ?? (!readOnly || !obscureText),
|
||||||
|
selectAllOnFocus = selectAllOnFocus ?? _defaultSelectAllOnFocus,
|
||||||
toolbarOptions =
|
toolbarOptions =
|
||||||
selectionControls is TextSelectionHandleControls &&
|
selectionControls is TextSelectionHandleControls &&
|
||||||
toolbarOptions == null
|
toolbarOptions == null
|
||||||
@@ -647,7 +585,10 @@ class EditableText extends StatefulWidget {
|
|||||||
...inputFormatters ?? const Iterable<TextInputFormatter>.empty(),
|
...inputFormatters ?? const Iterable<TextInputFormatter>.empty(),
|
||||||
]
|
]
|
||||||
: inputFormatters,
|
: inputFormatters,
|
||||||
showCursor = showCursor ?? !readOnly;
|
showCursor = showCursor ?? !readOnly,
|
||||||
|
selectionHeightStyle =
|
||||||
|
selectionHeightStyle ?? defaultSelectionHeightStyle,
|
||||||
|
selectionWidthStyle = selectionWidthStyle ?? defaultSelectionWidthStyle;
|
||||||
|
|
||||||
/// Controls the text being edited.
|
/// Controls the text being edited.
|
||||||
final RichTextEditingController controller;
|
final RichTextEditingController controller;
|
||||||
@@ -737,7 +678,7 @@ class EditableText extends StatefulWidget {
|
|||||||
/// {@template flutter.widgets.editableText.autocorrect}
|
/// {@template flutter.widgets.editableText.autocorrect}
|
||||||
/// Whether to enable autocorrection.
|
/// Whether to enable autocorrection.
|
||||||
///
|
///
|
||||||
/// Defaults to true.
|
/// False on iOS if [autofillHints] contains password-related hints, otherwise true.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
final bool autocorrect;
|
final bool autocorrect;
|
||||||
|
|
||||||
@@ -1302,7 +1243,7 @@ class EditableText extends StatefulWidget {
|
|||||||
/// [TextSelectionGestureDetectorBuilder] to wrap the [EditableText], and set
|
/// [TextSelectionGestureDetectorBuilder] to wrap the [EditableText], and set
|
||||||
/// [rendererIgnoresPointer] to true.
|
/// [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.
|
/// by this widget will not handle pointer events.
|
||||||
///
|
///
|
||||||
/// This property is false by default.
|
/// 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.
|
/// cut/copy/paste menu, and tapping to move the text caret.
|
||||||
///
|
///
|
||||||
/// When this is false, the text selection cannot be adjusted by
|
/// When this is false, the text selection cannot be adjusted by
|
||||||
/// the user, text cannot be copied, and the user cannot paste into
|
/// the user, the cut/copy/paste menu is hidden, and the shortcuts to
|
||||||
/// the text field from the clipboard.
|
/// 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.
|
/// Defaults to true.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
@@ -1480,6 +1422,16 @@ class EditableText extends StatefulWidget {
|
|||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
bool get selectionEnabled => enableInteractiveSelection;
|
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}
|
/// {@template flutter.widgets.editableText.autofillHints}
|
||||||
/// A list of strings that helps the autofill service identify the type of this
|
/// A list of strings that helps the autofill service identify the type of this
|
||||||
/// text input.
|
/// text input.
|
||||||
@@ -1714,12 +1666,62 @@ class EditableText extends StatefulWidget {
|
|||||||
/// {@macro flutter.widgets.magnifier.intro}
|
/// {@macro flutter.widgets.magnifier.intro}
|
||||||
final TextMagnifierConfiguration magnifierConfiguration;
|
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].
|
/// The default value for [stylusHandwritingEnabled].
|
||||||
static const bool defaultStylusHandwritingEnabled = true;
|
static const bool defaultStylusHandwritingEnabled = true;
|
||||||
|
|
||||||
bool get _userSelectionEnabled =>
|
bool get _userSelectionEnabled =>
|
||||||
enableInteractiveSelection && (!readOnly || !obscureText);
|
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
|
/// Returns the [ContextMenuButtonItem]s representing the buttons in this
|
||||||
/// platform's default selection menu for an editable field.
|
/// platform's default selection menu for an editable field.
|
||||||
///
|
///
|
||||||
@@ -1818,6 +1820,38 @@ class EditableText extends StatefulWidget {
|
|||||||
return resultButtonItem;
|
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.
|
// Infer the keyboard type of an `EditableText` if it's not specified.
|
||||||
static TextInputType _inferKeyboardType({
|
static TextInputType _inferKeyboardType({
|
||||||
required Iterable<String>? autofillHints,
|
required Iterable<String>? autofillHints,
|
||||||
@@ -2000,7 +2034,7 @@ class EditableText extends StatefulWidget {
|
|||||||
DiagnosticsProperty<bool>(
|
DiagnosticsProperty<bool>(
|
||||||
'autocorrect',
|
'autocorrect',
|
||||||
autocorrect,
|
autocorrect,
|
||||||
defaultValue: true,
|
defaultValue: null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
..add(
|
..add(
|
||||||
@@ -2136,6 +2170,13 @@ class EditableText extends StatefulWidget {
|
|||||||
? const <String>[]
|
? const <String>[]
|
||||||
: kDefaultContentInsertionMimeTypes,
|
: kDefaultContentInsertionMimeTypes,
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty<List<Locale>?>(
|
||||||
|
'hintLocales',
|
||||||
|
hintLocales,
|
||||||
|
defaultValue: null,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2438,6 +2479,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
if (selection.isCollapsed || widget.obscureText) {
|
if (selection.isCollapsed || widget.obscureText) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// bggRGjQaUbCoE copySelection
|
||||||
final String text =
|
final String text =
|
||||||
widget.controller.getSelectionText(selection) ??
|
widget.controller.getSelectionText(selection) ??
|
||||||
selection.textInside(textEditingValue.text);
|
selection.textInside(textEditingValue.text);
|
||||||
@@ -2455,7 +2497,6 @@ class EditableTextState extends State<EditableText>
|
|||||||
case TargetPlatform.android:
|
case TargetPlatform.android:
|
||||||
case TargetPlatform.fuchsia:
|
case TargetPlatform.fuchsia:
|
||||||
// Collapse the selection and hide the toolbar and handles.
|
// Collapse the selection and hide the toolbar and handles.
|
||||||
|
|
||||||
userUpdateTextEditingValue(
|
userUpdateTextEditingValue(
|
||||||
TextEditingValue(
|
TextEditingValue(
|
||||||
text: textEditingValue.text,
|
text: textEditingValue.text,
|
||||||
@@ -2480,6 +2521,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
if (selection.isCollapsed) {
|
if (selection.isCollapsed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// bggRGjQaUbCoE cutSelection
|
||||||
final String text =
|
final String text =
|
||||||
widget.controller.getSelectionText(selection) ??
|
widget.controller.getSelectionText(selection) ??
|
||||||
selection.textInside(textEditingValue.text);
|
selection.textInside(textEditingValue.text);
|
||||||
@@ -2528,12 +2570,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
selection.baseOffset,
|
selection.baseOffset,
|
||||||
selection.extentOffset,
|
selection.extentOffset,
|
||||||
);
|
);
|
||||||
// final TextEditingValue collapsedTextEditingValue =
|
// bggRGjQaUbCoE _pasteText
|
||||||
// textEditingValue.copyWith(
|
|
||||||
// selection: TextSelection.collapsed(offset: lastSelectionIndex),
|
|
||||||
// );
|
|
||||||
// final newValue = collapsedTextEditingValue.replaced(selection, text);
|
|
||||||
|
|
||||||
widget.controller.syncRichText(
|
widget.controller.syncRichText(
|
||||||
selection.isCollapsed
|
selection.isCollapsed
|
||||||
? TextEditingDeltaInsertion(
|
? TextEditingDeltaInsertion(
|
||||||
@@ -2551,15 +2588,12 @@ class EditableTextState extends State<EditableText>
|
|||||||
composing: TextRange.empty,
|
composing: TextRange.empty,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final newValue = _value.copyWith(
|
final newValue = _value.copyWith(
|
||||||
text: widget.controller.plainText,
|
text: widget.controller.plainText,
|
||||||
selection: widget.controller.newSelection,
|
selection: widget.controller.newSelection,
|
||||||
composing: TextRange.empty,
|
composing: TextRange.empty,
|
||||||
);
|
);
|
||||||
|
|
||||||
userUpdateTextEditingValue(newValue, cause);
|
userUpdateTextEditingValue(newValue, cause);
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.toolbar) {
|
if (cause == SelectionChangedCause.toolbar) {
|
||||||
// Schedule a call to bringIntoView() after renderEditable updates.
|
// Schedule a call to bringIntoView() after renderEditable updates.
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
@@ -2579,7 +2613,6 @@ class EditableTextState extends State<EditableText>
|
|||||||
// selecting it.
|
// selecting it.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
userUpdateTextEditingValue(
|
userUpdateTextEditingValue(
|
||||||
textEditingValue.copyWith(
|
textEditingValue.copyWith(
|
||||||
selection: TextSelection(
|
selection: TextSelection(
|
||||||
@@ -3520,7 +3553,9 @@ class EditableTextState extends State<EditableText>
|
|||||||
);
|
);
|
||||||
case FloatingCursorDragState.End:
|
case FloatingCursorDragState.End:
|
||||||
// Resume cursor blinking.
|
// Resume cursor blinking.
|
||||||
_startCursorBlink();
|
if (_hasFocus) {
|
||||||
|
_startCursorBlink();
|
||||||
|
}
|
||||||
// We skip animation if no update has happened.
|
// We skip animation if no update has happened.
|
||||||
if (_lastTextPosition != null && _lastBoundedOffset != null) {
|
if (_lastTextPosition != null && _lastBoundedOffset != null) {
|
||||||
_floatingCursorResetController!.value = 0.0;
|
_floatingCursorResetController!.value = 0.0;
|
||||||
@@ -4417,15 +4452,6 @@ class EditableTextState extends State<EditableText>
|
|||||||
final bool textCommitted =
|
final bool textCommitted =
|
||||||
!oldValue.composing.isCollapsed && value.composing.isCollapsed;
|
!oldValue.composing.isCollapsed && value.composing.isCollapsed;
|
||||||
final bool selectionChanged = oldValue.selection != value.selection;
|
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) {
|
if (textChanged || textCommitted) {
|
||||||
// Only apply input formatters if the text has changed (including uncommitted
|
// Only apply input formatters if the text has changed (including uncommitted
|
||||||
@@ -4689,17 +4715,9 @@ class EditableTextState extends State<EditableText>
|
|||||||
|
|
||||||
TextSelection? _adjustedSelectionWhenFocused() {
|
TextSelection? _adjustedSelectionWhenFocused() {
|
||||||
TextSelection? selection;
|
TextSelection? selection;
|
||||||
final bool isDesktop = switch (defaultTargetPlatform) {
|
|
||||||
TargetPlatform.android ||
|
|
||||||
TargetPlatform.iOS ||
|
|
||||||
TargetPlatform.fuchsia => false,
|
|
||||||
TargetPlatform.macOS ||
|
|
||||||
TargetPlatform.linux ||
|
|
||||||
TargetPlatform.windows => true,
|
|
||||||
};
|
|
||||||
final bool shouldSelectAll =
|
final bool shouldSelectAll =
|
||||||
|
widget.selectAllOnFocus &&
|
||||||
widget.selectionEnabled &&
|
widget.selectionEnabled &&
|
||||||
(kIsWeb || isDesktop) &&
|
|
||||||
!_isMultiline &&
|
!_isMultiline &&
|
||||||
!_nextFocusChangeIsInternal &&
|
!_nextFocusChangeIsInternal &&
|
||||||
!_justResumed;
|
!_justResumed;
|
||||||
@@ -5043,10 +5061,10 @@ class EditableTextState extends State<EditableText>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Shows the magnifier at the position given by `positionToShow`,
|
/// 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`,
|
/// 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
|
/// Does nothing if a magnifier couldn't be shown, such as when the selection
|
||||||
/// overlay does not currently exist.
|
/// overlay does not currently exist.
|
||||||
@@ -5055,22 +5073,20 @@ class EditableTextState extends State<EditableText>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_selectionOverlay!.magnifierIsVisible) {
|
if (_selectionOverlay!.magnifierExists) {
|
||||||
_selectionOverlay!.updateMagnifier(positionToShow);
|
_selectionOverlay!.updateMagnifier(positionToShow);
|
||||||
} else {
|
} else {
|
||||||
_selectionOverlay!.showMagnifier(positionToShow);
|
_selectionOverlay!.showMagnifier(positionToShow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides the magnifier if it is visible.
|
/// Hides the magnifier.
|
||||||
void hideMagnifier() {
|
void hideMagnifier() {
|
||||||
if (_selectionOverlay == null) {
|
if (_selectionOverlay == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_selectionOverlay!.magnifierIsVisible) {
|
_selectionOverlay!.hideMagnifier();
|
||||||
_selectionOverlay!.hideMagnifier();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
|
// Tracks the location a [_ScribblePlaceholder] should be rendered in the
|
||||||
@@ -5161,6 +5177,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
allowedMimeTypes: widget.contentInsertionConfiguration == null
|
allowedMimeTypes: widget.contentInsertionConfiguration == null
|
||||||
? const <String>[]
|
? const <String>[]
|
||||||
: widget.contentInsertionConfiguration!.allowedMimeTypes,
|
: widget.contentInsertionConfiguration!.allowedMimeTypes,
|
||||||
|
hintLocales: widget.hintLocales,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5357,10 +5374,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
|
|
||||||
void _replaceText(ReplaceTextIntent intent) {
|
void _replaceText(ReplaceTextIntent intent) {
|
||||||
final TextEditingValue oldValue = _value;
|
final TextEditingValue oldValue = _value;
|
||||||
// final TextEditingValue newValue = intent.currentTextEditingValue.replaced(
|
// bggRGjQaUbCoE _replaceText
|
||||||
// intent.replacementRange,
|
|
||||||
// intent.replacementText,
|
|
||||||
// );
|
|
||||||
widget.controller.syncRichText(
|
widget.controller.syncRichText(
|
||||||
intent.replacementText.isEmpty
|
intent.replacementText.isEmpty
|
||||||
? TextEditingDeltaDeletion(
|
? TextEditingDeltaDeletion(
|
||||||
@@ -5387,7 +5401,6 @@ class EditableTextState extends State<EditableText>
|
|||||||
selection: widget.controller.newSelection,
|
selection: widget.controller.newSelection,
|
||||||
composing: TextRange.empty,
|
composing: TextRange.empty,
|
||||||
);
|
);
|
||||||
|
|
||||||
userUpdateTextEditingValue(newValue, intent.cause);
|
userUpdateTextEditingValue(newValue, intent.cause);
|
||||||
|
|
||||||
// If there's no change in text and selection (e.g. when selecting and
|
// If there's no change in text and selection (e.g. when selecting and
|
||||||
@@ -5509,7 +5522,6 @@ class EditableTextState extends State<EditableText>
|
|||||||
}
|
}
|
||||||
|
|
||||||
bringIntoView(nextSelection.extent);
|
bringIntoView(nextSelection.extent);
|
||||||
|
|
||||||
userUpdateTextEditingValue(
|
userUpdateTextEditingValue(
|
||||||
_value.copyWith(selection: nextSelection),
|
_value.copyWith(selection: nextSelection),
|
||||||
SelectionChangedCause.keyboard,
|
SelectionChangedCause.keyboard,
|
||||||
@@ -5718,7 +5730,8 @@ class EditableTextState extends State<EditableText>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ScrollToDocumentBoundaryIntent: _makeOverridable(
|
ScrollToDocumentBoundaryIntent: _makeOverridable(
|
||||||
CallbackAction<ScrollToDocumentBoundaryIntent>(
|
_WebComposingDisablingCallbackAction<ScrollToDocumentBoundaryIntent>(
|
||||||
|
this,
|
||||||
onInvoke: _scrollToDocumentBoundary,
|
onInvoke: _scrollToDocumentBoundary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -5748,11 +5761,7 @@ class EditableTextState extends State<EditableText>
|
|||||||
// Copy Paste
|
// Copy Paste
|
||||||
SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
|
SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
|
||||||
CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)),
|
CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)),
|
||||||
PasteTextIntent: _makeOverridable(
|
PasteTextIntent: _makeOverridable(_PasteSelectionAction(this)),
|
||||||
CallbackAction<PasteTextIntent>(
|
|
||||||
onInvoke: (PasteTextIntent intent) => pasteText(intent.cause),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TransposeCharactersIntent: _makeOverridable(_transposeCharactersAction),
|
TransposeCharactersIntent: _makeOverridable(_transposeCharactersAction),
|
||||||
EditableTextTapOutsideIntent: _makeOverridable(
|
EditableTextTapOutsideIntent: _makeOverridable(
|
||||||
@@ -5826,7 +5835,13 @@ class EditableTextState extends State<EditableText>
|
|||||||
? AxisDirection.down
|
? AxisDirection.down
|
||||||
: AxisDirection.right,
|
: AxisDirection.right,
|
||||||
controller: _scrollController,
|
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,
|
dragStartBehavior: widget.dragStartBehavior,
|
||||||
restorationId: widget.restorationId,
|
restorationId: widget.restorationId,
|
||||||
// If a ScrollBehavior is not provided, only apply scrollbars when
|
// If a ScrollBehavior is not provided, only apply scrollbars when
|
||||||
@@ -5970,15 +5985,19 @@ class EditableTextState extends State<EditableText>
|
|||||||
final int placeholderLocation = _value.text.length - _placeholderLocation;
|
final int placeholderLocation = _value.text.length - _placeholderLocation;
|
||||||
if (_isMultiline) {
|
if (_isMultiline) {
|
||||||
// The zero size placeholder here allows the line to break and keep the caret on the first line.
|
// The zero size placeholder here allows the line to break and keep the caret on the first line.
|
||||||
placeholders.add(
|
placeholders
|
||||||
const _ScribblePlaceholder(child: SizedBox.shrink(), size: Size.zero),
|
..add(
|
||||||
);
|
const _ScribblePlaceholder(
|
||||||
placeholders.add(
|
child: SizedBox.shrink(),
|
||||||
_ScribblePlaceholder(
|
size: Size.zero,
|
||||||
child: const SizedBox.shrink(),
|
),
|
||||||
size: Size(renderEditable.size.width, 0.0),
|
)
|
||||||
),
|
..add(
|
||||||
);
|
_ScribblePlaceholder(
|
||||||
|
child: const SizedBox.shrink(),
|
||||||
|
size: Size(renderEditable.size.width, 0.0),
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
placeholders.add(
|
placeholders.add(
|
||||||
const _ScribblePlaceholder(
|
const _ScribblePlaceholder(
|
||||||
@@ -6061,8 +6080,8 @@ class _Editable extends MultiChildRenderObjectWidget {
|
|||||||
this.cursorRadius,
|
this.cursorRadius,
|
||||||
required this.cursorOffset,
|
required this.cursorOffset,
|
||||||
required this.paintCursorAboveText,
|
required this.paintCursorAboveText,
|
||||||
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
|
ui.BoxHeightStyle? selectionHeightStyle,
|
||||||
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
|
ui.BoxWidthStyle? selectionWidthStyle,
|
||||||
this.enableInteractiveSelection = true,
|
this.enableInteractiveSelection = true,
|
||||||
required this.textSelectionDelegate,
|
required this.textSelectionDelegate,
|
||||||
required this.devicePixelRatio,
|
required this.devicePixelRatio,
|
||||||
@@ -6070,7 +6089,11 @@ class _Editable extends MultiChildRenderObjectWidget {
|
|||||||
this.promptRectColor,
|
this.promptRectColor,
|
||||||
required this.clipBehavior,
|
required this.clipBehavior,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
}) : super(
|
}) : selectionHeightStyle =
|
||||||
|
selectionHeightStyle ?? EditableText.defaultSelectionHeightStyle,
|
||||||
|
selectionWidthStyle =
|
||||||
|
selectionWidthStyle ?? EditableText.defaultSelectionWidthStyle,
|
||||||
|
super(
|
||||||
children: WidgetSpan.extractFromInlineSpan(inlineSpan, textScaler),
|
children: WidgetSpan.extractFromInlineSpan(inlineSpan, textScaler),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -6203,6 +6226,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
|
@immutable
|
||||||
class _ScribbleCacheKey {
|
class _ScribbleCacheKey {
|
||||||
const _ScribbleCacheKey({
|
const _ScribbleCacheKey({
|
||||||
@@ -6454,11 +6491,7 @@ class _CodePointBoundary extends TextBoundary {
|
|||||||
// ------------------------------- Text Actions -------------------------------
|
// ------------------------------- Text Actions -------------------------------
|
||||||
class _DeleteTextAction<T extends DirectionalTextEditingIntent>
|
class _DeleteTextAction<T extends DirectionalTextEditingIntent>
|
||||||
extends ContextAction<T> {
|
extends ContextAction<T> {
|
||||||
_DeleteTextAction(
|
_DeleteTextAction(this.state, this.getTextBoundary, this._applyTextBoundary);
|
||||||
this.state,
|
|
||||||
this.getTextBoundary,
|
|
||||||
this._applyTextBoundary,
|
|
||||||
);
|
|
||||||
|
|
||||||
final EditableTextState state;
|
final EditableTextState state;
|
||||||
final TextBoundary Function() getTextBoundary;
|
final TextBoundary Function() getTextBoundary;
|
||||||
@@ -6658,7 +6691,15 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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<
|
class _UpdateTextSelectionVerticallyAction<
|
||||||
@@ -6745,7 +6786,33 @@ class _UpdateTextSelectionVerticallyAction<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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> {
|
class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
|
||||||
@@ -6755,6 +6822,10 @@ class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
|
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
|
||||||
|
if (!state.widget.selectionEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return Actions.invoke(
|
return Actions.invoke(
|
||||||
context!,
|
context!,
|
||||||
UpdateSelectionIntent(
|
UpdateSelectionIntent(
|
||||||
@@ -6764,9 +6835,6 @@ class _SelectAllAction extends ContextAction<SelectAllTextIntent> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isActionEnabled => state.widget.selectionEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CopySelectionAction extends ContextAction<CopySelectionTextIntent> {
|
class _CopySelectionAction extends ContextAction<CopySelectionTextIntent> {
|
||||||
@@ -6776,16 +6844,35 @@ class _CopySelectionAction extends ContextAction<CopySelectionTextIntent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void invoke(CopySelectionTextIntent intent, [BuildContext? context]) {
|
void invoke(CopySelectionTextIntent intent, [BuildContext? context]) {
|
||||||
|
if (!state._value.selection.isValid || state._value.selection.isCollapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.widget.selectionEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (intent.collapseSelection) {
|
if (intent.collapseSelection) {
|
||||||
state.cutSelection(intent.cause);
|
state.cutSelection(intent.cause);
|
||||||
} else {
|
} else {
|
||||||
state.copySelection(intent.cause);
|
state.copySelection(intent.cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PasteSelectionAction extends ContextAction<PasteTextIntent> {
|
||||||
|
_PasteSelectionAction(this.state);
|
||||||
|
|
||||||
|
final EditableTextState state;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get isActionEnabled =>
|
void invoke(PasteTextIntent intent, [BuildContext? context]) {
|
||||||
state._value.selection.isValid && !state._value.selection.isCollapsed;
|
if (!state.widget.selectionEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pasteText(intent.cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [ClipboardStatusNotifier] whose [value] is hardcoded to
|
/// A [ClipboardStatusNotifier] whose [value] is hardcoded to
|
||||||
119
lib/common/widgets/flutter/text_field/spell_check.dart
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||