mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-13 14:21:34 +08:00
Compare commits
153 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fad0d38a7 | ||
|
|
130bf36115 | ||
|
|
f571121b66 | ||
|
|
12a236f397 | ||
|
|
226d554249 | ||
|
|
f8dc7ece2d | ||
|
|
3d7583d376 | ||
|
|
e645274609 | ||
|
|
0817b183a4 | ||
|
|
591078d4e4 | ||
|
|
4075978dc4 | ||
|
|
9b8b9bd1ab | ||
|
|
ba192a0356 | ||
|
|
2dbbf28d13 | ||
|
|
b62d3a03bc | ||
|
|
32d2ec8ca0 | ||
|
|
26b437f5df | ||
|
|
25a38450c3 | ||
|
|
0c0dfc6fbe | ||
|
|
2d338450f4 | ||
|
|
54d13fe7f2 | ||
|
|
efcbd5b1a4 | ||
|
|
6a5faba5fd | ||
|
|
c29db2bc4f | ||
|
|
11c6d5ac7e | ||
|
|
c8fc48e24d | ||
|
|
d0687f6d5a | ||
|
|
d0dc89ab54 | ||
|
|
ca10033a7d | ||
|
|
69463d4945 | ||
|
|
7b3d132802 | ||
|
|
96fb17e6b1 | ||
|
|
cc74cec7ba | ||
|
|
a1bfe1f4ee | ||
|
|
ae88700b96 | ||
|
|
84bc0d55b7 | ||
|
|
358a14b3f6 | ||
|
|
0be2cac3a0 | ||
|
|
86dd54991a | ||
|
|
0ece0bd7fa | ||
|
|
c9fe3c6485 | ||
|
|
093b84ca3a | ||
|
|
6c4cf5a139 | ||
|
|
794fb9c7e4 | ||
|
|
57043166a9 | ||
|
|
9ab6dcff23 | ||
|
|
eec4aa50f0 | ||
|
|
3d94a15d5d | ||
|
|
dc75d59ccd | ||
|
|
ff00ade786 | ||
|
|
43fcc36165 | ||
|
|
33465db0c3 | ||
|
|
6d9651070e | ||
|
|
c8ad94343a | ||
|
|
df81a33ae0 | ||
|
|
f4261dd8f7 | ||
|
|
5ea594b747 | ||
|
|
e8c82f69d4 | ||
|
|
41dfab371e | ||
|
|
7e400701df | ||
|
|
5b1ec83a34 | ||
|
|
adf4b6fa5d | ||
|
|
d4e33c9636 | ||
|
|
e8396bd313 | ||
|
|
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 |
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
|
||||
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 }}"
|
||||
7
.github/workflows/ios.yml
vendored
7
.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,8 +24,10 @@ 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: |
|
||||
|
||||
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" />
|
||||
|
||||
@@ -17,7 +17,7 @@ class MainActivity : AudioServiceActivity() {
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
|
||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "pilipalax")
|
||||
methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "PiliPlus")
|
||||
methodChannel.setMethodCallHandler { call, result ->
|
||||
if (call.method == "back") {
|
||||
back()
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'skeleton.dart';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,12 +4,14 @@ 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(
|
||||
@@ -17,7 +19,10 @@ class CustomSliverPersistentHeaderDelegate
|
||||
//创建child子组件
|
||||
//shrinkOffset:child偏移值minExtent~maxExtent
|
||||
//overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false
|
||||
return child;
|
||||
return ColoredBox(
|
||||
color: bgColor,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
//SliverPersistentHeader最大高度
|
||||
@@ -31,6 +36,6 @@ class CustomSliverPersistentHeaderDelegate
|
||||
@override
|
||||
bool shouldRebuild(
|
||||
covariant CustomSliverPersistentHeaderDelegate oldDelegate) {
|
||||
return false;
|
||||
return oldDelegate.bgColor != bgColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class CustomToast extends StatelessWidget {
|
||||
const CustomToast({super.key, required this.msg});
|
||||
@@ -25,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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/utils/download.dart';
|
||||
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';
|
||||
|
||||
@@ -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,9 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/utils/download.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/http/init.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';
|
||||
@@ -42,8 +43,14 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
|
||||
this.minScale = 1.0,
|
||||
this.onPageChanged,
|
||||
this.onDismissed,
|
||||
this.setStatusBar,
|
||||
this.onClose,
|
||||
});
|
||||
|
||||
final VoidCallback? onClose;
|
||||
|
||||
final bool? setStatusBar;
|
||||
|
||||
/// The sources to show.
|
||||
final List<String> sources;
|
||||
|
||||
@@ -108,7 +115,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
});
|
||||
|
||||
currentIndex = widget.initIndex;
|
||||
setStatusBar();
|
||||
if (widget.setStatusBar != false) {
|
||||
setStatusBar();
|
||||
}
|
||||
}
|
||||
|
||||
setStatusBar() async {
|
||||
@@ -125,8 +134,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();
|
||||
@@ -214,6 +225,15 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
? '${widget.sources[index]}@${_quality}q.webp'.http2https
|
||||
: widget.sources[index].http2https;
|
||||
|
||||
void onClose() {
|
||||
if (widget.onClose != null) {
|
||||
widget.onClose!();
|
||||
} else {
|
||||
Get.back();
|
||||
widget.onDismissed?.call(_pageController!.page!.floor());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
@@ -227,10 +247,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
onNoBoundaryHit: _onNoBoundaryHit,
|
||||
maxScale: widget.maxScale,
|
||||
minScale: widget.minScale,
|
||||
onDismissed: () {
|
||||
Get.back();
|
||||
widget.onDismissed?.call(_pageController!.page!.floor());
|
||||
},
|
||||
onDismissed: onClose,
|
||||
onReset: () {
|
||||
if (!_enablePageView) {
|
||||
setState(() {
|
||||
@@ -247,7 +264,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: Get.back,
|
||||
onTap: onClose,
|
||||
onDoubleTapDown: (TapDownDetails details) {
|
||||
_doubleTapLocalPosition = details.localPosition;
|
||||
},
|
||||
@@ -293,10 +310,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.white),
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
widget.onDismissed?.call(_pageController!.page!.floor());
|
||||
},
|
||||
onPressed: onClose,
|
||||
),
|
||||
widget.sources.length > 1
|
||||
? Text(
|
||||
@@ -355,7 +369,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
// 图片分享
|
||||
void onShareImg(String imgUrl) async {
|
||||
SmartDialog.showLoading();
|
||||
var response = await Dio()
|
||||
var response = await Request()
|
||||
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
||||
final temp = await getTemporaryDirectory();
|
||||
SmartDialog.dismiss();
|
||||
@@ -485,7 +499,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(
|
||||
context,
|
||||
widget.sources as List<String>,
|
||||
widget.sources,
|
||||
);
|
||||
},
|
||||
dense: true,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/icon_button.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';
|
||||
@@ -79,15 +79,17 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
if (this.currentIndex != currentIndex) {
|
||||
this.currentIndex = currentIndex;
|
||||
try {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
itemScrollController[_index].jumpTo(
|
||||
index: currentIndex,
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// jump to current
|
||||
if (_ctr != null && widget.index != _ctr?.index) {
|
||||
_ctr?.animateTo(_index);
|
||||
Future.delayed(const Duration(milliseconds: 255)).then((_) {
|
||||
_ctr?.animateTo(_index, duration: const Duration(milliseconds: 200));
|
||||
Future.delayed(const Duration(milliseconds: 300)).then((_) {
|
||||
jumpToCurrent();
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/http_error.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget get loadingWidget => Center(child: CircularProgressIndicator());
|
||||
@@ -8,3 +9,36 @@ Widget errorWidget({errMsg, callback}) => HttpError(
|
||||
errMsg: errMsg,
|
||||
callback: callback,
|
||||
);
|
||||
|
||||
Widget scrollErrorWidget({errMsg, callback}) => CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: errMsg,
|
||||
callback: callback,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
Widget replyErrorWidget(context, isSliver, errMsg, onReload) => HttpError(
|
||||
isSliver: isSliver,
|
||||
errMsg:
|
||||
'${errMsg.startsWith('gRPC Error') ? '如无法加载评论:\n关闭代理\n或设置中关闭使用gRPC加载评论\n\n' : ''}$errMsg',
|
||||
callback: onReload,
|
||||
extraWidget: errMsg.startsWith('gRPC Error') && GlobalData().grpcReply
|
||||
? FilledButton.tonal(
|
||||
onPressed: () {
|
||||
GlobalData().grpcReply = false;
|
||||
onReload();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
||||
}),
|
||||
),
|
||||
child: Text(
|
||||
'暂时关闭gRPC加载评论',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
class NetworkImgLayer extends StatelessWidget {
|
||||
@@ -79,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,6 +1,6 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
|
||||
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';
|
||||
|
||||
@@ -1,12 +1,668 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
import 'package:flutter/material.dart' hide RefreshIndicator;
|
||||
|
||||
Widget refreshIndicator({
|
||||
required RefreshCallback onRefresh,
|
||||
required Widget child,
|
||||
}) {
|
||||
return RefreshIndicator(
|
||||
displacement: 20,
|
||||
displacement: displacement,
|
||||
onRefresh: onRefresh,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// The over-scroll distance that moves the indicator to its maximum
|
||||
// displacement, as a percentage of the scrollable's container extent.
|
||||
|
||||
double displacement = 20;
|
||||
double kDragContainerExtentPercentage = 0.25;
|
||||
|
||||
// How much the scroll's drag gesture can overshoot the RefreshIndicator's
|
||||
// displacement; max displacement = _kDragSizeFactorLimit * displacement.
|
||||
const double _kDragSizeFactorLimit = 1.5;
|
||||
|
||||
// When the scroll ends, the duration of the refresh indicator's animation
|
||||
// to the RefreshIndicator's displacement.
|
||||
const Duration _kIndicatorSnapDuration = Duration(milliseconds: 150);
|
||||
|
||||
// The duration of the ScaleTransition that starts when the refresh action
|
||||
// has completed.
|
||||
const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
|
||||
|
||||
/// The signature for a function that's called when the user has dragged a
|
||||
/// [RefreshIndicator] far enough to demonstrate that they want the app to
|
||||
/// refresh. The returned [Future] must complete when the refresh operation is
|
||||
/// finished.
|
||||
///
|
||||
/// Used by [RefreshIndicator.onRefresh].
|
||||
typedef RefreshCallback = Future<void> Function();
|
||||
|
||||
// The state machine moves through these modes only when the scrollable
|
||||
// identified by scrollableKey has been scrolled to its min or max limit.
|
||||
enum _RefreshIndicatorMode {
|
||||
drag, // Pointer is down.
|
||||
armed, // Dragged far enough that an up event will run the onRefresh callback.
|
||||
snap, // Animating to the indicator's final "displacement".
|
||||
refresh, // Running the refresh callback.
|
||||
done, // Animating the indicator's fade-out after refreshing.
|
||||
canceled, // Animating the indicator's fade-out after not arming.
|
||||
}
|
||||
|
||||
/// Used to configure how [RefreshIndicator] can be triggered.
|
||||
enum RefreshIndicatorTriggerMode {
|
||||
/// The indicator can be triggered regardless of the scroll position
|
||||
/// of the [Scrollable] when the drag starts.
|
||||
anywhere,
|
||||
|
||||
/// The indicator can only be triggered if the [Scrollable] is at the edge
|
||||
/// when the drag starts.
|
||||
onEdge,
|
||||
}
|
||||
|
||||
enum _IndicatorType { material, adaptive }
|
||||
|
||||
/// A widget that supports the Material "swipe to refresh" idiom.
|
||||
///
|
||||
/// {@youtube 560 315 https://www.youtube.com/watch?v=ORApMlzwMdM}
|
||||
///
|
||||
/// When the child's [Scrollable] descendant overscrolls, an animated circular
|
||||
/// progress indicator is faded into view. When the scroll ends, if the
|
||||
/// indicator has been dragged far enough for it to become completely opaque,
|
||||
/// the [onRefresh] callback is called. The callback is expected to update the
|
||||
/// scrollable's contents and then complete the [Future] it returns. The refresh
|
||||
/// indicator disappears after the callback's [Future] has completed.
|
||||
///
|
||||
/// The trigger mode is configured by [RefreshIndicator.triggerMode].
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This example shows how [RefreshIndicator] can be triggered in different ways.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This example shows how to trigger [RefreshIndicator] in a nested scroll view using
|
||||
/// the [notificationPredicate] property.
|
||||
///
|
||||
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.1.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// ## Troubleshooting
|
||||
///
|
||||
/// ### Refresh indicator does not show up
|
||||
///
|
||||
/// The [RefreshIndicator] will appear if its scrollable descendant can be
|
||||
/// overscrolled, i.e. if the scrollable's content is bigger than its viewport.
|
||||
/// To ensure that the [RefreshIndicator] will always appear, even if the
|
||||
/// scrollable's content fits within its viewport, set the scrollable's
|
||||
/// [Scrollable.physics] property to [AlwaysScrollableScrollPhysics]:
|
||||
///
|
||||
/// ```dart
|
||||
/// ListView(
|
||||
/// physics: const AlwaysScrollableScrollPhysics(),
|
||||
/// // ...
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// A [RefreshIndicator] can only be used with a vertical scroll view.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * <https://material.io/design/platform-guidance/android-swipe-to-refresh.html>
|
||||
/// * [RefreshIndicatorState], can be used to programmatically show the refresh indicator.
|
||||
/// * [RefreshProgressIndicator], widget used by [RefreshIndicator] to show
|
||||
/// the inner circular progress spinner during refreshes.
|
||||
/// * [CupertinoSliverRefreshControl], an iOS equivalent of the pull-to-refresh pattern.
|
||||
/// Must be used as a sliver inside a [CustomScrollView] instead of wrapping
|
||||
/// around a [ScrollView] because it's a part of the scrollable instead of
|
||||
/// being overlaid on top of it.
|
||||
class RefreshIndicator extends StatefulWidget {
|
||||
/// Creates a refresh indicator.
|
||||
///
|
||||
/// The [onRefresh], [child], and [notificationPredicate] arguments must be
|
||||
/// non-null. The default
|
||||
/// [displacement] is 40.0 logical pixels.
|
||||
///
|
||||
/// The [semanticsLabel] is used to specify an accessibility label for this widget.
|
||||
/// If it is null, it will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel].
|
||||
/// An empty string may be passed to avoid having anything read by screen reading software.
|
||||
/// The [semanticsValue] may be used to specify progress on the widget.
|
||||
const RefreshIndicator({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.displacement = 40.0,
|
||||
this.edgeOffset = 0.0,
|
||||
required this.onRefresh,
|
||||
this.color,
|
||||
this.backgroundColor,
|
||||
this.notificationPredicate = defaultScrollNotificationPredicate,
|
||||
this.semanticsLabel,
|
||||
this.semanticsValue,
|
||||
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
|
||||
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
|
||||
}) : _indicatorType = _IndicatorType.material;
|
||||
|
||||
/// Creates an adaptive [RefreshIndicator] based on whether the target
|
||||
/// platform is iOS or macOS, following Material design's
|
||||
/// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html).
|
||||
///
|
||||
/// When the descendant overscrolls, a different spinning progress indicator
|
||||
/// is shown depending on platform. On iOS and macOS,
|
||||
/// [CupertinoActivityIndicator] is shown, but on all other platforms,
|
||||
/// [CircularProgressIndicator] appears.
|
||||
///
|
||||
/// If a [CupertinoActivityIndicator] is shown, the following parameters are ignored:
|
||||
/// [backgroundColor], [semanticsLabel], [semanticsValue], [strokeWidth].
|
||||
///
|
||||
/// The target platform is based on the current [Theme]: [ThemeData.platform].
|
||||
///
|
||||
/// Notably the scrollable widget itself will have slightly different behavior
|
||||
/// from [CupertinoSliverRefreshControl], due to a difference in structure.
|
||||
const RefreshIndicator.adaptive({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.displacement = 40.0,
|
||||
this.edgeOffset = 0.0,
|
||||
required this.onRefresh,
|
||||
this.color,
|
||||
this.backgroundColor,
|
||||
this.notificationPredicate = defaultScrollNotificationPredicate,
|
||||
this.semanticsLabel,
|
||||
this.semanticsValue,
|
||||
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
|
||||
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
|
||||
}) : _indicatorType = _IndicatorType.adaptive;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// The refresh indicator will be stacked on top of this child. The indicator
|
||||
/// will appear when child's Scrollable descendant is over-scrolled.
|
||||
///
|
||||
/// Typically a [ListView] or [CustomScrollView].
|
||||
final Widget child;
|
||||
|
||||
/// The distance from the child's top or bottom [edgeOffset] where
|
||||
/// the refresh indicator will settle. During the drag that exposes the refresh
|
||||
/// indicator, its actual displacement may significantly exceed this value.
|
||||
///
|
||||
/// In most cases, [displacement] distance starts counting from the parent's
|
||||
/// edges. However, if [edgeOffset] is larger than zero then the [displacement]
|
||||
/// value is calculated from that offset instead of the parent's edge.
|
||||
final double displacement;
|
||||
|
||||
/// The offset where [RefreshProgressIndicator] starts to appear on drag start.
|
||||
///
|
||||
/// Depending whether the indicator is showing on the top or bottom, the value
|
||||
/// of this variable controls how far from the parent's edge the progress
|
||||
/// indicator starts to appear. This may come in handy when, for example, the
|
||||
/// UI contains a top [Widget] which covers the parent's edge where the progress
|
||||
/// indicator would otherwise appear.
|
||||
///
|
||||
/// By default, the edge offset is set to 0.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [displacement], can be used to change the distance from the edge that
|
||||
/// the indicator settles.
|
||||
final double edgeOffset;
|
||||
|
||||
/// A function that's called when the user has dragged the refresh indicator
|
||||
/// far enough to demonstrate that they want the app to refresh. The returned
|
||||
/// [Future] must complete when the refresh operation is finished.
|
||||
final RefreshCallback onRefresh;
|
||||
|
||||
/// The progress indicator's foreground color. The current theme's
|
||||
/// [ColorScheme.primary] by default.
|
||||
final Color? color;
|
||||
|
||||
/// The progress indicator's background color. The current theme's
|
||||
/// [ThemeData.canvasColor] by default.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// A check that specifies whether a [ScrollNotification] should be
|
||||
/// handled by this widget.
|
||||
///
|
||||
/// By default, checks whether `notification.depth == 0`. Set it to something
|
||||
/// else for more complicated layouts.
|
||||
final ScrollNotificationPredicate notificationPredicate;
|
||||
|
||||
/// {@macro flutter.progress_indicator.ProgressIndicator.semanticsLabel}
|
||||
///
|
||||
/// This will be defaulted to [MaterialLocalizations.refreshIndicatorSemanticLabel]
|
||||
/// if it is null.
|
||||
final String? semanticsLabel;
|
||||
|
||||
/// {@macro flutter.progress_indicator.ProgressIndicator.semanticsValue}
|
||||
final String? semanticsValue;
|
||||
|
||||
/// Defines [strokeWidth] for `RefreshIndicator`.
|
||||
///
|
||||
/// By default, the value of [strokeWidth] is 2.0 pixels.
|
||||
final double strokeWidth;
|
||||
|
||||
final _IndicatorType _indicatorType;
|
||||
|
||||
/// Defines how this [RefreshIndicator] can be triggered when users overscroll.
|
||||
///
|
||||
/// The [RefreshIndicator] can be pulled out in two cases,
|
||||
/// 1, Keep dragging if the scrollable widget at the edge with zero scroll position
|
||||
/// when the drag starts.
|
||||
/// 2, Keep dragging after overscroll occurs if the scrollable widget has
|
||||
/// a non-zero scroll position when the drag starts.
|
||||
///
|
||||
/// If this is [RefreshIndicatorTriggerMode.anywhere], both of the cases above can be triggered.
|
||||
///
|
||||
/// If this is [RefreshIndicatorTriggerMode.onEdge], only case 1 can be triggered.
|
||||
///
|
||||
/// Defaults to [RefreshIndicatorTriggerMode.onEdge].
|
||||
final RefreshIndicatorTriggerMode triggerMode;
|
||||
|
||||
@override
|
||||
RefreshIndicatorState createState() => RefreshIndicatorState();
|
||||
}
|
||||
|
||||
/// Contains the state for a [RefreshIndicator]. This class can be used to
|
||||
/// programmatically show the refresh indicator, see the [show] method.
|
||||
class RefreshIndicatorState extends State<RefreshIndicator>
|
||||
with TickerProviderStateMixin<RefreshIndicator> {
|
||||
late AnimationController _positionController;
|
||||
late AnimationController _scaleController;
|
||||
late Animation<double> _positionFactor;
|
||||
late Animation<double> _scaleFactor;
|
||||
late Animation<double> _value;
|
||||
late Animation<Color?> _valueColor;
|
||||
|
||||
_RefreshIndicatorMode? _mode;
|
||||
late Future<void> _pendingRefreshFuture;
|
||||
bool? _isIndicatorAtTop;
|
||||
double? _dragOffset;
|
||||
late Color _effectiveValueColor =
|
||||
widget.color ?? Theme.of(context).colorScheme.primary;
|
||||
|
||||
static final Animatable<double> _threeQuarterTween =
|
||||
Tween<double>(begin: 0.0, end: 0.75);
|
||||
static final Animatable<double> _kDragSizeFactorLimitTween =
|
||||
Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
|
||||
static final Animatable<double> _oneToZeroTween =
|
||||
Tween<double>(begin: 1.0, end: 0.0);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_positionController = AnimationController(vsync: this);
|
||||
_positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
|
||||
_value = _positionController.drive(
|
||||
_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
|
||||
|
||||
_scaleController = AnimationController(vsync: this);
|
||||
_scaleFactor = _scaleController.drive(_oneToZeroTween);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_setupColorTween();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant RefreshIndicator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.color != widget.color) {
|
||||
_setupColorTween();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_positionController.dispose();
|
||||
_scaleController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _setupColorTween() {
|
||||
// Reset the current value color.
|
||||
_effectiveValueColor =
|
||||
widget.color ?? Theme.of(context).colorScheme.primary;
|
||||
final Color color = _effectiveValueColor;
|
||||
if (color.alpha == 0x00) {
|
||||
// Set an always stopped animation instead of a driven tween.
|
||||
_valueColor = AlwaysStoppedAnimation<Color>(color);
|
||||
} else {
|
||||
// Respect the alpha of the given color.
|
||||
_valueColor = _positionController.drive(
|
||||
ColorTween(
|
||||
begin: color.withAlpha(0),
|
||||
end: color.withAlpha(color.alpha),
|
||||
).chain(
|
||||
CurveTween(
|
||||
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _shouldStart(ScrollNotification notification) {
|
||||
// If the notification.dragDetails is null, this scroll is not triggered by
|
||||
// user dragging. It may be a result of ScrollController.jumpTo or ballistic scroll.
|
||||
// In this case, we don't want to trigger the refresh indicator.
|
||||
return ((notification is ScrollStartNotification &&
|
||||
notification.dragDetails != null) ||
|
||||
(notification is ScrollUpdateNotification &&
|
||||
notification.dragDetails != null &&
|
||||
widget.triggerMode == RefreshIndicatorTriggerMode.anywhere)) &&
|
||||
((notification.metrics.axisDirection == AxisDirection.up &&
|
||||
notification.metrics.extentAfter == 0.0) ||
|
||||
(notification.metrics.axisDirection == AxisDirection.down &&
|
||||
notification.metrics.extentBefore == 0.0)) &&
|
||||
_mode == null &&
|
||||
_start(notification.metrics.axisDirection);
|
||||
}
|
||||
|
||||
bool _handleScrollNotification(ScrollNotification notification) {
|
||||
if (!widget.notificationPredicate(notification)) {
|
||||
return false;
|
||||
}
|
||||
if (_shouldStart(notification)) {
|
||||
setState(() {
|
||||
_mode = _RefreshIndicatorMode.drag;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
final bool? indicatorAtTopNow =
|
||||
switch (notification.metrics.axisDirection) {
|
||||
AxisDirection.down || AxisDirection.up => true,
|
||||
AxisDirection.left || AxisDirection.right => null,
|
||||
};
|
||||
if (indicatorAtTopNow != _isIndicatorAtTop) {
|
||||
if (_mode == _RefreshIndicatorMode.drag ||
|
||||
_mode == _RefreshIndicatorMode.armed) {
|
||||
_dismiss(_RefreshIndicatorMode.canceled);
|
||||
}
|
||||
} else if (notification is ScrollUpdateNotification) {
|
||||
if (_mode == _RefreshIndicatorMode.drag ||
|
||||
_mode == _RefreshIndicatorMode.armed) {
|
||||
if (notification.metrics.axisDirection == AxisDirection.down) {
|
||||
_dragOffset = _dragOffset! - notification.scrollDelta!;
|
||||
} else if (notification.metrics.axisDirection == AxisDirection.up) {
|
||||
_dragOffset = _dragOffset! + notification.scrollDelta!;
|
||||
}
|
||||
_checkDragOffset(notification.metrics.viewportDimension);
|
||||
}
|
||||
if (_mode == _RefreshIndicatorMode.armed &&
|
||||
notification.dragDetails == null) {
|
||||
// On iOS start the refresh when the Scrollable bounces back from the
|
||||
// overscroll (ScrollNotification indicating this don't have dragDetails
|
||||
// because the scroll activity is not directly triggered by a drag).
|
||||
_show();
|
||||
}
|
||||
} else if (notification is OverscrollNotification) {
|
||||
if (_mode == _RefreshIndicatorMode.drag ||
|
||||
_mode == _RefreshIndicatorMode.armed) {
|
||||
if (notification.metrics.axisDirection == AxisDirection.down) {
|
||||
_dragOffset = _dragOffset! - notification.overscroll;
|
||||
} else if (notification.metrics.axisDirection == AxisDirection.up) {
|
||||
_dragOffset = _dragOffset! + notification.overscroll;
|
||||
}
|
||||
_checkDragOffset(notification.metrics.viewportDimension);
|
||||
}
|
||||
} else if (notification is ScrollEndNotification) {
|
||||
switch (_mode) {
|
||||
case _RefreshIndicatorMode.armed:
|
||||
if (_positionController.value < 1.0) {
|
||||
_dismiss(_RefreshIndicatorMode.canceled);
|
||||
} else {
|
||||
_show();
|
||||
}
|
||||
case _RefreshIndicatorMode.drag:
|
||||
_dismiss(_RefreshIndicatorMode.canceled);
|
||||
case _RefreshIndicatorMode.canceled:
|
||||
case _RefreshIndicatorMode.done:
|
||||
case _RefreshIndicatorMode.refresh:
|
||||
case _RefreshIndicatorMode.snap:
|
||||
case null:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _handleIndicatorNotification(
|
||||
OverscrollIndicatorNotification notification) {
|
||||
if (notification.depth != 0 || !notification.leading) {
|
||||
return false;
|
||||
}
|
||||
if (_mode == _RefreshIndicatorMode.drag) {
|
||||
notification.disallowIndicator();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _start(AxisDirection direction) {
|
||||
assert(_mode == null);
|
||||
assert(_isIndicatorAtTop == null);
|
||||
assert(_dragOffset == null);
|
||||
switch (direction) {
|
||||
case AxisDirection.down:
|
||||
case AxisDirection.up:
|
||||
_isIndicatorAtTop = true;
|
||||
case AxisDirection.left:
|
||||
case AxisDirection.right:
|
||||
_isIndicatorAtTop = null;
|
||||
// we do not support horizontal scroll views.
|
||||
return false;
|
||||
}
|
||||
_dragOffset = 0.0;
|
||||
_scaleController.value = 0.0;
|
||||
_positionController.value = 0.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void _checkDragOffset(double containerExtent) {
|
||||
assert(_mode == _RefreshIndicatorMode.drag ||
|
||||
_mode == _RefreshIndicatorMode.armed);
|
||||
double newValue =
|
||||
_dragOffset! / (containerExtent * kDragContainerExtentPercentage);
|
||||
if (_mode == _RefreshIndicatorMode.armed) {
|
||||
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
|
||||
}
|
||||
_positionController.value =
|
||||
clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds
|
||||
if (_mode == _RefreshIndicatorMode.drag &&
|
||||
_valueColor.value!.alpha == _effectiveValueColor.alpha) {
|
||||
_mode = _RefreshIndicatorMode.armed;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop showing the refresh indicator.
|
||||
Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
|
||||
await Future<void>.value();
|
||||
// This can only be called from _show() when refreshing and
|
||||
// _handleScrollNotification in response to a ScrollEndNotification or
|
||||
// direction change.
|
||||
assert(newMode == _RefreshIndicatorMode.canceled ||
|
||||
newMode == _RefreshIndicatorMode.done);
|
||||
setState(() {
|
||||
_mode = newMode;
|
||||
});
|
||||
switch (_mode!) {
|
||||
case _RefreshIndicatorMode.done:
|
||||
await _scaleController.animateTo(1.0,
|
||||
duration: _kIndicatorScaleDuration);
|
||||
case _RefreshIndicatorMode.canceled:
|
||||
await _positionController.animateTo(0.0,
|
||||
duration: _kIndicatorScaleDuration);
|
||||
case _RefreshIndicatorMode.armed:
|
||||
case _RefreshIndicatorMode.drag:
|
||||
case _RefreshIndicatorMode.refresh:
|
||||
case _RefreshIndicatorMode.snap:
|
||||
assert(false);
|
||||
}
|
||||
if (mounted && _mode == newMode) {
|
||||
_dragOffset = null;
|
||||
_isIndicatorAtTop = null;
|
||||
setState(() {
|
||||
_mode = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _show() {
|
||||
assert(_mode != _RefreshIndicatorMode.refresh);
|
||||
assert(_mode != _RefreshIndicatorMode.snap);
|
||||
final Completer<void> completer = Completer<void>();
|
||||
_pendingRefreshFuture = completer.future;
|
||||
_mode = _RefreshIndicatorMode.snap;
|
||||
_positionController
|
||||
.animateTo(1.0 / _kDragSizeFactorLimit,
|
||||
duration: _kIndicatorSnapDuration)
|
||||
.then<void>((void value) {
|
||||
if (mounted && _mode == _RefreshIndicatorMode.snap) {
|
||||
setState(() {
|
||||
// Show the indeterminate progress indicator.
|
||||
_mode = _RefreshIndicatorMode.refresh;
|
||||
});
|
||||
|
||||
final Future<void> refreshResult = widget.onRefresh();
|
||||
refreshResult.whenComplete(() {
|
||||
if (mounted && _mode == _RefreshIndicatorMode.refresh) {
|
||||
completer.complete();
|
||||
_dismiss(_RefreshIndicatorMode.done);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Show the refresh indicator and run the refresh callback as if it had
|
||||
/// been started interactively. If this method is called while the refresh
|
||||
/// callback is running, it quietly does nothing.
|
||||
///
|
||||
/// Creating the [RefreshIndicator] with a [GlobalKey<RefreshIndicatorState>]
|
||||
/// makes it possible to refer to the [RefreshIndicatorState].
|
||||
///
|
||||
/// The future returned from this method completes when the
|
||||
/// [RefreshIndicator.onRefresh] callback's future completes.
|
||||
///
|
||||
/// If you await the future returned by this function from a [State], you
|
||||
/// should check that the state is still [mounted] before calling [setState].
|
||||
///
|
||||
/// When initiated in this manner, the refresh indicator is independent of any
|
||||
/// actual scroll view. It defaults to showing the indicator at the top. To
|
||||
/// show it at the bottom, set `atTop` to false.
|
||||
Future<void> show({bool atTop = true}) {
|
||||
if (_mode != _RefreshIndicatorMode.refresh &&
|
||||
_mode != _RefreshIndicatorMode.snap) {
|
||||
if (_mode == null) {
|
||||
_start(atTop ? AxisDirection.down : AxisDirection.up);
|
||||
}
|
||||
_show();
|
||||
}
|
||||
return _pendingRefreshFuture;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
final Widget child = NotificationListener<ScrollNotification>(
|
||||
onNotification: _handleScrollNotification,
|
||||
child: NotificationListener<OverscrollIndicatorNotification>(
|
||||
onNotification: _handleIndicatorNotification,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
assert(() {
|
||||
if (_mode == null) {
|
||||
assert(_dragOffset == null);
|
||||
assert(_isIndicatorAtTop == null);
|
||||
} else {
|
||||
assert(_dragOffset != null);
|
||||
assert(_isIndicatorAtTop != null);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
|
||||
final bool showIndeterminateIndicator =
|
||||
_mode == _RefreshIndicatorMode.refresh ||
|
||||
_mode == _RefreshIndicatorMode.done;
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
child,
|
||||
if (_mode != null)
|
||||
Positioned(
|
||||
top: _isIndicatorAtTop! ? widget.edgeOffset : null,
|
||||
bottom: !_isIndicatorAtTop! ? widget.edgeOffset : null,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: SizeTransition(
|
||||
axisAlignment: _isIndicatorAtTop! ? 1.0 : -1.0,
|
||||
sizeFactor: _positionFactor, // this is what brings it down
|
||||
child: Container(
|
||||
padding: _isIndicatorAtTop!
|
||||
? EdgeInsets.only(top: widget.displacement)
|
||||
: EdgeInsets.only(bottom: widget.displacement),
|
||||
alignment: _isIndicatorAtTop!
|
||||
? Alignment.topCenter
|
||||
: Alignment.bottomCenter,
|
||||
child: ScaleTransition(
|
||||
scale: _scaleFactor,
|
||||
child: AnimatedBuilder(
|
||||
animation: _positionController,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
final Widget materialIndicator = RefreshProgressIndicator(
|
||||
semanticsLabel: widget.semanticsLabel ??
|
||||
MaterialLocalizations.of(context)
|
||||
.refreshIndicatorSemanticLabel,
|
||||
semanticsValue: widget.semanticsValue,
|
||||
value: showIndeterminateIndicator ? null : _value.value,
|
||||
valueColor: _valueColor,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
strokeWidth: widget.strokeWidth,
|
||||
);
|
||||
|
||||
final Widget cupertinoIndicator =
|
||||
CupertinoActivityIndicator(
|
||||
color: widget.color,
|
||||
);
|
||||
|
||||
switch (widget._indicatorType) {
|
||||
case _IndicatorType.material:
|
||||
return materialIndicator;
|
||||
|
||||
case _IndicatorType.adaptive:
|
||||
{
|
||||
final ThemeData theme = Theme.of(context);
|
||||
switch (theme.platform) {
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
return materialIndicator;
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
return cupertinoIndicator;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
if (segmentEnd > segmentStart ||
|
||||
(segmentEnd == segmentStart && segmentStart > 0)) {
|
||||
if (segmentColors[i].title != null) {
|
||||
double fontSize = 8;
|
||||
double fontSize = 10;
|
||||
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
@@ -65,7 +65,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
double width = i == 0 ? segmentStart : segmentStart - prevStart;
|
||||
|
||||
while (textPainter.width > width - 2 && fontSize >= 2) {
|
||||
fontSize -= 1;
|
||||
fontSize -= 0.5;
|
||||
textPainter = getTextPainter();
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -41,7 +42,6 @@ class VideoCardH extends StatelessWidget {
|
||||
try {
|
||||
type = videoItem.type;
|
||||
} catch (_) {}
|
||||
final String heroTag = Utils.makeHeroTag(aid);
|
||||
return Stack(children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
@@ -78,11 +78,22 @@ class VideoCardH extends StatelessWidget {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
return;
|
||||
}
|
||||
if (videoItem is HotVideoItemModel &&
|
||||
videoItem.redirectUrl?.isNotEmpty == true) {
|
||||
if (Utils.viewPgcFromUri(videoItem.redirectUrl!)) {
|
||||
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());
|
||||
}
|
||||
@@ -105,14 +116,18 @@ class VideoCardH extends StatelessWidget {
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: videoItem.pic as String,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
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!),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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';
|
||||
@@ -59,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());
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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';
|
||||
@@ -27,7 +27,6 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
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(
|
||||
@@ -41,9 +40,18 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
|
||||
if (Utils.viewPgcFromUri(videoItem.uri!)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {'heroTag': heroTag});
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {
|
||||
'heroTag': Utils.makeHeroTag(aid),
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -68,14 +76,11 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
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,
|
||||
),
|
||||
NetworkImgLayer(
|
||||
src: videoItem.cover,
|
||||
// videoItem.season?['cover'] ?? videoItem.cover,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
// if (videoItem.season != null)
|
||||
// PBadge(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -35,12 +35,11 @@ class VideoCardV extends StatelessWidget {
|
||||
String goto = videoItem.goto;
|
||||
switch (goto) {
|
||||
case 'bangumi':
|
||||
if (videoItem.bangumiBadge == '电影') {
|
||||
SmartDialog.showToast('暂不支持电影观看');
|
||||
return;
|
||||
}
|
||||
int epId = videoItem.param;
|
||||
Utils.viewBangumi(epId: epId);
|
||||
// if (videoItem.bangumiBadge == '电影') {
|
||||
// SmartDialog.showToast('暂不支持电影观看');
|
||||
// return;
|
||||
// }
|
||||
Utils.viewBangumi(epId: videoItem.param);
|
||||
// SmartDialog.showLoading(msg: '资源获取中');
|
||||
// var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
|
||||
// SmartDialog.dismiss();
|
||||
@@ -81,11 +80,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':
|
||||
@@ -132,20 +134,12 @@ class VideoCardV extends StatelessWidget {
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast(videoItem.goto);
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': videoItem.uri,
|
||||
'type': 'url',
|
||||
'pageTitle': videoItem.title,
|
||||
},
|
||||
);
|
||||
Utils.handleWebview(videoItem.uri);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||
return Stack(children: [
|
||||
Semantics(
|
||||
label: Utils.videoItemSemantics(videoItem),
|
||||
@@ -158,7 +152,7 @@ class VideoCardV extends StatelessWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async => onPushDetail(heroTag),
|
||||
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.id)),
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
@@ -173,13 +167,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(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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';
|
||||
@@ -20,13 +20,13 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
void onPushDetail(heroTag) async {
|
||||
String goto = videoItem.goto ?? '';
|
||||
switch (goto) {
|
||||
// case 'bangumi':
|
||||
// if (videoItem.bangumiBadge == '电影') {
|
||||
// SmartDialog.showToast('暂不支持电影观看');
|
||||
// return;
|
||||
// }
|
||||
// int epId = videoItem.param;
|
||||
// Utils.viewBangumi(epId: epId);
|
||||
case 'bangumi':
|
||||
// if (videoItem.bangumiBadge == '电影') {
|
||||
// SmartDialog.showToast('暂不支持电影观看');
|
||||
// return;
|
||||
// }
|
||||
// int epId = videoItem.param;
|
||||
Utils.viewBangumi(epId: videoItem.param);
|
||||
|
||||
// SmartDialog.showLoading(msg: '资源获取中');
|
||||
// var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
|
||||
@@ -63,12 +63,20 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
// }
|
||||
// break;
|
||||
case 'av':
|
||||
if (videoItem.isPgc == true && videoItem.uri?.isNotEmpty == true) {
|
||||
if (Utils.viewPgcFromUri(videoItem.uri!)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
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':
|
||||
@@ -115,20 +123,12 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
// break;
|
||||
default:
|
||||
SmartDialog.showToast(goto);
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': videoItem.uri ?? '',
|
||||
'type': 'url',
|
||||
'pageTitle': videoItem.title ?? '',
|
||||
},
|
||||
);
|
||||
Utils.handleWebview(videoItem.uri ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(videoItem.bvid);
|
||||
// List<VideoCustomAction> actions =
|
||||
// VideoCustomActions(videoItem, context).actions;
|
||||
return Stack(children: [
|
||||
@@ -143,7 +143,7 @@ class VideoCardVMemberHome extends StatelessWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async => onPushDetail(heroTag),
|
||||
onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.bvid)),
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: videoItem.title,
|
||||
@@ -158,13 +158,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(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPalaX/pages/search/widgets/search_text.dart';
|
||||
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';
|
||||
@@ -10,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;
|
||||
@@ -194,6 +194,9 @@ class VideoCustomActions {
|
||||
SmartDialog.showToast(
|
||||
res['status'] ? "点踩成功" : res['msg'],
|
||||
);
|
||||
if (res['status']) {
|
||||
onRemove?.call();
|
||||
}
|
||||
},
|
||||
style: FilledButton.styleFrom(
|
||||
visualDensity: VisualDensity(
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -390,9 +390,8 @@ class Api {
|
||||
static const String bangumiList =
|
||||
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1©right=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
|
||||
|
||||
// 我的订阅
|
||||
static const String bangumiFollow =
|
||||
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
|
||||
// 我的追番/追剧 ?type=1&pn=1&ps=15
|
||||
static const String bangumiFollow = '/x/space/bangumi/follow/list';
|
||||
|
||||
// 黑名单
|
||||
static const String blackLst = '/x/relation/blacks';
|
||||
@@ -402,7 +401,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
|
||||
@@ -444,6 +443,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
|
||||
@@ -698,4 +702,8 @@ class Api {
|
||||
|
||||
/// 稍后再看&收藏夹视频列表
|
||||
static const String mediaList = '/x/v2/medialist/resource/list';
|
||||
|
||||
/// 我的关注 - 正在直播
|
||||
static const String getFollowingLive =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BangumiHttp {
|
||||
static Future<LoadingState> bangumiList({int? page}) async {
|
||||
var res =
|
||||
await Request().get(Api.bangumiList, queryParameters: {'page': page});
|
||||
static Future<LoadingState> bangumiList({
|
||||
int? page,
|
||||
int? indexType,
|
||||
}) async {
|
||||
var res = await Request().get(Api.bangumiList, queryParameters: {
|
||||
'page': page,
|
||||
if (indexType != null) 'index_type': indexType,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
BangumiListDataModel data =
|
||||
BangumiListDataModel.fromJson(res.data['data']);
|
||||
@@ -16,13 +21,20 @@ class BangumiHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiFollow({int? mid}) async {
|
||||
var res =
|
||||
await Request().get(Api.bangumiFollow, queryParameters: {'vmid': mid});
|
||||
static Future<LoadingState> bangumiFollow({
|
||||
dynamic mid,
|
||||
required int type,
|
||||
required int pn,
|
||||
}) async {
|
||||
var res = await Request().get(Api.bangumiFollow, queryParameters: {
|
||||
'vmid': mid,
|
||||
'type': type,
|
||||
'pn': pn,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
BangumiListDataModel data =
|
||||
BangumiListDataModel.fromJson(res.data['data']);
|
||||
return LoadingState.success(data.list);
|
||||
return LoadingState.success(data);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -8,7 +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:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
|
||||
@@ -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';
|
||||
@@ -46,10 +44,13 @@ class ApiInterceptor extends Interceptor {
|
||||
// handler.next(err);
|
||||
String url = err.requestOptions.uri.toString();
|
||||
debugPrint('🌹🌹ApiInterceptor: $url');
|
||||
// 屏蔽弹幕、心跳、人数请求的错误提示
|
||||
if (!url.contains('heartbeat') &&
|
||||
!url.contains('seg.so') &&
|
||||
!url.contains('online/total')) {
|
||||
if (url.contains('heartbeat') ||
|
||||
url.contains('seg.so') ||
|
||||
url.contains('online/total') ||
|
||||
url.contains('github') ||
|
||||
(url.contains('skipSegments') && err.requestOptions.method == 'GET')) {
|
||||
// skip
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err) + url,
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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:PiliPlus/models/live/follow.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import '../models/live/item.dart';
|
||||
import '../models/live/room_info.dart';
|
||||
@@ -136,4 +137,29 @@ class LiveHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 我的关注 正在直播
|
||||
static Future liveFollowing({required int pn, required int ps}) async {
|
||||
var res = await Request().get(
|
||||
Api.getFollowingLive,
|
||||
queryParameters: {
|
||||
'page': pn,
|
||||
'page_size': ps,
|
||||
'platform': 'web',
|
||||
'ignoreRecord': 1,
|
||||
'hit_ab': true,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': LiveFollowingModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,10 +100,36 @@ 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']}');
|
||||
return LoadingState.error(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,33 +157,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 +187,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,7 +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:PiliPlus/http/loading_state.dart';
|
||||
import '../models/bangumi/info.dart';
|
||||
import '../models/common/search_type.dart';
|
||||
import '../models/search/hot.dart';
|
||||
@@ -82,7 +82,7 @@ class SearchHttp {
|
||||
int? pubEnd,
|
||||
}) async {
|
||||
var reqData = {
|
||||
'search_type': searchType.type,
|
||||
'search_type': searchType.name,
|
||||
'keyword': keyword,
|
||||
// 'order_sort': 0,
|
||||
// 'user_type': 0,
|
||||
@@ -117,7 +117,7 @@ class SearchHttp {
|
||||
case SearchType.bili_user:
|
||||
data = SearchUserModel.fromJson(res.data['data']);
|
||||
break;
|
||||
case SearchType.media_bangumi:
|
||||
case SearchType.media_bangumi || SearchType.media_ft:
|
||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||
break;
|
||||
case SearchType.article:
|
||||
@@ -182,6 +182,7 @@ class SearchHttp {
|
||||
}
|
||||
final dynamic res = await Request()
|
||||
.get(Api.bangumiInfo, queryParameters: <String, dynamic>{...data});
|
||||
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 '../common/constants.dart';
|
||||
@@ -48,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,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
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 '../common/constants.dart';
|
||||
@@ -144,6 +144,12 @@ class VideoHttp {
|
||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||
(i['args'] != null &&
|
||||
!blackMidsList.contains(i['args']['up_id']))) {
|
||||
String banWordForZone = GStorage.banWordForZone;
|
||||
if (banWordForZone.isNotEmpty &&
|
||||
RegExp(banWordForZone, caseSensitive: false)
|
||||
.hasMatch(i['args']['rname'])) {
|
||||
continue;
|
||||
}
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
@@ -168,7 +174,15 @@ 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'])) {
|
||||
String banWordForZone = GStorage.banWordForZone;
|
||||
if (banWordForZone.isNotEmpty &&
|
||||
RegExp(banWordForZone, caseSensitive: false)
|
||||
.hasMatch(i['tname'])) {
|
||||
continue;
|
||||
}
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
@@ -229,13 +243,15 @@ class VideoHttp {
|
||||
'web_location': 1550101,
|
||||
});
|
||||
|
||||
late final isLogin = GStorage.isLogin;
|
||||
|
||||
try {
|
||||
var res = await Request().get(
|
||||
epid != null ? Api.bangumiVideoUrl : Api.videoUrl,
|
||||
epid != null && isLogin ? Api.bangumiVideoUrl : Api.videoUrl,
|
||||
queryParameters: params);
|
||||
if (res.data['code'] == 0) {
|
||||
late PlayUrlModel data;
|
||||
if (epid != null) {
|
||||
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'];
|
||||
@@ -918,6 +934,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']};
|
||||
@@ -1008,7 +1026,15 @@ 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'])) {
|
||||
String banWordForZone = GStorage.banWordForZone;
|
||||
if (banWordForZone.isNotEmpty &&
|
||||
RegExp(banWordForZone, caseSensitive: false)
|
||||
.hasMatch(i['tname'])) {
|
||||
continue;
|
||||
}
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPalaX/build_config.dart';
|
||||
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';
|
||||
@@ -11,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';
|
||||
|
||||
@@ -181,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,
|
||||
|
||||
@@ -80,6 +80,7 @@ class BangumiInfoModel {
|
||||
int? type;
|
||||
UserStatus? userStatus;
|
||||
String? staff;
|
||||
List<Section>? section;
|
||||
|
||||
BangumiInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
activity = json['activity'];
|
||||
@@ -89,8 +90,8 @@ class BangumiInfoModel {
|
||||
bkgCover = json['bkg_cover'];
|
||||
cover = json['cover'];
|
||||
enableVt = json['enableVt'];
|
||||
episodes = json['episodes']
|
||||
.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
|
||||
episodes = (json['episodes'] as List?)
|
||||
?.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
|
||||
.toList();
|
||||
evaluate = json['evaluate'];
|
||||
freya = json['freya'];
|
||||
@@ -125,6 +126,22 @@ class BangumiInfoModel {
|
||||
userStatus = UserStatus.fromJson(json['user_status']);
|
||||
}
|
||||
staff = json['staff'];
|
||||
section = (json['section'] as List?)
|
||||
?.map((item) => Section.fromJson(item))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Section {
|
||||
Section({
|
||||
this.episodes,
|
||||
});
|
||||
List<EpisodeItem>? episodes;
|
||||
|
||||
Section.fromJson(Map<String, dynamic> json) {
|
||||
episodes = (json['episodes'] as List?)
|
||||
?.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
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,
|
||||
}
|
||||
];
|
||||
|
||||
@@ -5,7 +5,7 @@ enum SearchType {
|
||||
// 番剧:media_bangumi,
|
||||
media_bangumi,
|
||||
// 影视:media_ft
|
||||
// media_ft,
|
||||
media_ft,
|
||||
// 直播间及主播:live
|
||||
// live,
|
||||
// 直播间:live_room
|
||||
@@ -23,9 +23,7 @@ enum SearchType {
|
||||
}
|
||||
|
||||
extension SearchTypeExtension on SearchType {
|
||||
String get type =>
|
||||
['video', 'media_bangumi', 'live_room', 'bili_user', 'article'][index];
|
||||
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||
String get label => ['视频', '番剧', '影视', '直播间', '用户', '专栏'][index];
|
||||
}
|
||||
|
||||
// 搜索类型为视频、专栏及相簿时
|
||||
|
||||
@@ -1,67 +1,78 @@
|
||||
import 'package:PiliPalaX/pages/rank/index.dart';
|
||||
import 'package:PiliPlus/pages/live/view.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';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
enum TabType { live, rcmd, hot, rank, bangumi }
|
||||
enum TabType { live, rcmd, hot, rank, bangumi, cinema }
|
||||
|
||||
extension TabTypeDesc on TabType {
|
||||
String get description => ['直播', '推荐', '热门', '分区', '番剧'][index];
|
||||
String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
|
||||
String get description => ['直播', '推荐', '热门', '分区', '番剧', '影视'][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 LivePage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.thumb_up_off_alt_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '推荐',
|
||||
'type': TabType.rcmd,
|
||||
'ctr': Get.find<RcmdController>,
|
||||
'page': const RcmdPage(),
|
||||
},
|
||||
{
|
||||
'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(tabType: TabType.bangumi),
|
||||
},
|
||||
{
|
||||
'icon': Icon(
|
||||
MdiIcons.theater,
|
||||
size: 15,
|
||||
),
|
||||
'label': '影视',
|
||||
'type': TabType.cinema,
|
||||
'ctr': Get.find<BangumiController>,
|
||||
'page': const BangumiPage(tabType: TabType.cinema),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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({
|
||||
|
||||
119
lib/models/live/follow.dart
Normal file
119
lib/models/live/follow.dart
Normal file
@@ -0,0 +1,119 @@
|
||||
class LiveFollowingModel {
|
||||
int? count;
|
||||
List<LiveFollowingItemModel>? list;
|
||||
int? liveCount;
|
||||
int? neverLivedCount;
|
||||
List? neverLivedFaces;
|
||||
int? pageSize;
|
||||
String? title;
|
||||
int? totalPage;
|
||||
|
||||
LiveFollowingModel({
|
||||
this.count,
|
||||
this.list,
|
||||
this.liveCount,
|
||||
this.neverLivedCount,
|
||||
this.neverLivedFaces,
|
||||
this.pageSize,
|
||||
this.title,
|
||||
this.totalPage,
|
||||
});
|
||||
|
||||
LiveFollowingModel.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
list = (json['list'] as List?)
|
||||
?.map((item) => LiveFollowingItemModel.fromJson(item))
|
||||
.toList() ??
|
||||
<LiveFollowingItemModel>[];
|
||||
liveCount = json['live_count'];
|
||||
neverLivedCount = json['never_lived_count'];
|
||||
neverLivedFaces = json['never_lived_faces'];
|
||||
pageSize = json['pageSize'];
|
||||
title = json['title'];
|
||||
totalPage = json['totalPage'];
|
||||
}
|
||||
}
|
||||
|
||||
class LiveFollowingItemModel {
|
||||
int? roomId;
|
||||
int? uid;
|
||||
String? uname;
|
||||
String? title;
|
||||
String? face;
|
||||
int? liveStatus;
|
||||
int? recordNum;
|
||||
String? recentRecordId;
|
||||
int? isAttention;
|
||||
int? clipNum;
|
||||
int? fansNum;
|
||||
String? areaName;
|
||||
String? areaValue;
|
||||
String? tags;
|
||||
String? recentRecordIdV2;
|
||||
int? recordNumV2;
|
||||
int? recordLiveTime;
|
||||
String? areaNameV2;
|
||||
String? roomNews;
|
||||
String? watchIcon;
|
||||
String? textSmall;
|
||||
String? roomCover;
|
||||
String? pic;
|
||||
int? parentAreaId;
|
||||
int? areaId;
|
||||
|
||||
LiveFollowingItemModel({
|
||||
this.roomId,
|
||||
this.uid,
|
||||
this.uname,
|
||||
this.title,
|
||||
this.face,
|
||||
this.liveStatus,
|
||||
this.recordNum,
|
||||
this.recentRecordId,
|
||||
this.isAttention,
|
||||
this.clipNum,
|
||||
this.fansNum,
|
||||
this.areaName,
|
||||
this.areaValue,
|
||||
this.tags,
|
||||
this.recentRecordIdV2,
|
||||
this.recordNumV2,
|
||||
this.recordLiveTime,
|
||||
this.areaNameV2,
|
||||
this.roomNews,
|
||||
this.watchIcon,
|
||||
this.textSmall,
|
||||
this.roomCover,
|
||||
this.pic,
|
||||
this.parentAreaId,
|
||||
this.areaId,
|
||||
});
|
||||
|
||||
LiveFollowingItemModel.fromJson(Map<String, dynamic> json) {
|
||||
roomId = json['roomid'];
|
||||
uid = json['uid'];
|
||||
uname = json['uname'];
|
||||
title = json['title'];
|
||||
face = json['face'];
|
||||
liveStatus = json['live_status'];
|
||||
recordNum = json['record_num'];
|
||||
recentRecordId = json['recent_record_id'];
|
||||
isAttention = json['is_attention'];
|
||||
clipNum = json['clipnum'];
|
||||
fansNum = json['fans_num'];
|
||||
areaName = json['area_name'];
|
||||
areaValue = json['area_value'];
|
||||
tags = json['tags'];
|
||||
recentRecordIdV2 = json['recent_record_id_v2'];
|
||||
recordNumV2 = json['record_num_v2'];
|
||||
recordLiveTime = json['record_live_time'];
|
||||
areaNameV2 = json['area_name_v2'];
|
||||
roomNews = json['room_news'];
|
||||
watchIcon = json['watch_icon'];
|
||||
textSmall = json['text_small'];
|
||||
roomCover = json['room_cover'];
|
||||
pic = json['room_cover'];
|
||||
parentAreaId = json['parent_area_id'];
|
||||
areaId = json['area_id'];
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -52,6 +52,7 @@ class Data {
|
||||
@JsonKey(name: 'digital_button')
|
||||
dynamic digitalButton;
|
||||
dynamic entry;
|
||||
dynamic live;
|
||||
|
||||
Data({
|
||||
this.relation,
|
||||
@@ -77,6 +78,7 @@ class Data {
|
||||
this.nftFaceButton,
|
||||
this.digitalButton,
|
||||
this.entry,
|
||||
this.live,
|
||||
});
|
||||
|
||||
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
|
||||
|
||||
@@ -61,6 +61,7 @@ Data _$DataFromJson(Map<String, dynamic> json) => Data(
|
||||
nftFaceButton: json['nft_face_button'],
|
||||
digitalButton: json['digital_button'],
|
||||
entry: json['entry'],
|
||||
live: json['live'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/build_config.dart';
|
||||
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: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 '../../http/init.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,11 +83,15 @@ 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(
|
||||
@@ -100,7 +103,8 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
),
|
||||
leading: const Icon(Icons.info_outline),
|
||||
onTap: () => Utils.launchURL(
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPalaX/commit/${BuildConfig.commitHash}'),
|
||||
'https://github.com/bggRGjQaUbCoE/PiliPlus/commit/${BuildConfig.commitHash}'),
|
||||
onLongPress: () => Utils.copyText(BuildConfig.commitHash),
|
||||
),
|
||||
// Obx(
|
||||
// () => ListTile(
|
||||
@@ -219,11 +223,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
Get.back();
|
||||
dynamic accessKey = GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {});
|
||||
dynamic cookies = (await CookieManager(PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
storage: FileStorage(await Utils.getCookiePath()),
|
||||
))
|
||||
.cookieJar
|
||||
dynamic cookies = (await Request.cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl)))
|
||||
.map(
|
||||
(Cookie cookie) => {
|
||||
@@ -237,14 +237,14 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
'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(
|
||||
@@ -437,7 +437,6 @@ Commit Hash: ${BuildConfig.commitHash}''',
|
||||
}
|
||||
|
||||
class AboutController extends GetxController {
|
||||
final SettingController settingController = Get.put(SettingController());
|
||||
RxString currentVersion = ''.obs;
|
||||
RxString remoteVersion = ''.obs;
|
||||
LatestDataModel? remoteAppInfo;
|
||||
|
||||
@@ -1,39 +1,91 @@
|
||||
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/models/bangumi/list.dart';
|
||||
import 'package:PiliPlus/models/common/tab_type.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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;
|
||||
RxBool userLogin = false.obs;
|
||||
late int mid;
|
||||
dynamic userInfo;
|
||||
BangumiController({required this.tabType});
|
||||
final TabType tabType;
|
||||
|
||||
Rx<LoadingState> followState = LoadingState.loading().obs;
|
||||
bool isLoadingMore = true;
|
||||
RxBool isLogin = false.obs;
|
||||
int? mid;
|
||||
dynamic userInfo;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null) {
|
||||
mid = userInfo.mid;
|
||||
}
|
||||
userLogin.value = userInfo != null;
|
||||
mid = userInfo?.mid;
|
||||
isLogin.value = userInfo != null;
|
||||
|
||||
queryData();
|
||||
queryBangumiFollow();
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
Future queryBangumiFollow() async {
|
||||
userInfo = userInfo ?? GStorage.userInfo.get('userInfoCache');
|
||||
if (userInfo != null) {
|
||||
followState.value = await BangumiHttp.bangumiFollow(mid: userInfo.mid);
|
||||
if (isLogin.value) {
|
||||
followController = ScrollController();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() =>
|
||||
BangumiHttp.bangumiList(page: currentPage);
|
||||
Future onRefresh() {
|
||||
if (isLogin.value) {
|
||||
followPage = 1;
|
||||
followEnd = false;
|
||||
}
|
||||
queryBangumiFollow();
|
||||
return super.onRefresh();
|
||||
}
|
||||
|
||||
late int followPage = 1;
|
||||
late RxInt followCount = (-1).obs;
|
||||
late bool followLoading = false;
|
||||
late bool followEnd = false;
|
||||
late Rx<LoadingState> followState = LoadingState.loading().obs;
|
||||
ScrollController? followController;
|
||||
|
||||
// 我的订阅
|
||||
Future queryBangumiFollow([bool isRefresh = true]) async {
|
||||
if (isLogin.value.not || followLoading || (isRefresh.not && followEnd)) {
|
||||
return;
|
||||
}
|
||||
followLoading = true;
|
||||
dynamic res = await BangumiHttp.bangumiFollow(
|
||||
mid: mid,
|
||||
type: tabType == TabType.bangumi ? 1 : 2,
|
||||
pn: followPage,
|
||||
);
|
||||
if (res is Success) {
|
||||
BangumiListDataModel data = res.response;
|
||||
followPage++;
|
||||
followEnd = data.hasNext == 0 || data.list.isNullOrEmpty;
|
||||
followCount.value = data.total ?? -1;
|
||||
if (isRefresh.not && followState.value is Success) {
|
||||
data.list?.insertAll(0, (followState.value as Success).response);
|
||||
}
|
||||
followState.value = LoadingState.success(data.list);
|
||||
if (isRefresh) {
|
||||
followController?.animToTop();
|
||||
}
|
||||
} else {
|
||||
followState.value = res;
|
||||
}
|
||||
followLoading = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => BangumiHttp.bangumiList(
|
||||
page: currentPage,
|
||||
indexType: tabType == TabType.cinema ? 102 : null, // TODO: sort
|
||||
);
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
followController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +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_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.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;
|
||||
@@ -34,6 +34,11 @@ class BangumiIntroController extends CommonController {
|
||||
? int.tryParse(Get.parameters['epId']!)
|
||||
: null;
|
||||
|
||||
late dynamic type =
|
||||
Get.parameters['type'] == '1' || Get.parameters['type'] == '4'
|
||||
? '追番'
|
||||
: '追剧';
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
@@ -51,7 +56,7 @@ class BangumiIntroController extends CommonController {
|
||||
// 是否收藏
|
||||
RxBool hasFav = false.obs;
|
||||
dynamic videoTags;
|
||||
bool userLogin = false;
|
||||
bool isLogin = false;
|
||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||
List addMediaIdsNew = [];
|
||||
List delMediaIdsNew = [];
|
||||
@@ -82,9 +87,9 @@ class BangumiIntroController extends CommonController {
|
||||
}
|
||||
}
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
userLogin = userInfo != null;
|
||||
isLogin = userInfo != null;
|
||||
|
||||
if (userLogin && epId != null) {
|
||||
if (isLogin && epId != null) {
|
||||
// // 获取点赞状态
|
||||
// queryHasLikeVideo();
|
||||
// // 获取投币状态
|
||||
@@ -96,7 +101,7 @@ class BangumiIntroController extends CommonController {
|
||||
|
||||
queryData();
|
||||
|
||||
if (userLogin && seasonId != null) {
|
||||
if (isLogin && seasonId != null) {
|
||||
queryIsFollowed();
|
||||
}
|
||||
}
|
||||
@@ -372,15 +377,23 @@ class BangumiIntroController extends CommonController {
|
||||
// 修改分P或番剧分集
|
||||
Future changeSeasonOrbangu(epId, bvid, cid, aid, cover) async {
|
||||
// 重新获取视频资源
|
||||
VideoDetailController videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
..epId = epId;
|
||||
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;
|
||||
}
|
||||
@@ -396,7 +409,7 @@ class BangumiIntroController extends CommonController {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (userLogin) {
|
||||
if (isLogin) {
|
||||
queryBangumiLikeCoinFav();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
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';
|
||||
@@ -235,13 +235,17 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () => context.imageView(
|
||||
imgList: [
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
videoDetailCtr.onViewImage();
|
||||
context.imageView(
|
||||
imgList: [
|
||||
!widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!
|
||||
],
|
||||
onDismissed: videoDetailCtr.onDismissed,
|
||||
);
|
||||
},
|
||||
child: Hero(
|
||||
tag: !widget.isLoading
|
||||
? widget.bangumiDetail!.cover!
|
||||
@@ -272,6 +276,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: showIntroDetail,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: SizedBox(
|
||||
height: isLandscape ? 115 : 115 / 0.75,
|
||||
child: Column(
|
||||
@@ -339,8 +344,8 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
child: Text(
|
||||
bangumiIntroController
|
||||
.isFollowed.value
|
||||
? '已追番'
|
||||
: '追番',
|
||||
? '已${bangumiIntroController.type}'
|
||||
: '${bangumiIntroController.type}',
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -628,10 +633,10 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
_followDialogItem(1, '想看'),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Padding(
|
||||
title: Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
'取消追番',
|
||||
'取消${bangumiIntroController.type}',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.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/models/common/tab_type.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';
|
||||
import 'widgets/bangumi_card_v.dart';
|
||||
|
||||
class BangumiPage extends StatefulWidget {
|
||||
const BangumiPage({super.key});
|
||||
const BangumiPage({
|
||||
super.key,
|
||||
required this.tabType,
|
||||
});
|
||||
|
||||
final TabType tabType;
|
||||
|
||||
@override
|
||||
State<BangumiPage> createState() => _BangumiPageState();
|
||||
@@ -24,7 +30,10 @@ class BangumiPage extends StatefulWidget {
|
||||
|
||||
class _BangumiPageState extends State<BangumiPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final BangumiController _bangumiController = Get.put(BangumiController());
|
||||
late final BangumiController _bangumiController = Get.put(
|
||||
BangumiController(tabType: widget.tabType),
|
||||
tag: widget.tabType.name,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -74,7 +83,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: _bangumiController.userLogin.value,
|
||||
visible: _bangumiController.isLogin.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
@@ -82,14 +91,19 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'最近追番',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
Obx(
|
||||
() => Text(
|
||||
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_bangumiController.queryBangumiFollow();
|
||||
_bangumiController
|
||||
..followPage = 1
|
||||
..followEnd = false
|
||||
..queryBangumiFollow();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
@@ -100,7 +114,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.maxRowWidth / 2 / 0.75 +
|
||||
height: Grid.mediumCardWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
@@ -149,7 +163,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
|
||||
maxCrossAxisExtent: Grid.smallCardWidth / 3 * 2,
|
||||
childAspectRatio: 0.75,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(50),
|
||||
),
|
||||
@@ -177,11 +191,15 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
|
||||
Widget _buildFollowList(Success loadingState) {
|
||||
return ListView.builder(
|
||||
controller: _bangumiController.followController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: loadingState.response.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == loadingState.response.length - 1) {
|
||||
_bangumiController.queryBangumiFollow(false);
|
||||
}
|
||||
return Container(
|
||||
width: Grid.maxRowWidth / 2,
|
||||
width: Grid.mediumCardWidth / 2,
|
||||
margin: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: index == loadingState.response.length - 1
|
||||
@@ -198,11 +216,20 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
|
||||
Widget _buildFollowBody(LoadingState loadingState) {
|
||||
return switch (loadingState) {
|
||||
Loading() => nil,
|
||||
Loading() => loadingWidget,
|
||||
Success() => (loadingState.response as List?)?.isNotEmpty == true
|
||||
? _buildFollowList(loadingState)
|
||||
: const Center(child: Text('还没有追番')),
|
||||
Error() => Center(child: Text(loadingState.errMsg)),
|
||||
: Center(
|
||||
child: Text(
|
||||
'还没有${widget.tabType == TabType.bangumi ? '追番' : '追剧'}')),
|
||||
Error() => Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
loadingState.errMsg,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
LoadingState() => throw UnimplementedError(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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 {
|
||||
@@ -20,110 +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(
|
||||
onLongPress: () => imageSaveDialog(
|
||||
context: context,
|
||||
title: bangumiItem.title,
|
||||
cover: bangumiItem.cover,
|
||||
),
|
||||
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.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,
|
||||
),
|
||||
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)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:PiliPalaX/common/widgets/image_save.dart';
|
||||
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 {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/utils/utils.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';
|
||||
import 'package:PiliPalaX/models/bangumi/info.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
import 'package:PiliPalaX/utils/storage.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({
|
||||
|
||||
@@ -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,19 +1,19 @@
|
||||
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/models/common/reply_type.dart';
|
||||
import 'package:PiliPalaX/models/video/reply/data.dart';
|
||||
import 'package:PiliPalaX/pages/common/common_controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/global_data.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/models/video/reply/data.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/pages/video/detail/reply_new/reply_page.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPalaX/models/video/reply/item.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
|
||||
|
||||
abstract class ReplyController extends CommonController {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPalaX/http/danmaku.dart';
|
||||
import 'package:PiliPalaX/models/danmaku/dm.pb.dart';
|
||||
import 'package:PiliPalaX/plugin/pl_player/controller.dart';
|
||||
import 'package:PiliPlus/http/danmaku.dart';
|
||||
import 'package:PiliPlus/models/danmaku/dm.pb.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||
|
||||
class PlDanmakuController {
|
||||
PlDanmakuController(
|
||||
@@ -86,7 +86,8 @@ class PlDanmakuController {
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (RegExp(filter['filter']).hasMatch(elem.content)) {
|
||||
if (RegExp(filter['filter'], caseSensitive: false)
|
||||
.hasMatch(elem.content)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:canvas_danmaku/canvas_danmaku.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/models/danmaku/dm.pb.dart';
|
||||
import 'package:PiliPalaX/pages/danmaku/index.dart';
|
||||
import 'package:PiliPalaX/plugin/pl_player/index.dart';
|
||||
import 'package:PiliPalaX/utils/danmaku.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/models/danmaku/dm.pb.dart';
|
||||
import 'package:PiliPlus/pages/danmaku/index.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||
import 'package:PiliPlus/utils/danmaku.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
/// 传入播放器控制器,监听播放进度,加载对应弹幕
|
||||
class PlDanmaku extends StatefulWidget {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
import '../../http/danmaku_block.dart';
|
||||
import '../../models/user/danmaku_block.dart';
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:PiliPalaX/http/follow.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/tab/view.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
import 'package:PiliPlus/http/follow.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/view.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/url_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/http/dynamics.dart';
|
||||
import 'package:PiliPalaX/http/search.dart';
|
||||
import 'package:PiliPalaX/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPalaX/models/dynamics/result.dart';
|
||||
import 'package:PiliPalaX/models/dynamics/up.dart';
|
||||
import 'package:PiliPalaX/models/live/item.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/dynamics/up.dart';
|
||||
import 'package:PiliPlus/models/live/item.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
import '../../models/follow/result.dart';
|
||||
|
||||
@@ -29,22 +30,28 @@ class DynamicsController extends GetxController
|
||||
late TabController tabController;
|
||||
RxList<int> tempBannedList = <int>[].obs;
|
||||
late List<Widget> tabsPageList;
|
||||
bool flag = false;
|
||||
RxInt initialValue = 0.obs;
|
||||
RxBool userLogin = false.obs;
|
||||
dynamic userInfo;
|
||||
RxBool isLogin = false.obs;
|
||||
dynamic ownerMid;
|
||||
dynamic face;
|
||||
RxBool isLoadingDynamic = false.obs;
|
||||
List<UpItem> hasUpdatedUps = <UpItem>[];
|
||||
List<UpItem> allFollowedUps = <UpItem>[];
|
||||
int allFollowedUpsPage = 1;
|
||||
int allFollowedUpsTotal = 0;
|
||||
|
||||
late int currentMid = -1;
|
||||
late bool showLiveItems = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
super.onInit();
|
||||
|
||||
dynamic userInfo = GStorage.userInfo.get('userInfoCache');
|
||||
ownerMid = userInfo?.mid;
|
||||
face = userInfo?.face;
|
||||
isLogin.value = userInfo != null;
|
||||
|
||||
tabController = TabController(
|
||||
length: tabsConfig.length,
|
||||
vsync: this,
|
||||
@@ -53,9 +60,7 @@ class DynamicsController extends GetxController
|
||||
);
|
||||
tabsPageList =
|
||||
tabsConfig.map((e) => DynamicsTabPage(dynamicsType: e['tag'])).toList();
|
||||
}
|
||||
|
||||
void refreshNotifier() {
|
||||
queryFollowUp();
|
||||
}
|
||||
|
||||
@@ -85,12 +90,31 @@ class DynamicsController extends GetxController
|
||||
arguments: {'item': item, 'floor': floor});
|
||||
break;
|
||||
case 'DYNAMIC_TYPE_AV':
|
||||
if (item.modules.moduleDynamic.major.archive.type == 2) {
|
||||
if (item.modules.moduleDynamic.major.archive.jumpUrl
|
||||
.startsWith('//')) {
|
||||
item.modules.moduleDynamic.major.archive.jumpUrl =
|
||||
'https:${item.modules.moduleDynamic.major.archive.jumpUrl}';
|
||||
}
|
||||
String? redirectUrl = await UrlUtils.parseRedirectUrl(
|
||||
item.modules.moduleDynamic.major.archive.jumpUrl, false);
|
||||
if (redirectUrl != null) {
|
||||
Utils.viewPgcFromUri(redirectUrl);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String bvid = item.modules.moduleDynamic.major.archive.bvid;
|
||||
String cover = item.modules.moduleDynamic.major.archive.cover;
|
||||
try {
|
||||
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
Utils.toDupNamed('/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {'pic': cover, 'heroTag': bvid});
|
||||
Utils.toDupNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'pic': cover,
|
||||
'heroTag': Utils.makeHeroTag(bvid),
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -114,14 +138,7 @@ class DynamicsController extends GetxController
|
||||
'dynamicType': url.split('//').last.split('/')[1]
|
||||
});
|
||||
} else {
|
||||
Utils.toDupNamed(
|
||||
'/webview',
|
||||
parameters: {
|
||||
'url': 'https:$url',
|
||||
'type': 'note',
|
||||
'pageTitle': title
|
||||
},
|
||||
);
|
||||
Utils.handleWebview('https:$url');
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -148,10 +165,7 @@ class DynamicsController extends GetxController
|
||||
'roomid': liveRcmd.roomId,
|
||||
'watched_show': liveRcmd.watchedShow,
|
||||
});
|
||||
Utils.toDupNamed('/liveRoom?roomid=${liveItem.roomId}', arguments: {
|
||||
'liveItem': liveItem,
|
||||
'heroTag': liveItem.roomId.toString()
|
||||
});
|
||||
Utils.toDupNamed('/liveRoom?roomid=${liveItem.roomId}');
|
||||
break;
|
||||
|
||||
/// 合集查看
|
||||
@@ -162,8 +176,13 @@ class DynamicsController extends GetxController
|
||||
String bvid = IdUtils.av2bv(aid);
|
||||
String cover = ugcSeason.cover!;
|
||||
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
Utils.toDupNamed('/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {'pic': cover, 'heroTag': bvid});
|
||||
Utils.toDupNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'pic': cover,
|
||||
'heroTag': Utils.makeHeroTag(bvid),
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
/// 番剧查看
|
||||
@@ -214,7 +233,7 @@ class DynamicsController extends GetxController
|
||||
return;
|
||||
}
|
||||
var res = await FollowHttp.followings(
|
||||
vmid: userInfo.mid,
|
||||
vmid: ownerMid,
|
||||
pn: allFollowedUpsPage,
|
||||
ps: 50,
|
||||
orderType: 'attention',
|
||||
@@ -237,9 +256,11 @@ class DynamicsController extends GetxController
|
||||
}
|
||||
|
||||
Future queryFollowUp({type = 'init'}) async {
|
||||
if (!userLogin.value) {
|
||||
return {'status': false, 'msg': '账号未登录'};
|
||||
if (!isLogin.value) {
|
||||
upData.value.errMsg = '账号未登录';
|
||||
upData.refresh();
|
||||
}
|
||||
upData.value.errMsg = null;
|
||||
if (type == 'init') {
|
||||
upData.value.upList = [];
|
||||
upData.value.liveUsers = LiveUsers();
|
||||
@@ -249,15 +270,19 @@ class DynamicsController extends GetxController
|
||||
allFollowedUpsPage = 1;
|
||||
Future f1 = DynamicsHttp.followUp();
|
||||
Future f2 = FollowHttp.followings(
|
||||
vmid: userInfo.mid,
|
||||
pn: allFollowedUpsPage,
|
||||
ps: 50,
|
||||
orderType: 'attention');
|
||||
vmid: ownerMid,
|
||||
pn: allFollowedUpsPage,
|
||||
ps: 50,
|
||||
orderType: 'attention',
|
||||
);
|
||||
List<dynamic> ress = await Future.wait([f1, f2]);
|
||||
if (!ress[0]['status']) {
|
||||
SmartDialog.showToast("获取关注动态失败:${ress[0]['msg']}");
|
||||
upData.value.errMsg = ress[0]['msg'];
|
||||
upData.refresh();
|
||||
} else {
|
||||
upData.value.liveUsers = ress[0]['data'].liveUsers;
|
||||
upData.refresh();
|
||||
hasUpdatedUps = ress[0]['data'].upList!;
|
||||
}
|
||||
if (!ress[1]['status']) {
|
||||
@@ -277,16 +302,19 @@ class DynamicsController extends GetxController
|
||||
}
|
||||
upData.value.upList =
|
||||
allFollowedUpsTotal > 0 ? allFollowedUps : hasUpdatedUps;
|
||||
return ress[0];
|
||||
}
|
||||
var res = await DynamicsHttp.followUp();
|
||||
if (res['status']) {
|
||||
upData.value = res['data'];
|
||||
if (upData.value.upList!.isEmpty) {
|
||||
mid.value = -1;
|
||||
upData.refresh();
|
||||
} else {
|
||||
var res = await DynamicsHttp.followUp();
|
||||
if (res['status']) {
|
||||
upData.value = res['data'];
|
||||
if (upData.value.upList!.isEmpty) {
|
||||
mid.value = -1;
|
||||
}
|
||||
} else {
|
||||
upData.value.errMsg = res['msg'];
|
||||
upData.refresh();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
onSelectUp(mid) async {
|
||||
@@ -299,8 +327,8 @@ class DynamicsController extends GetxController
|
||||
}
|
||||
|
||||
onRefresh() async {
|
||||
queryFollowUp();
|
||||
await Future.wait(<Future>[
|
||||
queryFollowUp(),
|
||||
Get.find<DynamicsTabController>(
|
||||
tag: tabsConfig[tabController.index]['tag'])
|
||||
.onRefresh()
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPalaX/http/loading_state.dart';
|
||||
import 'package:PiliPalaX/pages/common/reply_controller.dart';
|
||||
import 'package:PiliPalaX/utils/global_data.dart';
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/reply_controller.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/http/html.dart';
|
||||
import 'package:PiliPalaX/http/reply.dart';
|
||||
import 'package:PiliPlus/http/html.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
|
||||
class DynamicDetailController extends ReplyController {
|
||||
@@ -14,6 +15,8 @@ class DynamicDetailController extends ReplyController {
|
||||
dynamic item;
|
||||
int? floor;
|
||||
|
||||
late final horizontalPreview = GStorage.horizontalPreview;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user