mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-13 22:28:08 +08:00
Compare commits
192 Commits
release11
...
release18-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96ea6d60e3 | ||
|
|
f9c365011b | ||
|
|
8ce5026778 | ||
|
|
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 |
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
|
||||
@@ -31,9 +33,27 @@ jobs:
|
||||
channel: stable
|
||||
flutter-version-file: pubspec.yaml
|
||||
|
||||
- name: 修复3.24的stable显示中文不正确问题 // from orz12
|
||||
run: |
|
||||
version=$(grep -m 1 'flutter:' pubspec.yaml | awk '{print $2}')
|
||||
if [ "$(echo "$version < 3.27.0" | awk '{print ($1 < $2)}')" -eq 1 ]; then
|
||||
cd $FLUTTER_ROOT
|
||||
git config --global user.name "orz12"
|
||||
git config --global user.email "orz12@test.com"
|
||||
git cherry-pick d4124bd --strategy-option theirs
|
||||
# flutter precache
|
||||
flutter --version
|
||||
cd -
|
||||
fi
|
||||
|
||||
- name: 下载项目依赖
|
||||
run: flutter pub get
|
||||
|
||||
- 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,7 +65,10 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: flutter build apk
|
||||
run: flutter build apk --release --split-per-abi
|
||||
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 }}"
|
||||
9
.github/workflows/ios.yml
vendored
9
.github/workflows/ios.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -23,11 +24,15 @@ jobs:
|
||||
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"
|
||||
|
||||
21
README.md
21
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>
|
||||
|
||||
@@ -186,9 +186,10 @@
|
||||
|
||||
## 声明
|
||||
|
||||
此项目(PiliPalaX)是个人为了兴趣而开发, 仅用于学习和测试,请于下载后24小时内删除。
|
||||
此项目(PiliPlus)是个人为了兴趣而开发, 仅用于学习和测试,请于下载后24小时内删除。
|
||||
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||
在此致敬原作者:[guozhigq/pilipala](https://github.com/guozhigq/pilipala)
|
||||
在此致敬上游作者:[orz12/PiliPalaX](https://github.com/orz12/PiliPalaX)
|
||||
本仓库做了更激进的修改,感谢原作者的开源精神。
|
||||
|
||||
感谢使用
|
||||
|
||||
@@ -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,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>
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/models/dynamics/article_content_model.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';
|
||||
@@ -8,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)
|
||||
@@ -59,10 +60,17 @@ Widget articleContent({
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList: imgList,
|
||||
);
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/utils/extension.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(
|
||||
@@ -49,9 +50,13 @@ Widget htmlRender({
|
||||
tag: imgUrl,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [imgUrl],
|
||||
);
|
||||
if (callback != null) {
|
||||
callback([imgUrl], 0);
|
||||
} else {
|
||||
context.imageView(
|
||||
imgList: [imgUrl],
|
||||
);
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: isEmote ? 22 : constrainedWidth,
|
||||
@@ -69,7 +74,7 @@ Widget htmlRender({
|
||||
],
|
||||
style: {
|
||||
'html': Style(
|
||||
fontSize: FontSize(17),
|
||||
fontSize: FontSize(16),
|
||||
lineHeight: LineHeight.percent(160),
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
@@ -105,7 +110,7 @@ Widget htmlRender({
|
||||
margin: Margins.only(bottom: 8),
|
||||
),
|
||||
'h3,h4,h5': Style(
|
||||
fontSize: FontSize(17),
|
||||
fontSize: FontSize(16),
|
||||
fontWeight: FontWeight.bold,
|
||||
margin: Margins.only(bottom: 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/utils/extension.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 {
|
||||
@@ -25,10 +25,11 @@ class ImageModel {
|
||||
|
||||
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) {
|
||||
@@ -59,12 +60,16 @@ Widget imageview(
|
||||
tag: picArr[index].url,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onViewImage?.call();
|
||||
context.imageView(
|
||||
initialPage: index,
|
||||
imgList: picArr.map((item) => item.url).toList(),
|
||||
onDismissed: onDismissed,
|
||||
);
|
||||
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(
|
||||
alignment: Alignment.center,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/utils/download.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
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';
|
||||
@@ -41,10 +42,13 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
|
||||
this.minScale = 1.0,
|
||||
this.onPageChanged,
|
||||
this.onDismissed,
|
||||
this.setStatusBar,
|
||||
});
|
||||
|
||||
final bool? setStatusBar;
|
||||
|
||||
/// The sources to show.
|
||||
final List<T> sources;
|
||||
final List<String> sources;
|
||||
|
||||
/// The index of the first source in [sources] to show.
|
||||
final int initIndex;
|
||||
@@ -107,7 +111,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
});
|
||||
|
||||
currentIndex = widget.initIndex;
|
||||
setStatusBar();
|
||||
if (widget.setStatusBar != false) {
|
||||
setStatusBar();
|
||||
}
|
||||
}
|
||||
|
||||
setStatusBar() async {
|
||||
@@ -124,8 +130,10 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
_pageController?.dispose();
|
||||
_animationController.removeListener(() {});
|
||||
_animationController.dispose();
|
||||
if (Platform.isIOS || Platform.isAndroid) {
|
||||
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
|
||||
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();
|
||||
@@ -210,8 +218,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
|
||||
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
|
||||
? '${widget.sources[index]}@${_quality}q.webp'
|
||||
: widget.sources[index];
|
||||
? '${widget.sources[index]}@${_quality}q.webp'.http2https
|
||||
: widget.sources[index].http2https;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -334,7 +342,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onTap: () {
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
widget.sources[currentIndex!],
|
||||
widget.sources as List<String>,
|
||||
);
|
||||
},
|
||||
child: const Text("保存全部图片"),
|
||||
|
||||
@@ -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;
|
||||
@@ -33,7 +39,11 @@ class ListSheetContent extends StatefulWidget {
|
||||
final int? aid;
|
||||
final int currentCid;
|
||||
final Function changeFucCall;
|
||||
final VoidCallback onClose;
|
||||
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();
|
||||
@@ -137,7 +188,8 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
}
|
||||
}
|
||||
SmartDialog.showToast('切换到:$title');
|
||||
widget.onClose();
|
||||
widget.onClose?.call();
|
||||
currentIndex = index;
|
||||
widget.changeFucCall(
|
||||
episode is bangumi.EpisodeItem ? episode.epId : null,
|
||||
episode.runtimeType.toString() == "EpisodeItem"
|
||||
@@ -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: (context, snapshot) => snapshot.hasData
|
||||
? _mediumButton(
|
||||
? 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: (context, 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,
|
||||
|
||||
@@ -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,
|
||||
@@ -83,8 +79,8 @@ 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,
|
||||
|
||||
@@ -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,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("撤销"),
|
||||
),
|
||||
],
|
||||
@@ -254,10 +273,10 @@ class VideoCustomActions {
|
||||
);
|
||||
}),
|
||||
VideoCustomAction(
|
||||
"${MineController.anonymity ? '退出' : '进入'}无痕模式",
|
||||
"${MineController.anonymity.value ? '退出' : '进入'}无痕模式",
|
||||
'anonymity',
|
||||
Icon(
|
||||
MineController.anonymity
|
||||
MineController.anonymity.value
|
||||
? MdiIcons.incognitoOff
|
||||
: MdiIcons.incognito,
|
||||
size: 16,
|
||||
@@ -270,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
|
||||
@@ -295,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,226 +0,0 @@
|
||||
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/http/search.dart';
|
||||
import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/models/video/later.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
|
||||
class MediaListPanel extends StatefulWidget {
|
||||
const MediaListPanel({
|
||||
this.sheetHeight,
|
||||
required this.mediaList,
|
||||
this.changeMediaList,
|
||||
this.panelTitle,
|
||||
this.bvid,
|
||||
this.mediaId,
|
||||
this.hasMore = false,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final double? sheetHeight;
|
||||
final List<MediaVideoItemModel> mediaList;
|
||||
final Function? changeMediaList;
|
||||
final String? panelTitle;
|
||||
final String? bvid;
|
||||
final int? mediaId;
|
||||
final bool hasMore;
|
||||
|
||||
@override
|
||||
State<MediaListPanel> createState() => _MediaListPanelState();
|
||||
}
|
||||
|
||||
class _MediaListPanelState extends State<MediaListPanel> {
|
||||
RxList<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[].obs;
|
||||
bool _isEnd = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mediaList.value = widget.mediaList;
|
||||
_isEnd = widget.hasMore.not;
|
||||
}
|
||||
|
||||
void loadMore() async {
|
||||
var res = await UserHttp.getMediaList(
|
||||
type: 3,
|
||||
bizId: widget.mediaId ?? -1,
|
||||
ps: 20,
|
||||
oid: mediaList.last.id,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (res['data'].isNotEmpty) {
|
||||
mediaList.addAll(res['data']);
|
||||
} else {
|
||||
_isEnd = true;
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
toolbarHeight: 45,
|
||||
automaticallyImplyLeading: false,
|
||||
titleSpacing: 16,
|
||||
title: Text(widget.panelTitle ?? '稍后再看'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
onPressed: Get.back,
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Obx(
|
||||
() => ListView.builder(
|
||||
itemCount: mediaList.length,
|
||||
itemBuilder: ((context, index) {
|
||||
var item = mediaList[index];
|
||||
if (index == widget.mediaList.length - 1 && _isEnd.not) {
|
||||
loadMore();
|
||||
}
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
String bvid = item.bvid!;
|
||||
int? aid = item.id;
|
||||
String cover = item.cover ?? '';
|
||||
final int cid =
|
||||
await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
widget.changeMediaList?.call(bvid, cid, aid, cover);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
const double width = 120;
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
height: width / StyleString.aspectRatio,
|
||||
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: item.cover ?? '',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
PBadge(
|
||||
text: Utils.timeFormat(
|
||||
item.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
10, 0, 6, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title as String,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: item.bvid == widget.bvid
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: null,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
item.upper?.name as String,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
children: [
|
||||
statView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
view: item.cntInfo!['play']
|
||||
as int,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
statDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
danmu: item.cntInfo!['danmaku']
|
||||
as int,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import 'index.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/user/black.dart';
|
||||
import 'index.dart';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,9 +8,7 @@ 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';
|
||||
@@ -25,8 +22,6 @@ class Request {
|
||||
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;
|
||||
@@ -35,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,
|
||||
@@ -57,7 +51,7 @@ class Request {
|
||||
isHttpOnly: item.httpOnly,
|
||||
);
|
||||
}
|
||||
final userInfo = userInfoCache.get('userInfoCache');
|
||||
final userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null && userInfo.mid != null) {
|
||||
final List<Cookie> cookie2 = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||
@@ -146,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);
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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';
|
||||
@@ -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']}');
|
||||
@@ -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']);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -99,7 +97,7 @@ class SearchHttp {
|
||||
if (pubEnd != null) 'pubtime_end_s': pubEnd,
|
||||
};
|
||||
var res = await Request().get(Api.searchByType, queryParameters: reqData);
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['code'] is int && res.data['code'] == 0) {
|
||||
dynamic data;
|
||||
try {
|
||||
switch (searchType) {
|
||||
@@ -139,7 +137,7 @@ 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;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/video/later.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 'package:html/parser.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/model_hot_video_item.dart';
|
||||
import '../models/user/fav_detail.dart';
|
||||
@@ -51,7 +48,7 @@ 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, queryParameters: {
|
||||
'pn': pn,
|
||||
@@ -377,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, queryParameters: {
|
||||
'up_mid': mid,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
'platform': 'web',
|
||||
});
|
||||
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 {
|
||||
'status': true,
|
||||
'data': SubFolderModelData.fromJson(res.data['data'])
|
||||
};
|
||||
return LoadingState.success(
|
||||
SubFolderModelData.fromJson(res.data['data']).list);
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,10 +511,15 @@ class UserHttp {
|
||||
|
||||
// 稍后再看列表
|
||||
static Future getMediaList({
|
||||
required int type,
|
||||
required dynamic type,
|
||||
required int bizId,
|
||||
required int ps,
|
||||
int? oid,
|
||||
dynamic oid,
|
||||
int? otype,
|
||||
bool withCurrent = false,
|
||||
bool desc = true,
|
||||
dynamic sortField = 1,
|
||||
bool direction = false,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.mediaList,
|
||||
@@ -524,14 +527,14 @@ class UserHttp {
|
||||
'mobi_app': 'web',
|
||||
'type': type,
|
||||
'biz_id': bizId,
|
||||
'oid': oid ?? '',
|
||||
'otype': 2,
|
||||
if (oid != null) 'oid': oid,
|
||||
if (otype != null) 'otype': otype, // video:2 // bangumi: 24
|
||||
'ps': ps,
|
||||
'direction': false,
|
||||
'desc': true,
|
||||
'sort_field': 1,
|
||||
'direction': direction,
|
||||
'desc': desc,
|
||||
'sort_field': sortField,
|
||||
'tid': 0,
|
||||
'with_current': false,
|
||||
'with_current': withCurrent,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@@ -550,30 +553,30 @@ class UserHttp {
|
||||
}
|
||||
|
||||
// 解析收藏夹视频
|
||||
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()
|
||||
};
|
||||
}
|
||||
// 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,11 +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/foundation.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/common/reply_type.dart';
|
||||
import '../models/home/rcmd/result.dart';
|
||||
@@ -30,11 +29,8 @@ 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(
|
||||
@@ -76,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'] ??
|
||||
'')
|
||||
: '',
|
||||
@@ -172,7 +168,9 @@ class VideoHttp {
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title'])) {
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
@@ -199,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;
|
||||
}
|
||||
|
||||
@@ -229,12 +231,24 @@ class VideoHttp {
|
||||
'web_location': 1550101,
|
||||
});
|
||||
|
||||
late final isLogin = GStorage.isLogin;
|
||||
|
||||
try {
|
||||
var res = await Request().get(Api.videoUrl, queryParameters: 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 {
|
||||
@@ -743,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(),
|
||||
});
|
||||
}
|
||||
@@ -890,6 +922,8 @@ class VideoHttp {
|
||||
'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']};
|
||||
@@ -980,7 +1014,9 @@ class VideoHttp {
|
||||
List<int> blackMidsList = GStorage.blackMidsList;
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid']) &&
|
||||
!RecommendFilter.filterTitle(i['title'])) {
|
||||
!RecommendFilter.filterTitle(i['title']) &&
|
||||
!RecommendFilter.filterLikeRatio(
|
||||
i['stat']['like'], i['stat']['view'])) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -10,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';
|
||||
|
||||
@@ -51,6 +52,9 @@ void main() async {
|
||||
],
|
||||
);
|
||||
}
|
||||
if (GStorage.badCertificateCallback) {
|
||||
HttpOverrides.global = _CustomHttpOverrides();
|
||||
}
|
||||
await setupServiceLocator();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
@@ -58,6 +62,9 @@ void main() async {
|
||||
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(),
|
||||
[
|
||||
@@ -65,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(
|
||||
@@ -97,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)]
|
||||
@@ -161,7 +181,7 @@ class MyApp extends StatelessWidget {
|
||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||
return GetMaterialApp(
|
||||
// showSemanticsDebugger: true,
|
||||
title: 'PiliPalaX',
|
||||
title: 'PiliPlus',
|
||||
theme: _getThemeData(
|
||||
colorScheme: lightColorScheme,
|
||||
isDynamic: lightDynamic != null && isDynamicColor,
|
||||
@@ -258,3 +278,12 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomHttpOverrides extends HttpOverrides {
|
||||
@override
|
||||
HttpClient createHttpClient(SecurityContext? context) {
|
||||
return super.createHttpClient(context)
|
||||
..badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ class MediaVideoItemModel {
|
||||
int? type;
|
||||
Upper? upper;
|
||||
String? link;
|
||||
String? bvId;
|
||||
String? shortLink;
|
||||
Rights? rights;
|
||||
dynamic elecInfo;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/http/constants.dart';
|
||||
import 'package:PiliPalaX/services/loggeer.dart';
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/services/loggeer.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:PiliPalaX/models/github/latest.dart';
|
||||
import 'package:PiliPalaX/pages/setting/controller.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/models/github/latest.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import '../../utils/cache_manage.dart';
|
||||
|
||||
class AboutPage extends StatefulWidget {
|
||||
@@ -26,7 +25,7 @@ class AboutPage extends StatefulWidget {
|
||||
class _AboutPageState extends State<AboutPage> {
|
||||
final AboutController _aboutController = Get.put(AboutController());
|
||||
static const String _sourceCodeUrl =
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPalaX';
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus';
|
||||
static const String _originSourceCodeUrl =
|
||||
'https://github.com/guozhigq/pilipala';
|
||||
static const String _upstreamUrl = 'https://github.com/orz12/PiliPalaX';
|
||||
@@ -60,7 +59,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
)),
|
||||
),
|
||||
ListTile(
|
||||
title: Text('PiliPalaX',
|
||||
title: Text('PiliPlus',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
@@ -84,13 +83,29 @@ class _AboutPageState extends State<AboutPage> {
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
// onTap: () => _aboutController.tapOnVersion(),
|
||||
onTap: () => Utils.checkUpdate(false),
|
||||
onLongPress: () =>
|
||||
Utils.copyText(_aboutController.currentVersion.value),
|
||||
title: const Text('当前版本'),
|
||||
leading: const Icon(Icons.commit_outlined),
|
||||
trailing: Text(_aboutController.currentVersion.value,
|
||||
style: subTitleStyle),
|
||||
trailing: Text(
|
||||
_aboutController.currentVersion.value,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'''
|
||||
Build Time: ${BuildConfig.buildTime}
|
||||
Commit Hash: ${BuildConfig.commitHash}''',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
leading: const Icon(Icons.info_outline),
|
||||
onTap: () => Utils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/commit/${BuildConfig.commitHash}'),
|
||||
onLongPress: () => Utils.copyText(BuildConfig.commitHash),
|
||||
),
|
||||
// Obx(
|
||||
// () => ListTile(
|
||||
// onTap: () => _aboutController.onUpdate(),
|
||||
@@ -173,9 +188,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
onTap: () {
|
||||
Get.toNamed('/logs');
|
||||
},
|
||||
onLongPress: () {
|
||||
clearLogs();
|
||||
},
|
||||
onLongPress: clearLogs,
|
||||
leading: const Icon(Icons.bug_report_outlined),
|
||||
title: const Text('错误日志'),
|
||||
trailing: Icon(Icons.arrow_forward, size: 16, color: outline),
|
||||
@@ -201,11 +214,13 @@ class _AboutPageState extends State<AboutPage> {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
title: const Text('导入/导出登录信息'),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('导出'),
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
dynamic accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {});
|
||||
dynamic cookies = (await CookieManager(PersistCookieJar(
|
||||
@@ -226,14 +241,14 @@ class _AboutPageState extends State<AboutPage> {
|
||||
'cookies': cookies,
|
||||
});
|
||||
Utils.copyText('$res');
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
content: SelectableText('$res'),
|
||||
),
|
||||
);
|
||||
}
|
||||
// if (context.mounted) {
|
||||
// showDialog(
|
||||
// context: context,
|
||||
// builder: (context) => AlertDialog(
|
||||
// content: SelectableText('$res'),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
@@ -311,8 +326,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
onTap: () async {
|
||||
Get.back();
|
||||
String data = await GStorage.exportAllSettings();
|
||||
Clipboard.setData(ClipboardData(text: data));
|
||||
SmartDialog.showToast('已复制到剪贴板');
|
||||
Utils.copyText(data);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
@@ -427,8 +441,6 @@ class _AboutPageState extends State<AboutPage> {
|
||||
}
|
||||
|
||||
class AboutController extends GetxController {
|
||||
Box setting = GStorage.setting;
|
||||
final SettingController settingController = Get.put(SettingController());
|
||||
RxString currentVersion = ''.obs;
|
||||
RxString remoteVersion = ''.obs;
|
||||
LatestDataModel? remoteAppInfo;
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/http/bangumi.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/http/bangumi.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class BangumiController extends CommonController {
|
||||
bool isLoadingMore = true;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
RxBool isLogin = false.obs;
|
||||
late int mid;
|
||||
dynamic userInfo;
|
||||
|
||||
@@ -17,11 +15,11 @@ class BangumiController extends CommonController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null) {
|
||||
mid = userInfo.mid;
|
||||
}
|
||||
userLogin.value = userInfo != null;
|
||||
isLogin.value = userInfo != null;
|
||||
|
||||
queryData();
|
||||
queryBangumiFollow();
|
||||
@@ -29,7 +27,7 @@ class BangumiController extends CommonController {
|
||||
|
||||
// 我的订阅
|
||||
Future queryBangumiFollow() async {
|
||||
userInfo = userInfo ?? userInfoCache.get('userInfoCache');
|
||||
userInfo = userInfo ?? GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null) {
|
||||
followState.value = await BangumiHttp.bangumiFollow(mid: userInfo.mid);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/http/init.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/http/constants.dart';
|
||||
import 'package:PiliPalaX/http/search.dart';
|
||||
import 'package:PiliPalaX/http/video.dart';
|
||||
import 'package:PiliPalaX/models/bangumi/info.dart';
|
||||
import 'package:PiliPalaX/models/user/fav_folder.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply/index.dart';
|
||||
import 'package:PiliPalaX/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart';
|
||||
import 'package:PiliPlus/models/user/fav_folder.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/index.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply/index.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:html/parser.dart' as html_parser;
|
||||
import 'package:html/dom.dart' as dom;
|
||||
@@ -53,8 +51,7 @@ class BangumiIntroController extends CommonController {
|
||||
// 是否收藏
|
||||
RxBool hasFav = false.obs;
|
||||
dynamic videoTags;
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
bool userLogin = false;
|
||||
bool isLogin = false;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
List addMediaIdsNew = [];
|
||||
List delMediaIdsNew = [];
|
||||
@@ -84,10 +81,10 @@ class BangumiIntroController extends CommonController {
|
||||
// videoItem!['owner'] = args.owner;
|
||||
}
|
||||
}
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin = userInfo != null;
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
isLogin = userInfo != null;
|
||||
|
||||
if (userLogin && epId != null) {
|
||||
if (isLogin && epId != null) {
|
||||
// // 获取点赞状态
|
||||
// queryHasLikeVideo();
|
||||
// // 获取投币状态
|
||||
@@ -99,7 +96,7 @@ class BangumiIntroController extends CommonController {
|
||||
|
||||
queryData();
|
||||
|
||||
if (userLogin && seasonId != null) {
|
||||
if (isLogin && seasonId != null) {
|
||||
queryIsFollowed();
|
||||
}
|
||||
}
|
||||
@@ -327,8 +324,7 @@ class BangumiIntroController extends CommonController {
|
||||
),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
Clipboard.setData(ClipboardData(text: videoUrl));
|
||||
SmartDialog.showToast('已复制');
|
||||
Utils.copyText(videoUrl);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
@@ -376,14 +372,23 @@ class BangumiIntroController extends CommonController {
|
||||
// 修改分P或番剧分集
|
||||
Future changeSeasonOrbangu(epId, bvid, cid, aid, cover) async {
|
||||
// 重新获取视频资源
|
||||
VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
this.epId = epId;
|
||||
this.bvid = bvid;
|
||||
videoDetailCtr.bvid = bvid;
|
||||
videoDetailCtr.cid.value = cid;
|
||||
videoDetailCtr.danmakuCid.value = cid;
|
||||
videoDetailCtr.queryVideoUrl();
|
||||
|
||||
final videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
..plPlayerController.pause()
|
||||
..makeHeartBeat()
|
||||
..playedTime = null
|
||||
..videoUrl = null
|
||||
..audioUrl = null
|
||||
..vttSubtitlesIndex = null
|
||||
..savedDanmaku = null
|
||||
..epId = epId
|
||||
..bvid = bvid
|
||||
..cid.value = cid
|
||||
..danmakuCid.value = cid
|
||||
..queryVideoUrl();
|
||||
if (cover is String && cover.isNotEmpty) {
|
||||
videoDetailCtr.videoItem['pic'] = cover;
|
||||
}
|
||||
@@ -399,7 +404,7 @@ class BangumiIntroController extends CommonController {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (userLogin) {
|
||||
if (isLogin) {
|
||||
queryBangumiLikeCoinFav();
|
||||
}
|
||||
}
|
||||
@@ -458,9 +463,9 @@ class BangumiIntroController extends CommonController {
|
||||
int currentIndex =
|
||||
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||
int prevIndex = currentIndex - 1;
|
||||
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||
PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||
if (prevIndex < 0) {
|
||||
if (platRepeat == PlayRepeat.listCycle) {
|
||||
if (playRepeat == PlayRepeat.listCycle) {
|
||||
prevIndex = episodes.length - 1;
|
||||
} else {
|
||||
return false;
|
||||
@@ -480,12 +485,12 @@ class BangumiIntroController extends CommonController {
|
||||
late List episodes;
|
||||
VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||
PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||
|
||||
if ((loadingState.value as Success).response.episodes != null) {
|
||||
episodes = (loadingState.value as Success).response.episodes!;
|
||||
} else {
|
||||
if (platRepeat == PlayRepeat.autoPlayRelated) {
|
||||
if (playRepeat == PlayRepeat.autoPlayRelated) {
|
||||
return playRelated();
|
||||
}
|
||||
}
|
||||
@@ -493,10 +498,10 @@ class BangumiIntroController extends CommonController {
|
||||
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||
int nextIndex = currentIndex + 1;
|
||||
// 列表循环
|
||||
if (nextIndex == episodes.length - 1) {
|
||||
if (platRepeat == PlayRepeat.listCycle) {
|
||||
if (nextIndex >= episodes.length) {
|
||||
if (playRepeat == PlayRepeat.listCycle) {
|
||||
nextIndex = 0;
|
||||
} else if (platRepeat == PlayRepeat.autoPlayRelated) {
|
||||
} else if (playRepeat == PlayRepeat.autoPlayRelated) {
|
||||
return playRelated();
|
||||
} else {
|
||||
return false;
|
||||
@@ -554,6 +559,10 @@ class BangumiIntroController extends CommonController {
|
||||
scriptContent['props']['pageProps']['followState']['isFollowed'];
|
||||
followStatus.value =
|
||||
scriptContent['props']['pageProps']['followState']['followStatus'];
|
||||
// int progress = scriptContent['props']['pageProps']['dehydratedState']
|
||||
// ['queries'][0]['state']['data']['result']
|
||||
// ['play_view_business_info']['user_status']['watch_progress']
|
||||
// ['current_watch_progress'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPalaX/models/bangumi/info.dart';
|
||||
import 'package:PiliPalaX/pages/bangumi/widgets/bangumi_panel.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/action_item.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/action_row_item.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/widgets/bangumi_panel.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/index.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/action_item.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/action_row_item.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
|
||||
import '../../../utils/utils.dart';
|
||||
import 'controller.dart';
|
||||
@@ -43,6 +46,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
late BangumiIntroController bangumiIntroController;
|
||||
late VideoDetailController videoDetailCtr;
|
||||
late int cid;
|
||||
StreamSubscription? _listener;
|
||||
|
||||
// 添加页面缓存
|
||||
@override
|
||||
@@ -55,13 +59,19 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
bangumiIntroController =
|
||||
Get.put(BangumiIntroController(), tag: widget.heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: widget.heroTag);
|
||||
videoDetailCtr.cid.listen((int p0) {
|
||||
_listener = videoDetailCtr.cid.listen((int p0) {
|
||||
cid = p0;
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
@@ -72,7 +82,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
return switch (loadingState) {
|
||||
Loading() => BangumiInfo(
|
||||
heroTag: widget.heroTag,
|
||||
loadingStatus: true,
|
||||
isLoading: true,
|
||||
bangumiDetail: null,
|
||||
cid: cid,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
@@ -80,7 +90,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
),
|
||||
Success() => BangumiInfo(
|
||||
heroTag: widget.heroTag,
|
||||
loadingStatus: false,
|
||||
isLoading: false,
|
||||
bangumiDetail: loadingState.response,
|
||||
cid: cid,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
@@ -101,7 +111,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
class BangumiInfo extends StatefulWidget {
|
||||
const BangumiInfo({
|
||||
super.key,
|
||||
this.loadingStatus = false,
|
||||
this.isLoading = false,
|
||||
this.bangumiDetail,
|
||||
this.cid,
|
||||
required this.showEpisodes,
|
||||
@@ -109,7 +119,7 @@ class BangumiInfo extends StatefulWidget {
|
||||
required this.heroTag,
|
||||
});
|
||||
|
||||
final bool loadingStatus;
|
||||
final bool isLoading;
|
||||
final BangumiInfoModel? bangumiDetail;
|
||||
final int? cid;
|
||||
final Function showEpisodes;
|
||||
@@ -127,19 +137,19 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
late final BangumiInfoModel? bangumiItem;
|
||||
int? cid;
|
||||
bool isProcessing = false;
|
||||
void Function()? handleState(Future Function() action) {
|
||||
return isProcessing
|
||||
? null
|
||||
: () async {
|
||||
setState(() => isProcessing = true);
|
||||
await action();
|
||||
setState(() => isProcessing = false);
|
||||
};
|
||||
void handleState(Future Function() action) async {
|
||||
if (isProcessing.not) {
|
||||
isProcessing = true;
|
||||
await action();
|
||||
isProcessing = false;
|
||||
}
|
||||
}
|
||||
|
||||
late final _coinKey = GlobalKey<ActionItemState>();
|
||||
late final _favKey = GlobalKey<ActionItemState>();
|
||||
|
||||
StreamSubscription? _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -149,13 +159,19 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
bangumiItem = bangumiIntroController.bangumiItem;
|
||||
cid = widget.cid!;
|
||||
debugPrint('cid: $cid');
|
||||
videoDetailCtr.cid.listen((p0) {
|
||||
_listener = videoDetailCtr.cid.listen((p0) {
|
||||
cid = p0;
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 收藏
|
||||
showFavBottomSheet() {
|
||||
if (bangumiIntroController.userInfo == null) {
|
||||
@@ -203,11 +219,13 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
MediaQuery.of(context).orientation == Orientation.landscape;
|
||||
return SliverPadding(
|
||||
padding: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: isLandscape ? 10 : 20),
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: StyleString.safeSpace,
|
||||
bottom: StyleString.safeSpace + MediaQuery.paddingOf(context).bottom,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || bangumiItem != null
|
||||
child: !widget.isLoading || bangumiItem != null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -216,19 +234,37 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: isLandscape ? 160 : 105,
|
||||
height: isLandscape ? 105 : 160,
|
||||
src: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
semanticsLabel: '封面',
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
videoDetailCtr.onViewImage();
|
||||
context.imageView(
|
||||
imgList: [
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!
|
||||
],
|
||||
onDismissed: videoDetailCtr.onDismissed,
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: !widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
child: NetworkImgLayer(
|
||||
width: isLandscape ? 115 / 0.75 : 115,
|
||||
height: isLandscape ? 115 : 115 / 0.75,
|
||||
src: !widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
semanticsLabel: '封面',
|
||||
),
|
||||
),
|
||||
),
|
||||
if (bangumiItem != null &&
|
||||
bangumiItem!.rating != null)
|
||||
PBadge(
|
||||
text:
|
||||
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
|
||||
'评分 ${!widget.isLoading ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
|
||||
top: null,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
@@ -238,24 +274,24 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
child: GestureDetector(
|
||||
onTap: showIntroDetail,
|
||||
child: SizedBox(
|
||||
height: isLandscape ? 103 : 158,
|
||||
height: isLandscape ? 115 : 115 / 0.75,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.title!
|
||||
: bangumiItem!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -265,8 +301,12 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Obx(
|
||||
() => FilledButton.tonal(
|
||||
style: FilledButton.styleFrom(
|
||||
tapTargetSize:
|
||||
MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20, vertical: 10),
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
visualDensity: const VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -2,
|
||||
@@ -315,7 +355,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
statView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
view: !widget.loadingStatus
|
||||
view: !widget.isLoading
|
||||
? widget.bangumiDetail!.stat!['views']
|
||||
: bangumiItem!.stat!['views'],
|
||||
size: 'medium',
|
||||
@@ -324,7 +364,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
statDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
danmu: !widget.loadingStatus
|
||||
danmu: !widget.isLoading
|
||||
? widget
|
||||
.bangumiDetail!.stat!['danmakus']
|
||||
: bangumiItem!.stat!['danmakus'],
|
||||
@@ -357,7 +397,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
t: t),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||
'简介:${!widget.isLoading ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||
maxLines: isLandscape ? 2 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
@@ -386,7 +426,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
// 点赞收藏转发 布局样式2
|
||||
actionGrid(context, bangumiIntroController),
|
||||
// 番剧分p
|
||||
if ((!widget.loadingStatus &&
|
||||
if ((!widget.isLoading &&
|
||||
widget.bangumiDetail!.episodes!.isNotEmpty) ||
|
||||
bangumiItem != null &&
|
||||
bangumiItem!.episodes!.isNotEmpty) ...[
|
||||
@@ -431,12 +471,13 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: handleState(bangumiIntroController.actionLikeVideo),
|
||||
onTap: () =>
|
||||
handleState(bangumiIntroController.actionLikeVideo),
|
||||
onLongPress: bangumiIntroController.actionOneThree,
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
loadingStatus: false,
|
||||
semanticsLabel: '点赞',
|
||||
text: !widget.loadingStatus
|
||||
text: !widget.isLoading
|
||||
? Utils.numFormat(widget.bangumiDetail!.stat!['likes']!)
|
||||
: Utils.numFormat(
|
||||
bangumiItem!.stat!['likes']!,
|
||||
@@ -461,11 +502,12 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
key: _coinKey,
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(bangumiIntroController.actionCoinVideo),
|
||||
onTap: () =>
|
||||
handleState(bangumiIntroController.actionCoinVideo),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
loadingStatus: false,
|
||||
semanticsLabel: '投币',
|
||||
text: !widget.loadingStatus
|
||||
text: !widget.isLoading
|
||||
? Utils.numFormat(widget.bangumiDetail!.stat!['coins']!)
|
||||
: Utils.numFormat(
|
||||
bangumiItem!.stat!['coins']!,
|
||||
@@ -482,7 +524,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
loadingStatus: false,
|
||||
semanticsLabel: '收藏',
|
||||
text: !widget.loadingStatus
|
||||
text: !widget.isLoading
|
||||
? Utils.numFormat(
|
||||
widget.bangumiDetail!.stat!['favorite']!)
|
||||
: Utils.numFormat(
|
||||
@@ -498,7 +540,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
semanticsLabel: '评论',
|
||||
text: !widget.loadingStatus
|
||||
text: !widget.isLoading
|
||||
? Utils.numFormat(widget.bangumiDetail!.stat!['reply']!)
|
||||
: Utils.numFormat(bangumiItem!.stat!['reply']!),
|
||||
),
|
||||
@@ -508,7 +550,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
semanticsLabel: '转发',
|
||||
text: !widget.loadingStatus
|
||||
text: !widget.isLoading
|
||||
? Utils.numFormat(widget.bangumiDetail!.stat!['share']!)
|
||||
: Utils.numFormat(bangumiItem!.stat!['share']!)),
|
||||
],
|
||||
@@ -524,10 +566,10 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
onTap: () => handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: widget.isLoading,
|
||||
text: !widget.isLoading
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: '-',
|
||||
),
|
||||
@@ -536,10 +578,10 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
onTap: () => handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: widget.isLoading,
|
||||
text: !widget.isLoading
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: '-',
|
||||
),
|
||||
@@ -550,8 +592,8 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: widget.isLoading,
|
||||
text: !widget.isLoading
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: '-',
|
||||
),
|
||||
@@ -563,8 +605,8 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: widget.isLoading,
|
||||
text: !widget.isLoading
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: '-',
|
||||
),
|
||||
@@ -573,7 +615,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
loadingStatus: widget.isLoading,
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
@@ -649,7 +691,7 @@ class AreasAndPubTime extends StatelessWidget {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
!widget.isLoading
|
||||
? (widget.bangumiDetail!.areas!.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!.first['name']
|
||||
: '')
|
||||
@@ -663,7 +705,7 @@ class AreasAndPubTime extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.publish!['pub_time_show']
|
||||
: bangumiItem!.publish!['pub_time_show'],
|
||||
style: TextStyle(
|
||||
@@ -691,7 +733,7 @@ class NewEpDesc extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
!widget.loadingStatus
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.newEp!['desc']
|
||||
: bangumiItem!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:PiliPalaX/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPalaX/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../../../utils/utils.dart';
|
||||
@@ -53,7 +53,6 @@ class IntroDetail extends StatelessWidget {
|
||||
bangumiDetail!.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
@@ -122,10 +121,10 @@ class IntroDetail extends StatelessWidget {
|
||||
.map(
|
||||
(item) => SearchText(
|
||||
fontSize: 13,
|
||||
searchText: item['tag_name'],
|
||||
onSelect: (_) => Get.toNamed('/searchResult',
|
||||
text: item['tag_name'],
|
||||
onTap: (_) => Get.toNamed('/searchResult',
|
||||
parameters: {'keyword': item['tag_name']}),
|
||||
onLongSelect: (_) =>
|
||||
onLongPress: (_) =>
|
||||
Utils.copyText(item['tag_name']),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:nil/nil.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/pages/home/index.dart';
|
||||
import 'package:PiliPalaX/pages/main/index.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/pages/home/index.dart';
|
||||
import 'package:PiliPlus/pages/main/index.dart';
|
||||
|
||||
import '../../utils/grid.dart';
|
||||
import 'controller.dart';
|
||||
@@ -62,8 +62,10 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
super.build(context);
|
||||
return refreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _bangumiController.onRefresh();
|
||||
await _bangumiController.queryBangumiFollow();
|
||||
await Future.wait([
|
||||
_bangumiController.onRefresh(),
|
||||
_bangumiController.queryBangumiFollow(),
|
||||
]);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _bangumiController.scrollController,
|
||||
@@ -72,12 +74,11 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: _bangumiController.userLogin.value,
|
||||
visible: _bangumiController.isLogin.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: StyleString.safeSpace, bottom: 10, left: 16),
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
@@ -99,7 +100,8 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.maxRowWidth * 1,
|
||||
height: Grid.maxRowWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
_bangumiController.followState.value),
|
||||
@@ -143,13 +145,13 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
? SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
|
||||
childAspectRatio: 0.65,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(60),
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(50),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
@@ -180,7 +182,6 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
width: Grid.maxRowWidth / 2,
|
||||
height: Grid.maxRowWidth * 1,
|
||||
margin: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: index == loadingState.response.length - 1
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class BangumiCardV extends StatelessWidget {
|
||||
const BangumiCardV({
|
||||
super.key,
|
||||
required this.bangumiItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
});
|
||||
|
||||
final dynamic bangumiItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -23,109 +20,98 @@ class BangumiCardV extends StatelessWidget {
|
||||
return Card(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: GestureDetector(
|
||||
// onLongPress: () {
|
||||
// if (longPress != null) {
|
||||
// longPress!();
|
||||
// }
|
||||
// },
|
||||
// onLongPressEnd: (details) {
|
||||
// if (longPressEnd != null) {
|
||||
// longPressEnd!();
|
||||
// }
|
||||
// },
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
final int seasonId = bangumiItem.seasonId;
|
||||
Utils.viewBangumi(seasonId: seasonId);
|
||||
// SmartDialog.showLoading(msg: '获取中...');
|
||||
// final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
||||
// SmartDialog.dismiss().then((value) {
|
||||
// if (res['status']) {
|
||||
// if (res['data'].episodes.isEmpty) {
|
||||
// SmartDialog.showToast('资源加载失败');
|
||||
// return;
|
||||
// }
|
||||
// EpisodeItem episode = res['data'].episodes.first;
|
||||
// int? epId = res['data'].userStatus?.progress?.lastEpId;
|
||||
// if (epId == null) {
|
||||
// epId = episode.epId;
|
||||
// } else {
|
||||
// for (var item in res['data'].episodes) {
|
||||
// if (item.epId == epId) {
|
||||
// episode = item;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// String bvid = episode.bvid!;
|
||||
// int cid = episode.cid!;
|
||||
// String pic = episode.cover!;
|
||||
// // debugPrint('epId');
|
||||
// // debugPrint(epId);
|
||||
// String heroTag = Utils.makeHeroTag(cid);
|
||||
// Get.toNamed(
|
||||
// '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId',
|
||||
// arguments: {
|
||||
// 'pic': pic,
|
||||
// 'heroTag': heroTag,
|
||||
// 'videoType': SearchType.media_bangumi,
|
||||
// 'bangumiItem': res['data'],
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// SmartDialog.showToast(res['msg']);
|
||||
// }
|
||||
// });
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: StyleString.imgRadius,
|
||||
topRight: StyleString.imgRadius,
|
||||
bottomLeft: StyleString.imgRadius,
|
||||
bottomRight: StyleString.imgRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.65,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: bangumiItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
child: InkWell(
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: bangumiItem.title,
|
||||
cover: bangumiItem.cover,
|
||||
),
|
||||
onTap: () async {
|
||||
final int seasonId = bangumiItem.seasonId;
|
||||
Utils.viewBangumi(seasonId: seasonId);
|
||||
// SmartDialog.showLoading(msg: '获取中...');
|
||||
// final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
||||
// SmartDialog.dismiss().then((value) {
|
||||
// if (res['status']) {
|
||||
// if (res['data'].episodes.isEmpty) {
|
||||
// SmartDialog.showToast('资源加载失败');
|
||||
// return;
|
||||
// }
|
||||
// EpisodeItem episode = res['data'].episodes.first;
|
||||
// int? epId = res['data'].userStatus?.progress?.lastEpId;
|
||||
// if (epId == null) {
|
||||
// epId = episode.epId;
|
||||
// } else {
|
||||
// for (var item in res['data'].episodes) {
|
||||
// if (item.epId == epId) {
|
||||
// episode = item;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// String bvid = episode.bvid!;
|
||||
// int cid = episode.cid!;
|
||||
// String pic = episode.cover!;
|
||||
// // debugPrint('epId');
|
||||
// // debugPrint(epId);
|
||||
// String heroTag = Utils.makeHeroTag(cid);
|
||||
// Get.toNamed(
|
||||
// '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId&epId=$epId',
|
||||
// arguments: {
|
||||
// 'pic': pic,
|
||||
// 'heroTag': heroTag,
|
||||
// 'videoType': SearchType.media_bangumi,
|
||||
// 'bangumiItem': res['data'],
|
||||
// },
|
||||
// );
|
||||
// } else {
|
||||
// SmartDialog.showToast(res['msg']);
|
||||
// }
|
||||
// });
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(StyleString.imgRadius),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.75,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: bangumiItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (bangumiItem.badge != null)
|
||||
PBadge(
|
||||
text: bangumiItem.badge,
|
||||
top: 6,
|
||||
right: 6,
|
||||
bottom: null,
|
||||
left: null),
|
||||
if (bangumiItem.order != null)
|
||||
PBadge(
|
||||
text: bangumiItem.order,
|
||||
top: null,
|
||||
right: null,
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (bangumiItem.badge != null)
|
||||
PBadge(
|
||||
text: bangumiItem.badge,
|
||||
top: 6,
|
||||
right: 6,
|
||||
bottom: null,
|
||||
left: null,
|
||||
),
|
||||
if (bangumiItem.order != null)
|
||||
PBadge(
|
||||
text: bangumiItem.order,
|
||||
top: null,
|
||||
right: null,
|
||||
bottom: 6,
|
||||
left: 6,
|
||||
type: 'gray',
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
bagumiContent(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
bagumiContent(context)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -149,7 +135,6 @@ class BangumiCardV extends StatelessWidget {
|
||||
bangumiItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import 'package:PiliPalaX/models/space_archive/item.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class BangumiCardVMemberHome extends StatelessWidget {
|
||||
const BangumiCardVMemberHome({
|
||||
super.key,
|
||||
required this.bangumiItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
});
|
||||
|
||||
final Item bangumiItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -67,6 +64,11 @@ class BangumiCardVMemberHome extends StatelessWidget {
|
||||
// }
|
||||
// });
|
||||
},
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: bangumiItem.title,
|
||||
cover: bangumiItem.cover,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ClipRRect(
|
||||
@@ -77,7 +79,7 @@ class BangumiCardVMemberHome extends StatelessWidget {
|
||||
bottomRight: StyleString.imgRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.65,
|
||||
aspectRatio: 0.75,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
@@ -138,7 +140,6 @@ Widget bangumiContent(Item bangumiItem) {
|
||||
bangumiItem.title ?? '',
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 1,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/models/bangumi/info.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||
import 'package:PiliPlus/models/bangumi/info.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/index.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class BangumiPanel extends StatefulWidget {
|
||||
const BangumiPanel({
|
||||
@@ -33,14 +33,12 @@ class BangumiPanel extends StatefulWidget {
|
||||
class _BangumiPanelState extends State<BangumiPanel> {
|
||||
late int currentIndex;
|
||||
final ScrollController listViewScrollCtr = ScrollController();
|
||||
final ScrollController listViewScrollCtr_2 = ScrollController();
|
||||
Box userInfoCache = GStorage.userInfo;
|
||||
dynamic userInfo;
|
||||
// 默认未开通
|
||||
int vipStatus = 0;
|
||||
late int cid;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
final ItemScrollController itemScrollController = ItemScrollController();
|
||||
StreamSubscription? _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -48,13 +46,13 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
cid = widget.cid!;
|
||||
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
|
||||
scrollToIndex();
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null) {
|
||||
vipStatus = userInfo.vipStatus;
|
||||
}
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: widget.heroTag);
|
||||
|
||||
videoDetailCtr.cid.listen((int p0) {
|
||||
_listener = videoDetailCtr.cid.listen((int p0) {
|
||||
cid = p0;
|
||||
currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
|
||||
if (!mounted) return;
|
||||
@@ -65,8 +63,8 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.cancel();
|
||||
listViewScrollCtr.dispose();
|
||||
listViewScrollCtr_2.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -88,8 +86,12 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
void scrollToIndex() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// 在回调函数中获取更新后的状态
|
||||
listViewScrollCtr.animateTo(currentIndex * 150,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
listViewScrollCtr.animateTo(
|
||||
(currentIndex * 150.0).clamp(listViewScrollCtr.position.minScrollExtent,
|
||||
listViewScrollCtr.position.maxScrollExtent),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -149,7 +151,9 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
return Container(
|
||||
width: 150,
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
margin: EdgeInsets.only(
|
||||
right: i == widget.pages.length - 1 ? 0 : 10,
|
||||
),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
@@ -253,7 +257,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'package:PiliPalaX/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/http/black.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/black.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class BlackListPage extends StatefulWidget {
|
||||
const BlackListPage({super.key});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
|
||||
abstract class MultiSelectController extends CommonController {
|
||||
RxBool enableMultiSelect = false.obs;
|
||||
@@ -9,8 +9,8 @@ abstract class MultiSelectController extends CommonController {
|
||||
|
||||
onSelect(int index) {
|
||||
List list = (loadingState.value as Success).response;
|
||||
list[index].checked = !list[index].checked;
|
||||
checkedCount.value = list.where((item) => item.checked).length;
|
||||
list[index].checked = !(list[index]?.checked ?? false);
|
||||
checkedCount.value = list.where((item) => item.checked == true).length;
|
||||
loadingState.value = LoadingState.success(list);
|
||||
if (checkedCount.value == 0) {
|
||||
enableMultiSelect.value = false;
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class PopupController extends CommonController {
|
||||
List<OverlayEntry?> popupDialog = <OverlayEntry?>[];
|
||||
|
||||
void removePopupDialog() {
|
||||
popupDialog.last?.remove();
|
||||
popupDialog.removeLast();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user