mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
Compare commits
269 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56350b181f | ||
|
|
5982fd312b | ||
|
|
4d35dfe2f0 | ||
|
|
0eac1b2c69 | ||
|
|
89050c7ca8 | ||
|
|
ae16771b5e | ||
|
|
847f42fee3 | ||
|
|
8d4294ba75 | ||
|
|
0b9d4d970a | ||
|
|
34c024239d | ||
|
|
71daa6df29 | ||
|
|
20c1112a10 | ||
|
|
31e8c36653 | ||
|
|
e06a3d8f22 | ||
|
|
c77ceea262 | ||
|
|
28b6b769b2 | ||
|
|
57722eb579 | ||
|
|
d4e381380a | ||
|
|
21fdcdb2bb | ||
|
|
1a30e542a9 | ||
|
|
c1ce704e4e | ||
|
|
30a5889215 | ||
|
|
75a242de2a | ||
|
|
a0afbb2615 | ||
|
|
da3c087ade | ||
|
|
4dc0389624 | ||
|
|
488cb58b85 | ||
|
|
f5b50ffcb0 | ||
|
|
d9474a79c1 | ||
|
|
3a15353bc4 | ||
|
|
b239737498 | ||
|
|
5001f3b6d2 | ||
|
|
3d803cce9f | ||
|
|
d0046d0faf | ||
|
|
d59c364ba6 | ||
|
|
fee161e99b | ||
|
|
5a481dbaaf | ||
|
|
f3279b4177 | ||
|
|
242fde92f6 | ||
|
|
a9c542ac4e | ||
|
|
4aebc0aac5 | ||
|
|
51bf59e329 | ||
|
|
39716cc1d4 | ||
|
|
50cf99720b | ||
|
|
214239a6f8 | ||
|
|
0d63d6102f | ||
|
|
47e79ee7d8 | ||
|
|
22e6e19500 | ||
|
|
8ae92b859f | ||
|
|
78180a1dd1 | ||
|
|
f47c500c5b | ||
|
|
2e65b65b1d | ||
|
|
88578393c2 | ||
|
|
1643db4656 | ||
|
|
e4b8dfcada | ||
|
|
1a3f5414c6 | ||
|
|
789d8a77dd | ||
|
|
5efbdda107 | ||
|
|
2aa109b089 | ||
|
|
22abc4488b | ||
|
|
0d41731681 | ||
|
|
f467532f9d | ||
|
|
daf01df5aa | ||
|
|
738c057304 | ||
|
|
cf76cb6f63 | ||
|
|
27e39d4de5 | ||
|
|
58fd373e8c | ||
|
|
76b37437d3 | ||
|
|
8186307f98 | ||
|
|
be42ce97f8 | ||
|
|
5f6dcc9569 | ||
|
|
4539e0e5c5 | ||
|
|
d066262cdd | ||
|
|
7ac4a32468 | ||
|
|
9cf74c0db6 | ||
|
|
14f2c34d21 | ||
|
|
b7b4432d71 | ||
|
|
0be609db3d | ||
|
|
321b7933d7 | ||
|
|
1d51db0a62 | ||
|
|
18ee1d4e18 | ||
|
|
413a49bcb1 | ||
|
|
fd1bb0af30 | ||
|
|
f808012ec2 | ||
|
|
51e436faed | ||
|
|
168bb22670 | ||
|
|
1232116d22 | ||
|
|
621239551f | ||
|
|
f1a10a786d | ||
|
|
d0ef75bce7 | ||
|
|
3919e42b59 | ||
|
|
eafaa1b045 | ||
|
|
6e08735421 | ||
|
|
a5823e1e90 | ||
|
|
665f5cdeef | ||
|
|
28c2323ef1 | ||
|
|
d30dd96bbd | ||
|
|
1026fc79e1 | ||
|
|
1073d82008 | ||
|
|
30f3440b90 | ||
|
|
45e1282a0e | ||
|
|
2e480518b7 | ||
|
|
1e7ff89341 | ||
|
|
269fb033e0 | ||
|
|
dbc93883e8 | ||
|
|
144a9b604a | ||
|
|
dda0fc15c7 | ||
|
|
1dd7b9ed0a | ||
|
|
b7768e5886 | ||
|
|
7df4c5c4c7 | ||
|
|
952fd5fc38 | ||
|
|
cde0ea244b | ||
|
|
098e2220cc | ||
|
|
df41729d74 | ||
|
|
273e5649c3 | ||
|
|
de3edcfa13 | ||
|
|
1215d126cc | ||
|
|
20a89fbccb | ||
|
|
cbe814fdd6 | ||
|
|
04583e92b7 | ||
|
|
ae6c6431f3 | ||
|
|
2973299e29 | ||
|
|
52f9b0f83c | ||
|
|
2a1849d24c | ||
|
|
991ae8518a | ||
|
|
bef7a28229 | ||
|
|
753fdeea03 | ||
|
|
ef8d57ddfd | ||
|
|
582574a605 | ||
|
|
43583be6da | ||
|
|
836f1a9b06 | ||
|
|
90176a4787 | ||
|
|
a6cb49fd02 | ||
|
|
85733e071b | ||
|
|
bdd927e7e3 | ||
|
|
e2f8cb89a9 | ||
|
|
8fd51da8da | ||
|
|
0edb7f44a7 | ||
|
|
882f16bdab | ||
|
|
b6217f6e6e | ||
|
|
e9945ab63c | ||
|
|
eca69f3d6d | ||
|
|
f854e949cd | ||
|
|
e34fce6d0e | ||
|
|
b00708b498 | ||
|
|
d6ed1edc6f | ||
|
|
93560a6fb2 | ||
|
|
07307a666c | ||
|
|
0e253ecb83 | ||
|
|
8545a3cbe6 | ||
|
|
dbd8b80507 | ||
|
|
6260809e40 | ||
|
|
820c7aa324 | ||
|
|
ec8c010c96 | ||
|
|
de91bdff74 | ||
|
|
51f87cc49c | ||
|
|
821a6ad4b2 | ||
|
|
cbf0d050f8 | ||
|
|
7fab59acd2 | ||
|
|
fedb67c809 | ||
|
|
0e8502b087 | ||
|
|
64672dbdf9 | ||
|
|
329eb31387 | ||
|
|
8e8dc273aa | ||
|
|
91fc383723 | ||
|
|
e4f4a088ce | ||
|
|
45a965135e | ||
|
|
81a23ea59d | ||
|
|
79da08b285 | ||
|
|
d3c7b3830f | ||
|
|
5e0a46f268 | ||
|
|
5d1c1494dd | ||
|
|
ed3036cc43 | ||
|
|
5410a5cecc | ||
|
|
65be638b66 | ||
|
|
563edbb07c | ||
|
|
5664447e15 | ||
|
|
eee7eda1a2 | ||
|
|
513a3d2175 | ||
|
|
11dde3a887 | ||
|
|
234017cc8a | ||
|
|
f6406f47a6 | ||
|
|
a7fb8f6007 | ||
|
|
6810aaeba1 | ||
|
|
6acba93c2c | ||
|
|
169ae7d562 | ||
|
|
c371d74a0c | ||
|
|
00681e95b5 | ||
|
|
5eed75e353 | ||
|
|
9223f40f6d | ||
|
|
34bceeea39 | ||
|
|
36ee59c7da | ||
|
|
c23f15b195 | ||
|
|
94c077a4fe | ||
|
|
23ba9ad8c1 | ||
|
|
f29e49dc4c | ||
|
|
7603a72101 | ||
|
|
569cf6b4a3 | ||
|
|
e2b30200bf | ||
|
|
07d8504f91 | ||
|
|
952f1429eb | ||
|
|
c79364cef2 | ||
|
|
3ee1c9fdcd | ||
|
|
385ebd01cc | ||
|
|
a8d40b4aea | ||
|
|
dffea51223 | ||
|
|
812170ce38 | ||
|
|
c8e89653ed | ||
|
|
521c24f762 | ||
|
|
47641eeb28 | ||
|
|
ff8f6da0bb | ||
|
|
9536b5db6f | ||
|
|
a9e4f2081d | ||
|
|
9e8d34e0dc | ||
|
|
47241897de | ||
|
|
aed3b12b09 | ||
|
|
0fde99dc68 | ||
|
|
0ae2665c56 | ||
|
|
0b311d37c8 | ||
|
|
a01d54cd80 | ||
|
|
fe2b4f6735 | ||
|
|
6f5bd626b4 | ||
|
|
a7ffc3b05f | ||
|
|
45b4f9570b | ||
|
|
1d9f7f052d | ||
|
|
47fd90e4a5 | ||
|
|
ed4d2685b4 | ||
|
|
b9aa968a2e | ||
|
|
3852e21571 | ||
|
|
5a69e6abb0 | ||
|
|
726fd0b338 | ||
|
|
4aadc9b050 | ||
|
|
41c9367c42 | ||
|
|
52f888167f | ||
|
|
fee1ad56f7 | ||
|
|
4e7cf0a1bd | ||
|
|
bc0914e146 | ||
|
|
b898a78e62 | ||
|
|
1b71fd4ca6 | ||
|
|
94d055610e | ||
|
|
be371e002a | ||
|
|
7905f51067 | ||
|
|
c7fef4e998 | ||
|
|
5d8b42a928 | ||
|
|
22f668245d | ||
|
|
ce89a5fdb9 | ||
|
|
63a12ba6ed | ||
|
|
7cdfe26a26 | ||
|
|
dfd67219e3 | ||
|
|
979df1585e | ||
|
|
731a7dd3e5 | ||
|
|
4ec7a628a6 | ||
|
|
48d4e3ed34 | ||
|
|
c8a4be00ce | ||
|
|
98158c4f0c | ||
|
|
49fe27176d | ||
|
|
3d7583e010 | ||
|
|
64ff4e0d5c | ||
|
|
84ee106ddf | ||
|
|
cbdd8e77db | ||
|
|
a0b1e23727 | ||
|
|
aa05ae3f32 | ||
|
|
7d7ae3f130 | ||
|
|
f9ed31c65a | ||
|
|
43977c737b | ||
|
|
62a1768307 | ||
|
|
26e8553d9e | ||
|
|
018424d5bd | ||
|
|
a6f5bd8d7d |
223
.github/workflows/CI.yml
vendored
223
.github/workflows/CI.yml
vendored
@@ -1,223 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - 'main'
|
||||
# paths-ignore:
|
||||
# - '**.md'
|
||||
# - '**.txt'
|
||||
# - '.github/**'
|
||||
# - '.idea/**'
|
||||
# - '!.github/workflows/CI.yml'
|
||||
|
||||
jobs:
|
||||
update_version:
|
||||
name: Read and update version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
# 定义输出变量 version,以便在其他job中引用
|
||||
new_version: ${{ steps.version.outputs.new_version }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
#- name: 获取first parent commit次数
|
||||
# id: get-first-parent-commit-count
|
||||
# run: |
|
||||
# version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
# recent_release_tag=$(git tag -l | grep $version | egrep -v "[-|+]" || true)
|
||||
# if [[ "x$recent_release_tag" == "x" ]]; then
|
||||
# echo "当前版本tag不存在,请手动生成tag."
|
||||
# exit 1
|
||||
# fi
|
||||
# git log --oneline HEAD
|
||||
# first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
|
||||
# echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 从tag获取之前的version_code与beta版本号
|
||||
id: get-previous-codes
|
||||
run: |
|
||||
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
last_tag=$(git tag --sort=committerdate | tail -1)
|
||||
if (echo $last_tag | grep -v "+"); then
|
||||
echo "Tag格式不正确"
|
||||
exit 1
|
||||
elif (echo $last_tag | grep -v $version); then
|
||||
echo "当前版本tag不存在,请手动添加tag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_code=$(echo $last_tag | cut -d "+" -f 2)
|
||||
beta_code=$(echo $last_tag | cut -d "+" -f 1 | cut -d "." -f 4)
|
||||
beta_code=${beta_code:-0}
|
||||
|
||||
echo "beta-code=$beta_code" >> $GITHUB_OUTPUT
|
||||
echo "version-code=$version_code" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 更新版本号
|
||||
id: version
|
||||
run: |
|
||||
# 读取版本号
|
||||
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
let beta_code=${{ steps.get-previous-codes.outputs.beta-code }}+1
|
||||
let version_code=${{ steps.get-previous-codes.outputs.version-code }}+1
|
||||
# 构建新版本号
|
||||
NEW_VERSION=${version_name}-beta.${beta_code}+${version_code}
|
||||
|
||||
# 输出新版本号
|
||||
echo "New version: $NEW_VERSION"
|
||||
|
||||
# 设置新版本号为输出变量
|
||||
echo "new_version=$NEW_VERSION" >>$GITHUB_OUTPUT
|
||||
|
||||
android:
|
||||
name: Build CI (Android)
|
||||
needs: update_version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 构建Java环境
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "17"
|
||||
token: ${{secrets.GIT_TOKEN}}
|
||||
|
||||
- name: 检查缓存
|
||||
uses: actions/cache@v2
|
||||
id: cache-flutter
|
||||
with:
|
||||
path: /root/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:
|
||||
flutter-version: 3.24.0
|
||||
channel: any
|
||||
|
||||
- name: 下载项目依赖
|
||||
run: flutter pub get
|
||||
|
||||
- name: 解码生成 jks
|
||||
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||
|
||||
- name: 更新版本号
|
||||
id: version
|
||||
run: |
|
||||
# 更新pubspec.yaml文件中的版本号
|
||||
sed -i "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
|
||||
|
||||
- name: flutter build apk
|
||||
run: flutter build apk --release --split-per-abi
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
|
||||
|
||||
- name: flutter build apk
|
||||
run: |
|
||||
sed -i "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}0/g" pubspec.yaml
|
||||
flutter build apk --release
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
|
||||
|
||||
- name: 重命名应用
|
||||
run: |
|
||||
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
for file in build/app/outputs/flutter-apk/app-*.apk; do
|
||||
if [[ $file =~ app-(.?*)release.apk ]]; then
|
||||
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}${version_name}.apk"
|
||||
mv "$file" "$new_file_name"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Pilipala-CI
|
||||
path: |
|
||||
build/app/outputs/flutter-apk/Pili-*.apk
|
||||
|
||||
iOS:
|
||||
name: Build CI (iOS)
|
||||
needs: update_version
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 安装Flutter
|
||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||
uses: subosito/flutter-action@v2.10.0
|
||||
with:
|
||||
cache: true
|
||||
flutter-version: 3.24.0
|
||||
|
||||
- name: 更新版本号
|
||||
id: version
|
||||
run: |
|
||||
# 更新pubspec.yaml文件中的版本号
|
||||
sed -i "" "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
|
||||
|
||||
- name: flutter build ipa
|
||||
run: |
|
||||
flutter build ios --release --no-codesign
|
||||
ln -sf ./build/ios/iphoneos Payload
|
||||
zip -r9 app.ipa Payload/runner.app
|
||||
|
||||
- name: 重命名应用
|
||||
run: |
|
||||
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
for file in app.ipa; do
|
||||
new_file_name="build/Pili-${version_name}.ipa"
|
||||
mv "$file" "$new_file_name"
|
||||
done
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: Pilipala-CI
|
||||
path: |
|
||||
build/Pili-*.ipa
|
||||
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- update_version
|
||||
- android
|
||||
- iOS
|
||||
steps:
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Pilipala-CI
|
||||
path: ./Pilipala-CI
|
||||
|
||||
- name: Upload Pre-release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: ${{ needs.update_version.outputs.new_version }}
|
||||
token: ${{ secrets.GIT_TOKEN }}
|
||||
commit: main
|
||||
tag: ${{ needs.update_version.outputs.new_version }}
|
||||
prerelease: true
|
||||
allowUpdates: true
|
||||
artifacts: Pilipala-CI/*
|
||||
@@ -10,6 +10,8 @@ jobs:
|
||||
steps:
|
||||
- name: 代码迁出
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 构建Java环境
|
||||
uses: actions/setup-java@v4
|
||||
@@ -28,12 +30,30 @@ jobs:
|
||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: 3.24.0
|
||||
channel: any
|
||||
channel: stable
|
||||
flutter-version-file: pubspec.yaml
|
||||
|
||||
- name: 修复3.24的stable显示中文不正确问题 // from orz12
|
||||
run: |
|
||||
version=$(grep -m 1 'flutter:' pubspec.yaml | awk '{print $2}')
|
||||
if [ "$(echo "$version < 3.27.0" | awk '{print ($1 < $2)}')" -eq 1 ]; then
|
||||
cd $FLUTTER_ROOT
|
||||
git config --global user.name "orz12"
|
||||
git config --global user.email "orz12@test.com"
|
||||
git cherry-pick d4124bd --strategy-option theirs
|
||||
# flutter precache
|
||||
flutter --version
|
||||
cd -
|
||||
fi
|
||||
|
||||
- name: 下载项目依赖
|
||||
run: flutter pub get
|
||||
|
||||
- name: 更新版本号
|
||||
run: |
|
||||
version_name=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
sed -i "s/version: .*/version: $version_name-$(git rev-parse --short HEAD)+$(git rev-list --count HEAD)/g" pubspec.yaml
|
||||
|
||||
- name: Write key
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.SIGN_KEYSTORE_BASE64 }}" ]; then
|
||||
@@ -45,17 +65,10 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: flutter build apk
|
||||
run: flutter build apk --release --target-platform=android-arm64
|
||||
|
||||
- name: flutter build apk
|
||||
run: flutter build apk --release --split-per-abi
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-release
|
||||
path: |
|
||||
build/app/outputs/flutter-apk/app-release.apk
|
||||
run: |
|
||||
chmod +x lib/scripts/build.sh
|
||||
lib/scripts/build.sh
|
||||
flutter build apk --release --split-per-abi
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v4
|
||||
130
.github/workflows/build-ios.yml
vendored
130
.github/workflows/build-ios.yml
vendored
@@ -1,130 +0,0 @@
|
||||
name: Build iOS
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'build-ios'
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '**.txt'
|
||||
- '.github/**'
|
||||
- '.idea/**'
|
||||
- '!.github/workflows/build-ios.yml'
|
||||
|
||||
jobs:
|
||||
update_version:
|
||||
name: Read latest version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
# 定义输出变量 version,以便在其他job中引用
|
||||
new_version: ${{ steps.get-last-tag.outputs.tag}}
|
||||
last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 获取最后一次提交
|
||||
id: get-last-commit
|
||||
run: |
|
||||
last_commit=$(git log -1 --pretty="%h %s" --first-parent)
|
||||
echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 获取最后一个tag
|
||||
id: get-last-tag
|
||||
run: |
|
||||
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
last_tag=$(git tag --sort=committerdate | tail -1)
|
||||
if (echo $last_tag | grep -v "+"); then
|
||||
echo "Illegal tag!"
|
||||
exit 1
|
||||
elif (echo $last_tag | grep -v $version); then
|
||||
echo "No tags for current version in the repo, please add one manually."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "tag=$last_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
iOS:
|
||||
name: Build CI (iOS)
|
||||
needs: update_version
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- name: 安装Flutter
|
||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||
uses: subosito/flutter-action@v2.10.0
|
||||
with:
|
||||
cache: true
|
||||
flutter-version: 3.24.0
|
||||
|
||||
- name: 更新版本号
|
||||
id: version
|
||||
run: |
|
||||
# 更新pubspec.yaml文件中的版本号
|
||||
sed -i "" "s/version: .*/version: ${{ needs.update_version.outputs.new_version }}/g" pubspec.yaml
|
||||
|
||||
- name: flutter build ipa
|
||||
run: |
|
||||
flutter build ios --release --no-codesign
|
||||
ln -sf ./build/ios/iphoneos Payload
|
||||
zip -r9 app.ipa Payload/runner.app
|
||||
|
||||
- name: 重命名应用
|
||||
run: |
|
||||
for file in app.ipa; do
|
||||
new_file_name="build/Pili-${{ needs.update_version.outputs.new_version }}.ipa"
|
||||
mv "$file" "$new_file_name"
|
||||
done
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
if-no-files-found: error
|
||||
name: PiliPalaX-iOS
|
||||
path: |
|
||||
build/Pili-*.ipa
|
||||
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- update_version
|
||||
- iOS
|
||||
steps:
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: PiliPalaX-iOS
|
||||
path: ./PiliPalaX-iOS
|
||||
|
||||
# - name: Upload Pre-release
|
||||
# uses: ncipollo/release-action@v1
|
||||
# with:
|
||||
# name: ${{ needs.update_version.outputs.new_version }}
|
||||
# token: ${{ secrets.GIT_TOKEN }}
|
||||
# commit: main
|
||||
# tag: ${{ needs.update_version.outputs.new_version }}
|
||||
# prerelease: true
|
||||
# allowUpdates: true
|
||||
# artifacts: Pilipala-CI/*
|
||||
|
||||
- name: 发送到Telegram频道
|
||||
uses: xireiki/channel-post@v1.0.7
|
||||
with:
|
||||
bot_token: ${{ secrets.BOT_TOKEN }}
|
||||
chat_id: ${{ secrets.CHAT_ID }}
|
||||
large_file: false
|
||||
method: sendFile
|
||||
path: PiliPalaX-iOS/*
|
||||
parse_mode: Markdown
|
||||
context: "*v${{ needs.update_version.outputs.new_version }}*\n${{ needs.update_version.outputs.last_commit }}"
|
||||
10
.github/workflows/ios.yml
vendored
10
.github/workflows/ios.yml
vendored
@@ -16,17 +16,23 @@ jobs:
|
||||
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: Set up xcode
|
||||
uses: BoundfoxStudios/action-xcode-select@v1
|
||||
- name: 更新版本号
|
||||
run: |
|
||||
version_name=$(yq e '.version' pubspec.yaml | cut -d "+" -f 1)
|
||||
sed -i '' "s/version: .*/version: $version_name+$(git rev-list --count HEAD)/" pubspec.yaml
|
||||
|
||||
- name: Build iOS
|
||||
run: |
|
||||
chmod +x lib/scripts/build.sh
|
||||
lib/scripts/build.sh
|
||||
flutter build ios --release --no-codesign
|
||||
ln -sf ./build/ios/iphoneos Payload
|
||||
zip -r9 ios-release-no-sign.ipa Payload/runner.app
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -133,4 +133,6 @@ app.*.symbols
|
||||
!**/ios/**/default.perspectivev3
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
!.vscode/settings.json
|
||||
!.vscode/settings.json
|
||||
|
||||
/lib/build_config.dart
|
||||
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -5,18 +5,18 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "pilipala",
|
||||
"name": "piliplus",
|
||||
"request": "launch",
|
||||
"type": "dart"
|
||||
},
|
||||
{
|
||||
"name": "pilipala (profile mode)",
|
||||
"name": "piliplus (profile mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "profile"
|
||||
},
|
||||
{
|
||||
"name": "pilipala (release mode)",
|
||||
"name": "piliplus (release mode)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "release"
|
||||
|
||||
25
README.md
25
README.md
@@ -1,24 +1,24 @@
|
||||
<div align="center">
|
||||
<img width="200" height="200" src="https://github.com/orz12/pilipala/blob/main/assets/images/logo/logo_android.png">
|
||||
<img width="200" height="200" src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/images/logo/logo_android.png">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div align="center">
|
||||
<h1>PiliPalaX</h1>
|
||||
<h1>PiliPlus</h1>
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
</div>
|
||||
<p>使用Flutter开发的BiliBili第三方客户端</p>
|
||||
|
||||
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/850shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
|
||||
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/850shots_so.png" width="32%" alt="home" />
|
||||
<br/>
|
||||
<img src="https://github.com/orz12/pilipala/blob/main/assets/screenshots/main_screen.png" width="96%" alt="home" />
|
||||
<img src="https://github.com/bggRGjQaUbCoE/PiliPlus/blob/main/assets/screenshots/main_screen.png" width="96%" alt="home" />
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
@@ -47,6 +47,10 @@
|
||||
|
||||
## feat
|
||||
|
||||
- [x] 显示视频分段信息
|
||||
- [x] 调节字幕大小
|
||||
- [x] 调节全屏弹幕大小
|
||||
- [x] 收藏夹/稍后再看多选删除
|
||||
- [x] 搜索用户动态
|
||||
- [x] 直播弹幕
|
||||
- [x] 修改头像/用户名/签名/性别/生日
|
||||
@@ -182,9 +186,10 @@
|
||||
|
||||
## 声明
|
||||
|
||||
此项目(PiliPalaX)是个人为了兴趣而开发, 仅用于学习和测试,请于下载后24小时内删除。
|
||||
此项目(PiliPlus)是个人为了兴趣而开发, 仅用于学习和测试,请于下载后24小时内删除。
|
||||
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||
在此致敬原作者:[guozhigq/pilipala](https://github.com/guozhigq/pilipala)
|
||||
在此致敬上游作者:[orz12/PiliPalaX](https://github.com/orz12/PiliPalaX)
|
||||
本仓库做了更激进的修改,感谢原作者的开源精神。
|
||||
|
||||
感谢使用
|
||||
|
||||
@@ -35,7 +35,7 @@ def _keyAlias = System.getenv("KEY_ALIAS") ?: keystoreProperties["keyAlias"]
|
||||
def _keyPassword = System.getenv("KEY_PASSWORD") ?: keystoreProperties["keyPassword"]
|
||||
|
||||
android {
|
||||
compileSdkVersion 34
|
||||
compileSdkVersion flutter.compileSdkVersion
|
||||
|
||||
namespace 'com.example.pilipalax'
|
||||
ndkVersion flutter.ndkVersion
|
||||
@@ -61,7 +61,7 @@ android {
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
minSdkVersion 21
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/>
|
||||
|
||||
<application
|
||||
android:label="PiliPalaX Debug"
|
||||
android:label="PiliPlus Debug"
|
||||
tools:replace="android:label">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -36,7 +36,7 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:label="PiliPalaX Debug">
|
||||
<intent-filter android:label="PiliPlus Debug">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -60,7 +60,7 @@
|
||||
<!--<data android:host="bangumi.bilibili.com"/>-->
|
||||
<!--<data android:host="space.bilibili.com"/>-->
|
||||
</intent-filter>
|
||||
<intent-filter android:label="PiliPalaX Debug">
|
||||
<intent-filter android:label="PiliPlus Debug">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:label="PiliPalaX"
|
||||
android:label="PiliPlus"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
@@ -67,7 +67,7 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter android:label="PiliPalaX+">
|
||||
<intent-filter android:label="PiliPlus">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
@@ -91,7 +91,7 @@
|
||||
<!--<data android:host="bangumi.bilibili.com"/>-->
|
||||
<!--<data android:host="space.bilibili.com"/>-->
|
||||
</intent-filter>
|
||||
<intent-filter android:label="PiliPalaX+">
|
||||
<intent-filter android:label="PiliPlus">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
@@ -16,7 +16,8 @@ class MainActivity : AudioServiceActivity() {
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
methodChannel = MethodChannel(flutterEngine!!.getDartExecutor()!!.getBinaryMessenger(), CHANNEL)
|
||||
|
||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "PiliPlus")
|
||||
methodChannel.setMethodCallHandler { call, result ->
|
||||
if (call.method == "back") {
|
||||
back()
|
||||
@@ -53,10 +54,6 @@ class MainActivity : AudioServiceActivity() {
|
||||
methodChannel.invokeMethod("onUserLeaveHint", null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL = "onUserLeaveHint"
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
||||
MethodChannel(
|
||||
@@ -7,6 +7,7 @@
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#212121</item>
|
||||
<item name="android:defaultFocusHighlightEnabled">false</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:defaultFocusHighlightEnabled">false</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#ffffff</item>
|
||||
<item name="android:defaultFocusHighlightEnabled">false</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:defaultFocusHighlightEnabled">false</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableJetifier=true
|
||||
@@ -1,5 +1,5 @@
|
||||
PiliPalaX is a third-party Bilibili client developed in Flutter,
|
||||
fork from PiliPala (https://github.com/guozhigq/pilipala).
|
||||
PiliPlus is a third-party Bilibili client developed in Flutter,
|
||||
fork from PiliPalaX (https://github.com/orz12/PiliPalaX).
|
||||
|
||||
Top Features:
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
PiliPalaX
|
||||
PiliPlus
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
PiliPalaX 是使用 Flutter 开发的 BiliBili 第三方客户端,
|
||||
是由PiliPala仓库fork并进行了差异化开发的版本
|
||||
PiliPlus 是使用 Flutter 开发的 BiliBili 第三方客户端,
|
||||
是由PiliPalaX仓库fork并进行了差异化开发的版本
|
||||
|
||||
主要功能:
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
PiliPalaX
|
||||
PiliPlus
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>PiliPalaX</string>
|
||||
<string>PiliPlus</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>PiliPalaX</string>
|
||||
<string>PiliPlus</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
@@ -42,7 +42,7 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
|
||||
@@ -21,13 +21,10 @@ class Constants {
|
||||
static const String traceId =
|
||||
'11111111111111111111111111111111:1111111111111111:0:0';
|
||||
static const String userAgent =
|
||||
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android build/1462100 channel/bili innerVer/1462100 osVer/14 network/2';
|
||||
'Mozilla/5.0 BiliDroid/1.46.2 (bbcallen@gmail.com) os/android model/vivo mobi_app/android_hd build/2001100 channel/yingyongbao innerVer/2001100 osVer/14 network/2';
|
||||
static const String statistics =
|
||||
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||
// jsonEncode(
|
||||
// {"appId": 5, "platform": 3, "version": "1.46.2", "abtest": ""});
|
||||
// Uri.encodeComponent(
|
||||
// '{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
|
||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||
static const List<Map<String, dynamic>> internationalDialingPrefix = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
|
||||
import 'skeleton.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
@@ -8,79 +8,88 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Skeleton(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius:
|
||||
BorderRadius.circular(StyleString.imgRadius.x),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// VideoContent(videoItem: videoItem)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius:
|
||||
BorderRadius.circular(StyleString.imgRadius.x),
|
||||
width: 200,
|
||||
height: 11,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
// VideoContent(videoItem: videoItem)
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 200,
|
||||
height: 11,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 150,
|
||||
height: 13,
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 100,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
Container(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 150,
|
||||
height: 13,
|
||||
),
|
||||
const Spacer(),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 100,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
@@ -45,6 +45,17 @@ class VideoCardVSkeleton extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
Container(
|
||||
width: 110,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(bottom: 5),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
Container(
|
||||
width: 75,
|
||||
height: 13,
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/common/widgets/no_splash_factory.dart';
|
||||
import 'package:PiliPalaX/common/widgets/overlay_pop.dart';
|
||||
import 'package:PiliPlus/common/widgets/no_splash_factory.dart';
|
||||
import 'package:PiliPlus/common/widgets/overlay_pop.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedDialog extends StatefulWidget {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/models/dynamics/article_content_model.dart';
|
||||
import 'package:PiliPalaX/pages/preview/view.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
@@ -9,6 +8,7 @@ import 'package:flutter_html/flutter_html.dart';
|
||||
Widget articleContent({
|
||||
required BuildContext context,
|
||||
required List<ArticleContentModel> list,
|
||||
Function(List<String>, int)? callback,
|
||||
}) {
|
||||
List<String>? imgList = list
|
||||
.where((item) => item.pic != null)
|
||||
@@ -17,7 +17,7 @@ Widget articleContent({
|
||||
.toList();
|
||||
return SliverList.separated(
|
||||
itemCount: list.length,
|
||||
itemBuilder: (_, index) {
|
||||
itemBuilder: (context, index) {
|
||||
ArticleContentModel item = list[index];
|
||||
if (item.text != null) {
|
||||
List<InlineSpan> spanList = [];
|
||||
@@ -26,7 +26,7 @@ Widget articleContent({
|
||||
text: item.word?.words,
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.3,
|
||||
fontSize: FontSize.large.value,
|
||||
fontSize: 17,
|
||||
height: LineHeight.percent(125).size,
|
||||
fontStyle:
|
||||
item.word?.style?.italic == true ? FontStyle.italic : null,
|
||||
@@ -56,25 +56,29 @@ Widget articleContent({
|
||||
);
|
||||
} else if (item.pic != null) {
|
||||
return LayoutBuilder(
|
||||
builder: (_, constraints) => GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(
|
||||
builder: (context, constraints) => Hero(
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(
|
||||
imgList,
|
||||
imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
);
|
||||
} else {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList: imgList,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -42,8 +42,8 @@ class PBadge extends StatelessWidget {
|
||||
color = Colors.white;
|
||||
}
|
||||
if (type == 'color') {
|
||||
bgColor = t.primaryContainer.withOpacity(0.5);
|
||||
color = t.primary;
|
||||
bgColor = t.secondaryContainer.withOpacity(0.5);
|
||||
color = t.onSecondaryContainer;
|
||||
}
|
||||
if (type == 'line') {
|
||||
bgColor = Colors.transparent;
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomSliverPersistentHeaderDelegate
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
CustomSliverPersistentHeaderDelegate({
|
||||
required this.child,
|
||||
required this.bgColor,
|
||||
double extent = 45,
|
||||
}) : _minExtent = extent,
|
||||
_maxExtent = extent;
|
||||
final double _minExtent;
|
||||
final double _maxExtent;
|
||||
final Widget child;
|
||||
final Color bgColor;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
//创建child子组件
|
||||
//shrinkOffset:child偏移值minExtent~maxExtent
|
||||
//overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false
|
||||
return ColoredBox(
|
||||
color: bgColor,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
//SliverPersistentHeader最大高度
|
||||
@override
|
||||
double get maxExtent => _maxExtent;
|
||||
|
||||
//SliverPersistentHeader最小高度
|
||||
@override
|
||||
double get minExtent => _minExtent;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(
|
||||
covariant CustomSliverPersistentHeaderDelegate oldDelegate) {
|
||||
return oldDelegate.bgColor != bgColor;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class CustomToast extends StatelessWidget {
|
||||
const CustomToast({super.key, required this.msg});
|
||||
@@ -11,8 +8,8 @@ class CustomToast extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double toastOpacity =
|
||||
setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
|
||||
final double toastOpacity = GStorage.setting
|
||||
.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
|
||||
return Container(
|
||||
margin:
|
||||
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),
|
||||
@@ -28,7 +25,7 @@ class CustomToast extends StatelessWidget {
|
||||
msg,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -133,32 +133,9 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
}
|
||||
if (_height == 0) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
// Padding which centers the flexible space within the app bar
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: MediaQuery.paddingOf(context).top / 2),
|
||||
child: Container(
|
||||
key: _childKey,
|
||||
child:
|
||||
widget.flexibleSpace ?? SizedBox(height: kToolbarHeight)),
|
||||
),
|
||||
Positioned.fill(
|
||||
// 10 is the magic number which the app bar is pushed down within the sliver app bar. Couldnt find exactly where this number
|
||||
// comes from and found it through trial and error.
|
||||
top: 10,
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: widget.leading,
|
||||
actions: widget.actions,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
child: Container(
|
||||
key: _childKey,
|
||||
child: widget.flexibleSpace ?? SizedBox(height: kToolbarHeight),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/pages/preview/view.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'network_img_layer.dart';
|
||||
@@ -9,6 +9,7 @@ Widget htmlRender({
|
||||
int? imgCount,
|
||||
List<String>? imgList,
|
||||
required double constrainedWidth,
|
||||
Function(List<String>, int)? callback,
|
||||
}) {
|
||||
return SelectionArea(
|
||||
child: Html(
|
||||
@@ -40,29 +41,29 @@ Widget htmlRender({
|
||||
// extensionContext.element!.previousElementSibling == null ||
|
||||
// extensionContext.element!.nextElementSibling == null;
|
||||
// imgUrl = Utils().imageUrl(imgUrl!);
|
||||
// return Image.network(
|
||||
// imgUrl,
|
||||
// return CachedNetworkImage(
|
||||
// imageUrl: imgUrl,
|
||||
// width: isEmote ? 22 : null,
|
||||
// height: isEmote ? 22 : null,
|
||||
// );
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(
|
||||
initialPage: 0,
|
||||
return Hero(
|
||||
tag: imgUrl,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback([imgUrl], 0);
|
||||
} else {
|
||||
context.imageView(
|
||||
imgList: [imgUrl],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: isEmote ? 22 : constrainedWidth,
|
||||
height: isEmote ? 22 : 200,
|
||||
src: imgUrl,
|
||||
ignoreHeight: !isEmote,
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: isEmote ? 22 : constrainedWidth,
|
||||
height: isEmote ? 22 : 200,
|
||||
src: imgUrl,
|
||||
ignoreHeight: !isEmote,
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -73,7 +74,7 @@ Widget htmlRender({
|
||||
],
|
||||
style: {
|
||||
'html': Style(
|
||||
fontSize: FontSize.large,
|
||||
fontSize: FontSize(16),
|
||||
lineHeight: LineHeight.percent(160),
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
@@ -91,7 +92,7 @@ Widget htmlRender({
|
||||
// margin: Margins.zero,
|
||||
),
|
||||
'span': Style(
|
||||
fontSize: FontSize.medium,
|
||||
fontSize: FontSize.large,
|
||||
height: Height(1.8),
|
||||
),
|
||||
'div': Style(height: Height.auto()),
|
||||
@@ -109,12 +110,12 @@ Widget htmlRender({
|
||||
margin: Margins.only(bottom: 8),
|
||||
),
|
||||
'h3,h4,h5': Style(
|
||||
fontSize: FontSize.large,
|
||||
fontSize: FontSize(16),
|
||||
fontWeight: FontWeight.bold,
|
||||
margin: Margins.only(bottom: 4),
|
||||
),
|
||||
'figcaption': Style(
|
||||
fontSize: FontSize.medium,
|
||||
fontSize: FontSize.large,
|
||||
textAlign: TextAlign.center,
|
||||
// margin: Margins.only(top: 4),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ class HttpError extends StatelessWidget {
|
||||
this.errMsg,
|
||||
this.callback,
|
||||
this.btnText,
|
||||
this.extraWidget,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@@ -14,6 +15,7 @@ class HttpError extends StatelessWidget {
|
||||
final String? errMsg;
|
||||
final Function()? callback;
|
||||
final String? btnText;
|
||||
final Widget? extraWidget;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -44,8 +46,13 @@ class HttpError extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
if (extraWidget != null) ...[
|
||||
const SizedBox(height: 10),
|
||||
extraWidget!,
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
if (callback != null) ...[
|
||||
const SizedBox(height: 20),
|
||||
if (extraWidget == null) const SizedBox(height: 20),
|
||||
FilledButton.tonal(
|
||||
onPressed: callback,
|
||||
style: ButtonStyle(
|
||||
|
||||
@@ -6,6 +6,7 @@ Widget iconButton({
|
||||
required IconData icon,
|
||||
required VoidCallback? onPressed,
|
||||
double size = 36,
|
||||
double? iconSize,
|
||||
Color? bgColor,
|
||||
Color? iconColor,
|
||||
}) {
|
||||
@@ -17,7 +18,7 @@ Widget iconButton({
|
||||
onPressed: onPressed,
|
||||
icon: Icon(
|
||||
icon,
|
||||
size: size / 2,
|
||||
size: iconSize ?? size / 2,
|
||||
color: iconColor ?? Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
style: IconButton.styleFrom(
|
||||
@@ -28,3 +29,22 @@ Widget iconButton({
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget mediumButton({
|
||||
String? tooltip,
|
||||
IconData? icon,
|
||||
VoidCallback? onPressed,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: tooltip,
|
||||
icon: Icon(icon),
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
96
lib/common/widgets/image_save.dart
Normal file
96
lib/common/widgets/image_save.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/utils/download.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
void imageSaveDialog({
|
||||
required BuildContext context,
|
||||
required String? title,
|
||||
required String? cover,
|
||||
}) {
|
||||
final double imgWidth = min(Get.width, Get.height) - 8 * 2;
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerScale_otherSlide,
|
||||
builder: (context) => Container(
|
||||
width: imgWidth,
|
||||
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: SmartDialog.dismiss,
|
||||
child: NetworkImgLayer(
|
||||
width: imgWidth,
|
||||
height: imgWidth / StyleString.aspectRatio,
|
||||
src: cover,
|
||||
quality: 100,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 8,
|
||||
top: 8,
|
||||
child: Container(
|
||||
width: 30,
|
||||
height: 30,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: SmartDialog.dismiss,
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SelectableText(
|
||||
title ?? '',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
IconButton(
|
||||
tooltip: '保存封面图',
|
||||
onPressed: () async {
|
||||
bool saveStatus = await DownloadUtils.downloadImg(
|
||||
context,
|
||||
[cover ?? ''],
|
||||
);
|
||||
// 保存成功,自动关闭弹窗
|
||||
if (saveStatus) {
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.download, size: 20),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/common/widgets/nine_grid_view.dart';
|
||||
import 'package:PiliPalaX/pages/preview/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ImageModel {
|
||||
@@ -23,10 +23,13 @@ class ImageModel {
|
||||
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
|
||||
}
|
||||
|
||||
Widget image(
|
||||
Widget imageview(
|
||||
double maxWidth,
|
||||
List<ImageModel> picArr,
|
||||
) {
|
||||
List<ImageModel> picArr, {
|
||||
VoidCallback? onViewImage,
|
||||
ValueChanged<int>? onDismissed,
|
||||
Function(List<String>, int)? callback,
|
||||
}) {
|
||||
double imageWidth = (maxWidth - 2 * 5) / 3;
|
||||
double imageHeight = imageWidth;
|
||||
if (picArr.length == 1) {
|
||||
@@ -53,39 +56,43 @@ Widget image(
|
||||
height: picArr.length == 1 ? imageHeight : null,
|
||||
width: picArr.length == 1 ? imageWidth : maxWidth,
|
||||
itemCount: picArr.length,
|
||||
itemBuilder: (context, index) => GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(
|
||||
itemBuilder: (context, index) => Hero(
|
||||
tag: picArr[index].url,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(picArr.map((item) => item.url).toList(), index);
|
||||
} else {
|
||||
onViewImage?.call();
|
||||
context.imageView(
|
||||
initialPage: index,
|
||||
imgList: picArr.map((item) => item.url).toList(),
|
||||
onDismissed: onDismissed,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: NetworkImgLayer(
|
||||
src: picArr[index].url,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
isLongPic: () => picArr[index].isLongPic,
|
||||
callback: () =>
|
||||
picArr[index].safeWidth <= picArr[index].safeHeight,
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: NetworkImgLayer(
|
||||
src: picArr[index].url,
|
||||
width: imageWidth,
|
||||
height: imageHeight,
|
||||
isLongPic: () => picArr[index].isLongPic,
|
||||
callback: () =>
|
||||
picArr[index].safeWidth <= picArr[index].safeHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (picArr[index].isLongPic)
|
||||
const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
),
|
||||
],
|
||||
if (picArr[index].isLongPic)
|
||||
const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
bottom: 8,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// https://github.com/qq326646683/interactiveviewer_gallery
|
||||
|
||||
/// A [PageRoute] with a semi transparent background.
|
||||
///
|
||||
/// Similar to calling [showDialog] except it can be used with a [Navigator] to
|
||||
/// show a [Hero] animation.
|
||||
class HeroDialogRoute<T> extends PageRoute<T> {
|
||||
HeroDialogRoute({
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@override
|
||||
bool get opaque => false;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
String? get barrierLabel => null;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => const Duration(milliseconds: 300);
|
||||
|
||||
@override
|
||||
bool get maintainState => true;
|
||||
|
||||
@override
|
||||
Color? get barrierColor => null;
|
||||
|
||||
@override
|
||||
Widget buildTransitions(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return FadeTransition(
|
||||
opacity: CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildPage(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
) {
|
||||
final Widget child = builder(context);
|
||||
final Widget result = Semantics(
|
||||
scopesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
child: child,
|
||||
);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
1515
lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart
Normal file
1515
lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,240 @@
|
||||
import 'interactive_viewer.dart' as custom;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// https://github.com/qq326646683/interactiveviewer_gallery
|
||||
|
||||
/// A callback for the [InteractiveViewerBoundary] that is called when the scale
|
||||
/// changed.
|
||||
typedef ScaleChanged = void Function(double scale);
|
||||
|
||||
/// Builds an [InteractiveViewer] and provides callbacks that are called when a
|
||||
/// horizontal boundary has been hit.
|
||||
///
|
||||
/// The callbacks are called when an interaction ends by listening to the
|
||||
/// [InteractiveViewer.onInteractionEnd] callback.
|
||||
class InteractiveViewerBoundary extends StatefulWidget {
|
||||
const InteractiveViewerBoundary({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.boundaryWidth,
|
||||
this.controller,
|
||||
this.onScaleChanged,
|
||||
this.onLeftBoundaryHit,
|
||||
this.onRightBoundaryHit,
|
||||
this.onNoBoundaryHit,
|
||||
required this.maxScale,
|
||||
required this.minScale,
|
||||
this.onDismissed,
|
||||
this.onReset,
|
||||
this.dismissThreshold = 0.2,
|
||||
});
|
||||
|
||||
final VoidCallback? onReset;
|
||||
final double dismissThreshold;
|
||||
final VoidCallback? onDismissed;
|
||||
|
||||
final Widget child;
|
||||
|
||||
/// The max width this widget can have.
|
||||
///
|
||||
/// If the [InteractiveViewer] can take up the entire screen width, this
|
||||
/// should be set to `MediaQuery.of(context).size.width`.
|
||||
final double boundaryWidth;
|
||||
|
||||
/// The [TransformationController] for the [InteractiveViewer].
|
||||
final custom.TransformationController? controller;
|
||||
|
||||
/// Called when the scale changed after an interaction ended.
|
||||
final ScaleChanged? onScaleChanged;
|
||||
|
||||
/// Called when the left boundary has been hit after an interaction ended.
|
||||
final VoidCallback? onLeftBoundaryHit;
|
||||
|
||||
/// Called when the right boundary has been hit after an interaction ended.
|
||||
final VoidCallback? onRightBoundaryHit;
|
||||
|
||||
/// Called when no boundary has been hit after an interaction ended.
|
||||
final VoidCallback? onNoBoundaryHit;
|
||||
|
||||
final double maxScale;
|
||||
|
||||
final double minScale;
|
||||
|
||||
@override
|
||||
InteractiveViewerBoundaryState createState() =>
|
||||
InteractiveViewerBoundaryState();
|
||||
}
|
||||
|
||||
class InteractiveViewerBoundaryState extends State<InteractiveViewerBoundary>
|
||||
with SingleTickerProviderStateMixin {
|
||||
custom.TransformationController? _controller;
|
||||
|
||||
double? _scale;
|
||||
|
||||
late AnimationController _animateController;
|
||||
late Animation<Offset> _slideAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<Decoration> _opacityAnimation;
|
||||
|
||||
Offset _offset = Offset.zero;
|
||||
bool _dragging = false;
|
||||
|
||||
bool get _isActive => _dragging || _animateController.isAnimating;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = widget.controller ?? custom.TransformationController();
|
||||
|
||||
_animateController = AnimationController(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_updateMoveAnimation();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller!.dispose();
|
||||
_animateController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _updateMoveAnimation() {
|
||||
final double endX = _offset.dx.sign * (_offset.dx.abs() / _offset.dy.abs());
|
||||
final double endY = _offset.dy.sign;
|
||||
|
||||
_slideAnimation = _animateController.drive(
|
||||
Tween<Offset>(
|
||||
begin: Offset.zero,
|
||||
end: Offset(endX, endY),
|
||||
),
|
||||
);
|
||||
|
||||
_scaleAnimation = _animateController.drive(
|
||||
Tween<double>(
|
||||
begin: 1,
|
||||
end: 0.25,
|
||||
),
|
||||
);
|
||||
|
||||
_opacityAnimation = _animateController.drive(
|
||||
DecorationTween(
|
||||
begin: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
),
|
||||
end: const BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDragStart(ScaleStartDetails details) {
|
||||
_dragging = true;
|
||||
|
||||
if (_animateController.isAnimating) {
|
||||
_animateController.stop();
|
||||
} else {
|
||||
_offset = Offset.zero;
|
||||
_animateController.value = 0.0;
|
||||
}
|
||||
setState(_updateMoveAnimation);
|
||||
}
|
||||
|
||||
void _handleDragUpdate(ScaleUpdateDetails details) {
|
||||
if (!_isActive || _animateController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
_offset += details.focalPointDelta;
|
||||
|
||||
setState(_updateMoveAnimation);
|
||||
|
||||
if (!_animateController.isAnimating) {
|
||||
_animateController.value = _offset.dy.abs() / context.size!.height;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragEnd(ScaleEndDetails details) {
|
||||
if (!_isActive || _animateController.isAnimating) {
|
||||
return;
|
||||
}
|
||||
|
||||
_dragging = false;
|
||||
|
||||
if (_animateController.isCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_animateController.isDismissed) {
|
||||
// if the dragged value exceeded the dismissThreshold, call onDismissed
|
||||
// else animate back to initial position.
|
||||
if (_animateController.value > widget.dismissThreshold) {
|
||||
widget.onDismissed?.call();
|
||||
} else {
|
||||
_animateController.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateBoundaryDetection() {
|
||||
final double scale = _controller!.value.row0[0];
|
||||
|
||||
if (_scale != scale) {
|
||||
// the scale changed
|
||||
_scale = scale;
|
||||
widget.onScaleChanged?.call(scale);
|
||||
}
|
||||
|
||||
if (scale <= 1.01) {
|
||||
// cant hit any boundaries when the child is not scaled
|
||||
return;
|
||||
}
|
||||
|
||||
final double xOffset = _controller!.value.row0[3];
|
||||
final double boundaryWidth = widget.boundaryWidth;
|
||||
final double boundaryEnd = boundaryWidth * scale;
|
||||
final double xPos = boundaryEnd + xOffset;
|
||||
|
||||
if (boundaryEnd.round() == xPos.round()) {
|
||||
// left boundary hit
|
||||
widget.onLeftBoundaryHit?.call();
|
||||
} else if (boundaryWidth.round() == xPos.round()) {
|
||||
// right boundary hit
|
||||
widget.onRightBoundaryHit?.call();
|
||||
} else {
|
||||
widget.onNoBoundaryHit?.call();
|
||||
}
|
||||
}
|
||||
|
||||
Widget get content => DecoratedBoxTransition(
|
||||
decoration: _opacityAnimation,
|
||||
child: SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleAnimation,
|
||||
child: widget.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return custom.InteractiveViewer(
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
transformationController: _controller,
|
||||
onInteractionEnd: (_) => _updateBoundaryDetection(),
|
||||
onPanStart: _handleDragStart,
|
||||
onPanUpdate: _handleDragUpdate,
|
||||
onPanEnd: _handleDragEnd,
|
||||
onReset: widget.onReset,
|
||||
isAnimating: () => _animateController.value != 0,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/utils/download.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:status_bar_control/status_bar_control.dart';
|
||||
import 'interactive_viewer_boundary.dart';
|
||||
import 'interactive_viewer.dart' as custom;
|
||||
|
||||
/// https://github.com/qq326646683/interactiveviewer_gallery
|
||||
|
||||
/// Builds a carousel controlled by a [PageView] for the tweet media sources.
|
||||
///
|
||||
/// Used for showing a full screen view of the [TweetMedia] sources.
|
||||
///
|
||||
/// The sources can be panned and zoomed interactively using an
|
||||
/// [InteractiveViewer].
|
||||
/// An [InteractiveViewerBoundary] is used to detect when the boundary of the
|
||||
/// source is hit after zooming in to disable or enable the swiping gesture of
|
||||
/// the [PageView].
|
||||
///
|
||||
typedef IndexedFocusedWidgetBuilder = Widget Function(
|
||||
BuildContext context, int index, bool isFocus, bool enablePageView);
|
||||
|
||||
typedef IndexedTagStringBuilder = String Function(int index);
|
||||
|
||||
class InteractiveviewerGallery<T> extends StatefulWidget {
|
||||
const InteractiveviewerGallery({
|
||||
super.key,
|
||||
required this.sources,
|
||||
required this.initIndex,
|
||||
this.itemBuilder,
|
||||
this.maxScale = 8,
|
||||
this.minScale = 1.0,
|
||||
this.onPageChanged,
|
||||
this.onDismissed,
|
||||
this.setStatusBar,
|
||||
});
|
||||
|
||||
final bool? setStatusBar;
|
||||
|
||||
/// The sources to show.
|
||||
final List<String> sources;
|
||||
|
||||
/// The index of the first source in [sources] to show.
|
||||
final int initIndex;
|
||||
|
||||
/// The item content
|
||||
final IndexedFocusedWidgetBuilder? itemBuilder;
|
||||
|
||||
final double maxScale;
|
||||
|
||||
final double minScale;
|
||||
|
||||
final ValueChanged<int>? onPageChanged;
|
||||
|
||||
final ValueChanged<int>? onDismissed;
|
||||
|
||||
@override
|
||||
State<InteractiveviewerGallery> createState() =>
|
||||
_InteractiveviewerGalleryState();
|
||||
}
|
||||
|
||||
class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
with SingleTickerProviderStateMixin {
|
||||
PageController? _pageController;
|
||||
custom.TransformationController? _transformationController;
|
||||
|
||||
/// The controller to animate the transformation value of the
|
||||
/// [InteractiveViewer] when it should reset.
|
||||
late AnimationController _animationController;
|
||||
Animation<Matrix4>? _animation;
|
||||
|
||||
/// `true` when an source is zoomed in and not at the at a horizontal boundary
|
||||
/// to disable the [PageView].
|
||||
bool _enablePageView = true;
|
||||
|
||||
late Offset _doubleTapLocalPosition;
|
||||
|
||||
int? currentIndex;
|
||||
|
||||
late List<bool> _thumbList;
|
||||
late int _quality;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_quality =
|
||||
GStorage.setting.get(SettingBoxKey.previewQuality, defaultValue: 80);
|
||||
_thumbList = List.generate(widget.sources.length, (_) => true);
|
||||
|
||||
_pageController = PageController(initialPage: widget.initIndex);
|
||||
|
||||
_transformationController = custom.TransformationController();
|
||||
|
||||
_animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
)..addListener(() {
|
||||
_transformationController!.value =
|
||||
_animation?.value ?? Matrix4.identity();
|
||||
});
|
||||
|
||||
currentIndex = widget.initIndex;
|
||||
if (widget.setStatusBar != false) {
|
||||
setStatusBar();
|
||||
}
|
||||
}
|
||||
|
||||
setStatusBar() async {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
await StatusBarControl.setHidden(
|
||||
true,
|
||||
animation: StatusBarAnimation.FADE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
_pageController?.dispose();
|
||||
_animationController.removeListener(() {});
|
||||
_animationController.dispose();
|
||||
if (widget.setStatusBar != false) {
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
|
||||
}
|
||||
}
|
||||
for (int index = 0; index < widget.sources.length; index++) {
|
||||
CachedNetworkImageProvider(_getActualUrl(index)).evict();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// When the source gets scaled up, the swipe up / down to dismiss gets
|
||||
/// disabled.
|
||||
///
|
||||
/// When the scale resets, the dismiss and the page view swiping gets enabled.
|
||||
void _onScaleChanged(double scale) {
|
||||
final bool initialScale = scale <= widget.minScale;
|
||||
|
||||
if (initialScale) {
|
||||
if (!_enablePageView) {
|
||||
setState(() {
|
||||
_enablePageView = true;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (_enablePageView) {
|
||||
setState(() {
|
||||
_enablePageView = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When the left boundary has been hit after scaling up the source, the page
|
||||
/// view swiping gets enabled if it has a page to swipe to.
|
||||
void _onLeftBoundaryHit() {
|
||||
if (!_enablePageView && _pageController!.page!.floor() > 0) {
|
||||
setState(() {
|
||||
_enablePageView = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// When the right boundary has been hit after scaling up the source, the page
|
||||
/// view swiping gets enabled if it has a page to swipe to.
|
||||
void _onRightBoundaryHit() {
|
||||
if (!_enablePageView &&
|
||||
_pageController!.page!.floor() < widget.sources.length - 1) {
|
||||
setState(() {
|
||||
_enablePageView = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// When the source has been scaled up and no horizontal boundary has been hit,
|
||||
/// the page view swiping gets disabled.
|
||||
void _onNoBoundaryHit() {
|
||||
if (_enablePageView) {
|
||||
setState(() {
|
||||
_enablePageView = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// When the page view changed its page, the source will animate back into the
|
||||
/// original scale if it was scaled up.
|
||||
///
|
||||
/// Additionally the swipe up / down to dismiss gets enabled.
|
||||
void _onPageChanged(int page) {
|
||||
setState(() {
|
||||
currentIndex = page;
|
||||
});
|
||||
widget.onPageChanged?.call(page);
|
||||
if (_transformationController!.value != Matrix4.identity()) {
|
||||
// animate the reset for the transformation of the interactive viewer
|
||||
|
||||
_animation = Matrix4Tween(
|
||||
begin: _transformationController!.value,
|
||||
end: Matrix4.identity(),
|
||||
).animate(
|
||||
CurveTween(curve: Curves.easeOut).animate(_animationController),
|
||||
);
|
||||
|
||||
_animationController.forward(from: 0);
|
||||
}
|
||||
}
|
||||
|
||||
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
|
||||
? '${widget.sources[index]}@${_quality}q.webp'.http2https
|
||||
: widget.sources[index].http2https;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
InteractiveViewerBoundary(
|
||||
controller: _transformationController,
|
||||
boundaryWidth: MediaQuery.of(context).size.width,
|
||||
onScaleChanged: _onScaleChanged,
|
||||
onLeftBoundaryHit: _onLeftBoundaryHit,
|
||||
onRightBoundaryHit: _onRightBoundaryHit,
|
||||
onNoBoundaryHit: _onNoBoundaryHit,
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
onDismissed: () {
|
||||
Get.back();
|
||||
widget.onDismissed?.call(_pageController!.page!.floor());
|
||||
},
|
||||
onReset: () {
|
||||
if (!_enablePageView) {
|
||||
setState(() {
|
||||
_enablePageView = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: PageView.builder(
|
||||
onPageChanged: _onPageChanged,
|
||||
controller: _pageController,
|
||||
physics:
|
||||
_enablePageView ? null : const NeverScrollableScrollPhysics(),
|
||||
itemCount: widget.sources.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: Get.back,
|
||||
onDoubleTapDown: (TapDownDetails details) {
|
||||
_doubleTapLocalPosition = details.localPosition;
|
||||
},
|
||||
onDoubleTap: onDoubleTap,
|
||||
onLongPress: onLongPress,
|
||||
child: widget.itemBuilder != null
|
||||
? widget.itemBuilder!(
|
||||
context,
|
||||
index,
|
||||
index == currentIndex,
|
||||
_enablePageView,
|
||||
)
|
||||
: _itemBuilder(index),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
12,
|
||||
8,
|
||||
20,
|
||||
MediaQuery.of(context).padding.bottom + 8,
|
||||
),
|
||||
decoration: _enablePageView
|
||||
? BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.3)
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
widget.onDismissed?.call(_pageController!.page!.floor());
|
||||
},
|
||||
),
|
||||
widget.sources.length > 1
|
||||
? Text(
|
||||
"${currentIndex! + 1}/${widget.sources.length}",
|
||||
style: const TextStyle(color: Colors.white),
|
||||
)
|
||||
: const SizedBox(),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
PopupMenuItem(
|
||||
value: 0,
|
||||
onTap: () => onShareImg(widget.sources[currentIndex!]),
|
||||
child: const Text("分享图片"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 1,
|
||||
onTap: () {
|
||||
Utils.copyText(widget.sources[currentIndex!]);
|
||||
},
|
||||
child: const Text("复制链接"),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 2,
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
[widget.sources[currentIndex!]],
|
||||
);
|
||||
},
|
||||
child: const Text("保存图片"),
|
||||
),
|
||||
if (widget.sources.length > 1)
|
||||
PopupMenuItem(
|
||||
value: 3,
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
widget.sources as List<String>,
|
||||
);
|
||||
},
|
||||
child: const Text("保存全部图片"),
|
||||
),
|
||||
];
|
||||
},
|
||||
child: const Icon(Icons.more_horiz, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 图片分享
|
||||
void onShareImg(String imgUrl) async {
|
||||
SmartDialog.showLoading();
|
||||
var response = await Dio()
|
||||
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
||||
final temp = await getTemporaryDirectory();
|
||||
SmartDialog.dismiss();
|
||||
String imgName =
|
||||
"plpl_pic_${DateTime.now().toString().split('-').join()}.jpg";
|
||||
var path = '${temp.path}/$imgName';
|
||||
File(path).writeAsBytesSync(response.data);
|
||||
Share.shareXFiles([XFile(path)], subject: imgUrl);
|
||||
}
|
||||
|
||||
Widget _itemBuilder(index) {
|
||||
return Center(
|
||||
child: Hero(
|
||||
tag: widget.sources[index],
|
||||
child: CachedNetworkImage(
|
||||
fadeInDuration: const Duration(milliseconds: 0),
|
||||
fadeOutDuration: const Duration(milliseconds: 0),
|
||||
imageUrl: _getActualUrl(index),
|
||||
// fit: BoxFit.contain,
|
||||
progressIndicatorBuilder: (context, url, progress) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: 150.0,
|
||||
child: LinearProgressIndicator(value: progress.progress ?? 0),
|
||||
),
|
||||
);
|
||||
},
|
||||
// errorListener: (value) {
|
||||
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// setState(() {
|
||||
// _thumbList[index] = false;
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
onDoubleTap() {
|
||||
Matrix4 matrix = _transformationController!.value.clone();
|
||||
double currentScale = matrix.row0.x;
|
||||
|
||||
double targetScale = widget.minScale;
|
||||
|
||||
if (currentScale <= widget.minScale) {
|
||||
targetScale = widget.maxScale * 0.4;
|
||||
}
|
||||
|
||||
double offSetX = targetScale == 1.0
|
||||
? 0.0
|
||||
: -_doubleTapLocalPosition.dx * (targetScale - 1);
|
||||
double offSetY = targetScale == 1.0
|
||||
? 0.0
|
||||
: -_doubleTapLocalPosition.dy * (targetScale - 1);
|
||||
|
||||
matrix = Matrix4.fromList([
|
||||
targetScale,
|
||||
matrix.row1.x,
|
||||
matrix.row2.x,
|
||||
matrix.row3.x,
|
||||
matrix.row0.y,
|
||||
targetScale,
|
||||
matrix.row2.y,
|
||||
matrix.row3.y,
|
||||
matrix.row0.z,
|
||||
matrix.row1.z,
|
||||
targetScale,
|
||||
matrix.row3.z,
|
||||
offSetX,
|
||||
offSetY,
|
||||
matrix.row2.w,
|
||||
matrix.row3.w
|
||||
]);
|
||||
|
||||
_animation = Matrix4Tween(
|
||||
begin: _transformationController!.value,
|
||||
end: matrix,
|
||||
).animate(
|
||||
CurveTween(curve: Curves.easeOut).animate(_animationController),
|
||||
);
|
||||
_animationController
|
||||
.forward(from: 0)
|
||||
.whenComplete(() => _onScaleChanged(targetScale));
|
||||
}
|
||||
|
||||
onLongPress() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () {
|
||||
onShareImg(widget.sources[currentIndex!]);
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
title: const Text('分享', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Utils.copyText(widget.sources[currentIndex!]);
|
||||
},
|
||||
dense: true,
|
||||
title: const Text('复制链接', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
[widget.sources[currentIndex!]],
|
||||
);
|
||||
},
|
||||
dense: true,
|
||||
title: const Text('保存图片', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
if (widget.sources.length > 1)
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
widget.sources as List<String>,
|
||||
);
|
||||
},
|
||||
dense: true,
|
||||
title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/http/video.dart';
|
||||
import 'package:PiliPalaX/models/bangumi/info.dart' as bangumi;
|
||||
import 'package:PiliPalaX/models/video_detail_res.dart' as video;
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart' as bangumi;
|
||||
import 'package:PiliPlus/models/video_detail_res.dart' as video;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
@@ -16,14 +18,18 @@ import '../../utils/utils.dart';
|
||||
class ListSheetContent extends StatefulWidget {
|
||||
const ListSheetContent({
|
||||
super.key,
|
||||
this.index,
|
||||
this.index, // tab index
|
||||
this.season,
|
||||
required this.episodes,
|
||||
this.episodes,
|
||||
this.bvid,
|
||||
this.aid,
|
||||
required this.currentCid,
|
||||
required this.changeFucCall,
|
||||
required this.onClose,
|
||||
this.onClose,
|
||||
this.onReverse,
|
||||
this.showTitle,
|
||||
this.isSupportReverse,
|
||||
this.isReversed,
|
||||
});
|
||||
|
||||
final dynamic index;
|
||||
@@ -34,6 +40,10 @@ class ListSheetContent extends StatefulWidget {
|
||||
final int currentCid;
|
||||
final Function changeFucCall;
|
||||
final VoidCallback? onClose;
|
||||
final VoidCallback? onReverse;
|
||||
final bool? showTitle;
|
||||
final bool? isSupportReverse;
|
||||
final bool? isReversed;
|
||||
|
||||
@override
|
||||
State<ListSheetContent> createState() => _ListSheetContentState();
|
||||
@@ -42,26 +52,63 @@ class ListSheetContent extends StatefulWidget {
|
||||
class _ListSheetContentState extends State<ListSheetContent>
|
||||
with TickerProviderStateMixin {
|
||||
late List<ItemScrollController> itemScrollController = [];
|
||||
late final int currentIndex =
|
||||
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
|
||||
0;
|
||||
late int currentIndex = _currentIndex;
|
||||
late List<bool> reverse;
|
||||
|
||||
int get _index => widget.index ?? 0;
|
||||
bool get _isList =>
|
||||
widget.season != null &&
|
||||
late final bool _isList = widget.season != null &&
|
||||
widget.season?.sections is List &&
|
||||
widget.season.sections.length > 1;
|
||||
dynamic get episodes =>
|
||||
widget.episodes ?? widget.season?.sections[_index].episodes;
|
||||
TabController? _ctr;
|
||||
StreamController? _indexStream;
|
||||
int? _seasonFav;
|
||||
StreamController? _favStream;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(ListSheetContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.showTitle != false) {
|
||||
return;
|
||||
}
|
||||
|
||||
int currentIndex = _currentIndex;
|
||||
|
||||
void jumpToCurrent() {
|
||||
if (this.currentIndex != currentIndex) {
|
||||
this.currentIndex = currentIndex;
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(
|
||||
index: currentIndex,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// jump to current
|
||||
if (_ctr != null && widget.index != _ctr?.index) {
|
||||
_ctr?.animateTo(_index, duration: const Duration(milliseconds: 200));
|
||||
Future.delayed(const Duration(milliseconds: 300)).then((_) {
|
||||
jumpToCurrent();
|
||||
});
|
||||
} else {
|
||||
jumpToCurrent();
|
||||
}
|
||||
}
|
||||
|
||||
int get _currentIndex => max(
|
||||
0,
|
||||
_isList
|
||||
? widget.season.sections[_index].episodes
|
||||
.indexWhere((e) => e.cid == widget.currentCid)
|
||||
: episodes.indexWhere((e) => e.cid == widget.currentCid));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (_isList) {
|
||||
_indexStream = StreamController<int>();
|
||||
_indexStream ??= StreamController<int>.broadcast();
|
||||
_ctr = TabController(
|
||||
vsync: this,
|
||||
length: widget.season.sections.length,
|
||||
@@ -77,11 +124,8 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
reverse = _isList
|
||||
? List.generate(widget.season.sections.length, (_) => false)
|
||||
: [false];
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
});
|
||||
if (widget.bvid != null && widget.season != null) {
|
||||
_favStream = StreamController<int>();
|
||||
_favStream ??= StreamController<int>();
|
||||
() async {
|
||||
dynamic result = await VideoHttp.videoRelation(bvid: widget.bvid);
|
||||
if (result['status']) {
|
||||
@@ -90,12 +134,19 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
}
|
||||
}();
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
} catch (_) {}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_favStream?.close();
|
||||
_favStream = null;
|
||||
_indexStream?.close();
|
||||
_indexStream = null;
|
||||
_ctr?.removeListener(() {});
|
||||
_ctr?.dispose();
|
||||
super.dispose();
|
||||
@@ -138,6 +189,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
}
|
||||
SmartDialog.showToast('切换到:$title');
|
||||
widget.onClose?.call();
|
||||
currentIndex = index;
|
||||
widget.changeFucCall(
|
||||
episode is bangumi.EpisodeItem ? episode.epId : null,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
@@ -171,7 +223,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
)
|
||||
: null,
|
||||
child: LayoutBuilder(
|
||||
builder: (_, constraints) => NetworkImgLayer(
|
||||
builder: (context, constraints) => NetworkImgLayer(
|
||||
radius: 6,
|
||||
src: episode is video.EpisodeItem
|
||||
? episode.arc?.pic
|
||||
@@ -230,17 +282,19 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : widget.episodes!.length})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (_, snapshot) => snapshot.hasData
|
||||
? _mediumButton(
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
@@ -262,39 +316,43 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
_mediumButton(
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: widget.episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
_mediumButton(
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: widget.episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
_mediumButton(
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
@@ -302,21 +360,36 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: 0,
|
||||
builder: (_, snapshot) => _mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '正序' : '反序',
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortAscending
|
||||
: MdiIcons.sortDescending,
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
|
||||
@@ -324,11 +397,12 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
},
|
||||
),
|
||||
),
|
||||
_mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -359,36 +433,41 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
)
|
||||
: _buildBody(null, widget.episodes),
|
||||
: _buildBody(null, episodes),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _mediumButton({
|
||||
String? tooltip,
|
||||
IconData? icon,
|
||||
VoidCallback? onPressed,
|
||||
}) {
|
||||
return SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
tooltip: tooltip,
|
||||
icon: Icon(icon),
|
||||
style: ButtonStyle(
|
||||
padding: WidgetStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget get _reverseButton => mediumButton(
|
||||
tooltip: widget.isReversed == true ? '正序播放' : '倒序播放',
|
||||
icon: widget.isReversed == true
|
||||
? MdiIcons.sortDescending
|
||||
: MdiIcons.sortAscending,
|
||||
onPressed: () async {
|
||||
if (widget.showTitle == false) {
|
||||
// jump to current
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
widget.onReverse?.call();
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBody(i, episodes) => Material(
|
||||
child: ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
@@ -405,7 +484,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (_, index) => Divider(
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
|
||||
@@ -80,7 +80,7 @@ class LiveCard extends StatelessWidget {
|
||||
Text(
|
||||
liveItem.title as String,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w400),
|
||||
style: const TextStyle(fontSize: 13),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget get loadingWidget => Center(child: CircularProgressIndicator());
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/global_data.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
Box<dynamic> setting = GStorage.setting;
|
||||
|
||||
class NetworkImgLayer extends StatelessWidget {
|
||||
const NetworkImgLayer({
|
||||
super.key,
|
||||
@@ -67,7 +63,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
child: Builder(
|
||||
builder: (context) => CachedNetworkImage(
|
||||
imageUrl:
|
||||
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
|
||||
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
|
||||
width: width,
|
||||
height: ignoreHeight == null || ignoreHeight == false
|
||||
? height
|
||||
@@ -83,17 +79,17 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
fadeInDuration:
|
||||
fadeInDuration ?? const Duration(milliseconds: 120),
|
||||
filterQuality: FilterQuality.low,
|
||||
errorWidget: (BuildContext context, String url, Object error) =>
|
||||
placeholder(context),
|
||||
// errorWidget: (BuildContext context, String url, Object error) =>
|
||||
// placeholder(context),
|
||||
placeholder: (BuildContext context, String url) =>
|
||||
placeholder(context),
|
||||
imageBuilder: imageBuilder,
|
||||
errorListener: (value) {
|
||||
thumbnail = false;
|
||||
if (context.mounted) {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
},
|
||||
// errorListener: (value) {
|
||||
// thumbnail = false;
|
||||
// if (context.mounted) {
|
||||
// (context as Element).markNeedsBuild();
|
||||
// }
|
||||
// },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../utils/download.dart';
|
||||
@@ -83,14 +82,8 @@ class OverlayPop extends StatelessWidget {
|
||||
context,
|
||||
[
|
||||
videoItem is card.Card
|
||||
? (videoItem as card.Card)
|
||||
.smallCoverV5
|
||||
.base
|
||||
.cover
|
||||
.http2https
|
||||
: (videoItem.pic != null
|
||||
? (videoItem.pic as String).http2https
|
||||
: (videoItem.cover as String).http2https)
|
||||
? (videoItem as card.Card).smallCoverV5.base.cover
|
||||
: videoItem.pic ?? videoItem.cover
|
||||
],
|
||||
);
|
||||
closeFn?.call();
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui show Image;
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
double get maxDragOffset => 100;
|
||||
double hideHeight = maxDragOffset / 2.3;
|
||||
double refreshHeight = maxDragOffset / 1.5;
|
||||
|
||||
class PullToRefreshHeader extends StatelessWidget {
|
||||
const PullToRefreshHeader(
|
||||
this.info,
|
||||
this.lastRefreshTime, {
|
||||
this.color,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final PullToRefreshScrollNotificationInfo? info;
|
||||
final DateTime? lastRefreshTime;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PullToRefreshScrollNotificationInfo? infos = info;
|
||||
if (infos == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
String text = '';
|
||||
if (infos.mode == PullToRefreshIndicatorMode.armed) {
|
||||
text = 'Release to refresh';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.refresh ||
|
||||
infos.mode == PullToRefreshIndicatorMode.snap) {
|
||||
text = 'Loading...';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.done) {
|
||||
text = 'Refresh completed.';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.drag) {
|
||||
text = 'Pull to refresh';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.canceled) {
|
||||
text = 'Cancel refresh';
|
||||
}
|
||||
|
||||
final TextStyle ts = const TextStyle(
|
||||
color: Colors.grey,
|
||||
).copyWith(fontSize: 14);
|
||||
|
||||
final double dragOffset = info?.dragOffset ?? 0.0;
|
||||
|
||||
final DateTime time = lastRefreshTime ?? DateTime.now();
|
||||
final double top = -hideHeight + dragOffset;
|
||||
return Container(
|
||||
height: dragOffset,
|
||||
color: color ?? Colors.transparent,
|
||||
// padding: EdgeInsets.only(top: dragOffset / 3),
|
||||
// padding: EdgeInsets.only(bottom: 5.0),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: top,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: const EdgeInsets.only(right: 12.0),
|
||||
child: RefreshImage(top: top),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Text(text, style: ts),
|
||||
Text(
|
||||
'Last updated:${DateFormat('yyyy-MM-dd hh:mm').format(time)}',
|
||||
style: ts.copyWith(fontSize: 14),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RefreshImage extends StatelessWidget {
|
||||
const RefreshImage({
|
||||
super.key,
|
||||
required this.top,
|
||||
});
|
||||
|
||||
final double top;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double imageSize = 30;
|
||||
return ExtendedImage.asset(
|
||||
'assets/flutterCandies_grey.png',
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
|
||||
final double imageHeight = image.height.toDouble();
|
||||
final double imageWidth = image.width.toDouble();
|
||||
final Size size = rect.size;
|
||||
final double y =
|
||||
(1 - min(top / (refreshHeight - hideHeight), 1)) * imageHeight;
|
||||
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
|
||||
Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
|
||||
size.width, (imageHeight - y) / imageHeight * size.height),
|
||||
Paint()
|
||||
..colorFilter =
|
||||
const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
|
||||
..isAntiAlias = false
|
||||
..filterQuality = FilterQuality.low,
|
||||
);
|
||||
|
||||
//canvas.restore();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,16 +4,27 @@ class Segment {
|
||||
final double start;
|
||||
final double end;
|
||||
final Color color;
|
||||
final String? title;
|
||||
final String? url;
|
||||
final int? from;
|
||||
final int? to;
|
||||
|
||||
Segment(this.start, this.end, this.color);
|
||||
Segment(
|
||||
this.start,
|
||||
this.end,
|
||||
this.color, [
|
||||
this.title,
|
||||
this.url,
|
||||
this.from,
|
||||
this.to,
|
||||
]);
|
||||
}
|
||||
|
||||
class SegmentProgressBar extends CustomPainter {
|
||||
final double progress;
|
||||
final List<Segment> segmentColors;
|
||||
late double _defHeight;
|
||||
|
||||
SegmentProgressBar({
|
||||
required this.progress,
|
||||
required this.segmentColors,
|
||||
});
|
||||
|
||||
@@ -21,29 +32,89 @@ class SegmentProgressBar extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..style = PaintingStyle.fill;
|
||||
|
||||
for (var segment in segmentColors) {
|
||||
paint.color = segment.color;
|
||||
final segmentStart = segment.start * size.width;
|
||||
final segmentEnd = segment.end * size.width;
|
||||
final progressEnd = progress * size.width;
|
||||
for (int i = 0; i < segmentColors.length; i++) {
|
||||
paint.color = segmentColors[i].color;
|
||||
final segmentStart = segmentColors[i].start * size.width;
|
||||
final segmentEnd = segmentColors[i].end * size.width;
|
||||
|
||||
if (progressEnd < segmentStart) {
|
||||
break;
|
||||
}
|
||||
if (segmentEnd > segmentStart ||
|
||||
(segmentEnd == segmentStart && segmentStart > 0)) {
|
||||
if (segmentColors[i].title != null) {
|
||||
double fontSize = 8;
|
||||
|
||||
final segmentWidth =
|
||||
(progressEnd < segmentEnd ? progressEnd : segmentEnd) - segmentStart;
|
||||
if (segmentWidth > 0) {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(segmentStart, 0, segmentWidth, size.height),
|
||||
paint,
|
||||
);
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
text: segmentColors[i].title,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
TextPainter textPainter = getTextPainter();
|
||||
if (i == 0) {
|
||||
_defHeight = textPainter.height;
|
||||
}
|
||||
|
||||
late double prevStart;
|
||||
if (i != 0) {
|
||||
prevStart = segmentColors[i - 1].start * size.width;
|
||||
}
|
||||
double width = i == 0 ? segmentStart : segmentStart - prevStart;
|
||||
|
||||
while (textPainter.width > width - 2 && fontSize >= 2) {
|
||||
fontSize -= 1;
|
||||
textPainter = getTextPainter();
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(
|
||||
0,
|
||||
-_defHeight,
|
||||
size.width,
|
||||
0,
|
||||
),
|
||||
Paint()..color = Colors.grey[600]!.withOpacity(0.45),
|
||||
);
|
||||
}
|
||||
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
-_defHeight,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height + _defHeight,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
|
||||
double textX = i == 0
|
||||
? (segmentStart - textPainter.width) / 2
|
||||
: (segmentStart - prevStart - textPainter.width) / 2 +
|
||||
prevStart +
|
||||
1;
|
||||
double textY = (-_defHeight - textPainter.height) / 2;
|
||||
textPainter.paint(canvas, Offset(textX, textY));
|
||||
} else {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
0,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
Widget statDanMu({
|
||||
required BuildContext context,
|
||||
@@ -24,7 +24,6 @@ Widget statDanMu({
|
||||
Text(
|
||||
Utils.numFormat(danmu!),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
Widget statView({
|
||||
required BuildContext context,
|
||||
@@ -27,7 +27,6 @@ Widget statView({
|
||||
Text(
|
||||
Utils.numFormat(view!),
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/models/model_hot_video_item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../http/search.dart';
|
||||
@@ -16,24 +17,22 @@ class VideoCardH extends StatelessWidget {
|
||||
const VideoCardH({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.source = 'normal',
|
||||
this.showOwner = true,
|
||||
this.showView = true,
|
||||
this.showDanmaku = true,
|
||||
this.showPubdate = false,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
});
|
||||
final dynamic videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final String source;
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
final bool showDanmaku;
|
||||
final bool showPubdate;
|
||||
final GestureTapCallback? onTap;
|
||||
final VoidCallback? onTap;
|
||||
final VoidCallback? onLongPress;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -43,21 +42,33 @@ class VideoCardH extends StatelessWidget {
|
||||
try {
|
||||
type = videoItem.type;
|
||||
} catch (_) {}
|
||||
List<VideoCustomAction> actions =
|
||||
VideoCustomActions(videoItem, context).actions;
|
||||
final String heroTag = Utils.makeHeroTag(aid);
|
||||
return Stack(children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
excludeSemantics: true,
|
||||
customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
for (var item in actions)
|
||||
CustomSemanticsAction(
|
||||
label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
|
||||
},
|
||||
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
// for (var item in actions)
|
||||
// CustomSemanticsAction(
|
||||
// label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
|
||||
// },
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onLongPress: longPress,
|
||||
onLongPress: () {
|
||||
if (onLongPress != null) {
|
||||
onLongPress!();
|
||||
} else {
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title is String
|
||||
? videoItem.title
|
||||
: videoItem.title is List
|
||||
? (videoItem.title as List)
|
||||
.map((item) => item['text'])
|
||||
.join()
|
||||
: '',
|
||||
cover: videoItem.pic,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap?.call();
|
||||
@@ -67,76 +78,102 @@ class VideoCardH extends StatelessWidget {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
return;
|
||||
}
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.pgcLabel?.isNotEmpty == true &&
|
||||
videoItem.redirectUrl?.isNotEmpty == true) {
|
||||
String? id = RegExp(r'(ep|ss)\d+')
|
||||
.firstMatch(videoItem.redirectUrl)
|
||||
?.group(0);
|
||||
if (id != null) {
|
||||
Utils.viewBangumi(
|
||||
seasonId:
|
||||
id.startsWith('ss') ? id.replaceFirst('ss', '') : null,
|
||||
epId: id.startsWith('ep') ? id.replaceFirst('ep', '') : null,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final int cid =
|
||||
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': videoItem,
|
||||
'heroTag': Utils.makeHeroTag(aid)
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context,
|
||||
BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoItem.pic as String,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder:
|
||||
(BuildContext context, BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: videoItem.pic as String,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.pgcLabel?.isNotEmpty == true)
|
||||
PBadge(
|
||||
text: videoItem.pgcLabel,
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
),
|
||||
if (videoItem.duration != 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (type != 'video')
|
||||
PBadge(
|
||||
text: type,
|
||||
left: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'primary',
|
||||
),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
// pBadge(videoItem.rcmdReason.content, context,
|
||||
// 6.0, 6.0, null, null),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
if (videoItem.duration != 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (type != 'video')
|
||||
PBadge(
|
||||
text: type,
|
||||
left: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'primary',
|
||||
),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
// pBadge(videoItem.rcmdReason.content, context,
|
||||
// 6.0, 6.0, null, null),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
videoContent(context)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
videoContent(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (source == 'normal')
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
right: 12,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
actions: actions,
|
||||
videoItem: videoItem,
|
||||
),
|
||||
),
|
||||
]);
|
||||
@@ -153,13 +190,12 @@ class VideoCardH extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (videoItem.title is String) ...[
|
||||
if (videoItem.title is String)
|
||||
Expanded(
|
||||
child: Text(
|
||||
videoItem.title as String,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
|
||||
height: 1.42,
|
||||
letterSpacing: 0.3,
|
||||
@@ -167,19 +203,19 @@ class VideoCardH extends StatelessWidget {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: RichText(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
text: TextSpan(
|
||||
children: [
|
||||
for (final i in videoItem.title) ...[
|
||||
TextSpan(
|
||||
text: i['text'] as String,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
@@ -195,7 +231,6 @@ class VideoCardH extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
// const Spacer(),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
@@ -221,7 +256,6 @@ class VideoCardH extends StatelessWidget {
|
||||
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPalaX/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../../utils/utils.dart';
|
||||
@@ -12,8 +13,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
const VideoCardHGrpc({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.source = 'normal',
|
||||
this.showOwner = true,
|
||||
this.showView = true,
|
||||
@@ -21,8 +20,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
this.showPubdate = false,
|
||||
});
|
||||
final card.Card videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final String source;
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
@@ -50,7 +47,11 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
// },
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onLongPress: longPress,
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.smallCoverV5.base.title,
|
||||
cover: videoItem.smallCoverV5.base.cover,
|
||||
),
|
||||
onTap: () async {
|
||||
if (type == 'ketang') {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
@@ -58,13 +59,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
}
|
||||
try {
|
||||
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
|
||||
// final int cid =
|
||||
// videoItem.smallCoverV5.base.playerArgs.cid.toInt() ??
|
||||
// await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
// Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||
// arguments: {'heroTag': heroTag});
|
||||
// Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||
// arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -150,7 +144,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
videoItem.smallCoverV5.base.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
|
||||
height: 1.42,
|
||||
letterSpacing: 0.3,
|
||||
@@ -183,7 +176,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
videoItem.smallCoverV5.rightDesc1,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
@@ -195,7 +187,6 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
videoItem.smallCoverV5.rightDesc2,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPalaX/common/widgets/video_popup_menu.dart';
|
||||
import 'package:PiliPalaX/models/space_archive/item.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -15,90 +16,105 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
const VideoCardHMemberVideo({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.onTap,
|
||||
this.bvid,
|
||||
});
|
||||
final Item videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final VoidCallback? onTap;
|
||||
final dynamic bvid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int aid = int.tryParse(videoItem.param ?? '') ?? -1;
|
||||
final String bvid = videoItem.bvid ?? '';
|
||||
final String heroTag = Utils.makeHeroTag(aid);
|
||||
return Stack(children: [
|
||||
InkWell(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onLongPress: longPress,
|
||||
onTap: () async {
|
||||
try {
|
||||
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {'heroTag': heroTag});
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder:
|
||||
(BuildContext context, BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoItem.cover,
|
||||
// videoItem.season?['cover'] ?? videoItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
// if (videoItem.season != null)
|
||||
// PBadge(
|
||||
// text: '合集: ${videoItem.season?['count']}',
|
||||
// right: 6.0,
|
||||
// bottom: 6.0,
|
||||
// type: 'gray',
|
||||
// )
|
||||
// else
|
||||
if (videoItem.duration != null)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
videoContent(context)
|
||||
],
|
||||
);
|
||||
return Stack(
|
||||
children: [
|
||||
InkWell(
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.cover,
|
||||
),
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {
|
||||
'heroTag': Utils.makeHeroTag(aid),
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context,
|
||||
BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
src: videoItem.cover,
|
||||
// videoItem.season?['cover'] ?? videoItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
// if (videoItem.season != null)
|
||||
// PBadge(
|
||||
// text: '合集: ${videoItem.season?['count']}',
|
||||
// right: 6.0,
|
||||
// bottom: 6.0,
|
||||
// type: 'gray',
|
||||
// )
|
||||
// else
|
||||
if (videoItem.duration != null)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
videoContent(context)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
actions: VideoCustomActions(videoItem, context).actions,
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 12,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
),
|
||||
),
|
||||
),
|
||||
]);
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget videoContent(context) {
|
||||
@@ -115,10 +131,13 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
videoItem.title ?? '',
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontWeight: videoItem.bvid == bvid ? FontWeight.bold : null,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
|
||||
height: 1.42,
|
||||
letterSpacing: 0.3,
|
||||
color: videoItem.bvid == bvid
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -131,7 +150,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
: videoItem.publishTimeText ?? '',
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
height: 1,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPalaX/http/search.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../models/home/rcmd/result.dart';
|
||||
@@ -18,14 +18,12 @@ import 'video_popup_menu.dart';
|
||||
// 视频卡片 - 垂直布局
|
||||
class VideoCardV extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final VoidCallback? onRemove;
|
||||
|
||||
const VideoCardV({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.onRemove,
|
||||
});
|
||||
|
||||
bool isStringNumeric(String str) {
|
||||
@@ -83,11 +81,14 @@ class VideoCardV extends StatelessWidget {
|
||||
if (cid == -1) {
|
||||
cid = await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
|
||||
}
|
||||
Get.toNamed('/video?bvid=$bvid&cid=$cid', arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.pic,
|
||||
'heroTag': heroTag,
|
||||
});
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.pic,
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
break;
|
||||
// 动态
|
||||
case 'picture':
|
||||
@@ -135,7 +136,7 @@ class VideoCardV extends StatelessWidget {
|
||||
default:
|
||||
SmartDialog.showToast(videoItem.goto);
|
||||
Get.toNamed(
|
||||
'/webviewnew',
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': videoItem.uri,
|
||||
'type': 'url',
|
||||
@@ -147,23 +148,24 @@ class VideoCardV extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||
List<VideoCustomAction> actions =
|
||||
VideoCustomActions(videoItem, context).actions;
|
||||
return Stack(children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
excludeSemantics: true,
|
||||
customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
for (var item in actions)
|
||||
CustomSemanticsAction(label: item.title): item.onTap!,
|
||||
},
|
||||
// customSemanticsActions: <CustomSemanticsAction, void Function()>{
|
||||
// for (var item in actions)
|
||||
// CustomSemanticsAction(label: item.title): item.onTap!,
|
||||
// },
|
||||
child: Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async => onPushDetail(heroTag),
|
||||
onLongPress: longPress,
|
||||
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)),
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.pic,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
@@ -173,13 +175,10 @@ class VideoCardV extends StatelessWidget {
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
NetworkImgLayer(
|
||||
src: videoItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (videoItem.duration > 0)
|
||||
PBadge(
|
||||
@@ -203,13 +202,15 @@ class VideoCardV extends StatelessWidget {
|
||||
),
|
||||
if (videoItem.goto == 'av')
|
||||
Positioned(
|
||||
right: -5,
|
||||
bottom: -2,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
actions: actions,
|
||||
)),
|
||||
right: -5,
|
||||
bottom: -2,
|
||||
child: VideoPopupMenu(
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
onRemove: onRemove,
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -229,7 +230,6 @@ class VideoCardV extends StatelessWidget {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.38,
|
||||
)),
|
||||
),
|
||||
@@ -247,7 +247,8 @@ class VideoCardV extends StatelessWidget {
|
||||
size: 'small',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
if (videoItem.rcmdReason != null) ...[
|
||||
PBadge(
|
||||
@@ -255,7 +256,8 @@ class VideoCardV extends StatelessWidget {
|
||||
stack: 'normal',
|
||||
size: 'small',
|
||||
type: 'color',
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
if (videoItem.goto == 'picture') ...[
|
||||
const PBadge(
|
||||
@@ -264,7 +266,8 @@ class VideoCardV extends StatelessWidget {
|
||||
size: 'small',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
if (videoItem.isFollowed == 1) ...[
|
||||
const PBadge(
|
||||
@@ -272,7 +275,8 @@ class VideoCardV extends StatelessWidget {
|
||||
stack: 'normal',
|
||||
size: 'small',
|
||||
type: 'color',
|
||||
)
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
Expanded(
|
||||
flex: 1,
|
||||
@@ -307,7 +311,7 @@ class VideoCardV extends StatelessWidget {
|
||||
view: videoItem.stat.view,
|
||||
goto: videoItem.goto,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox(width: 4),
|
||||
if (videoItem.goto != 'picture')
|
||||
statDanMu(
|
||||
context: context,
|
||||
@@ -320,6 +324,7 @@ class VideoCardV extends StatelessWidget {
|
||||
flex: 0,
|
||||
child: RichText(
|
||||
maxLines: 1,
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
@@ -342,6 +347,7 @@ class VideoCardV extends StatelessWidget {
|
||||
flex: 0,
|
||||
child: RichText(
|
||||
maxLines: 1,
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPalaX/models/space/item.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/models/space/item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -10,14 +11,10 @@ import 'network_img_layer.dart';
|
||||
// 视频卡片 - 垂直布局
|
||||
class VideoCardVMemberHome extends StatelessWidget {
|
||||
final Item videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
|
||||
const VideoCardVMemberHome({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
});
|
||||
|
||||
void onPushDetail(heroTag) async {
|
||||
@@ -67,11 +64,14 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
// break;
|
||||
case 'av':
|
||||
String bvid = videoItem.bvid ?? '';
|
||||
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}', arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.cover,
|
||||
'heroTag': heroTag,
|
||||
});
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {
|
||||
// 'videoItem': videoItem,
|
||||
'pic': videoItem.cover,
|
||||
'heroTag': heroTag,
|
||||
},
|
||||
);
|
||||
break;
|
||||
// 动态
|
||||
// case 'picture':
|
||||
@@ -119,7 +119,7 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
default:
|
||||
SmartDialog.showToast(goto);
|
||||
Get.toNamed(
|
||||
'/webviewnew',
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': videoItem.uri ?? '',
|
||||
'type': 'url',
|
||||
@@ -131,7 +131,6 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.bvid);
|
||||
// List<VideoCustomAction> actions =
|
||||
// VideoCustomActions(videoItem, context).actions;
|
||||
return Stack(children: [
|
||||
@@ -146,8 +145,12 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async => onPushDetail(heroTag),
|
||||
onLongPress: longPress,
|
||||
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
cover: videoItem.cover,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AspectRatio(
|
||||
@@ -157,13 +160,10 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
NetworkImgLayer(
|
||||
src: videoItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if ((videoItem.duration ?? -1) > 0)
|
||||
PBadge(
|
||||
@@ -214,70 +214,69 @@ Widget videoContent(BuildContext context, Item videoItem) {
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.38,
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
// const Spacer(),
|
||||
// const SizedBox(height: 2),
|
||||
// VideoStat(
|
||||
// videoItem: videoItem,
|
||||
// ),
|
||||
Row(
|
||||
children: [
|
||||
// if (videoItem.goto == 'bangumi') ...[
|
||||
// PBadge(
|
||||
// text: videoItem.bangumiBadge,
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'line',
|
||||
// fs: 9,
|
||||
// )
|
||||
// ],
|
||||
// if (videoItem.rcmdReason != null) ...[
|
||||
// PBadge(
|
||||
// text: videoItem.rcmdReason,
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'color',
|
||||
// )
|
||||
// ],
|
||||
if (videoItem.goto == 'picture') ...[
|
||||
const PBadge(
|
||||
text: '动态',
|
||||
stack: 'normal',
|
||||
size: 'small',
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
)
|
||||
],
|
||||
// if (videoItem.isFollowed == 1) ...[
|
||||
// const PBadge(
|
||||
// text: '已关注',
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'color',
|
||||
// )
|
||||
// ],
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
videoItem.author ?? '',
|
||||
// semanticsLabel: "Up主:${videoItem.owner.name}",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.clip,
|
||||
style: TextStyle(
|
||||
height: 1.5,
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (videoItem.goto == 'av') const SizedBox(width: 10)
|
||||
],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// if (videoItem.goto == 'bangumi') ...[
|
||||
// PBadge(
|
||||
// text: videoItem.bangumiBadge,
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'line',
|
||||
// fs: 9,
|
||||
// )
|
||||
// ],
|
||||
// if (videoItem.rcmdReason != null) ...[
|
||||
// PBadge(
|
||||
// text: videoItem.rcmdReason,
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'color',
|
||||
// )
|
||||
// ],
|
||||
// if (videoItem.goto == 'picture') ...[
|
||||
// const PBadge(
|
||||
// text: '动态',
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'line',
|
||||
// fs: 9,
|
||||
// )
|
||||
// ],
|
||||
// if (videoItem.isFollowed == 1) ...[
|
||||
// const PBadge(
|
||||
// text: '已关注',
|
||||
// stack: 'normal',
|
||||
// size: 'small',
|
||||
// type: 'color',
|
||||
// )
|
||||
// ],
|
||||
// Expanded(
|
||||
// flex: 1,
|
||||
// child: Text(
|
||||
// videoItem.author ?? '',
|
||||
// // semanticsLabel: "Up主:${videoItem.owner.name}",
|
||||
// maxLines: 1,
|
||||
// overflow: TextOverflow.clip,
|
||||
// style: TextStyle(
|
||||
// height: 1.5,
|
||||
// fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (videoItem.goto == 'av') const SizedBox(width: 10)
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -9,7 +10,7 @@ import '../../http/video.dart';
|
||||
import '../../models/home/rcmd/result.dart';
|
||||
import '../../pages/mine/controller.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import 'package:PiliPalaX/models/space_archive/item.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
|
||||
class VideoCustomAction {
|
||||
String title;
|
||||
@@ -23,7 +24,9 @@ class VideoCustomActions {
|
||||
dynamic videoItem;
|
||||
BuildContext context;
|
||||
late List<VideoCustomAction> actions;
|
||||
VideoCustomActions(this.videoItem, this.context) {
|
||||
VoidCallback? onRemove;
|
||||
|
||||
VideoCustomActions(this.videoItem, this.context, [this.onRemove]) {
|
||||
actions = [
|
||||
if ((videoItem.bvid as String?)?.isNotEmpty == true)
|
||||
VideoCustomAction(
|
||||
@@ -82,12 +85,10 @@ class VideoCustomActions {
|
||||
return;
|
||||
}
|
||||
Widget actionButton(DislikeReason? r, FeedbackReason? f) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12.0, vertical: 0.0),
|
||||
),
|
||||
onPressed: () async {
|
||||
return SearchText(
|
||||
text: r?.name ?? f?.name ?? '未知',
|
||||
onTap: (_) async {
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '正在提交');
|
||||
var res = await VideoHttp.feedDislike(
|
||||
reasonId: r?.id,
|
||||
@@ -97,10 +98,12 @@ class VideoCustomActions {
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? (r?.toast ?? f?.toast) : res['msg']);
|
||||
Get.back();
|
||||
res['status'] ? (r?.toast ?? f?.toast) : res['msg'],
|
||||
);
|
||||
if (res['status']) {
|
||||
onRemove?.call();
|
||||
}
|
||||
},
|
||||
child: Text(r?.name ?? f?.name ?? '未知'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -108,53 +111,57 @@ class VideoCustomActions {
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('请选择'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (tp.dislikeReasons != null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text('我不想看'),
|
||||
),
|
||||
if (tp.dislikeReasons != null)
|
||||
if (tp.dislikeReasons != null) ...[
|
||||
Text('我不想看'),
|
||||
const SizedBox(height: 5),
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
runSpacing: 2.0,
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: tp.dislikeReasons!.map((item) {
|
||||
return actionButton(item, null);
|
||||
}).toList(),
|
||||
),
|
||||
if (tp.feedbacks != null)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Text('反馈'),
|
||||
),
|
||||
if (tp.feedbacks != null)
|
||||
],
|
||||
if (tp.feedbacks != null) ...[
|
||||
const SizedBox(height: 5),
|
||||
Text('反馈'),
|
||||
const SizedBox(height: 5),
|
||||
Wrap(
|
||||
spacing: 5.0,
|
||||
runSpacing: 2.0,
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: tp.feedbacks!.map((item) {
|
||||
return actionButton(null, item);
|
||||
}).toList(),
|
||||
),
|
||||
//分割线
|
||||
],
|
||||
const Divider(),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.showLoading(msg: '正在提交');
|
||||
var res = await VideoHttp.feedDislikeCancel(
|
||||
// reasonId: r?.id,
|
||||
// feedbackId: f?.id,
|
||||
id: v.param!,
|
||||
goto: v.goto!,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "成功" : res['msg']);
|
||||
Get.back();
|
||||
},
|
||||
child: const Text("撤销"),
|
||||
Center(
|
||||
child: FilledButton.tonal(
|
||||
onPressed: () async {
|
||||
SmartDialog.showLoading(msg: '正在提交');
|
||||
var res = await VideoHttp.feedDislikeCancel(
|
||||
// reasonId: r?.id,
|
||||
// feedbackId: f?.id,
|
||||
id: v.param!,
|
||||
goto: v.goto!,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "成功" : res['msg']);
|
||||
Get.back();
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
visualDensity: VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: const Text("撤销"),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -167,7 +174,6 @@ class VideoCustomActions {
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('点踩该视频?'),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -178,20 +184,28 @@ class VideoCustomActions {
|
||||
spacing: 5.0,
|
||||
runSpacing: 2.0,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
FilledButton.tonal(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '正在提交');
|
||||
var res = await VideoHttp.dislikeVideo(
|
||||
bvid: videoItem.bvid as String, type: true);
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "点踩成功" : res['msg']);
|
||||
Get.back();
|
||||
res['status'] ? "点踩成功" : res['msg'],
|
||||
);
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
visualDensity: VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: const Text("点踩"),
|
||||
),
|
||||
ElevatedButton(
|
||||
FilledButton.tonal(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '正在提交');
|
||||
var res = await VideoHttp.dislikeVideo(
|
||||
bvid: videoItem.bvid as String,
|
||||
@@ -199,8 +213,13 @@ class VideoCustomActions {
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "取消踩" : res['msg']);
|
||||
Get.back();
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
visualDensity: VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
),
|
||||
),
|
||||
child: const Text("撤销"),
|
||||
),
|
||||
],
|
||||
@@ -240,13 +259,9 @@ class VideoCustomActions {
|
||||
act: 5,
|
||||
reSrc: 11,
|
||||
);
|
||||
List<int> blackMidsList = GStorage.localCache
|
||||
.get(LocalCacheKey.blackMidsList, defaultValue: [-1])
|
||||
.map<int>((i) => i as int)
|
||||
.toList();
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
blackMidsList.insert(0, videoItem.owner.mid);
|
||||
GStorage.localCache
|
||||
.put(LocalCacheKey.blackMidsList, blackMidsList);
|
||||
GStorage.setBlackMidsList(blackMidsList);
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg'] ?? '成功');
|
||||
},
|
||||
@@ -258,10 +273,10 @@ class VideoCustomActions {
|
||||
);
|
||||
}),
|
||||
VideoCustomAction(
|
||||
"${MineController.anonymity ? '退出' : '进入'}无痕模式",
|
||||
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
|
||||
'anonymity',
|
||||
Icon(
|
||||
MineController.anonymity
|
||||
MineController.anonymity.value
|
||||
? MdiIcons.incognitoOff
|
||||
: MdiIcons.incognito,
|
||||
size: 16,
|
||||
@@ -274,14 +289,16 @@ class VideoCustomActions {
|
||||
class VideoPopupMenu extends StatelessWidget {
|
||||
final double? size;
|
||||
final double? iconSize;
|
||||
final List<VideoCustomAction> actions;
|
||||
final double menuItemHeight = 45;
|
||||
final dynamic videoItem;
|
||||
final VoidCallback? onRemove;
|
||||
|
||||
const VideoPopupMenu({
|
||||
super.key,
|
||||
required this.size,
|
||||
required this.iconSize,
|
||||
required this.actions,
|
||||
required this.videoItem,
|
||||
this.onRemove,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -299,7 +316,8 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
),
|
||||
position: PopupMenuPosition.under,
|
||||
onSelected: (String type) {},
|
||||
itemBuilder: (BuildContext context) => actions.map((e) {
|
||||
itemBuilder: (BuildContext context) =>
|
||||
VideoCustomActions(videoItem, context, onRemove).actions.map((e) {
|
||||
return PopupMenuItem<String>(
|
||||
value: e.value,
|
||||
height: menuItemHeight,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1;
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2;
|
||||
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pbgrpc.dart';
|
||||
import 'package:PiliPalaX/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPalaX/grpc/app/show/popular/v1/popular.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v1/dynamic.pbgrpc.dart' as v1;
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pbgrpc.dart' as v2;
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/show/popular/v1/popular.pbgrpc.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
|
||||
class GrpcClient {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v1/dynamic.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/app/dynamic/v2/dynamic.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPalaX/grpc/app/show/popular/v1/popular.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/device/device.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/fawkes/fawkes.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/grpc_client.dart';
|
||||
import 'package:PiliPalaX/grpc/locale/locale.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/metadata/metadata.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/network/network.pb.dart' as network;
|
||||
import 'package:PiliPalaX/grpc/restriction/restriction.pb.dart';
|
||||
import 'package:PiliPalaX/utils/login.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v1/dynamic.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/dynamic/v2/dynamic.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/app/playeronline/v1/playeronline.pbgrpc.dart';
|
||||
import 'package:PiliPlus/grpc/app/show/popular/v1/popular.pb.dart';
|
||||
import 'package:PiliPlus/grpc/device/device.pb.dart';
|
||||
import 'package:PiliPlus/grpc/fawkes/fawkes.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_client.dart';
|
||||
import 'package:PiliPlus/grpc/locale/locale.pb.dart';
|
||||
import 'package:PiliPlus/grpc/metadata/metadata.pb.dart';
|
||||
import 'package:PiliPlus/grpc/network/network.pb.dart' as network;
|
||||
import 'package:PiliPlus/grpc/restriction/restriction.pb.dart';
|
||||
import 'package:PiliPlus/utils/login.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:fixnum/src/int64.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
@@ -112,8 +112,8 @@ class GrpcRepo {
|
||||
e.details?.firstOrNull?.getFieldOrNull(2),
|
||||
allowMalformed: true,
|
||||
);
|
||||
msg = msg.replaceAll(
|
||||
RegExp(r"[^a-zA-Z0-9\u4e00-\u9fa5,.;!?,。;!?]"), '');
|
||||
msg =
|
||||
msg.replaceAll(RegExp(r"[^a-zA-Z0-9\u4e00-\u9fa5,.;?,。;!?]"), '');
|
||||
if (msg.isNotEmpty) {
|
||||
return {'status': false, 'msg': msg};
|
||||
} else {
|
||||
|
||||
@@ -19,8 +19,8 @@ class Api {
|
||||
static const String videoUrl = '/x/player/wbi/playurl';
|
||||
|
||||
// 番剧视频流
|
||||
// https://api.bilibili.com/pgc/player/web/playurl?cid=104236640&bvid=BV13t411n7ex
|
||||
static const String bangumiVideoUrl = '/pgc/player/web/playurl';
|
||||
// https://api.bilibili.com/pgc/player/web/v2/playurl?cid=104236640&bvid=BV13t411n7ex
|
||||
static const String bangumiVideoUrl = '/pgc/player/web/v2/playurl';
|
||||
|
||||
// 字幕
|
||||
// aid, cid
|
||||
@@ -247,6 +247,8 @@ class Api {
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
|
||||
static const String heartBeat = '/x/click-interface/web/heartbeat';
|
||||
|
||||
static const String mediaListHistory = '/x/v1/medialist/history';
|
||||
|
||||
// 查询视频分P列表 (avid/bvid转cid)
|
||||
static const String ab2c = '/x/player/pagelist';
|
||||
|
||||
@@ -400,7 +402,7 @@ class Api {
|
||||
|
||||
// github 获取最新版
|
||||
static const String latestApp =
|
||||
'https://api.github.com/repos/orz12/pilipala/releases';
|
||||
'https://api.github.com/repos/bggRGjQaUbCoE/PiliPlus/releases';
|
||||
|
||||
// 多少人在看
|
||||
// https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
|
||||
@@ -442,6 +444,11 @@ class Api {
|
||||
// 获取指定分组下的up
|
||||
static const String followUpGroup = '/x/relation/tag';
|
||||
|
||||
// 获取未读私信数
|
||||
// https://api.vc.bilibili.com/session_svr/v1/session_svr/single_unread
|
||||
static const String msgUnread =
|
||||
'${HttpString.tUrl}/session_svr/v1/session_svr/single_unread';
|
||||
|
||||
// 获取消息中心未读信息
|
||||
static const String msgFeedUnread = '/x/msgfeed/unread';
|
||||
//https://api.bilibili.com/x/msgfeed/reply?platform=web&build=0&mobi_app=web
|
||||
@@ -587,6 +594,9 @@ class Api {
|
||||
static const String safeCenterSmsVerify =
|
||||
'${HttpString.passBaseUrl}/x/safecenter/login/tel/verify';
|
||||
|
||||
static const String oauth2AccessToken =
|
||||
'${HttpString.passBaseUrl}/x/passport-login/oauth2/access_token';
|
||||
|
||||
/// 密码加密密钥
|
||||
/// disable_rcmd
|
||||
/// local_id
|
||||
@@ -690,4 +700,7 @@ class Api {
|
||||
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||
|
||||
static const String seasonFav = '/x/v3/fav/season/'; // + fav unfav
|
||||
|
||||
/// 稍后再看&收藏夹视频列表
|
||||
static const String mediaList = '/x/v2/medialist/resource/list';
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BangumiHttp {
|
||||
static Future<LoadingState> bangumiList({int? page}) async {
|
||||
var res = await Request().get(Api.bangumiList, data: {'page': page});
|
||||
var res =
|
||||
await Request().get(Api.bangumiList, queryParameters: {'page': page});
|
||||
if (res.data['code'] == 0) {
|
||||
BangumiListDataModel data =
|
||||
BangumiListDataModel.fromJson(res.data['data']);
|
||||
@@ -16,7 +17,8 @@ class BangumiHttp {
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiFollow({int? mid}) async {
|
||||
var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid});
|
||||
var res =
|
||||
await Request().get(Api.bangumiFollow, queryParameters: {'vmid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
BangumiListDataModel data =
|
||||
BangumiListDataModel.fromJson(res.data['data']);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/user/black.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BlackHttp {
|
||||
static Future<LoadingState> blackList({required int pn, int? ps}) async {
|
||||
var res = await Request().get(Api.blackLst, data: {
|
||||
var res = await Request().get(Api.blackLst, queryParameters: {
|
||||
'pn': pn,
|
||||
'ps': ps ?? 50,
|
||||
're_version': 0,
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'index.dart';
|
||||
|
||||
class CommonHttp {
|
||||
static Future unReadDynamic() async {
|
||||
var res = await Request().get(Api.getUnreadDynamic, data: {
|
||||
var res = await Request().get(Api.getUnreadDynamic, queryParameters: {
|
||||
'alltype_offset': 0,
|
||||
'video_offset': 0,
|
||||
'article_offset': 0,
|
||||
|
||||
@@ -16,7 +16,7 @@ class DanmakaHttp {
|
||||
};
|
||||
var response = await Request().get(
|
||||
Api.webDanmaku,
|
||||
data: params,
|
||||
queryParameters: params,
|
||||
options: Options(responseType: ResponseType.bytes),
|
||||
);
|
||||
if (response.statusCode != 200 || response.data == null) {
|
||||
@@ -29,8 +29,8 @@ class DanmakaHttp {
|
||||
int type = 1, //弹幕类选择(1:视频弹幕 2:漫画弹幕)
|
||||
required int oid, // 视频cid
|
||||
required String msg, //弹幕文本(长度小于 100 字符)
|
||||
int mode =
|
||||
1, // 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
|
||||
// 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
|
||||
int mode = 1,
|
||||
// String? aid,// 稿件avid
|
||||
// String? bvid,// bvid与aid必须有一个
|
||||
required String bvid,
|
||||
@@ -47,7 +47,6 @@ class DanmakaHttp {
|
||||
// 构建参数对象
|
||||
// assert(aid != null || bvid != null);
|
||||
// assert(csrf != null || access_key != null);
|
||||
assert(msg.length < 100);
|
||||
// 构建参数对象
|
||||
var params = <String, dynamic>{
|
||||
'type': type,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/dynamics/result.dart';
|
||||
import '../models/dynamics/up.dart';
|
||||
@@ -20,7 +20,7 @@ class DynamicsHttp {
|
||||
data['host_mid'] = mid;
|
||||
data.remove('timezone_offset');
|
||||
}
|
||||
var res = await Request().get(Api.followDynamic, data: data);
|
||||
var res = await Request().get(Api.followDynamic, queryParameters: data);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']);
|
||||
@@ -80,7 +80,7 @@ class DynamicsHttp {
|
||||
static Future dynamicDetail({
|
||||
String? id,
|
||||
}) async {
|
||||
var res = await Request().get(Api.dynamicDetail, data: {
|
||||
var res = await Request().get(Api.dynamicDetail, queryParameters: {
|
||||
'timezone_offset': -480,
|
||||
'id': id,
|
||||
'features': 'itemOpusStyle',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/fans/result.dart';
|
||||
import 'index.dart';
|
||||
@@ -6,7 +6,7 @@ import 'index.dart';
|
||||
class FanHttp {
|
||||
static Future<LoadingState> fans(
|
||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
||||
var res = await Request().get(Api.fans, data: {
|
||||
var res = await Request().get(Api.fans, queryParameters: {
|
||||
'vmid': vmid,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'index.dart';
|
||||
class FollowHttp {
|
||||
static Future followings(
|
||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
||||
var res = await Request().get(Api.followings, data: {
|
||||
var res = await Request().get(Api.followings, queryParameters: {
|
||||
'vmid': vmid,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/models/dynamics/article_content_model.dart';
|
||||
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:html/dom.dart' as dom;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// ignore_for_file: avoid_print
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
@@ -9,23 +8,20 @@ import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
import 'interceptor_anonymity.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
|
||||
class Request {
|
||||
static final Request _instance = Request._internal();
|
||||
static late CookieManager cookieManager;
|
||||
static late final Dio dio;
|
||||
factory Request() => _instance;
|
||||
Box setting = GStorage.setting;
|
||||
static Box localCache = GStorage.localCache;
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
@@ -34,7 +30,6 @@ class Request {
|
||||
|
||||
/// 设置cookie
|
||||
static setCookie() async {
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
final String cookiePath = await Utils.getCookiePath();
|
||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
@@ -43,9 +38,20 @@ class Request {
|
||||
cookieManager = CookieManager(cookieJar);
|
||||
dio.interceptors.add(cookieManager);
|
||||
dio.interceptors.add(AnonymityInterceptor());
|
||||
// final List<Cookie> cookie = await cookieManager.cookieJar
|
||||
// .loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
final userInfo = userInfoCache.get('userInfoCache');
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (Cookie item in cookies) {
|
||||
await web.CookieManager().setCookie(
|
||||
url: web.WebUri(item.domain ?? ''),
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
path: item.path ?? '',
|
||||
domain: item.domain,
|
||||
isSecure: item.secure,
|
||||
isHttpOnly: item.httpOnly,
|
||||
);
|
||||
}
|
||||
final userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null && userInfo.mid != null) {
|
||||
final List<Cookie> cookie2 = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||
@@ -65,7 +71,7 @@ class Request {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
|
||||
// final String cookieString = cookie
|
||||
// final String cookieString = cookies
|
||||
// .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
// .join('; ');
|
||||
// dio.options.headers['cookie'] = cookieString;
|
||||
@@ -134,12 +140,12 @@ class Request {
|
||||
headers: {},
|
||||
);
|
||||
|
||||
enableSystemProxy = setting.get(SettingBoxKey.enableSystemProxy,
|
||||
defaultValue: false) as bool;
|
||||
enableSystemProxy = GStorage.setting
|
||||
.get(SettingBoxKey.enableSystemProxy, defaultValue: false) as bool;
|
||||
systemProxyHost =
|
||||
setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
|
||||
GStorage.setting.get(SettingBoxKey.systemProxyHost, defaultValue: '');
|
||||
systemProxyPort =
|
||||
setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
|
||||
GStorage.setting.get(SettingBoxKey.systemProxyPort, defaultValue: '');
|
||||
|
||||
dio = Dio(options);
|
||||
|
||||
@@ -147,7 +153,7 @@ class Request {
|
||||
// ..httpClientAdapter = Http2Adapter(
|
||||
// ConnectionManager(
|
||||
// idleTimeout: const Duration(milliseconds: 10000),
|
||||
// onClientCreate: (_, ClientSetting config) =>
|
||||
// onClientCreate: (context, ClientSetting config) =>
|
||||
// config.onBadCertificate = (_) => true,
|
||||
// ),
|
||||
// );
|
||||
@@ -189,7 +195,8 @@ class Request {
|
||||
/*
|
||||
* get请求
|
||||
*/
|
||||
Future<Response> get(url, {data, options, cancelToken, extra}) async {
|
||||
Future<Response> get(url,
|
||||
{queryParameters, options, cancelToken, extra}) async {
|
||||
Response response;
|
||||
if (extra != null) {
|
||||
if (extra['ua'] != null) {
|
||||
@@ -202,7 +209,7 @@ class Request {
|
||||
try {
|
||||
response = await dio.get(
|
||||
url,
|
||||
queryParameters: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -27,8 +25,7 @@ class ApiInterceptor extends Interceptor {
|
||||
// final String? accessKey = uri.queryParameters['access_key'];
|
||||
// final String? mid = uri.queryParameters['mid'];
|
||||
// try {
|
||||
// Box localCache = GStorage.localCache;
|
||||
// localCache.put(LocalCacheKey.accessKey,
|
||||
// GStorage.localCache.put(LocalCacheKey.accessKey,
|
||||
// <String, String?>{'mid': mid, 'value': accessKey});
|
||||
// } catch (_) {}
|
||||
// }
|
||||
@@ -50,7 +47,9 @@ class ApiInterceptor extends Interceptor {
|
||||
// 屏蔽弹幕、心跳、人数请求的错误提示
|
||||
if (!url.contains('heartbeat') &&
|
||||
!url.contains('seg.so') &&
|
||||
!url.contains('online/total')) {
|
||||
!url.contains('online/total') &&
|
||||
!url.contains('github') &&
|
||||
(!url.contains('skipSegments') && err.requestOptions.method != 'GET')) {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err) + url,
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
@@ -77,29 +76,13 @@ class ApiInterceptor extends Interceptor {
|
||||
case DioExceptionType.sendTimeout:
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res = await checkConnect();
|
||||
final String res =
|
||||
(await Connectivity().checkConnectivity()).first.title;
|
||||
return '$res网络异常 ${error.error}';
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> checkConnect() async {
|
||||
final List<ConnectivityResult> connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
switch (connectivityResult.first) {
|
||||
case ConnectivityResult.mobile:
|
||||
return '流量';
|
||||
case ConnectivityResult.wifi:
|
||||
return 'Wi-Fi';
|
||||
case ConnectivityResult.ethernet:
|
||||
return '局域';
|
||||
case ConnectivityResult.vpn:
|
||||
return '代理';
|
||||
case ConnectivityResult.other:
|
||||
return '其他';
|
||||
case ConnectivityResult.none:
|
||||
return '无';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension _ConnectivityResultExt on ConnectivityResult {
|
||||
String get title => ['蓝牙', 'Wi-Fi', '局域', '流量', '无', '代理', '其他'][index];
|
||||
}
|
||||
|
||||
@@ -24,12 +24,13 @@ class AnonymityInterceptor extends Interceptor {
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (MineController.anonymity) {
|
||||
if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
@@ -39,7 +40,7 @@ class AnonymityInterceptor extends Interceptor {
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
options.headers[HttpHeaders.cookieHeader] = "";
|
||||
if(options.data != null && options.data.csrf != null) {
|
||||
if (options.data != null && options.data.csrf != null) {
|
||||
options.data.csrf = "";
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/live/danmu_info.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/live/danmu_info.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../models/live/item.dart';
|
||||
import '../models/live/room_info.dart';
|
||||
@@ -12,7 +12,7 @@ class LiveHttp {
|
||||
static Future<LoadingState> liveList(
|
||||
{int? vmid, int? pn, int? ps, String? orderType}) async {
|
||||
var res = await Request().get(Api.liveList,
|
||||
data: {'page': pn, 'page_size': 30, 'platform': 'web'});
|
||||
queryParameters: {'page': pn, 'page_size': 30, 'platform': 'web'});
|
||||
if (res.data['code'] == 0) {
|
||||
List<LiveItemModel> list = res.data['data']['list']
|
||||
.map<LiveItemModel>((e) => LiveItemModel.fromJson(e))
|
||||
@@ -67,7 +67,7 @@ class LiveHttp {
|
||||
}
|
||||
|
||||
static Future liveRoomInfo({roomId, qn}) async {
|
||||
var res = await Request().get(Api.liveRoomInfo, data: {
|
||||
var res = await Request().get(Api.liveRoomInfo, queryParameters: {
|
||||
'room_id': roomId,
|
||||
'protocol': '0, 1',
|
||||
'format': '0, 1, 2',
|
||||
@@ -90,7 +90,7 @@ class LiveHttp {
|
||||
}
|
||||
|
||||
static Future liveRoomInfoH5({roomId, qn}) async {
|
||||
var res = await Request().get(Api.liveRoomInfoH5, data: {
|
||||
var res = await Request().get(Api.liveRoomInfoH5, queryParameters: {
|
||||
'room_id': roomId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -108,7 +108,7 @@ class LiveHttp {
|
||||
}
|
||||
|
||||
static Future liveRoomDanmaPrefetch({roomId}) async {
|
||||
var res = await Request().get(Api.liveRoomDmPrefetch, data: {
|
||||
var res = await Request().get(Api.liveRoomDmPrefetch, queryParameters: {
|
||||
'roomid': roomId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -123,7 +123,7 @@ class LiveHttp {
|
||||
}
|
||||
|
||||
static Future liveRoomGetDanmakuToken({roomId}) async {
|
||||
var res = await Request().get(Api.liveRoomDmToken, data: {
|
||||
var res = await Request().get(Api.liveRoomDmToken, queryParameters: {
|
||||
'id': roomId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:convert';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/login/index.dart';
|
||||
import '../utils/login.dart';
|
||||
@@ -43,6 +42,7 @@ class LoginHttp {
|
||||
);
|
||||
var res = await Request()
|
||||
.post(Api.getTVCode, queryParameters: {...params, 'sign': sign});
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -107,9 +107,10 @@ class LoginHttp {
|
||||
int timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
var data = {
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
'cid': cid,
|
||||
// if (deviceTouristId != null) 'device_tourist_id': deviceTouristId,
|
||||
'disable_rcmd': '0',
|
||||
@@ -142,6 +143,7 @@ class LoginHttp {
|
||||
headers: headers,
|
||||
),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0 && res.data['data']['recaptcha_url'] == "") {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -158,7 +160,7 @@ class LoginHttp {
|
||||
// dynamic publicKey = RSAKeyParser().parse(key);
|
||||
// var params = {
|
||||
// 'appkey': Constants.appKey,
|
||||
// 'build': '1462100',
|
||||
// 'build': '2001100',
|
||||
// 'buvid': buvid,
|
||||
// 'c_locale': 'zh_CN',
|
||||
// 'channel': 'yingyongbao',
|
||||
@@ -185,7 +187,7 @@ class LoginHttp {
|
||||
// contentType: Headers.formUrlEncodedContentType,
|
||||
// headers: headers,
|
||||
// ));
|
||||
// debugPrint("getGuestId: $res");
|
||||
// print("getGuestId: $res");
|
||||
// if (res.data['code'] == 0) {
|
||||
// return {'status': true, 'data': res.data['data']};
|
||||
// } else {
|
||||
@@ -211,7 +213,7 @@ class LoginHttp {
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '1462100',
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
'c_locale': 'zh_CN',
|
||||
'channel': 'yingyongbao',
|
||||
@@ -247,7 +249,6 @@ class LoginHttp {
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
debugPrint('$key: $value');
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
var res = await Request().post(
|
||||
@@ -259,6 +260,7 @@ class LoginHttp {
|
||||
//responseType: ResponseType.plain
|
||||
),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
@@ -287,7 +289,7 @@ class LoginHttp {
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'bili_local_id': deviceId,
|
||||
'build': '1462100',
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
'c_locale': 'zh_CN',
|
||||
'captcha_key': captchaKey,
|
||||
@@ -321,7 +323,6 @@ class LoginHttp {
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
debugPrint('$key: $value');
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
var res = await Request().post(
|
||||
@@ -333,6 +334,7 @@ class LoginHttp {
|
||||
//responseType: ResponseType.plain
|
||||
),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -349,9 +351,12 @@ class LoginHttp {
|
||||
static Future safeCenterGetInfo({
|
||||
required String tmpCode,
|
||||
}) async {
|
||||
var res = await Request().get(Api.safeCenterGetInfo, data: {
|
||||
'tmp_code': tmpCode,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.safeCenterGetInfo,
|
||||
queryParameters: {
|
||||
'tmp_code': tmpCode,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -364,9 +369,10 @@ class LoginHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 风控验证手机前的验证码
|
||||
// 风控验证手机前的极验验证码
|
||||
static Future preCapture() async {
|
||||
var res = await Request().post(Api.preCapture);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -379,23 +385,40 @@ class LoginHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 风控验证手机
|
||||
// 风控验证手机:发送短信验证码
|
||||
static Future safeCenterSmsCode({
|
||||
String? smsType,
|
||||
required String tmpCode,
|
||||
required String geeChallenge,
|
||||
required String geeSeccode,
|
||||
required String geeValidate,
|
||||
required String recaptchaToken,
|
||||
String? geeChallenge,
|
||||
String? geeSeccode,
|
||||
String? geeValidate,
|
||||
String? recaptchaToken,
|
||||
required String refererUrl,
|
||||
}) async {
|
||||
var res = await Request().post(Api.safeCenterSmsCode, data: {
|
||||
Map<String, String> data = {
|
||||
'disable_rcmd': '0',
|
||||
'sms_type': smsType ?? 'loginTelCheck',
|
||||
'tmp_code': tmpCode,
|
||||
'gee_challenge': geeChallenge,
|
||||
'gee_seccode': geeSeccode,
|
||||
'gee_validate': geeValidate,
|
||||
'recaptcha_token': recaptchaToken,
|
||||
});
|
||||
if (geeChallenge != null) 'gee_challenge': geeChallenge,
|
||||
if (geeSeccode != null) 'gee_seccode': geeSeccode,
|
||||
if (geeValidate != null) 'gee_validate': geeValidate,
|
||||
if (recaptchaToken != null) 'recaptcha_token': recaptchaToken,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsCode,
|
||||
data: data,
|
||||
options:
|
||||
Options(contentType: Headers.formUrlEncodedContentType, headers: {
|
||||
"Referer": refererUrl,
|
||||
}),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -408,21 +431,93 @@ class LoginHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future safeCenterSmsVerify(
|
||||
{String? type,
|
||||
required String code,
|
||||
required String tmpCode,
|
||||
required String requestId,
|
||||
required String source,
|
||||
required String captchaKey}) async {
|
||||
var res = await Request().post(Api.safeCenterSmsVerify, data: {
|
||||
// 风控验证手机:提交短信验证码
|
||||
static Future safeCenterSmsVerify({
|
||||
String? type,
|
||||
required String code,
|
||||
required String tmpCode,
|
||||
required String requestId,
|
||||
required String source,
|
||||
required String captchaKey,
|
||||
required String refererUrl,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'type': type ?? 'loginTelCheck',
|
||||
'code': code,
|
||||
'tmp_code': tmpCode,
|
||||
'request_id': requestId,
|
||||
'source': source,
|
||||
'captcha_key': captchaKey,
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
var res = await Request().post(
|
||||
Api.safeCenterSmsVerify,
|
||||
data: data,
|
||||
options:
|
||||
Options(contentType: Headers.formUrlEncodedContentType, headers: {
|
||||
"Referer": refererUrl,
|
||||
}),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'code': res.data['code'],
|
||||
'msg': res.data['message'],
|
||||
'data': res.data['data']
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 风控验证手机:用oauthCode换回accessToken
|
||||
static Future oauth2AccessToken({
|
||||
required String code,
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'appkey': Constants.appKey,
|
||||
'build': '2001100',
|
||||
'buvid': buvid,
|
||||
// 'c_locale': 'zh_CN',
|
||||
// 'channel': 'yingyongbao',
|
||||
'code': code,
|
||||
// 'device': 'phone',
|
||||
// 'device_id': deviceId,
|
||||
// 'device_name': 'vivo',
|
||||
// 'device_platform': 'Android14vivo',
|
||||
'disable_rcmd': '0',
|
||||
'grant_type': 'authorization_code',
|
||||
'local_id': buvid,
|
||||
'mobi_app': 'android_hd',
|
||||
'platform': 'android',
|
||||
// 's_locale': 'zh_CN',
|
||||
// 'statistics': Constants.statistics,
|
||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
||||
};
|
||||
String sign = Utils.appSign(
|
||||
data,
|
||||
Constants.appKey,
|
||||
Constants.appSec,
|
||||
);
|
||||
data['sign'] = sign;
|
||||
data.map((key, value) {
|
||||
return MapEntry<String, dynamic>(key, value);
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.oauth2AccessToken,
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
headers: headers,
|
||||
),
|
||||
);
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPalaX/http/constants.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/space/data.dart';
|
||||
import 'package:PiliPalaX/models/space_fav/space_fav.dart';
|
||||
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/space/data.dart';
|
||||
import 'package:PiliPlus/models/space_fav/space_fav.dart';
|
||||
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart'
|
||||
show ContributeType;
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -91,7 +91,7 @@ class MemberHttp {
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceArticle,
|
||||
data: data,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
@@ -136,7 +136,7 @@ class MemberHttp {
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
Api.spaceFav,
|
||||
data: data,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
@@ -206,7 +206,7 @@ class MemberHttp {
|
||||
: type == ContributeType.series
|
||||
? Api.spaceSeries
|
||||
: Api.spaceBangumi,
|
||||
data: data,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
@@ -251,7 +251,7 @@ class MemberHttp {
|
||||
int? _mid = GStorage.userInfo.get('userInfoCache')?.mid;
|
||||
dynamic res = await Request().get(
|
||||
Api.space,
|
||||
data: data,
|
||||
queryParameters: data,
|
||||
options: Options(
|
||||
headers: {
|
||||
'env': 'prod',
|
||||
@@ -284,7 +284,7 @@ class MemberHttp {
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberInfo,
|
||||
data: params,
|
||||
queryParameters: params,
|
||||
extra: {'ua': 'pc'},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -302,7 +302,7 @@ class MemberHttp {
|
||||
}
|
||||
|
||||
static Future memberStat({int? mid}) async {
|
||||
var res = await Request().get(Api.userStat, data: {'vmid': mid});
|
||||
var res = await Request().get(Api.userStat, queryParameters: {'vmid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -316,7 +316,7 @@ class MemberHttp {
|
||||
|
||||
static Future memberCardInfo({int? mid}) async {
|
||||
var res = await Request()
|
||||
.get(Api.memberCardInfo, data: {'mid': mid, 'photo': true});
|
||||
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -336,6 +336,7 @@ class MemberHttp {
|
||||
String? keyword,
|
||||
String order = 'pubdate',
|
||||
bool orderAvoided = true,
|
||||
dynamic wwebid,
|
||||
}) async {
|
||||
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
@@ -353,10 +354,11 @@ class MemberHttp {
|
||||
'dm_img_str': dmImgStr,
|
||||
'dm_cover_img_str': dmCoverImgStr,
|
||||
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
|
||||
'w_webid': wwebid,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberArchive,
|
||||
data: params,
|
||||
queryParameters: params,
|
||||
extra: {'ua': 'Mozilla/5.0'},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -393,7 +395,7 @@ class MemberHttp {
|
||||
'x-bili-device-req-json': jsonEncode({"platform": "web", "device": "pc"}),
|
||||
'x-bili-web-req-json': jsonEncode({"spm_id": "333.999"}),
|
||||
});
|
||||
var res = await Request().get(Api.memberDynamic, data: params);
|
||||
var res = await Request().get(Api.memberDynamic, queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(DynamicsDataModel.fromJson(res.data['data']));
|
||||
} else {
|
||||
@@ -412,7 +414,7 @@ class MemberHttp {
|
||||
int? mid,
|
||||
required String keyword,
|
||||
}) async {
|
||||
var res = await Request().get(Api.memberDynamicSearch, data: {
|
||||
var res = await Request().get(Api.memberDynamicSearch, queryParameters: {
|
||||
'keyword': keyword,
|
||||
'mid': mid,
|
||||
'pn': pn,
|
||||
@@ -508,7 +510,7 @@ class MemberHttp {
|
||||
int? pn,
|
||||
int? ps,
|
||||
) async {
|
||||
var res = await Request().get(Api.followUpGroup, data: {
|
||||
var res = await Request().get(Api.followUpGroup, queryParameters: {
|
||||
'mid': mid,
|
||||
'tagid': tagid,
|
||||
'pn': pn,
|
||||
@@ -552,7 +554,7 @@ class MemberHttp {
|
||||
|
||||
// 获取uo专栏
|
||||
static Future getMemberSeasons(int? mid, int? pn, int? ps) async {
|
||||
var res = await Request().get(Api.getMemberSeasonsApi, data: {
|
||||
var res = await Request().get(Api.getMemberSeasonsApi, queryParameters: {
|
||||
'mid': mid,
|
||||
'page_num': pn,
|
||||
'page_size': ps,
|
||||
@@ -580,7 +582,7 @@ class MemberHttp {
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.getRecentCoinVideoApi,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'vmid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
@@ -613,7 +615,7 @@ class MemberHttp {
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.getRecentLikeVideoApi,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'vmid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
@@ -645,7 +647,7 @@ class MemberHttp {
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.getSeasonDetailApi,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'mid': mid,
|
||||
'season_id': seasonId,
|
||||
'sort_reverse': sortReverse,
|
||||
@@ -673,7 +675,8 @@ class MemberHttp {
|
||||
|
||||
// 获取up播放数、点赞数
|
||||
static Future memberView({required int mid}) async {
|
||||
var res = await Request().get(Api.getMemberViewApi, data: {'mid': mid});
|
||||
var res = await Request()
|
||||
.get(Api.getMemberViewApi, queryParameters: {'mid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -703,7 +706,7 @@ class MemberHttp {
|
||||
'web_location': 333.999,
|
||||
};
|
||||
Map params = await WbiSign().makSign(data);
|
||||
var res = await Request().get(Api.followSearch, data: {
|
||||
var res = await Request().get(Api.followSearch, queryParameters: {
|
||||
...data,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:math';
|
||||
import 'package:PiliPalaX/http/constants.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/view.dart' show ReplyOption;
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/view.dart' show ReplyOption;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'init.dart';
|
||||
|
||||
class MsgHttp {
|
||||
static Future msgFeedReplyMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedReply, data: {
|
||||
var res = await Request().get(Api.msgFeedReply, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
'reply_time': cursorTime == -1 ? null : cursorTime,
|
||||
});
|
||||
@@ -31,7 +31,7 @@ class MsgHttp {
|
||||
}
|
||||
|
||||
static Future msgFeedAtMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedAt, data: {
|
||||
var res = await Request().get(Api.msgFeedAt, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
'at_time': cursorTime == -1 ? null : cursorTime,
|
||||
});
|
||||
@@ -50,7 +50,7 @@ class MsgHttp {
|
||||
}
|
||||
|
||||
static Future msgFeedLikeMe({int cursor = -1, int cursorTime = -1}) async {
|
||||
var res = await Request().get(Api.msgFeedLike, data: {
|
||||
var res = await Request().get(Api.msgFeedLike, queryParameters: {
|
||||
'id': cursor == -1 ? null : cursor,
|
||||
'like_time': cursorTime == -1 ? null : cursorTime,
|
||||
});
|
||||
@@ -70,7 +70,7 @@ class MsgHttp {
|
||||
|
||||
static Future msgFeedSysUserNotify() async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.msgSysUserNotify, data: {
|
||||
var res = await Request().get(Api.msgSysUserNotify, queryParameters: {
|
||||
'csrf': csrf,
|
||||
'page_size': 20,
|
||||
});
|
||||
@@ -90,7 +90,7 @@ class MsgHttp {
|
||||
|
||||
static Future msgFeedSysUnifiedNotify() async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.msgSysUnifiedNotify, data: {
|
||||
var res = await Request().get(Api.msgSysUnifiedNotify, queryParameters: {
|
||||
'csrf': csrf,
|
||||
'page_size': 10,
|
||||
});
|
||||
@@ -110,7 +110,7 @@ class MsgHttp {
|
||||
|
||||
static Future msgSysUpdateCursor(int cursor) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.msgSysUpdateCursor, data: {
|
||||
var res = await Request().get(Api.msgSysUpdateCursor, queryParameters: {
|
||||
'csrf': csrf,
|
||||
'cursor': cursor,
|
||||
});
|
||||
@@ -149,6 +149,7 @@ class MsgHttp {
|
||||
List? pics,
|
||||
int? publishTime,
|
||||
ReplyOption replyOption = ReplyOption.allow,
|
||||
int? privatePub,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().post(
|
||||
@@ -181,6 +182,10 @@ class MsgHttp {
|
||||
: pics != null
|
||||
? 2
|
||||
: 1,
|
||||
if (privatePub != null)
|
||||
'create_option': {
|
||||
'private_pub': privatePub,
|
||||
},
|
||||
if (pics != null) 'pics': pics,
|
||||
"attach_card": null,
|
||||
"upload_id":
|
||||
@@ -400,7 +405,7 @@ class MsgHttp {
|
||||
}
|
||||
|
||||
Map signParams = await WbiSign().makSign(params);
|
||||
var res = await Request().get(Api.sessionList, data: signParams);
|
||||
var res = await Request().get(Api.sessionList, queryParameters: signParams);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
@@ -424,7 +429,7 @@ class MsgHttp {
|
||||
}
|
||||
|
||||
static Future accountList(uids) async {
|
||||
var res = await Request().get(Api.sessionAccountList, data: {
|
||||
var res = await Request().get(Api.sessionAccountList, queryParameters: {
|
||||
'uids': uids,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
@@ -460,7 +465,7 @@ class MsgHttp {
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
});
|
||||
var res = await Request().get(Api.sessionMsg, data: params);
|
||||
var res = await Request().get(Api.sessionMsg, queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
@@ -494,7 +499,7 @@ class MsgHttp {
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf
|
||||
});
|
||||
var res = await Request().get(Api.ackSessionMsg, data: params);
|
||||
var res = await Request().get(Api.ackSessionMsg, queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPalaX/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../models/video/reply/data.dart';
|
||||
@@ -28,7 +29,7 @@ class ReplyHttp {
|
||||
var res = !isLogin
|
||||
? await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}/main',
|
||||
data: {
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
'pagination_str':
|
||||
@@ -39,7 +40,7 @@ class ReplyHttp {
|
||||
)
|
||||
: await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}',
|
||||
data: {
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
'sort': sort,
|
||||
@@ -49,7 +50,44 @@ class ReplyHttp {
|
||||
options: options,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(ReplyData.fromJson(res.data['data']));
|
||||
ReplyData replyData = ReplyData.fromJson(res.data['data']);
|
||||
String banWordForReply = GStorage.banWordForReply;
|
||||
if (banWordForReply.isNotEmpty) {
|
||||
// topReplies
|
||||
if (replyData.topReplies?.isNotEmpty == true) {
|
||||
replyData.topReplies!.removeWhere((item) {
|
||||
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content?.message ?? '');
|
||||
// remove subreplies
|
||||
if (hasMatch.not) {
|
||||
if (item.replies?.isNotEmpty == true) {
|
||||
item.replies!.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content?.message ?? ''));
|
||||
}
|
||||
}
|
||||
return hasMatch;
|
||||
});
|
||||
}
|
||||
|
||||
// replies
|
||||
if (replyData.replies?.isNotEmpty == true) {
|
||||
replyData.replies!.removeWhere((item) {
|
||||
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content?.message ?? '');
|
||||
// remove subreplies
|
||||
if (hasMatch.not) {
|
||||
if (item.replies?.isNotEmpty == true) {
|
||||
item.replies!.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content?.message ?? ''));
|
||||
}
|
||||
}
|
||||
return hasMatch;
|
||||
});
|
||||
}
|
||||
}
|
||||
return LoadingState.success(replyData);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
@@ -62,7 +100,34 @@ class ReplyHttp {
|
||||
}) async {
|
||||
dynamic res = await GrpcRepo.mainList(type: type, oid: oid, cursor: cursor);
|
||||
if (res['status']) {
|
||||
return LoadingState.success(res['data']);
|
||||
MainListReply mainListReply = res['data'];
|
||||
String banWordForReply = GStorage.banWordForReply;
|
||||
if (banWordForReply.isNotEmpty) {
|
||||
// upTop
|
||||
if (mainListReply.hasUpTop() &&
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(mainListReply.upTop.content.message)) {
|
||||
mainListReply.clearUpTop();
|
||||
}
|
||||
|
||||
// replies
|
||||
if (mainListReply.replies.isNotEmpty) {
|
||||
mainListReply.replies.removeWhere((item) {
|
||||
bool hasMatch = RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content.message);
|
||||
// remove subreplies
|
||||
if (hasMatch.not) {
|
||||
if (item.replies.isNotEmpty) {
|
||||
item.replies.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content.message));
|
||||
}
|
||||
}
|
||||
return hasMatch;
|
||||
});
|
||||
}
|
||||
}
|
||||
return LoadingState.success(mainListReply);
|
||||
} else {
|
||||
return LoadingState.error(
|
||||
'${res['msg'].startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}${res['msg']}');
|
||||
@@ -82,7 +147,7 @@ class ReplyHttp {
|
||||
: null;
|
||||
var res = await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
|
||||
data: {
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'root': root,
|
||||
'pn': pageNum,
|
||||
@@ -93,33 +158,21 @@ class ReplyHttp {
|
||||
options: options,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(ReplyReplyData.fromJson(res.data['data']));
|
||||
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
|
||||
String banWordForReply = GStorage.banWordForReply;
|
||||
if (banWordForReply.isNotEmpty) {
|
||||
if (replyData.replies?.isNotEmpty == true) {
|
||||
replyData.replies!.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content?.message ?? ''));
|
||||
}
|
||||
}
|
||||
return LoadingState.success(replyData);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> dialogListGrpc({
|
||||
int type = 1,
|
||||
required int oid,
|
||||
required int root,
|
||||
required int rpid,
|
||||
required CursorReq cursor,
|
||||
}) async {
|
||||
dynamic res = await GrpcRepo.dialogList(
|
||||
type: type,
|
||||
oid: oid,
|
||||
root: root,
|
||||
rpid: rpid,
|
||||
cursor: cursor,
|
||||
);
|
||||
if (res['status']) {
|
||||
return LoadingState.success(res['data']);
|
||||
} else {
|
||||
return LoadingState.error(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> replyReplyListGrpc({
|
||||
int type = 1,
|
||||
required int oid,
|
||||
@@ -135,7 +188,46 @@ class ReplyHttp {
|
||||
cursor: cursor,
|
||||
);
|
||||
if (res['status']) {
|
||||
return LoadingState.success(res['data']);
|
||||
DetailListReply detailListReply = res['data'];
|
||||
String banWordForReply = GStorage.banWordForReply;
|
||||
if (banWordForReply.isNotEmpty) {
|
||||
if (detailListReply.root.replies.isNotEmpty) {
|
||||
detailListReply.root.replies.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content.message));
|
||||
}
|
||||
}
|
||||
return LoadingState.success(detailListReply);
|
||||
} else {
|
||||
return LoadingState.error(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> dialogListGrpc({
|
||||
int type = 1,
|
||||
required int oid,
|
||||
required int root,
|
||||
required int rpid,
|
||||
required CursorReq cursor,
|
||||
}) async {
|
||||
dynamic res = await GrpcRepo.dialogList(
|
||||
type: type,
|
||||
oid: oid,
|
||||
root: root,
|
||||
rpid: rpid,
|
||||
cursor: cursor,
|
||||
);
|
||||
if (res['status']) {
|
||||
DialogListReply dialogListReply = res['data'];
|
||||
String banWordForReply = GStorage.banWordForReply;
|
||||
if (banWordForReply.isNotEmpty) {
|
||||
if (dialogListReply.replies.isNotEmpty) {
|
||||
dialogListReply.replies.removeWhere((item) =>
|
||||
RegExp(banWordForReply, caseSensitive: false)
|
||||
.hasMatch(item.content.message));
|
||||
}
|
||||
}
|
||||
return LoadingState.success(dialogListReply);
|
||||
} else {
|
||||
return LoadingState.error(res['msg']);
|
||||
}
|
||||
@@ -200,7 +292,7 @@ class ReplyHttp {
|
||||
}
|
||||
|
||||
static Future<LoadingState> getEmoteList({String? business}) async {
|
||||
var res = await Request().get(Api.myEmote, data: {
|
||||
var res = await Request().get(Api.myEmote, queryParameters: {
|
||||
'business': business ?? 'reply',
|
||||
'web_location': '333.1245',
|
||||
});
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import '../models/bangumi/info.dart';
|
||||
import '../models/common/search_type.dart';
|
||||
import '../models/search/hot.dart';
|
||||
@@ -12,7 +11,6 @@ import '../utils/storage.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class SearchHttp {
|
||||
static Box localCache = GStorage.localCache;
|
||||
static Future hotSearchList() async {
|
||||
var res = await Request().get(Api.hotSearchList);
|
||||
if (res.data is String) {
|
||||
@@ -40,7 +38,7 @@ class SearchHttp {
|
||||
// 获取搜索建议
|
||||
static Future searchSuggest({required term}) async {
|
||||
var res = await Request().get(Api.searchSuggest,
|
||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||
queryParameters: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||
if (res.data is String) {
|
||||
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||
if (resultMap['code'] == 0) {
|
||||
@@ -98,14 +96,13 @@ class SearchHttp {
|
||||
if (pubBegin != null) 'pubtime_begin_s': pubBegin,
|
||||
if (pubEnd != null) 'pubtime_end_s': pubEnd,
|
||||
};
|
||||
var res = await Request().get(Api.searchByType, data: reqData);
|
||||
if (res.data['code'] == 0) {
|
||||
var res = await Request().get(Api.searchByType, queryParameters: reqData);
|
||||
if (res.data['code'] is int && res.data['code'] == 0) {
|
||||
dynamic data;
|
||||
try {
|
||||
switch (searchType) {
|
||||
case SearchType.video:
|
||||
List<int> blackMidsList = localCache
|
||||
.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
if (res.data['data']['result'] != null) {
|
||||
for (var i in res.data['data']['result']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
@@ -140,15 +137,15 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int> ab2c({int? aid, String? bvid}) async {
|
||||
static Future<int> ab2c({dynamic aid, dynamic bvid}) async {
|
||||
Map<String, dynamic> data = {};
|
||||
if (aid != null) {
|
||||
data['aid'] = aid;
|
||||
} else if (bvid != null) {
|
||||
data['bvid'] = bvid;
|
||||
}
|
||||
final dynamic res =
|
||||
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
|
||||
final dynamic res = await Request()
|
||||
.get(Api.ab2c, queryParameters: <String, dynamic>{...data});
|
||||
if (res.data['code'] == 0) {
|
||||
return res.data['data'].first['cid'];
|
||||
} else {
|
||||
@@ -160,7 +157,7 @@ class SearchHttp {
|
||||
static Future<LoadingState> bangumiInfoNew({int? seasonId, int? epId}) async {
|
||||
final dynamic res = await Request().get(
|
||||
Api.bangumiInfo,
|
||||
data: {
|
||||
queryParameters: {
|
||||
if (seasonId != null) 'season_id': seasonId,
|
||||
if (epId != null) 'ep_id': epId,
|
||||
},
|
||||
@@ -183,8 +180,8 @@ class SearchHttp {
|
||||
} else if (epId != null) {
|
||||
data['ep_id'] = epId;
|
||||
}
|
||||
final dynamic res =
|
||||
await Request().get(Api.bangumiInfo, data: <String, dynamic>{...data});
|
||||
final dynamic res = await Request()
|
||||
.get(Api.bangumiInfo, queryParameters: <String, dynamic>{...data});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/video/later.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../common/constants.dart';
|
||||
@@ -15,7 +16,7 @@ import 'init.dart';
|
||||
|
||||
class UserHttp {
|
||||
static Future<dynamic> userStat({required int mid}) async {
|
||||
var res = await Request().get(Api.userStat, data: {'vmid': mid});
|
||||
var res = await Request().get(Api.userStat, queryParameters: {'vmid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -47,9 +48,9 @@ class UserHttp {
|
||||
static Future<LoadingState> userfavFolder({
|
||||
required int pn,
|
||||
required int ps,
|
||||
required int mid,
|
||||
required dynamic mid,
|
||||
}) async {
|
||||
var res = await Request().get(Api.userFavFolder, data: {
|
||||
var res = await Request().get(Api.userFavFolder, queryParameters: {
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
'up_mid': mid,
|
||||
@@ -113,7 +114,7 @@ class UserHttp {
|
||||
static Future folderInfo({
|
||||
dynamic mediaId,
|
||||
}) async {
|
||||
var res = await Request().get(Api.folderInfo, data: {
|
||||
var res = await Request().get(Api.folderInfo, queryParameters: {
|
||||
'media_id': mediaId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -130,7 +131,7 @@ class UserHttp {
|
||||
String keyword = '',
|
||||
String order = 'mtime',
|
||||
int type = 0}) async {
|
||||
var res = await Request().get(Api.userFavFolderDetail, data: {
|
||||
var res = await Request().get(Api.userFavFolderDetail, queryParameters: {
|
||||
'media_id': mediaId,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
@@ -172,7 +173,7 @@ class UserHttp {
|
||||
int? max,
|
||||
int? viewAt,
|
||||
}) async {
|
||||
var res = await Request().get(Api.historyList, data: {
|
||||
var res = await Request().get(Api.historyList, queryParameters: {
|
||||
'type': 'all',
|
||||
'ps': 20,
|
||||
'max': max ?? 0,
|
||||
@@ -265,7 +266,7 @@ class UserHttp {
|
||||
static Future thirdLogin() async {
|
||||
var res = await Request().get(
|
||||
'https://passport.bilibili.com/login/app/third',
|
||||
data: {
|
||||
queryParameters: {
|
||||
'appkey': Constants.appKey,
|
||||
'api': Constants.thirdApi,
|
||||
'sign': Constants.thirdSign,
|
||||
@@ -319,7 +320,7 @@ class UserHttp {
|
||||
static Future hasFollow(int mid) async {
|
||||
var res = await Request().get(
|
||||
Api.hasFollow,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'fid': mid,
|
||||
},
|
||||
);
|
||||
@@ -359,7 +360,7 @@ class UserHttp {
|
||||
{required int pn, required String keyword}) async {
|
||||
var res = await Request().get(
|
||||
Api.searchHistory,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'pn': pn,
|
||||
'keyword': keyword,
|
||||
'business': 'all',
|
||||
@@ -373,24 +374,25 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
static Future userSubFolder({
|
||||
static Future<LoadingState> userSubFolder({
|
||||
required int mid,
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(Api.userSubFolder, data: {
|
||||
'up_mid': mid,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
'platform': 'web',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SubFolderModelData.fromJson(res.data['data'])
|
||||
};
|
||||
var res = await Request().get(
|
||||
Api.userSubFolder,
|
||||
queryParameters: {
|
||||
'up_mid': mid,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
'platform': 'web',
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0 && res.data['data'] is Map) {
|
||||
return LoadingState.success(
|
||||
SubFolderModelData.fromJson(res.data['data']).list);
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +401,7 @@ class UserHttp {
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(Api.favSeasonList, data: {
|
||||
var res = await Request().get(Api.favSeasonList, queryParameters: {
|
||||
'season_id': id,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
@@ -419,7 +421,7 @@ class UserHttp {
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(Api.favResourceList, data: {
|
||||
var res = await Request().get(Api.favResourceList, queryParameters: {
|
||||
'media_id': id,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
@@ -463,11 +465,118 @@ class UserHttp {
|
||||
}
|
||||
|
||||
static videoTags({required String bvid}) async {
|
||||
var res = await Request().get(Api.videoTags, data: {'bvid': bvid});
|
||||
var res =
|
||||
await Request().get(Api.videoTags, queryParameters: {'bvid': bvid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false};
|
||||
}
|
||||
}
|
||||
|
||||
// 稍后再看播放全部
|
||||
// static Future toViewPlayAll({required int oid, required String bvid}) async {
|
||||
// var res = await Request().get(
|
||||
// Api.watchLaterHtml,
|
||||
// data: {
|
||||
// 'oid': oid,
|
||||
// 'bvid': bvid,
|
||||
// },
|
||||
// );
|
||||
// String scriptContent =
|
||||
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
// int startIndex = scriptContent.indexOf('{');
|
||||
// int endIndex = scriptContent.lastIndexOf('};');
|
||||
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// // 解析JSON字符串为Map
|
||||
// Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// // 输出解析后的数据
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
static List<String> extractScriptContents(String htmlContent) {
|
||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||
List<String> scriptContents = [];
|
||||
for (Match match in matches) {
|
||||
String scriptContent = match.group(1)!;
|
||||
scriptContents.add(scriptContent);
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
// 稍后再看列表
|
||||
static Future getMediaList({
|
||||
required dynamic type,
|
||||
required int bizId,
|
||||
required int ps,
|
||||
dynamic oid,
|
||||
int? otype,
|
||||
bool withCurrent = false,
|
||||
bool desc = true,
|
||||
dynamic sortField = 1,
|
||||
bool direction = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.mediaList,
|
||||
queryParameters: {
|
||||
'mobi_app': 'web',
|
||||
'type': type,
|
||||
'biz_id': bizId,
|
||||
if (oid != null) 'oid': oid,
|
||||
if (otype != null) 'otype': otype, // video:2 // bangumi: 24
|
||||
'ps': ps,
|
||||
'direction': direction,
|
||||
'desc': desc,
|
||||
'sort_field': sortField,
|
||||
'tid': 0,
|
||||
'with_current': withCurrent,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']['media_list'] != null
|
||||
? res.data['data']['media_list']
|
||||
.map<MediaVideoItemModel>(
|
||||
(e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
: []
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 解析收藏夹视频
|
||||
// static Future parseFavVideo({
|
||||
// required int mediaId,
|
||||
// required int oid,
|
||||
// required String bvid,
|
||||
// }) async {
|
||||
// var res = await Request().get(
|
||||
// 'https://www.bilibili.com/list/ml$mediaId',
|
||||
// queryParameters: {
|
||||
// 'oid': mediaId,
|
||||
// 'bvid': bvid,
|
||||
// },
|
||||
// );
|
||||
// String scriptContent =
|
||||
// extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||
// int startIndex = scriptContent.indexOf('{');
|
||||
// int endIndex = scriptContent.lastIndexOf('};');
|
||||
// String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||
// // 解析JSON字符串为Map
|
||||
// Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPalaX/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/grpc/app/card/v1/card.pb.dart' as card;
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/common/reply_type.dart';
|
||||
import '../models/home/rcmd/result.dart';
|
||||
@@ -31,18 +29,15 @@ import 'login.dart';
|
||||
/// 返回{'status': bool, 'data': List}
|
||||
/// view层根据 status 判断渲染逻辑
|
||||
class VideoHttp {
|
||||
static Box localCache = GStorage.localCache;
|
||||
static Box setting = GStorage.setting;
|
||||
static bool enableRcmdDynamic =
|
||||
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||
static Box userInfoCache = GStorage.userInfo;
|
||||
GStorage.setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||
|
||||
// 首页推荐视频
|
||||
static Future<LoadingState> rcmdVideoList(
|
||||
{required int ps, required int freshIdx}) async {
|
||||
var res = await Request().get(
|
||||
Api.recommendListWeb,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'version': 1,
|
||||
'feed_version': 'V8',
|
||||
'homepage_ver': 1,
|
||||
@@ -54,8 +49,7 @@ class VideoHttp {
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
if (i['goto'] == 'av' &&
|
||||
@@ -78,7 +72,7 @@ class VideoHttp {
|
||||
{bool loginStatus = true, required int freshIdx}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': loginStatus
|
||||
? (localCache
|
||||
? (GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'')
|
||||
: '',
|
||||
@@ -121,7 +115,7 @@ class VideoHttp {
|
||||
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
data: data,
|
||||
queryParameters: data,
|
||||
options: Options(headers: {
|
||||
'Host': 'app.bilibili.com',
|
||||
'buvid': LoginHttp.buvid,
|
||||
@@ -141,8 +135,7 @@ class VideoHttp {
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
if (i['card_goto'] != 'ad_av' &&
|
||||
@@ -168,16 +161,16 @@ class VideoHttp {
|
||||
{required int pn, required int ps}) async {
|
||||
var res = await Request().get(
|
||||
Api.hotList,
|
||||
data: {'pn': pn, 'ps': ps},
|
||||
queryParameters: {'pn': pn, 'ps': ps},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<int> blackMidsList = localCache
|
||||
.get(LocalCacheKey.blackMidsList, defaultValue: [-1])
|
||||
.map<int>((e) => e as int)
|
||||
.toList();
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid'])) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
@@ -191,8 +184,7 @@ class VideoHttp {
|
||||
dynamic res = await GrpcRepo.popular(idx);
|
||||
if (res['status']) {
|
||||
List<card.Card> list = [];
|
||||
List<int> blackMidsList =
|
||||
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (card.Card item in res['data']) {
|
||||
if (!blackMidsList.contains(item.smallCoverV5.up.id.toInt())) {
|
||||
list.add(item);
|
||||
@@ -205,25 +197,29 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 视频流
|
||||
static Future videoUrl(
|
||||
{int? avid, String? bvid, required int cid, int? qn}) async {
|
||||
static Future videoUrl({
|
||||
int? avid,
|
||||
String? bvid,
|
||||
required int cid,
|
||||
int? qn,
|
||||
dynamic epid,
|
||||
dynamic seasonId,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
if (avid != null) 'avid': avid,
|
||||
if (bvid != null) 'bvid': bvid,
|
||||
if (epid != null) 'ep_id': epid,
|
||||
if (seasonId != null) 'season_id': seasonId,
|
||||
'cid': cid,
|
||||
'qn': qn ?? 80,
|
||||
// 获取所有格式的视频
|
||||
'fnval': 4048,
|
||||
};
|
||||
if (avid != null) {
|
||||
data['avid'] = avid;
|
||||
}
|
||||
if (bvid != null) {
|
||||
data['bvid'] = bvid;
|
||||
}
|
||||
|
||||
// 免登录查看1080p
|
||||
if ((userInfoCache.get('userInfoCache') == null ||
|
||||
MineController.anonymity) &&
|
||||
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
if ((GStorage.userInfo.get('userInfoCache') == null ||
|
||||
MineController.anonymity.value) &&
|
||||
GStorage.setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||
data['try_look'] = 1;
|
||||
}
|
||||
|
||||
@@ -235,12 +231,24 @@ class VideoHttp {
|
||||
'web_location': 1550101,
|
||||
});
|
||||
|
||||
late final isLogin = GStorage.isLogin;
|
||||
|
||||
try {
|
||||
var res = await Request().get(Api.videoUrl, data: params);
|
||||
var res = await Request().get(
|
||||
epid != null && isLogin ? Api.bangumiVideoUrl : Api.videoUrl,
|
||||
queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
late PlayUrlModel data;
|
||||
if (epid != null && isLogin) {
|
||||
data = PlayUrlModel.fromJson(res.data['result']['video_info'])
|
||||
..lastPlayTime = res.data['result']['play_view_business_info']
|
||||
['user_status']['watch_progress']['current_watch_progress'];
|
||||
} else {
|
||||
data = PlayUrlModel.fromJson(res.data['data']);
|
||||
}
|
||||
return {
|
||||
'status': true,
|
||||
'data': PlayUrlModel.fromJson(res.data['data'])
|
||||
'data': data,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -257,7 +265,8 @@ class VideoHttp {
|
||||
|
||||
// 视频信息 标题、简介
|
||||
static Future videoIntro({required String bvid}) async {
|
||||
var res = await Request().get(Api.videoIntro, data: {'bvid': bvid});
|
||||
var res =
|
||||
await Request().get(Api.videoIntro, queryParameters: {'bvid': bvid});
|
||||
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
|
||||
if (result.code == 0) {
|
||||
return {
|
||||
@@ -312,7 +321,7 @@ class VideoHttp {
|
||||
static Future videoRelation({required dynamic bvid}) async {
|
||||
var res = await Request().get(
|
||||
Api.videoRelation,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'bvid': bvid,
|
||||
},
|
||||
@@ -332,7 +341,8 @@ class VideoHttp {
|
||||
|
||||
// 相关视频
|
||||
static Future<LoadingState> relatedVideoList({required String bvid}) async {
|
||||
var res = await Request().get(Api.relatedList, data: {'bvid': bvid});
|
||||
var res =
|
||||
await Request().get(Api.relatedList, queryParameters: {'bvid': bvid});
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']) {
|
||||
@@ -351,7 +361,7 @@ class VideoHttp {
|
||||
static Future bangumiLikeCoinFav({dynamic epId}) async {
|
||||
var res = await Request().get(
|
||||
Api.bangumiLikeCoinFav,
|
||||
data: {'ep_id': epId},
|
||||
queryParameters: {'ep_id': epId},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -362,7 +372,8 @@ class VideoHttp {
|
||||
|
||||
// 获取点赞状态
|
||||
static Future hasLikeVideo({required String bvid}) async {
|
||||
var res = await Request().get(Api.hasLikeVideo, data: {'bvid': bvid});
|
||||
var res =
|
||||
await Request().get(Api.hasLikeVideo, queryParameters: {'bvid': bvid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -372,7 +383,8 @@ class VideoHttp {
|
||||
|
||||
// 获取投币状态
|
||||
static Future hasCoinVideo({required String bvid}) async {
|
||||
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
|
||||
var res =
|
||||
await Request().get(Api.hasCoinVideo, queryParameters: {'bvid': bvid});
|
||||
debugPrint('res: $res');
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@@ -404,7 +416,8 @@ class VideoHttp {
|
||||
|
||||
// 获取收藏状态
|
||||
static Future hasFavVideo({required int aid}) async {
|
||||
var res = await Request().get(Api.hasFavVideo, data: {'aid': aid});
|
||||
var res =
|
||||
await Request().get(Api.hasFavVideo, queryParameters: {'aid': aid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -514,7 +527,7 @@ class VideoHttp {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
assert((reasonId != null) ^ (feedbackId != null));
|
||||
var res = await Request().get(Api.feedDislike, data: {
|
||||
var res = await Request().get(Api.feedDislike, queryParameters: {
|
||||
'goto': goto,
|
||||
'id': id,
|
||||
// 'mid': mid,
|
||||
@@ -544,7 +557,7 @@ class VideoHttp {
|
||||
return {'status': false, 'msg': "请退出账号后重新登录"};
|
||||
}
|
||||
// assert ((reasonId != null) ^ (feedbackId != null));
|
||||
var res = await Request().get(Api.feedDislikeCancel, data: {
|
||||
var res = await Request().get(Api.feedDislikeCancel, queryParameters: {
|
||||
'goto': goto,
|
||||
'id': id,
|
||||
// 'mid': mid,
|
||||
@@ -644,7 +657,7 @@ class VideoHttp {
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.videoInFolder,
|
||||
data: {
|
||||
queryParameters: {
|
||||
'up_mid': mid,
|
||||
'rid': rid,
|
||||
if (type != null) 'type': type,
|
||||
@@ -719,7 +732,7 @@ class VideoHttp {
|
||||
|
||||
// 查询是否关注up
|
||||
static Future hasFollow({required int mid}) async {
|
||||
var res = await Request().get(Api.hasFollow, data: {'fid': mid});
|
||||
var res = await Request().get(Api.hasFollow, queryParameters: {'fid': mid});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -744,18 +757,36 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 视频播放进度
|
||||
static Future heartBeat({bvid, cid, progress, realtime}) async {
|
||||
static Future heartBeat({
|
||||
bvid,
|
||||
cid,
|
||||
progress,
|
||||
epid,
|
||||
seasonId,
|
||||
subType,
|
||||
}) async {
|
||||
await Request().post(Api.heartBeat, queryParameters: {
|
||||
// 'aid': aid,
|
||||
'bvid': bvid,
|
||||
'cid': cid,
|
||||
// 'epid': '',
|
||||
// 'sid': '',
|
||||
// 'mid': '',
|
||||
if (epid != null) 'epid': epid,
|
||||
if (seasonId != null) 'sid': seasonId,
|
||||
if (epid != null) 'type': 4,
|
||||
if (subType != null) 'sub_type': subType,
|
||||
'played_time': progress,
|
||||
// 'realtime': realtime,
|
||||
// 'type': '',
|
||||
// 'sub_type': '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
}
|
||||
|
||||
static Future medialistHistory({
|
||||
required int desc,
|
||||
required dynamic oid,
|
||||
required dynamic upperMid,
|
||||
}) async {
|
||||
await Request().post(Api.mediaListHistory, queryParameters: {
|
||||
'desc': desc,
|
||||
'oid': oid,
|
||||
'upper_mid': upperMid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
}
|
||||
@@ -825,7 +856,7 @@ class VideoHttp {
|
||||
|
||||
// 查看视频同时在看人数
|
||||
static Future onlineTotal({int? aid, String? bvid, int? cid}) async {
|
||||
var res = await Request().get(Api.onlineTotal, data: {
|
||||
var res = await Request().get(Api.onlineTotal, queryParameters: {
|
||||
'aid': aid,
|
||||
'bvid': bvid,
|
||||
'cid': cid,
|
||||
@@ -847,7 +878,7 @@ class VideoHttp {
|
||||
'cid': cid,
|
||||
'up_mid': upMid,
|
||||
});
|
||||
var res = await Request().get(Api.aiConclusion, data: params);
|
||||
var res = await Request().get(Api.aiConclusion, queryParameters: params);
|
||||
if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
@@ -861,11 +892,14 @@ class VideoHttp {
|
||||
static Future subtitlesJson(
|
||||
{String? aid, String? bvid, required int cid}) async {
|
||||
assert(aid != null || bvid != null);
|
||||
var res = await Request().get(Api.subtitleUrl, data: {
|
||||
if (aid != null) 'aid': aid,
|
||||
if (bvid != null) 'bvid': bvid,
|
||||
'cid': cid,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.subtitleUrl,
|
||||
queryParameters: {
|
||||
if (aid != null) 'aid': aid,
|
||||
if (bvid != null) 'bvid': bvid,
|
||||
'cid': cid,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
dynamic data = res.data['data'];
|
||||
List subtitlesJson = data['subtitle']['subtitles'];
|
||||
@@ -887,6 +921,9 @@ class VideoHttp {
|
||||
return {
|
||||
'status': true,
|
||||
'data': subtitlesJson,
|
||||
'view_points': data['view_points'],
|
||||
// 'last_play_time': data['last_play_time'],
|
||||
'last_play_cid': data['last_play_cid'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
@@ -910,6 +947,12 @@ class VideoHttp {
|
||||
return "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:${s.toString().padLeft(2, '0')}.${ms.toString().padLeft(3, '0')}";
|
||||
}
|
||||
|
||||
String processList(List list) {
|
||||
return list.fold('WEBVTT\n\n', (previous, item) {
|
||||
return '$previous${item?['sid'] ?? 0}\n${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n${item['content'].trim()}\n\n';
|
||||
});
|
||||
}
|
||||
|
||||
for (var i in subtitlesJson) {
|
||||
var res =
|
||||
await Request().get("https://${i['subtitle_url'].split('//')[1]}");
|
||||
@@ -944,21 +987,16 @@ class VideoHttp {
|
||||
]
|
||||
}
|
||||
*/
|
||||
if (res.data != null) {
|
||||
String vttData = "WEBVTT\n\n";
|
||||
for (var item in res.data['body']) {
|
||||
vttData += "${item['sid'] ?? 0}\n";
|
||||
vttData +=
|
||||
"${subtitleTimecode(item['from'])} --> ${subtitleTimecode(item['to'])}\n";
|
||||
vttData += "${item['content'].trim()}\n\n";
|
||||
}
|
||||
if (res.data != null && res.data?['body'] is List) {
|
||||
String vttData = await compute(processList, res.data['body'] as List);
|
||||
subtitlesVtt.add({
|
||||
'language': i['lan'],
|
||||
'title': i['lan_doc'],
|
||||
'text': vttData,
|
||||
});
|
||||
} else {
|
||||
SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
|
||||
// SmartDialog.showToast("字幕${i['lan_doc']}加载失败, ${res.data['message']}");
|
||||
debugPrint('字幕${i['lan_doc']}加载失败, ${res.data['message']}');
|
||||
}
|
||||
}
|
||||
if (subtitlesVtt.isNotEmpty) {
|
||||
@@ -973,10 +1011,12 @@ class VideoHttp {
|
||||
var res = await Request().get(rankApi);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
localCache.get(LocalCacheKey.blackMidsList, defaultValue: <int>[]);
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid'])) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
|
||||
115
lib/main.dart
115
lib/main.dart
@@ -1,6 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/utils/cache_manage.dart';
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
@@ -9,18 +11,18 @@ import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPalaX/http/init.dart';
|
||||
import 'package:PiliPalaX/models/common/color_type.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/router/app_pages.dart';
|
||||
import 'package:PiliPalaX/pages/main/view.dart';
|
||||
import 'package:PiliPalaX/services/service_locator.dart';
|
||||
import 'package:PiliPalaX/utils/app_scheme.dart';
|
||||
import 'package:PiliPalaX/utils/data.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/models/common/color_type.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/index.dart';
|
||||
import 'package:PiliPlus/router/app_pages.dart';
|
||||
import 'package:PiliPlus/pages/main/view.dart';
|
||||
import 'package:PiliPlus/services/service_locator.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/data.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
|
||||
import 'package:PiliPalaX/utils/recommend_filter.dart';
|
||||
import 'package:PiliPlus/utils/recommend_filter.dart';
|
||||
import 'package:catcher_2/catcher_2.dart';
|
||||
import './services/loggeer.dart';
|
||||
|
||||
@@ -50,13 +52,19 @@ void main() async {
|
||||
],
|
||||
);
|
||||
}
|
||||
if (GStorage.badCertificateCallback) {
|
||||
HttpOverrides.global = _CustomHttpOverrides();
|
||||
}
|
||||
await setupServiceLocator();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
RecommendFilter();
|
||||
SmartDialog.config.toast =
|
||||
SmartConfigToast(displayType: SmartToastType.normal);
|
||||
SmartDialog.config.loading =
|
||||
SmartConfigLoading(backType: SmartBackType.normal);
|
||||
// 异常捕获 logo记录
|
||||
final String buildConfig = '''\n
|
||||
Build Time: ${BuildConfig.buildTime}
|
||||
Commit Hash: ${BuildConfig.commitHash}''';
|
||||
final Catcher2Options debugConfig = Catcher2Options(
|
||||
SilentReportMode(),
|
||||
[
|
||||
@@ -64,13 +72,25 @@ void main() async {
|
||||
ConsoleHandler(
|
||||
enableDeviceParameters: false,
|
||||
enableApplicationParameters: false,
|
||||
enableCustomParameters: true,
|
||||
)
|
||||
],
|
||||
customParameters: {
|
||||
'BuildConfig': buildConfig,
|
||||
},
|
||||
);
|
||||
|
||||
final Catcher2Options releaseConfig = Catcher2Options(
|
||||
SilentReportMode(),
|
||||
[FileHandler(await getLogsPath())],
|
||||
[
|
||||
FileHandler(await getLogsPath()),
|
||||
ConsoleHandler(
|
||||
enableCustomParameters: true,
|
||||
)
|
||||
],
|
||||
customParameters: {
|
||||
'BuildConfig': buildConfig,
|
||||
},
|
||||
);
|
||||
|
||||
Catcher2(
|
||||
@@ -96,9 +116,10 @@ void main() async {
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
Box get setting => GStorage.setting;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Box setting = GStorage.setting;
|
||||
// 主题色
|
||||
Color defaultColor =
|
||||
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
||||
@@ -110,8 +131,10 @@ class MyApp extends StatelessWidget {
|
||||
// 字体缩放大小
|
||||
double textScale =
|
||||
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||
DynamicSchemeVariant dynamicSchemeVariant =
|
||||
DynamicSchemeVariant.values[GStorage.schemeVariant];
|
||||
// DynamicSchemeVariant dynamicSchemeVariant =
|
||||
// DynamicSchemeVariant.values[GStorage.schemeVariant];
|
||||
FlexSchemeVariant variant =
|
||||
FlexSchemeVariant.values[GStorage.schemeVariant];
|
||||
|
||||
// 强制设置高帧率
|
||||
if (Platform.isAndroid) {
|
||||
@@ -139,29 +162,36 @@ class MyApp extends StatelessWidget {
|
||||
darkColorScheme = darkDynamic.harmonized();
|
||||
} else {
|
||||
// dynamic取色失败,采用品牌色
|
||||
lightColorScheme = ColorScheme.fromSeed(
|
||||
seedColor: brandColor,
|
||||
lightColorScheme = SeedColorScheme.fromSeeds(
|
||||
primaryKey: brandColor,
|
||||
brightness: Brightness.light,
|
||||
dynamicSchemeVariant: dynamicSchemeVariant,
|
||||
variant: variant,
|
||||
// dynamicSchemeVariant: dynamicSchemeVariant,
|
||||
// tones: FlexTones.soft(Brightness.light),
|
||||
);
|
||||
darkColorScheme = ColorScheme.fromSeed(
|
||||
seedColor: brandColor,
|
||||
darkColorScheme = SeedColorScheme.fromSeeds(
|
||||
primaryKey: brandColor,
|
||||
brightness: Brightness.dark,
|
||||
dynamicSchemeVariant: dynamicSchemeVariant,
|
||||
variant: variant,
|
||||
// dynamicSchemeVariant: dynamicSchemeVariant,
|
||||
// tones: FlexTones.soft(Brightness.dark),
|
||||
);
|
||||
}
|
||||
// 图片缓存
|
||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||
return GetMaterialApp(
|
||||
// showSemanticsDebugger: true,
|
||||
title: 'PiliPalaX',
|
||||
title: 'PiliPlus',
|
||||
theme: _getThemeData(
|
||||
colorScheme: lightColorScheme,
|
||||
isDynamic: lightDynamic != null && isDynamicColor,
|
||||
variant: variant,
|
||||
),
|
||||
darkTheme: _getThemeData(
|
||||
colorScheme: darkColorScheme,
|
||||
isDynamic: darkDynamic != null && isDynamicColor,
|
||||
isDark: true,
|
||||
variant: variant,
|
||||
),
|
||||
themeMode: GStorage.themeMode,
|
||||
localizationsDelegates: const [
|
||||
@@ -196,9 +226,9 @@ class MyApp extends StatelessWidget {
|
||||
ThemeData _getThemeData({
|
||||
required ColorScheme colorScheme,
|
||||
required bool isDynamic,
|
||||
bool isDark = false,
|
||||
required FlexSchemeVariant variant,
|
||||
}) {
|
||||
Color surfaceTintColor =
|
||||
isDynamic ? colorScheme.surfaceTint : colorScheme.surfaceContainer;
|
||||
return ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: true,
|
||||
@@ -207,19 +237,17 @@ class MyApp extends StatelessWidget {
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: Platform.isIOS ? colorScheme.surface : null,
|
||||
backgroundColor: isDynamic ? null : colorScheme.surface,
|
||||
titleTextStyle: TextStyle(fontSize: 16, color: colorScheme.onSurface),
|
||||
),
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
actionTextColor: colorScheme.primary,
|
||||
backgroundColor: colorScheme.secondaryContainer,
|
||||
closeIconColor: colorScheme.secondary,
|
||||
contentTextStyle: TextStyle(
|
||||
color: colorScheme.secondary,
|
||||
),
|
||||
contentTextStyle: TextStyle(color: colorScheme.secondary),
|
||||
elevation: 20,
|
||||
),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
@@ -230,19 +258,32 @@ class MyApp extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
popupMenuTheme: PopupMenuThemeData(
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
surfaceTintColor: isDynamic ? colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
cardTheme: CardTheme(
|
||||
elevation: 1,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
surfaceTintColor: isDynamic
|
||||
? colorScheme.onSurfaceVariant
|
||||
: isDark
|
||||
? colorScheme.onSurfaceVariant
|
||||
: null,
|
||||
shadowColor: Colors.transparent,
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
),
|
||||
// dialogTheme: DialogTheme(
|
||||
// surfaceTintColor: isDark ? colorScheme.onSurfaceVariant : null,
|
||||
// ),
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
refreshBackgroundColor: colorScheme.onSecondary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final List<Map<String, dynamic>> colorThemeTypes = [
|
||||
{'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'},
|
||||
{'color': const Color.fromARGB(255, 251, 114, 153), 'label': '粉红色'},
|
||||
{'color': const Color(0xFF5CB67B), 'label': '默认绿'},
|
||||
{'color': const Color(0xFFFF7299), 'label': '粉红色'},
|
||||
{'color': Colors.red, 'label': '红色'},
|
||||
{'color': Colors.orange, 'label': '橙色'},
|
||||
{'color': Colors.amber, 'label': '琥珀色'},
|
||||
|
||||
@@ -4,6 +4,8 @@ extension DynamicBadgeModeDesc on DynamicBadgeMode {
|
||||
String get description => ['隐藏', '红点', '数字'][index];
|
||||
}
|
||||
|
||||
extension DynamicBadgeModeCode on DynamicBadgeMode {
|
||||
int get code => [0, 1, 2][index];
|
||||
enum MsgUnReadType { pm, reply, at, like, sysMsg, all }
|
||||
|
||||
extension MsgUnReadTypeExt on MsgUnReadType {
|
||||
String get title => ['私信', '回复我的', '@我', '收到的赞', '系统通知', '全部'][index];
|
||||
}
|
||||
|
||||
@@ -11,35 +11,35 @@ extension BusinessTypeExtension on DynamicsType {
|
||||
String get labels => ['全部', '投稿', '番剧', '专栏', 'UP'][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
{
|
||||
'tag': 'all',
|
||||
'value': DynamicsType.all,
|
||||
'label': '全部',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'video',
|
||||
'value': DynamicsType.video,
|
||||
'label': '投稿',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'pgc',
|
||||
'value': DynamicsType.pgc,
|
||||
'label': '番剧',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'article',
|
||||
'value': DynamicsType.article,
|
||||
'label': '专栏',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'up',
|
||||
'value': DynamicsType.up,
|
||||
'label': 'UP',
|
||||
'enabled': true,
|
||||
},
|
||||
];
|
||||
List get tabsConfig => [
|
||||
{
|
||||
'tag': 'all',
|
||||
'value': DynamicsType.all,
|
||||
'label': '全部',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'video',
|
||||
'value': DynamicsType.video,
|
||||
'label': '投稿',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'pgc',
|
||||
'value': DynamicsType.pgc,
|
||||
'label': '番剧',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'article',
|
||||
'value': DynamicsType.article,
|
||||
'label': '专栏',
|
||||
'enabled': true,
|
||||
},
|
||||
{
|
||||
'tag': 'up',
|
||||
'value': DynamicsType.up,
|
||||
'label': 'UP',
|
||||
'enabled': true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
List defaultNavigationBars = [
|
||||
{
|
||||
'id': 0,
|
||||
'icon': const Icon(
|
||||
Icons.home_outlined,
|
||||
size: 23,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.home,
|
||||
size: 23,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 1,
|
||||
'icon': const Icon(
|
||||
Icons.motion_photos_on_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.motion_photos_on,
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'icon': const Icon(
|
||||
Icons.video_collection_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.video_collection,
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
];
|
||||
List get defaultNavigationBars => [
|
||||
{
|
||||
'id': 0,
|
||||
'icon': const Icon(
|
||||
Icons.home_outlined,
|
||||
size: 23,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.home,
|
||||
size: 23,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 1,
|
||||
'icon': const Icon(
|
||||
Icons.motion_photos_on_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.motion_photos_on,
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'icon': const Icon(
|
||||
Icons.video_collection_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.video_collection,
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:PiliPalaX/pages/rank/index.dart';
|
||||
import 'package:PiliPlus/pages/rank/index.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/pages/bangumi/index.dart';
|
||||
import 'package:PiliPalaX/pages/hot/index.dart';
|
||||
import 'package:PiliPalaX/pages/live/index.dart';
|
||||
import 'package:PiliPalaX/pages/rcmd/index.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/index.dart';
|
||||
import 'package:PiliPlus/pages/hot/index.dart';
|
||||
import 'package:PiliPlus/pages/live/index.dart';
|
||||
import 'package:PiliPlus/pages/rcmd/index.dart';
|
||||
|
||||
enum TabType { live, rcmd, hot, rank, bangumi }
|
||||
|
||||
@@ -13,55 +13,55 @@ extension TabTypeDesc on TabType {
|
||||
String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '直播',
|
||||
'type': TabType.live,
|
||||
'ctr': Get.find<LiveController>,
|
||||
'page': const RcmdPage(tabType: TabType.live),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.thumb_up_off_alt_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '推荐',
|
||||
'type': TabType.rcmd,
|
||||
'ctr': Get.find<RcmdController>,
|
||||
'page': const RcmdPage(tabType: TabType.rcmd),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.whatshot_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '热门',
|
||||
'type': TabType.hot,
|
||||
'ctr': Get.find<HotController>,
|
||||
'page': const HotPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.category_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '分区',
|
||||
'type': TabType.rank,
|
||||
'ctr': Get.find<RankController>,
|
||||
'page': const RankPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.play_circle_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '番剧',
|
||||
'type': TabType.bangumi,
|
||||
'ctr': Get.find<BangumiController>,
|
||||
'page': const BangumiPage(),
|
||||
},
|
||||
];
|
||||
List get tabsConfig => [
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '直播',
|
||||
'type': TabType.live,
|
||||
'ctr': Get.find<LiveController>,
|
||||
'page': const RcmdPage(tabType: TabType.live),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.thumb_up_off_alt_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '推荐',
|
||||
'type': TabType.rcmd,
|
||||
'ctr': Get.find<RcmdController>,
|
||||
'page': const RcmdPage(tabType: TabType.rcmd),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.whatshot_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '热门',
|
||||
'type': TabType.hot,
|
||||
'ctr': Get.find<HotController>,
|
||||
'page': const HotPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.category_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '分区',
|
||||
'type': TabType.rank,
|
||||
'ctr': Get.find<RankController>,
|
||||
'page': const RankPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.play_circle_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '番剧',
|
||||
'type': TabType.bangumi,
|
||||
'ctr': Get.find<BangumiController>,
|
||||
'page': const BangumiPage(),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -416,6 +416,8 @@ class DynamicMajorModel {
|
||||
this.none,
|
||||
this.type,
|
||||
this.courses,
|
||||
this.common,
|
||||
this.music,
|
||||
});
|
||||
|
||||
DynamicArchiveModel? archive;
|
||||
@@ -431,6 +433,8 @@ class DynamicMajorModel {
|
||||
// MAJOR_TYPE_OPUS 图文/文章
|
||||
String? type;
|
||||
Map? courses;
|
||||
Map? common;
|
||||
Map? music;
|
||||
|
||||
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
|
||||
archive = json['archive'] != null
|
||||
@@ -454,6 +458,8 @@ class DynamicMajorModel {
|
||||
json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null;
|
||||
type = json['type'];
|
||||
courses = json['courses'] ?? {};
|
||||
common = json['common'] ?? {};
|
||||
music = json['music'] ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ class FollowUpModel {
|
||||
this.upList,
|
||||
});
|
||||
|
||||
String? errMsg;
|
||||
LiveUsers? liveUsers;
|
||||
List<UpItem>? upList;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
|
||||
class RecVideoItemAppModel {
|
||||
RecVideoItemAppModel({
|
||||
|
||||
@@ -27,7 +27,9 @@ class HotVideoItemModel {
|
||||
this.seasontype,
|
||||
this.isOgv,
|
||||
this.rcmdReason,
|
||||
required this.checked,
|
||||
this.checked,
|
||||
this.pgcLabel,
|
||||
this.redirectUrl,
|
||||
});
|
||||
|
||||
int? aid;
|
||||
@@ -55,7 +57,9 @@ class HotVideoItemModel {
|
||||
int? seasontype;
|
||||
bool? isOgv;
|
||||
RcmdReason? rcmdReason;
|
||||
late bool checked;
|
||||
bool? checked;
|
||||
String? pgcLabel;
|
||||
String? redirectUrl;
|
||||
|
||||
HotVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
aid = json["aid"];
|
||||
@@ -85,7 +89,8 @@ class HotVideoItemModel {
|
||||
rcmdReason = json['rcmd_reason'] != '' && json['rcmd_reason'] != null
|
||||
? RcmdReason.fromJson(json['rcmd_reason'])
|
||||
: null;
|
||||
checked = false;
|
||||
pgcLabel = json['pgc_label'];
|
||||
redirectUrl = json['redirect_url'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class AccountListModel {
|
||||
mid = json['mid'];
|
||||
name = json['name'] ?? '';
|
||||
sex = json['sex'];
|
||||
face = json['face'];
|
||||
face = json['face'] ?? json['pic_url'];
|
||||
sign = json['sign'];
|
||||
rank = json['rank'];
|
||||
level = json['level'];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/models/msg/account.dart';
|
||||
import 'package:PiliPlus/models/msg/account.dart';
|
||||
|
||||
class SessionDataModel {
|
||||
SessionDataModel({
|
||||
@@ -47,6 +47,7 @@ class SessionList {
|
||||
this.liveStatus,
|
||||
this.bizMsgUnreadCount,
|
||||
// this.userLabel,
|
||||
this.accountInfo,
|
||||
});
|
||||
|
||||
int? talkerId;
|
||||
@@ -105,6 +106,9 @@ class SessionList {
|
||||
liveStatus = json["live_status"];
|
||||
bizMsgUnreadCount = json["biz_msg_unread_count"];
|
||||
// userLabel = json["user_label"];
|
||||
accountInfo = json['account_info'] == null
|
||||
? null
|
||||
: AccountListModel.fromJson(json['account_info']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/utils/em.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/utils/em.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class SearchVideoModel {
|
||||
SearchVideoModel({
|
||||
|
||||
@@ -29,16 +29,16 @@ class SearchSuggestItem {
|
||||
String? value;
|
||||
String? term;
|
||||
int? spid;
|
||||
Widget? textRich;
|
||||
dynamic textRich;
|
||||
|
||||
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
|
||||
value = json['value'];
|
||||
term = json['term'];
|
||||
textRich = highlightText(json['name']);
|
||||
textRich = json['name'];
|
||||
}
|
||||
}
|
||||
|
||||
Widget highlightText(String str) {
|
||||
Widget highlightText(BuildContext context, String str) {
|
||||
// 创建正则表达式,匹配 <em class="suggest_high_light">...</em> 格式的文本
|
||||
RegExp regex = RegExp(r'<em class="suggest_high_light">(.*?)<\/em>');
|
||||
|
||||
@@ -72,7 +72,7 @@ Widget highlightText(String str) {
|
||||
text: highlightedText,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(Get.context!).colorScheme.primary),
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
));
|
||||
|
||||
// 更新当前索引位置
|
||||
@@ -86,7 +86,7 @@ Widget highlightText(String str) {
|
||||
// 将剩余的普通文本部分添加到 children 列表中
|
||||
children.add(TextSpan(
|
||||
text: remainingText,
|
||||
style: DefaultTextStyle.of(Get.context!).style,
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/models/space/space_tag_bottom.dart';
|
||||
import 'package:PiliPlus/models/space/space_tag_bottom.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'achieve.dart';
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'series.dart';
|
||||
import 'setting.dart';
|
||||
import 'tab.dart';
|
||||
import 'tab2.dart';
|
||||
import 'package:PiliPalaX/models/space_article/data.dart' as space;
|
||||
import 'package:PiliPlus/models/space_article/data.dart' as space;
|
||||
|
||||
part 'data.g.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/models/space_archive/item.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'season.g.dart';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/models/model_owner.dart';
|
||||
import 'package:PiliPalaX/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/models/model_owner.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
|
||||
class FavDetailData {
|
||||
FavDetailData({
|
||||
@@ -47,7 +47,7 @@ class FavDetailItemData {
|
||||
this.stat,
|
||||
this.cid,
|
||||
this.epId,
|
||||
required this.checked,
|
||||
this.checked,
|
||||
});
|
||||
|
||||
int? id;
|
||||
@@ -70,7 +70,7 @@ class FavDetailItemData {
|
||||
Stat? stat;
|
||||
int? cid;
|
||||
String? epId;
|
||||
late bool checked;
|
||||
bool? checked;
|
||||
|
||||
FavDetailItemData.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
@@ -95,7 +95,6 @@ class FavDetailItemData {
|
||||
if (json['link'] != null && json['link'].contains('/bangumi')) {
|
||||
epId = resolveEpId(json['link']);
|
||||
}
|
||||
checked = false;
|
||||
}
|
||||
|
||||
String resolveEpId(url) {
|
||||
|
||||
@@ -85,7 +85,7 @@ class HisListItem {
|
||||
this.kid,
|
||||
this.tagName,
|
||||
this.liveStatus,
|
||||
required this.checked,
|
||||
this.checked,
|
||||
});
|
||||
|
||||
String? title;
|
||||
@@ -112,7 +112,7 @@ class HisListItem {
|
||||
int? kid;
|
||||
String? tagName;
|
||||
int? liveStatus;
|
||||
late bool checked;
|
||||
bool? checked;
|
||||
void isFullScreen;
|
||||
|
||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||
@@ -140,7 +140,6 @@ class HisListItem {
|
||||
kid = json['kid'];
|
||||
tagName = json['tag_name'];
|
||||
liveStatus = json['live_status'];
|
||||
checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
276
lib/models/video/later.dart
Normal file
276
lib/models/video/later.dart
Normal file
@@ -0,0 +1,276 @@
|
||||
class MediaVideoItemModel {
|
||||
MediaVideoItemModel({
|
||||
this.id,
|
||||
this.aid,
|
||||
this.bvid,
|
||||
this.cid,
|
||||
this.offset,
|
||||
this.index,
|
||||
this.intro,
|
||||
this.attr,
|
||||
this.tid,
|
||||
this.copyRight,
|
||||
this.cntInfo,
|
||||
this.cover,
|
||||
this.duration,
|
||||
this.pubtime,
|
||||
this.likeState,
|
||||
this.favState,
|
||||
this.page,
|
||||
this.pages,
|
||||
this.title,
|
||||
this.type,
|
||||
this.upper,
|
||||
this.link,
|
||||
this.shortLink,
|
||||
this.rights,
|
||||
this.elecInfo,
|
||||
this.coin,
|
||||
this.progressPercent,
|
||||
this.badge,
|
||||
this.forbidFav,
|
||||
this.moreType,
|
||||
this.businessOid,
|
||||
});
|
||||
|
||||
int? id;
|
||||
int? aid;
|
||||
String? bvid;
|
||||
int? cid;
|
||||
int? offset;
|
||||
int? index;
|
||||
String? intro;
|
||||
int? attr;
|
||||
int? tid;
|
||||
int? copyRight;
|
||||
Map? cntInfo;
|
||||
String? cover;
|
||||
int? duration;
|
||||
int? pubtime;
|
||||
int? likeState;
|
||||
int? favState;
|
||||
int? page;
|
||||
List<Page>? pages;
|
||||
String? title;
|
||||
int? type;
|
||||
Upper? upper;
|
||||
String? link;
|
||||
String? shortLink;
|
||||
Rights? rights;
|
||||
dynamic elecInfo;
|
||||
Coin? coin;
|
||||
double? progressPercent;
|
||||
dynamic badge;
|
||||
bool? forbidFav;
|
||||
int? moreType;
|
||||
int? businessOid;
|
||||
|
||||
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
MediaVideoItemModel(
|
||||
id: json["id"],
|
||||
aid: json["id"],
|
||||
bvid: json["bv_id"],
|
||||
cid: json["pages"] == null ? -1 : json["pages"].first['id'],
|
||||
offset: json["offset"],
|
||||
index: json["index"],
|
||||
intro: json["intro"],
|
||||
attr: json["attr"],
|
||||
tid: json["tid"],
|
||||
copyRight: json["copy_right"],
|
||||
cntInfo: json["cnt_info"],
|
||||
cover: json["cover"],
|
||||
duration: json["duration"],
|
||||
pubtime: json["pubtime"],
|
||||
likeState: json["like_state"],
|
||||
favState: json["fav_state"],
|
||||
page: json["page"],
|
||||
// json["pages"] 可能为null
|
||||
pages: json["pages"] == null
|
||||
? []
|
||||
: List<Page>.from(json["pages"].map((x) => Page.fromJson(x))),
|
||||
title: json["title"],
|
||||
type: json["type"],
|
||||
upper: Upper.fromJson(json["upper"]),
|
||||
link: json["link"],
|
||||
shortLink: json["short_link"],
|
||||
rights: Rights.fromJson(json["rights"]),
|
||||
elecInfo: json["elec_info"],
|
||||
coin: Coin.fromJson(json["coin"]),
|
||||
progressPercent: json["progress_percent"].toDouble(),
|
||||
badge: json["badge"],
|
||||
forbidFav: json["forbid_fav"],
|
||||
moreType: json["more_type"],
|
||||
businessOid: json["business_oid"],
|
||||
);
|
||||
}
|
||||
|
||||
class Coin {
|
||||
Coin({
|
||||
this.maxNum,
|
||||
this.coinNumber,
|
||||
});
|
||||
|
||||
int? maxNum;
|
||||
int? coinNumber;
|
||||
|
||||
factory Coin.fromJson(Map<String, dynamic> json) => Coin(
|
||||
maxNum: json["max_num"],
|
||||
coinNumber: json["coin_number"],
|
||||
);
|
||||
}
|
||||
|
||||
class Page {
|
||||
Page({
|
||||
this.id,
|
||||
this.title,
|
||||
this.intro,
|
||||
this.duration,
|
||||
this.link,
|
||||
this.page,
|
||||
this.metas,
|
||||
this.from,
|
||||
this.dimension,
|
||||
});
|
||||
|
||||
int? id;
|
||||
String? title;
|
||||
String? intro;
|
||||
int? duration;
|
||||
String? link;
|
||||
int? page;
|
||||
List<Meta>? metas;
|
||||
String? from;
|
||||
Dimension? dimension;
|
||||
|
||||
factory Page.fromJson(Map<String, dynamic> json) => Page(
|
||||
id: json["id"],
|
||||
title: json["title"],
|
||||
intro: json["intro"],
|
||||
duration: json["duration"],
|
||||
link: json["link"],
|
||||
page: json["page"],
|
||||
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
|
||||
from: json["from"],
|
||||
dimension: Dimension.fromJson(json["dimension"]),
|
||||
);
|
||||
}
|
||||
|
||||
class Dimension {
|
||||
Dimension({
|
||||
this.width,
|
||||
this.height,
|
||||
this.rotate,
|
||||
});
|
||||
|
||||
int? width;
|
||||
int? height;
|
||||
int? rotate;
|
||||
|
||||
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||
width: json["width"],
|
||||
height: json["height"],
|
||||
rotate: json["rotate"],
|
||||
);
|
||||
}
|
||||
|
||||
class Meta {
|
||||
Meta({
|
||||
this.quality,
|
||||
this.size,
|
||||
});
|
||||
|
||||
int? quality;
|
||||
int? size;
|
||||
|
||||
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
|
||||
quality: json["quality"],
|
||||
size: json["size"],
|
||||
);
|
||||
}
|
||||
|
||||
class Rights {
|
||||
Rights({
|
||||
this.bp,
|
||||
this.elec,
|
||||
this.download,
|
||||
this.movie,
|
||||
this.pay,
|
||||
this.ugcPay,
|
||||
this.hd5,
|
||||
this.noReprint,
|
||||
this.autoplay,
|
||||
this.noBackground,
|
||||
});
|
||||
|
||||
int? bp;
|
||||
int? elec;
|
||||
int? download;
|
||||
int? movie;
|
||||
int? pay;
|
||||
int? ugcPay;
|
||||
int? hd5;
|
||||
int? noReprint;
|
||||
int? autoplay;
|
||||
int? noBackground;
|
||||
|
||||
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
|
||||
bp: json["bp"],
|
||||
elec: json["elec"],
|
||||
download: json["download"],
|
||||
movie: json["movie"],
|
||||
pay: json["pay"],
|
||||
ugcPay: json["ugc_pay"],
|
||||
hd5: json["hd5"],
|
||||
noReprint: json["no_reprint"],
|
||||
autoplay: json["autoplay"],
|
||||
noBackground: json["no_background"],
|
||||
);
|
||||
}
|
||||
|
||||
class Upper {
|
||||
Upper({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
this.followed,
|
||||
this.fans,
|
||||
this.vipType,
|
||||
this.vipStatue,
|
||||
this.vipDueDate,
|
||||
this.vipPayType,
|
||||
this.officialRole,
|
||||
this.officialTitle,
|
||||
this.officialDesc,
|
||||
this.displayName,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
int? followed;
|
||||
int? fans;
|
||||
int? vipType;
|
||||
int? vipStatue;
|
||||
int? vipDueDate;
|
||||
int? vipPayType;
|
||||
int? officialRole;
|
||||
String? officialTitle;
|
||||
String? officialDesc;
|
||||
String? displayName;
|
||||
|
||||
factory Upper.fromJson(Map<String, dynamic> json) => Upper(
|
||||
mid: json["mid"],
|
||||
name: json["name"],
|
||||
face: json["face"],
|
||||
followed: json["followed"],
|
||||
fans: json["fans"],
|
||||
vipType: json["vip_type"],
|
||||
vipStatue: json["vip_statue"],
|
||||
vipDueDate: json["vip_due_date"],
|
||||
vipPayType: json["vip_pay_type"],
|
||||
officialRole: json["official_role"],
|
||||
officialTitle: json["official_title"],
|
||||
officialDesc: json["official_desc"],
|
||||
displayName: json["display_name"],
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/models/video/play/quality.dart';
|
||||
import 'package:PiliPlus/models/video/play/quality.dart';
|
||||
|
||||
class PlayUrlModel {
|
||||
PlayUrlModel({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/models/video/reply/item.dart';
|
||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
||||
|
||||
import 'config.dart';
|
||||
import 'page.dart';
|
||||
|
||||
@@ -49,6 +49,7 @@ class VideoDetailData {
|
||||
Map<String, int>? rights;
|
||||
Owner? owner;
|
||||
Stat? stat;
|
||||
String? argueMsg;
|
||||
String? videoDynamic;
|
||||
int? cid;
|
||||
Dimension? dimension;
|
||||
@@ -68,6 +69,8 @@ class VideoDetailData {
|
||||
bool? needJumpBv;
|
||||
String? epId;
|
||||
List<Staff>? staff;
|
||||
late bool isPageReversed;
|
||||
late bool isSeasonReversed;
|
||||
|
||||
VideoDetailData({
|
||||
this.bvid,
|
||||
@@ -87,6 +90,7 @@ class VideoDetailData {
|
||||
this.rights,
|
||||
this.owner,
|
||||
this.stat,
|
||||
this.argueMsg,
|
||||
this.videoDynamic,
|
||||
this.cid,
|
||||
this.dimension,
|
||||
@@ -105,6 +109,8 @@ class VideoDetailData {
|
||||
this.needJumpBv,
|
||||
this.epId,
|
||||
this.staff,
|
||||
this.isPageReversed = false,
|
||||
this.isSeasonReversed = false,
|
||||
});
|
||||
|
||||
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
||||
@@ -128,6 +134,7 @@ class VideoDetailData {
|
||||
Map.from(json["rights"]!).map((k, v) => MapEntry<String, int>(k, v));
|
||||
owner = json["owner"] == null ? null : Owner.fromJson(json["owner"]);
|
||||
stat = json["stat"] == null ? null : Stat.fromJson(json["stat"]);
|
||||
argueMsg = json['argue_info']?['argue_msg'];
|
||||
videoDynamic = json["dynamic"];
|
||||
cid = json["cid"];
|
||||
dimension = json["dimension"] == null
|
||||
@@ -160,6 +167,8 @@ class VideoDetailData {
|
||||
if (json['redirect_url'] != null) {
|
||||
epId = resolveEpId(json['redirect_url']);
|
||||
}
|
||||
isPageReversed = false;
|
||||
isSeasonReversed = false;
|
||||
}
|
||||
|
||||
String resolveEpId(url) {
|
||||
@@ -485,7 +494,6 @@ class Stat {
|
||||
int? like;
|
||||
int? dislike;
|
||||
String? evaluation;
|
||||
String? argueMsg;
|
||||
|
||||
Stat({
|
||||
this.aid,
|
||||
@@ -500,7 +508,6 @@ class Stat {
|
||||
this.like,
|
||||
this.dislike,
|
||||
this.evaluation,
|
||||
this.argueMsg,
|
||||
});
|
||||
|
||||
fromRawJson(String str) => Stat.fromJson(json.decode(str));
|
||||
@@ -520,7 +527,6 @@ class Stat {
|
||||
like = json["like"];
|
||||
dislike = json["dislike"];
|
||||
evaluation = json["evaluation"];
|
||||
argueMsg = json["argue_msg"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -538,7 +544,6 @@ class Stat {
|
||||
data["like"] = like;
|
||||
data["dislike"] = dislike;
|
||||
data["evaluation"] = evaluation;
|
||||
data["argue_msg"] = argueMsg;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user