Compare commits
263 Commits
v1.0.19.01
...
feature-ap
Author | SHA1 | Date | |
---|---|---|---|
53b72bec25 | |||
20745a4541 | |||
4db5a950f3 | |||
c9327c97e5 | |||
8ff387d54a | |||
72f1a82650 | |||
b5a46d1be0 | |||
7b15f19895 | |||
14338dc33d | |||
c216c9bd65 | |||
3701fdef97 | |||
59641f0216 | |||
4dbcd2e0ec | |||
6d276fce4c | |||
710361caea | |||
00b81b194f | |||
734db176bf | |||
c23fe9c2bf | |||
79fcd017ab | |||
64292d523f | |||
af96d16062 | |||
12c299685b | |||
1182a58cb4 | |||
e04a7e5702 | |||
e9dc6f7fdb | |||
25d1ccc87a | |||
33d28f51d1 | |||
a37f3b8b5b | |||
a57f5e8b2f | |||
beb640ac83 | |||
1b54f07bc3 | |||
fca0588377 | |||
4865948609 | |||
09eb180fc7 | |||
3fab47780b | |||
453cbd7b1c | |||
0bf2326c73 | |||
2985c624ab | |||
32cbc2759e | |||
13c77957fe | |||
f382c8f377 | |||
491bc87251 | |||
b13d7b475b | |||
5e8d9b524b | |||
606f1b5c64 | |||
c4fec14517 | |||
f368ef83ee | |||
357133fa97 | |||
337cdafef3 | |||
bc74e32c10 | |||
a5558de872 | |||
73693c5bbb | |||
2ad9c3c993 | |||
cab74cff17 | |||
a161df2c7b | |||
4def3ffb80 | |||
da2bbeedff | |||
70317f92e2 | |||
bc9ea43cd2 | |||
882957e2f8 | |||
d3766ae31b | |||
641cf4ebb3 | |||
99e6abdad9 | |||
98aaca286b | |||
dc1edf7e73 | |||
31405750e6 | |||
6fdfcb888d | |||
3ece2bb173 | |||
b7b75e956f | |||
bf37c33291 | |||
06fb3e8d2f | |||
504be6fbda | |||
df4539a035 | |||
a3e1fd4e91 | |||
f41bb02bae | |||
105a29f311 | |||
3bf6136bc6 | |||
ab24da5f55 | |||
ed0b43eff1 | |||
ab9ae3a481 | |||
d728b1fb6d | |||
12e947ef84 | |||
3fad86e7e3 | |||
fea70011cb | |||
32cdb27f7c | |||
eb4435045b | |||
f1b829cec1 | |||
b248158e62 | |||
83b0ff02e4 | |||
8109314aaf | |||
c4b3446956 | |||
d804d95d78 | |||
234dfe9d64 | |||
c20df8fd81 | |||
19f0b1b28f | |||
481fa0d934 | |||
caca16a957 | |||
602d795909 | |||
800f714f4a | |||
75f569cb79 | |||
0e888537e8 | |||
a3ce15bd9e | |||
40f94e7ace | |||
370dcaf419 | |||
f81f348a3e | |||
4191cafe78 | |||
ae33cbf7ca | |||
fca7c36203 | |||
5fc783ebc2 | |||
98122aeaae | |||
f0d8e2a122 | |||
f815affff9 | |||
fce701090a | |||
d6da2a8a47 | |||
b3e162c8d3 | |||
e5eae93a78 | |||
962dcca6d4 | |||
be56fb721f | |||
ce1c80fd86 | |||
33ef18ef1d | |||
ba61e38c9b | |||
0b0db1a2b1 | |||
a9d73a9f1b | |||
0b5397ec00 | |||
466214b26a | |||
699be4125b | |||
45cc46d6d6 | |||
3f9fcabc2d | |||
65d2bfd844 | |||
4642c2a847 | |||
04186cdd5b | |||
40cc4e0dd1 | |||
95bc4a9f46 | |||
3bf3fd9a46 | |||
83ad11402f | |||
cfeb0588c1 | |||
381e832f3c | |||
6c20a434ed | |||
fc2da3ce57 | |||
a3abed0a03 | |||
db03cdd442 | |||
542975d0ec | |||
835ea0a9ff | |||
89501d3daa | |||
90c0256766 | |||
c2767486f5 | |||
e2489ef0e3 | |||
b2a4c54565 | |||
bf071ea9e1 | |||
f8a8c0967a | |||
078e4716b4 | |||
4da6667b81 | |||
e2befb11ff | |||
cb0ff334b3 | |||
e536f58ff4 | |||
d9992663d8 | |||
b1a05c5c27 | |||
02cc164635 | |||
35dc94014c | |||
5746b85b27 | |||
740d5f1ddd | |||
a0f92df5b5 | |||
fce96d4976 | |||
dd6c537135 | |||
bcf94e287a | |||
841d0f25f5 | |||
4811dc5ba5 | |||
41af6c799b | |||
e8f63f6114 | |||
d1e8068e51 | |||
6de9b1977c | |||
3c0f54bfd7 | |||
3d2c6a122a | |||
8950658f08 | |||
7a78729a44 | |||
03e5e22fef | |||
aa93ce0b89 | |||
0c365ad049 | |||
3d5c578fef | |||
0a22f0f543 | |||
5bf7b69d79 | |||
d57f84a1d7 | |||
32b2f0ceff | |||
bae871cfa1 | |||
d95fe9fe14 | |||
eb006e4c55 | |||
cb88d0c9ae | |||
3efad736ae | |||
42ad959155 | |||
cdf800c49f | |||
569277572a | |||
19b84571c1 | |||
0812b8339e | |||
b817a0c807 | |||
3da70d7e27 | |||
5e59db85be | |||
77477ff4dd | |||
89026e671c | |||
1c8e7e53a5 | |||
b264427be6 | |||
d5134f972d | |||
e2fd01a6d5 | |||
289cc99bc2 | |||
3d5ebe7e99 | |||
d9964d37a4 | |||
5da39a9c52 | |||
44a162762c | |||
d0f036ec35 | |||
10b928474b | |||
94f3b7c1e4 | |||
fb8b2de115 | |||
0d5d33a365 | |||
c39e91073b | |||
d258474a5a | |||
b0c56feef5 | |||
191472d0c4 | |||
40c666e3d1 | |||
63d600070b | |||
ebdeec6730 | |||
ee2a273d8b | |||
083739e562 | |||
71ccb9c0e5 | |||
4a5f4ca2ca | |||
78ade4a193 | |||
ae14653e72 | |||
01ac2c13e1 | |||
9e471b83d9 | |||
a560d66567 | |||
80b39daaff | |||
fb32388536 | |||
5f92a0c293 | |||
3de009ac43 | |||
b29256f598 | |||
e7cf472a0f | |||
03c59d23b8 | |||
b6f805f0e4 | |||
e23c2469ed | |||
387c799de1 | |||
230dd81342 | |||
47bdfec8c2 | |||
6a844da259 | |||
18bb58d293 | |||
045186b3c8 | |||
b531599893 | |||
1da84508d8 | |||
4c44fab217 | |||
5c3d438a7e | |||
92a8efdee1 | |||
eb1e2ca5f4 | |||
5b1022628c | |||
33f61ac0fa | |||
0b349e102e | |||
81371c5a31 | |||
85a59e11b9 | |||
e24ccc16fa | |||
e603942b5f | |||
0c4bad406e | |||
10d2995429 | |||
b0d8f5d0b6 | |||
23c8b34189 | |||
932be48125 | |||
9122dd7f3a | |||
41ddeab41a |
208
.github/workflows/beta_ci.yml
vendored
Normal file
208
.github/workflows/beta_ci.yml
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
name: Pilipala Beta
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**.txt"
|
||||
- ".github/**"
|
||||
- ".idea/**"
|
||||
- "!.github/workflows/**"
|
||||
|
||||
jobs:
|
||||
update_version:
|
||||
name: Read and update version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
# 定义输出变量 version,以便在其他job中引用
|
||||
new_version: ${{ steps.version.outputs.new_version }}
|
||||
last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
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 --first-parent $recent_release_tag..HEAD
|
||||
first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
|
||||
echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 获取最后一次提交
|
||||
id: get-last-commit
|
||||
run: |
|
||||
last_commit=$(git log -1 --pretty="%h %s" --first-parent)
|
||||
echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: 更新版本号
|
||||
id: version
|
||||
run: |
|
||||
# 读取版本号
|
||||
VERSION=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
|
||||
|
||||
# 获取GitHub Actions的run_number
|
||||
#RUN_NUMBER=${{ github.run_number }}
|
||||
|
||||
# 构建新版本号
|
||||
NEW_VERSION=$VERSION-beta.${{ steps.get-first-parent-commit-count.outputs.count }}
|
||||
|
||||
# 输出新版本号
|
||||
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@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
|
||||
- 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.16.5
|
||||
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: 重命名应用
|
||||
run: |
|
||||
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]}v${{ needs.update_version.outputs.new_version }}.apk"
|
||||
mv "$file" "$new_file_name"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 上传
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: Pilipala-Beta
|
||||
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@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.16.5
|
||||
|
||||
- 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: |
|
||||
DATE=${{ steps.date.outputs.date }}
|
||||
for file in app.ipa; do
|
||||
new_file_name="build/Pili-v${{ 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: Pilipala-Beta
|
||||
path: |
|
||||
build/Pili-*.ipa
|
||||
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- update_version
|
||||
- android
|
||||
- iOS
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Pilipala-Beta
|
||||
path: ./Pilipala-Beta
|
||||
|
||||
- name: 发送到Telegram频道
|
||||
uses: xireiki/channel-post@v1.0.7
|
||||
with:
|
||||
bot_token: ${{ secrets.BOT_TOKEN }}
|
||||
chat_id: ${{ secrets.CHAT_ID }}
|
||||
large_file: true
|
||||
api_id: ${{ secrets.TELEGRAM_API_ID }}
|
||||
api_hash: ${{ secrets.TELEGRAM_API_HASH }}
|
||||
method: sendFile
|
||||
path: Pilipala-Beta/*
|
||||
parse_mode: Markdown
|
||||
context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}](${{ github.event.head_commit.url }})"
|
@ -223,6 +223,10 @@
|
||||
android:pathPattern="/mobile/video/.*" />
|
||||
<data android:scheme="https" android:host="www.bilibili.com"
|
||||
android:pathPattern="/mobile/video/.*" />
|
||||
<data android:scheme="https" android:host="b23.tv"
|
||||
android:pathPattern="/*" />
|
||||
<data android:scheme="https" android:host="space.bilibili.com"
|
||||
android:pathPattern="/*" />
|
||||
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
BIN
assets/images/live/default_bg.webp
Normal file
BIN
assets/images/live/default_bg.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
1
assets/images/video/danmu_close.svg
Normal file
1
assets/images/video/danmu_close.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24"><path fill-rule="evenodd" d="m8.085 4.891-.999-1.499a1.008 1.008 0 0 1 1.679-1.118l1.709 2.566c.54-.008 1.045-.012 1.515-.012h.13c.345 0 .707.003 1.088.007l1.862-2.59a1.008 1.008 0 0 1 1.637 1.177l-1.049 1.46c.788.02 1.631.046 2.53.078 1.958.069 3.468 1.6 3.74 3.507.088.613.13 2.158.16 3.276l.001.027c.01.333.017.63.025.856a.987.987 0 0 1-1.974.069c-.008-.23-.016-.539-.025-.881v-.002c-.028-1.103-.066-2.541-.142-3.065-.143-1.004-.895-1.78-1.854-1.813-2.444-.087-4.466-.13-6.064-.131-1.598 0-3.619.044-6.063.13a2.037 2.037 0 0 0-1.945 1.748c-.15 1.04-.225 2.341-.225 3.904 0 1.874.11 3.474.325 4.798.154.949.95 1.66 1.91 1.708a97.58 97.58 0 0 0 5.416.139.988.988 0 0 1 0 1.975c-2.196 0-3.61-.047-5.513-.141A4.012 4.012 0 0 1 2.197 17.7c-.236-1.446-.351-3.151-.351-5.116 0-1.64.08-3.035.245-4.184A4.013 4.013 0 0 1 5.92 4.96c.761-.027 1.483-.05 2.164-.069Zm4.436 4.707h-1.32v4.63h2.222v.848h-2.618v1.078h2.431a5.01 5.01 0 0 1 3.575-3.115V9.598h-1.276a8.59 8.59 0 0 0 .748-1.42l-1.089-.384a14.232 14.232 0 0 1-.814 1.804h-1.518l.693-.308a8.862 8.862 0 0 0-.814-1.408l-1.045.352c.297.396.572.847.825 1.364Zm-4.18 3.564.154-1.485h1.98V8.289h-3.2v.979h2.067v1.43H7.483l-.308 3.454h2.277c0 1.166-.044 1.925-.12 2.277-.078.352-.386.528-.936.528-.308 0-.616-.022-.902-.055l.297 1.067.062.004c.285.02.551.04.818.04 1.001-.066 1.562-.418 1.694-1.056.11-.638.176-1.903.176-3.795h-2.2Zm7.458.11v-.858h-1.254v.858H15.8Zm-2.376-.858v.858h-1.199v-.858h1.2Zm-1.199-.946h1.2v-.902h-1.2v.902Zm2.321 0v-.902H15.8v.902h-1.254Zm3.517 10.594a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-.002-1.502a2.5 2.5 0 0 1-2.217-3.657l3.326 3.398a2.49 2.49 0 0 1-1.109.259Zm2.5-2.5c0 .42-.103.815-.286 1.162l-3.328-3.401a2.5 2.5 0 0 1 3.614 2.239Z" clip-rule="evenodd"></path></svg>
|
After Width: | Height: | Size: 1.8 KiB |
1
assets/images/video/danmu_open.svg
Normal file
1
assets/images/video/danmu_open.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24" id="svgcontent" overflow="visible" width="24" height="24" x="597" y="286"><g class="layer" style="pointer-events:all"><title style="pointer-events:inherit">Layer 1</title><path fill-rule="evenodd" d="M11.99,4.83C11.52,4.83 11.01,4.83 10.47,4.84L8.76,2.27A1.01,1.01 0 0 0 7.09,3.39L8.08,4.89C7.4,4.91 6.68,4.93 5.92,4.96A4.01,4.01 0 0 0 2.09,8.4C1.93,9.55 1.85,10.94 1.85,12.58C1.85,14.55 1.96,16.25 2.2,17.7A4.01,4.01 0 0 0 5.96,21.06L6.86,21.11C8.07,21.17 8.67,21.2 10.47,21.2A0.99,0.99 0 0 0 10.47,19.23C8.71,19.23 8.13,19.2 6.97,19.14L6.06,19.09A2.04,2.04 0 0 1 4.15,17.38C3.93,16.06 3.82,14.46 3.82,12.58C3.82,11.02 3.9,9.72 4.05,8.68C4.19,7.7 5.01,6.97 5.99,6.93C8.43,6.85 10.46,6.8 12.05,6.8C13.65,6.8 15.67,6.85 18.12,6.93C19.08,6.97 19.83,7.74 19.97,8.75C20.05,9.27 20.09,10.71 20.11,11.81L20.11,11.81C20.12,12.16 20.13,12.46 20.14,12.69A0.99,0.99 0 1 0 22.11,12.63C22.1,12.4 22.1,12.1 22.09,11.77L22.09,11.74C22.06,10.62 22.01,9.08 21.93,8.47C21.65,6.56 20.14,5.03 18.19,4.96C17.29,4.93 16.44,4.9 15.66,4.88L16.71,3.42A1.01,1.01 0 0 0 15.07,2.24L13.21,4.83C12.83,4.83 12.46,4.83 12.12,4.83L11.99,4.83zM12.51,9.6L11.19,9.6L11.19,14.23L13.41,14.23L13.41,15.08L10.79,15.08L10.79,16.16L13.41,16.16L13.42,16.84C13.78,16.86 14.13,17 14.43,17.24L14.54,17.24L14.54,16.16L17.23,16.16L17.23,15.08L14.53,15.08L14.53,14.23L16.8,14.23L16.8,9.6L15.52,9.6A8.59,8.59 0 0 0 16.27,8.18L15.18,7.8A14.23,14.23 0 0 1 14.37,9.6L12.85,9.6L13.54,9.3A8.86,8.86 0 0 0 12.73,7.89L11.68,8.24C11.98,8.64 12.26,9.09 12.51,9.6zM8.33,13.17L8.48,11.68L10.46,11.68L10.46,8.29L7.26,8.29L7.26,9.27L9.33,9.27L9.33,10.7L7.47,10.7L7.16,14.16L9.44,14.16C9.44,15.32 9.4,16.08 9.32,16.43C9.24,16.79 8.94,16.96 8.39,16.96C8.08,16.96 7.77,16.94 7.48,16.91L7.78,17.97L7.84,17.98C8.13,18 8.39,18.02 8.66,18.02C9.66,17.95 10.22,17.6 10.35,16.96C10.46,16.32 10.53,15.06 10.53,13.17L8.33,13.17zM15.79,13.28L15.79,12.42L14.53,12.42L14.53,13.28L15.79,13.28zM13.41,12.42L13.41,13.28L12.21,13.28L12.21,12.42L13.41,12.42zM12.21,11.47L13.41,11.47L13.41,10.57L12.21,10.57L12.21,11.47zM14.53,11.47L14.53,10.57L15.79,10.57L15.79,11.47L14.53,11.47z" clip-rule="evenodd" id="svg_1"></path><path fill="#000000" fill-rule="evenodd" d="M22.85,14.63A1,1 0 0 0 21.43,14.7L16.34,20.41L14.13,18.13L14.03,18.04L14.02,18.04A1,1 0 0 0 12.7,19.53L15.66,22.57L15.76,22.66L15.76,22.66C16.17,22.98 16.76,22.93 17.12,22.54L22.93,16.03L23.01,15.93L23.01,15.92A1,1 0 0 0 22.85,14.63z" clip-rule="evenodd" id="svg_2"></path></g></svg>
|
After Width: | Height: | Size: 2.5 KiB |
31
change_log/1.0.20.0303.md
Normal file
31
change_log/1.0.20.0303.md
Normal file
@ -0,0 +1,31 @@
|
||||
## 1.0.20
|
||||
|
||||
|
||||
### 功能
|
||||
+ 评论区增加表情
|
||||
+ 首页渐变背景开关
|
||||
+ 媒体库显示「我的订阅」
|
||||
+ 评论区链接解析
|
||||
+ 默认启动页设置
|
||||
|
||||
### 修复
|
||||
+ 评论区内容重复
|
||||
+ pip相关问题
|
||||
+ 播放多p视频评论不刷新
|
||||
+ 视频评论翻页重复
|
||||
|
||||
### 优化
|
||||
+ url scheme优化
|
||||
+ 图片预览放大
|
||||
+ 图片加载速度
|
||||
+ 视频评论区复制
|
||||
+ 全屏显示视频标题
|
||||
+ 网络异常处理
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
9
change_log/1.0.21.0306.md
Normal file
9
change_log/1.0.21.0306.md
Normal file
@ -0,0 +1,9 @@
|
||||
## 1.0.21
|
||||
|
||||
### 修复
|
||||
+ 推荐视频全屏问题
|
||||
+ 番剧全屏播放时灰屏问题
|
||||
+ 评论回调导致页面卡死问题
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
@ -9,6 +9,7 @@ PODS:
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
@ -38,7 +39,7 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.1.1):
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- saver_gallery (0.0.1):
|
||||
@ -71,7 +72,7 @@ DEPENDENCIES:
|
||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
|
||||
@ -113,7 +114,7 @@ EXTERNAL SOURCES:
|
||||
auto_orientation:
|
||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
@ -166,7 +167,7 @@ SPEC CHECKSUMS:
|
||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
@ -180,7 +181,7 @@ SPEC CHECKSUMS:
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
@ -193,7 +194,7 @@ SPEC CHECKSUMS:
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
|
||||
|
||||
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
||||
|
||||
|
@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
|
||||
"assets/images/error.svg",
|
||||
height: 200,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const SizedBox(height: 30),
|
||||
Text(
|
||||
errMsg ?? '请求异常',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
OutlinedButton.icon(
|
||||
const SizedBox(height: 20),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
fn!();
|
||||
},
|
||||
icon: const Icon(Icons.arrow_forward_outlined, size: 20),
|
||||
label: Text(btnText ?? '点击重试'),
|
||||
)
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
||||
}),
|
||||
),
|
||||
child: Text(
|
||||
btnText ?? '点击重试',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/extension.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
@ -32,8 +33,10 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int defaultImgQuality = GlobalData().imgQuality;
|
||||
final String imageUrl =
|
||||
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? 100}q.webp';
|
||||
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp';
|
||||
// print(imageUrl);
|
||||
int? memCacheWidth, memCacheHeight;
|
||||
double aspectRatio = (width / height).toDouble();
|
||||
|
||||
@ -81,7 +84,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
fadeOutDuration ?? const Duration(milliseconds: 120),
|
||||
fadeInDuration:
|
||||
fadeInDuration ?? const Duration(milliseconds: 120),
|
||||
filterQuality: FilterQuality.high,
|
||||
filterQuality: FilterQuality.low,
|
||||
errorWidget: (BuildContext context, String url, Object error) =>
|
||||
placeholder(context),
|
||||
placeholder: (BuildContext context, String url) =>
|
||||
@ -104,17 +107,19 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
? 0
|
||||
: StyleString.imgRadius.x),
|
||||
),
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
type == 'avatar'
|
||||
? 'assets/images/noface.jpeg'
|
||||
: 'assets/images/loading.png',
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
cacheHeight: height.cacheSize(context),
|
||||
),
|
||||
),
|
||||
child: type == 'bg'
|
||||
? const SizedBox()
|
||||
: Center(
|
||||
child: Image.asset(
|
||||
type == 'avatar'
|
||||
? 'assets/images/noface.jpeg'
|
||||
: 'assets/images/loading.png',
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
cacheHeight: height.cacheSize(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class StatDanMu extends StatelessWidget {
|
||||
final String? theme;
|
||||
final int? danmu;
|
||||
final dynamic danmu;
|
||||
final String? size;
|
||||
|
||||
const StatDanMu({Key? key, this.theme, this.danmu, this.size})
|
||||
|
@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class StatView extends StatelessWidget {
|
||||
final String? theme;
|
||||
final int? view;
|
||||
final dynamic view;
|
||||
final String? size;
|
||||
|
||||
const StatView({Key? key, this.theme, this.view, this.size})
|
||||
|
@ -38,6 +38,10 @@ class VideoCardH extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final int aid = videoItem.aid;
|
||||
final String bvid = videoItem.bvid;
|
||||
String type = 'video';
|
||||
try {
|
||||
type = videoItem.type;
|
||||
} catch (_) {}
|
||||
final String heroTag = Utils.makeHeroTag(aid);
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
@ -53,6 +57,10 @@ class VideoCardH extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
if (type == 'ketang') {
|
||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||
return;
|
||||
}
|
||||
final int cid =
|
||||
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||
@ -95,12 +103,20 @@ class VideoCardH extends StatelessWidget {
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (videoItem.duration != 0)
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (type != 'video')
|
||||
PBadge(
|
||||
text: type,
|
||||
left: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'primary',
|
||||
),
|
||||
// if (videoItem.rcmdReason != null &&
|
||||
// videoItem.rcmdReason.content != '')
|
||||
// pBadge(videoItem.rcmdReason.content, context,
|
||||
@ -324,8 +340,9 @@ class VideoContent extends StatelessWidget {
|
||||
reSrc: 11,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
SmartDialog.showToast(
|
||||
res['msg'] ?? '成功');
|
||||
SmartDialog.showToast(res['code'] == 0
|
||||
? '成功'
|
||||
: res['msg']);
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../models/model_rec_video_item.dart';
|
||||
import 'stat/danmu.dart';
|
||||
import 'stat/view.dart';
|
||||
import '../../http/dynamics.dart';
|
||||
import '../../http/search.dart';
|
||||
import '../../http/user.dart';
|
||||
@ -158,12 +161,12 @@ class VideoCardV extends StatelessWidget {
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (videoItem.duration != null)
|
||||
if (videoItem.duration > 0)
|
||||
if (crossAxisCount == 1) ...[
|
||||
PBadge(
|
||||
bottom: 10,
|
||||
right: 10,
|
||||
text: videoItem.duration,
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
)
|
||||
] else ...[
|
||||
PBadge(
|
||||
@ -171,7 +174,7 @@ class VideoCardV extends StatelessWidget {
|
||||
right: 7,
|
||||
size: 'small',
|
||||
type: 'gray',
|
||||
text: videoItem.duration,
|
||||
text: Utils.timeFormat(videoItem.duration),
|
||||
)
|
||||
],
|
||||
],
|
||||
@ -228,6 +231,7 @@ class VideoContent extends StatelessWidget {
|
||||
const SizedBox(height: 2),
|
||||
VideoStat(
|
||||
videoItem: videoItem,
|
||||
crossAxisCount: crossAxisCount,
|
||||
),
|
||||
],
|
||||
if (crossAxisCount == 1) const SizedBox(height: 4),
|
||||
@ -291,6 +295,7 @@ class VideoContent extends StatelessWidget {
|
||||
),
|
||||
VideoStat(
|
||||
videoItem: videoItem,
|
||||
crossAxisCount: crossAxisCount,
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
@ -314,29 +319,41 @@ class VideoContent extends StatelessWidget {
|
||||
|
||||
class VideoStat extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final int crossAxisCount;
|
||||
|
||||
const VideoStat({
|
||||
Key? key,
|
||||
required this.videoItem,
|
||||
required this.crossAxisCount,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RichText(
|
||||
maxLines: 1,
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: MediaQuery.textScalerOf(context)
|
||||
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
return Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view,
|
||||
),
|
||||
children: [
|
||||
if (videoItem.stat.view != '-')
|
||||
TextSpan(text: '${videoItem.stat.view}观看'),
|
||||
if (videoItem.stat.danmu != '-')
|
||||
TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmu,
|
||||
),
|
||||
if (videoItem is RecVideoItemModel) ...<Widget>[
|
||||
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
||||
RichText(
|
||||
maxLines: 1,
|
||||
text: TextSpan(
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +214,9 @@ class Api {
|
||||
// https://api.bilibili.com/x/relation/tags
|
||||
static const String followingsClass = '/x/relation/tags';
|
||||
|
||||
// 搜索follow
|
||||
static const followSearch = '/x/relation/followings/search';
|
||||
|
||||
// 粉丝
|
||||
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
|
||||
// order_type 排序规则 最近访问传空,最常访问传 attention
|
||||
@ -230,6 +233,10 @@ class Api {
|
||||
static const String liveRoomInfo =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo';
|
||||
|
||||
// 直播间详情 H5
|
||||
static const String liveRoomInfoH5 =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getH5InfoByRoom';
|
||||
|
||||
// 用户信息 需要Wbi签名
|
||||
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482
|
||||
static const String memberInfo = '/x/space/wbi/acc/info';
|
||||
@ -470,4 +477,30 @@ class Api {
|
||||
|
||||
/// 获取未读动态数
|
||||
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
|
||||
|
||||
/// 用户动态主页
|
||||
static const dynamicSpmPrefix = 'https://space.bilibili.com/1/dynamic';
|
||||
|
||||
/// 激活buvid3
|
||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||
|
||||
/// 我的订阅
|
||||
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
||||
|
||||
/// 我的订阅详情
|
||||
static const userSubFolderDetail = '/x/space/fav/season/list';
|
||||
|
||||
/// 表情
|
||||
static const emojiList = '/x/emote/user/panel/web';
|
||||
|
||||
/// 已读标记
|
||||
static const String ackSessionMsg =
|
||||
'${HttpString.tUrl}/session_svr/v1/session_svr/update_ack';
|
||||
|
||||
/// 发送私信
|
||||
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
|
||||
|
||||
/// 排行榜
|
||||
static const String getRankApi = "/x/web-interface/ranking/v2";
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
// ignore_for_file: avoid_print
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
@ -11,6 +13,7 @@ import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
|
||||
@ -24,6 +27,7 @@ class Request {
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
static final RegExp spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||
|
||||
/// 设置cookie
|
||||
static setCookie() async {
|
||||
@ -51,13 +55,12 @@ class Request {
|
||||
}
|
||||
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
|
||||
|
||||
if (cookie.isEmpty) {
|
||||
try {
|
||||
await Request().get(HttpString.baseUrl);
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
try {
|
||||
await buvidActivate();
|
||||
} catch (e) {
|
||||
log("setCookie, ${e.toString()}");
|
||||
}
|
||||
|
||||
final String cookieString = cookie
|
||||
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
.join('; ');
|
||||
@ -87,6 +90,33 @@ class Request {
|
||||
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
||||
}
|
||||
|
||||
static Future buvidActivate() async {
|
||||
var html = await Request().get(Api.dynamicSpmPrefix);
|
||||
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
||||
Random rand = Random();
|
||||
String rand_png_end = base64.encode(
|
||||
List<int>.generate(32, (_) => rand.nextInt(256)) +
|
||||
List<int>.filled(4, 0) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => rand.nextInt(256))
|
||||
);
|
||||
|
||||
String jsonData = json.encode({
|
||||
'3064': 1,
|
||||
'39c8': '${spmPrefix}.fp.risk',
|
||||
'3c43': {
|
||||
'adca': 'Linux',
|
||||
'bfe9': rand_png_end.substring(rand_png_end.length - 50),
|
||||
},
|
||||
});
|
||||
|
||||
await Request().post(
|
||||
Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: 'application/json')
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* config it and create
|
||||
*/
|
||||
|
@ -45,10 +45,13 @@ class ApiInterceptor extends Interceptor {
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||
// 处理网络请求错误
|
||||
// handler.next(err);
|
||||
SmartDialog.showToast(
|
||||
await dioError(err),
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
);
|
||||
String url = err.requestOptions.uri.toString();
|
||||
if (!url.contains('heartBeat')) {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err),
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
);
|
||||
}
|
||||
super.onError(err, handler);
|
||||
}
|
||||
|
||||
@ -75,23 +78,24 @@ class ApiInterceptor extends Interceptor {
|
||||
}
|
||||
|
||||
static Future<String> checkConnect() async {
|
||||
final ConnectivityResult connectivityResult =
|
||||
final List<ConnectivityResult> connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
switch (connectivityResult) {
|
||||
case ConnectivityResult.mobile:
|
||||
return '正在使用移动流量';
|
||||
case ConnectivityResult.wifi:
|
||||
return '正在使用wifi';
|
||||
case ConnectivityResult.ethernet:
|
||||
return '正在使用局域网';
|
||||
case ConnectivityResult.vpn:
|
||||
return '正在使用代理网络';
|
||||
case ConnectivityResult.other:
|
||||
return '正在使用其他网络';
|
||||
case ConnectivityResult.none:
|
||||
return '未连接到任何网络';
|
||||
default:
|
||||
return '';
|
||||
if (connectivityResult.contains(ConnectivityResult.mobile)) {
|
||||
return '正在使用移动流量';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.wifi)) {
|
||||
return '正在使用wifi';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
|
||||
return '正在使用局域网';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.vpn)) {
|
||||
return '正在使用代理网络';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
|
||||
return '正在使用蓝牙网络';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.other)) {
|
||||
return '正在使用其他网络';
|
||||
} else if (connectivityResult.contains(ConnectivityResult.none)) {
|
||||
return '未连接到任何网络';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import '../models/live/item.dart';
|
||||
import '../models/live/room_info.dart';
|
||||
import '../models/live/room_info_h5.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
@ -46,4 +47,22 @@ class LiveHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future liveRoomInfoH5({roomId, qn}) async {
|
||||
var res = await Request().get(Api.liveRoomInfoH5, data: {
|
||||
'room_id': roomId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': RoomInfoH5Model.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ class MemberHttp {
|
||||
String order = 'pubdate',
|
||||
bool orderAvoided = true,
|
||||
}) async {
|
||||
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
Map params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'ps': ps,
|
||||
@ -88,7 +90,11 @@ class MemberHttp {
|
||||
'order': order,
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
'order_avoided': orderAvoided
|
||||
'order_avoided': orderAvoided,
|
||||
'dm_img_list': '[]',
|
||||
'dm_img_str': dmImgStr.substring(0, dmImgStr.length - 2),
|
||||
'dm_cover_img_str': dmCoverImgStr.substring(0, dmCoverImgStr.length - 2),
|
||||
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.memberArchive,
|
||||
@ -101,10 +107,13 @@ class MemberHttp {
|
||||
'data': MemberArchiveDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
Map errMap = {
|
||||
-352: '风控校验失败,请检查登录状态',
|
||||
};
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
'msg': errMap[res.data['code']] ?? res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -123,10 +132,13 @@ class MemberHttp {
|
||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
Map errMap = {
|
||||
-352: '风控校验失败,请检查登录状态',
|
||||
};
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
'msg': errMap[res.data['code']] ?? res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -461,4 +473,41 @@ class MemberHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索follow
|
||||
static Future getfollowSearch({
|
||||
required int mid,
|
||||
required int ps,
|
||||
required int pn,
|
||||
required String name,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
'vmid': mid,
|
||||
'pn': pn,
|
||||
'ps': ps,
|
||||
'order': 'desc',
|
||||
'order_type': 'attention',
|
||||
'gaia_source': 'main_web',
|
||||
'name': name,
|
||||
'web_location': 333.999,
|
||||
};
|
||||
Map params = await WbiSign().makSign(data);
|
||||
var res = await Request().get(Api.followSearch, data: {
|
||||
...data,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': FollowDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'dart:math';
|
||||
import '../models/msg/account.dart';
|
||||
import '../models/msg/session.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
@ -22,14 +23,22 @@ class MsgHttp {
|
||||
Map signParams = await WbiSign().makSign(params);
|
||||
var res = await Request().get(Api.sessionList, data: signParams);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SessionDataModel.fromJson(res.data['data']),
|
||||
};
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SessionDataModel.fromJson(res.data['data']),
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': err.toString(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
@ -42,12 +51,16 @@ class MsgHttp {
|
||||
'mobi_app': 'web',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']
|
||||
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']
|
||||
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} catch (err) {
|
||||
print('err🔟: $err');
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
@ -86,4 +99,125 @@ class MsgHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 消息标记已读
|
||||
static Future ackSessionMsg({
|
||||
int? talkerId,
|
||||
int? ackSeqno,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map params = await WbiSign().makSign({
|
||||
'talker_id': talkerId,
|
||||
'session_type': 1,
|
||||
'ack_seqno': ackSeqno,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf
|
||||
});
|
||||
var res = await Request().get(Api.ackSessionMsg, data: params);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 发送私信
|
||||
static Future sendMsg({
|
||||
int? senderUid,
|
||||
int? receiverId,
|
||||
int? receiverType,
|
||||
int? msgType,
|
||||
dynamic content,
|
||||
}) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
Map<String, dynamic> params = await WbiSign().makSign({
|
||||
'msg[sender_uid]': senderUid,
|
||||
'msg[receiver_id]': receiverId,
|
||||
'msg[receiver_type]': receiverType ?? 1,
|
||||
'msg[msg_type]': msgType ?? 1,
|
||||
'msg[msg_status]': 0,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'msg[new_face_version]': 0,
|
||||
'msg[content]': content,
|
||||
'from_firework': 0,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
});
|
||||
var res =
|
||||
await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{
|
||||
...params,
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
}, data: {
|
||||
'w_sender_uid': params['msg[sender_uid]'],
|
||||
'w_receiver_id': params['msg[receiver_id]'],
|
||||
'w_dev_id': params['msg[dev_id]'],
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static String getDevId() {
|
||||
final List<String> b = [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'D',
|
||||
'E',
|
||||
'F'
|
||||
];
|
||||
final List<String> s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split('');
|
||||
for (int i = 0; i < s.length; i++) {
|
||||
if ('-' == s[i] || '4' == s[i]) {
|
||||
continue;
|
||||
}
|
||||
final int randomInt = Random().nextInt(16);
|
||||
if ('x' == s[i]) {
|
||||
s[i] = b[randomInt];
|
||||
} else {
|
||||
s[i] = b[3 & randomInt | 8];
|
||||
}
|
||||
}
|
||||
return s.join();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import '../models/video/reply/data.dart';
|
||||
import '../models/video/reply/emote.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
@ -100,4 +101,23 @@ class ReplyHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future getEmoteList({String? business}) async {
|
||||
var res = await Request().get(Api.emojiList, data: {
|
||||
'business': business ?? 'reply',
|
||||
'web_location': '333.1245',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': EmoteModelData.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import '../models/user/fav_folder.dart';
|
||||
import '../models/user/history.dart';
|
||||
import '../models/user/info.dart';
|
||||
import '../models/user/stat.dart';
|
||||
import '../models/user/sub_detail.dart';
|
||||
import '../models/user/sub_folder.dart';
|
||||
import 'api.dart';
|
||||
import 'init.dart';
|
||||
|
||||
@ -305,4 +307,46 @@ class UserHttp {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 我的订阅
|
||||
static Future userSubFolder({
|
||||
required int mid,
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(Api.userSubFolder, data: {
|
||||
'up_mid': mid,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
'platform': 'web',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SubFolderModelData.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future userSubFolderDetail({
|
||||
required int seasonId,
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(Api.userSubFolderDetail, data: {
|
||||
'season_id': seasonId,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': SubDetailModelData.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import '../models/user/fav_folder.dart';
|
||||
import '../models/video/ai.dart';
|
||||
import '../models/video/play/url.dart';
|
||||
import '../models/video_detail_res.dart';
|
||||
import '../utils/recommend_filter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
import 'api.dart';
|
||||
@ -46,8 +47,13 @@ class VideoHttp {
|
||||
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||
for (var i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
if (i['goto'] == 'av' && !blackMidsList.contains(i['owner']['mid'])) {
|
||||
list.add(RecVideoItemModel.fromJson(i));
|
||||
if (i['goto'] == 'av' &&
|
||||
(i['owner'] != null &&
|
||||
!blackMidsList.contains(i['owner']['mid']))) {
|
||||
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
@ -59,7 +65,9 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future rcmdVideoListApp({int? ps, required int freshIdx}) async {
|
||||
// 添加额外的loginState变量模拟未登录状态
|
||||
static Future rcmdVideoListApp(
|
||||
{bool loginStatus = true, required int freshIdx}) async {
|
||||
try {
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
@ -72,9 +80,11 @@ class VideoHttp {
|
||||
'device_name': 'vivo',
|
||||
'pull': freshIdx == 0 ? 'true' : 'false',
|
||||
'appkey': Constants.appKey,
|
||||
'access_key': localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
''
|
||||
'access_key': loginStatus
|
||||
? (localCache.get(LocalCacheKey.accessKey,
|
||||
defaultValue: {})['value'] ??
|
||||
'')
|
||||
: ''
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
@ -87,12 +97,15 @@ class VideoHttp {
|
||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||
(i['args'] != null &&
|
||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||
list.add(RecVideoItemAppModel.fromJson(i));
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': ''};
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err.toString()};
|
||||
@ -117,7 +130,7 @@ class VideoHttp {
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
@ -203,7 +216,10 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem, relatedVideos: true)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
@ -224,10 +240,11 @@ class VideoHttp {
|
||||
// 获取投币状态
|
||||
static Future hasCoinVideo({required String bvid}) async {
|
||||
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
|
||||
print('res: $res');
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': true, 'data': []};
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
@ -305,7 +322,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': []};
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@ -361,7 +378,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': true, 'data': []};
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +394,7 @@ class VideoHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': true, 'data': []};
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,6 +450,8 @@ class VideoHttp {
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': null, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@ -453,10 +472,30 @@ class VideoHttp {
|
||||
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': []
|
||||
};
|
||||
return {'status': false, 'data': []};
|
||||
}
|
||||
}
|
||||
|
||||
// 视频排行
|
||||
static Future getRankVideoList(int rid) async {
|
||||
try {
|
||||
var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
|
||||
var res = await Request().get(rankApi);
|
||||
if (res.data['code'] == 0) {
|
||||
List<HotVideoItemModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||
for (var i in res.data['data']['list']) {
|
||||
if (!blackMidsList.contains(i['owner']['mid'])) {
|
||||
list.add(HotVideoItemModel.fromJson(i));
|
||||
}
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,11 +16,13 @@ import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/router/app_pages.dart';
|
||||
import 'package:pilipala/pages/main/view.dart';
|
||||
import 'package:pilipala/services/disable_battery_opt.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/app_scheme.dart';
|
||||
import 'package:pilipala/utils/data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
|
||||
import 'package:pilipala/utils/recommend_filter.dart';
|
||||
import 'package:catcher_2/catcher_2.dart';
|
||||
import './services/loggeer.dart';
|
||||
|
||||
@ -34,6 +36,7 @@ void main() async {
|
||||
await setupServiceLocator();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
RecommendFilter();
|
||||
|
||||
// 异常捕获 logo记录
|
||||
final Catcher2Options debugConfig = Catcher2Options(
|
||||
@ -68,8 +71,8 @@ void main() async {
|
||||
statusBarColor: Colors.transparent,
|
||||
));
|
||||
Data.init();
|
||||
GStrorage.lazyInit();
|
||||
PiliSchame.init();
|
||||
DisableBatteryOpt();
|
||||
});
|
||||
}
|
||||
|
||||
|
9
lib/models/common/dynamic_badge_mode.dart
Normal file
9
lib/models/common/dynamic_badge_mode.dart
Normal file
@ -0,0 +1,9 @@
|
||||
enum DynamicBadgeMode { hidden, point, number }
|
||||
|
||||
extension DynamicBadgeModeDesc on DynamicBadgeMode {
|
||||
String get description => ['隐藏', '红点', '数字'][index];
|
||||
}
|
||||
|
||||
extension DynamicBadgeModeCode on DynamicBadgeMode {
|
||||
int get code => [0, 1, 2][index];
|
||||
}
|
@ -7,5 +7,5 @@ enum DynamicsType {
|
||||
|
||||
extension BusinessTypeExtension on DynamicsType {
|
||||
String get values => ['all', 'video', 'pgc', 'article'][index];
|
||||
String get labels => ['全部', '视频', '追番', '专栏'][index];
|
||||
String get labels => ['全部', '投稿', '番剧', '专栏'][index];
|
||||
}
|
||||
|
12
lib/models/common/gesture_mode.dart
Normal file
12
lib/models/common/gesture_mode.dart
Normal file
@ -0,0 +1,12 @@
|
||||
enum FullScreenGestureMode {
|
||||
/// 从上滑到下
|
||||
fromToptoBottom,
|
||||
|
||||
/// 从下滑到上
|
||||
fromBottomtoTop,
|
||||
}
|
||||
|
||||
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
||||
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
|
||||
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
|
||||
}
|
4
lib/models/common/index.dart
Normal file
4
lib/models/common/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library commonn_model;
|
||||
|
||||
export './business_type.dart';
|
||||
export './gesture_mode.dart';
|
56
lib/models/common/nav_bar_config.dart
Normal file
56
lib/models/common/nav_bar_config.dart
Normal file
@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
List defaultNavigationBars = [
|
||||
{
|
||||
'id': 0,
|
||||
'icon': const Icon(
|
||||
Icons.home_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.home,
|
||||
size: 21,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 1,
|
||||
'icon': const Icon(
|
||||
Icons.trending_up,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.trending_up_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'label': "排行榜",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'icon': const Icon(
|
||||
Icons.motion_photos_on_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.motion_photos_on,
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'icon': const Icon(
|
||||
Icons.video_collection_outlined,
|
||||
size: 20,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.video_collection,
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
];
|
240
lib/models/common/rank_type.dart
Normal file
240
lib/models/common/rank_type.dart
Normal file
@ -0,0 +1,240 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/rank/zone/index.dart';
|
||||
|
||||
enum RandType {
|
||||
all,
|
||||
creation,
|
||||
animation,
|
||||
music,
|
||||
dance,
|
||||
game,
|
||||
knowledge,
|
||||
technology,
|
||||
sport,
|
||||
car,
|
||||
life,
|
||||
food,
|
||||
animal,
|
||||
madness,
|
||||
fashion,
|
||||
entertainment,
|
||||
film,
|
||||
origin,
|
||||
rookie
|
||||
}
|
||||
|
||||
extension RankTypeDesc on RandType {
|
||||
String get description => [
|
||||
'全站',
|
||||
'国创相关',
|
||||
'动画',
|
||||
'音乐',
|
||||
'舞蹈',
|
||||
'游戏',
|
||||
'知识',
|
||||
'科技',
|
||||
'运动',
|
||||
'汽车',
|
||||
'生活',
|
||||
'美食',
|
||||
'动物圈',
|
||||
'鬼畜',
|
||||
'时尚',
|
||||
'娱乐',
|
||||
'影视'
|
||||
][index];
|
||||
|
||||
String get id => [
|
||||
'all',
|
||||
'creation',
|
||||
'animation',
|
||||
'music',
|
||||
'dance',
|
||||
'game',
|
||||
'knowledge',
|
||||
'technology',
|
||||
'sport',
|
||||
'car',
|
||||
'life',
|
||||
'food',
|
||||
'animal',
|
||||
'madness',
|
||||
'fashion',
|
||||
'entertainment',
|
||||
'film'
|
||||
][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '全站',
|
||||
'type': RandType.all,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 0),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '国创相关',
|
||||
'type': RandType.creation,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 168),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '动画',
|
||||
'type': RandType.animation,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 1),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '音乐',
|
||||
'type': RandType.music,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 3),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '舞蹈',
|
||||
'type': RandType.dance,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 129),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '游戏',
|
||||
'type': RandType.game,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 4),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '知识',
|
||||
'type': RandType.knowledge,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 36),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '科技',
|
||||
'type': RandType.technology,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 188),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '运动',
|
||||
'type': RandType.sport,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 234),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '汽车',
|
||||
'type': RandType.car,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 223),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '生活',
|
||||
'type': RandType.life,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 160),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '美食',
|
||||
'type': RandType.food,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 211),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '动物圈',
|
||||
'type': RandType.animal,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 217),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '鬼畜',
|
||||
'type': RandType.madness,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 119),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '时尚',
|
||||
'type': RandType.fashion,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 155),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '娱乐',
|
||||
'type': RandType.entertainment,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 5),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.live_tv_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '影视',
|
||||
'type': RandType.film,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'page': const ZonePage(rid: 181),
|
||||
}
|
||||
];
|
@ -1,7 +1,7 @@
|
||||
// 首页推荐类型
|
||||
enum RcmdType { web, app }
|
||||
enum RcmdType { web, app, notLogin }
|
||||
|
||||
extension RcmdTypeExtension on RcmdType {
|
||||
String get values => ['web', 'app'][index];
|
||||
String get labels => ['web端', 'app端'][index];
|
||||
String get values => ['web', 'app', 'notLogin'][index];
|
||||
String get labels => ['web端', 'app端', '游客模式'][index];
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
enum ReplySortType { time, like, reply }
|
||||
enum ReplySortType { time, like }
|
||||
|
||||
extension ReplySortTypeExtension on ReplySortType {
|
||||
String get titles => ['最新评论', '最热评论', '回复最多'][index];
|
||||
String get labels => ['最新', '最热', '最多回复'][index];
|
||||
String get titles => ['最新评论', '最热评论'][index];
|
||||
String get labels => ['最新', '最热'][index];
|
||||
}
|
||||
|
@ -2,18 +2,28 @@ class FollowUpModel {
|
||||
FollowUpModel({
|
||||
this.liveUsers,
|
||||
this.upList,
|
||||
this.liveList,
|
||||
this.myInfo,
|
||||
});
|
||||
|
||||
LiveUsers? liveUsers;
|
||||
List<UpItem>? upList;
|
||||
List<LiveUserItem>? liveList;
|
||||
MyInfo? myInfo;
|
||||
|
||||
FollowUpModel.fromJson(Map<String, dynamic> json) {
|
||||
liveUsers = json['live_users'] != null
|
||||
? LiveUsers.fromJson(json['live_users'])
|
||||
: null;
|
||||
liveList = json['live_users'] != null
|
||||
? json['live_users']['items']
|
||||
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
upList = json['up_list'] != null
|
||||
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
||||
: [];
|
||||
myInfo = json['my_info'] != null ? MyInfo.fromJson(json['my_info']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,3 +103,21 @@ class UpItem {
|
||||
uname = json['uname'];
|
||||
}
|
||||
}
|
||||
|
||||
class MyInfo {
|
||||
MyInfo({
|
||||
this.face,
|
||||
this.mid,
|
||||
this.name,
|
||||
});
|
||||
|
||||
String? face;
|
||||
int? mid;
|
||||
String? name;
|
||||
|
||||
MyInfo.fromJson(Map<String, dynamic> json) {
|
||||
face = json['face'];
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,9 @@ class LatestDataModel {
|
||||
url = json['url'];
|
||||
tagName = json['tag_name'];
|
||||
createdAt = json['created_at'];
|
||||
assets =
|
||||
json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList();
|
||||
assets = json['assets'] != null
|
||||
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
|
||||
: [];
|
||||
body = json['body'];
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
|
||||
class RecVideoItemAppModel {
|
||||
RecVideoItemAppModel({
|
||||
this.id,
|
||||
@ -28,7 +30,7 @@ class RecVideoItemAppModel {
|
||||
int? cid;
|
||||
String? pic;
|
||||
RcmdStat? stat;
|
||||
String? duration;
|
||||
int? duration;
|
||||
String? title;
|
||||
int? isFollowed;
|
||||
RcmdOwner? owner;
|
||||
@ -50,17 +52,32 @@ class RecVideoItemAppModel {
|
||||
? json['player_args']['aid']
|
||||
: int.parse(json['param'] ?? '-1');
|
||||
aid = json['player_args'] != null ? json['player_args']['aid'] : -1;
|
||||
bvid = null;
|
||||
bvid = json['player_args'] != null
|
||||
? IdUtils.av2bv(json['player_args']['aid'])
|
||||
: '';
|
||||
cid = json['player_args'] != null ? json['player_args']['cid'] : -1;
|
||||
pic = json['cover'];
|
||||
stat = RcmdStat.fromJson(json);
|
||||
duration = json['cover_right_text'];
|
||||
// 改用player_args中的duration作为原始数据(秒数)
|
||||
duration =
|
||||
json['player_args'] != null ? json['player_args']['duration'] : -1;
|
||||
//duration = json['cover_right_text'];
|
||||
title = json['title'];
|
||||
isFollowed = 0;
|
||||
owner = RcmdOwner.fromJson(json);
|
||||
rcmdReason = json['rcmd_reason_style'] != null
|
||||
? RcmdReason.fromJson(json['rcmd_reason_style'])
|
||||
: null;
|
||||
// 由于app端api并不会直接返回与owner的关注状态
|
||||
// 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效
|
||||
isFollowed = rcmdReason != null &&
|
||||
rcmdReason!.content != null &&
|
||||
rcmdReason!.content!.contains('关注')
|
||||
? 1
|
||||
: 0;
|
||||
// 如果是,就无需再显示推荐原因,交由view统一处理即可
|
||||
if (isFollowed == 1) {
|
||||
rcmdReason = null;
|
||||
}
|
||||
goto = json['goto'];
|
||||
param = int.parse(json['param']);
|
||||
uri = json['uri'];
|
||||
|
43
lib/models/live/quality.dart
Normal file
43
lib/models/live/quality.dart
Normal file
@ -0,0 +1,43 @@
|
||||
enum LiveQuality {
|
||||
dolby,
|
||||
super4K,
|
||||
origin,
|
||||
bluRay,
|
||||
superHD,
|
||||
smooth,
|
||||
flunt,
|
||||
}
|
||||
|
||||
extension LiveQualityCode on LiveQuality {
|
||||
static final List<int> _codeList = [
|
||||
30000,
|
||||
20000,
|
||||
10000,
|
||||
400,
|
||||
250,
|
||||
150,
|
||||
80,
|
||||
];
|
||||
int get code => _codeList[index];
|
||||
|
||||
static LiveQuality? fromCode(int code) {
|
||||
final index = _codeList.indexOf(code);
|
||||
if (index != -1) {
|
||||
return LiveQuality.values[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
extension VideoQualityDesc on LiveQuality {
|
||||
static final List<String> _descList = [
|
||||
'杜比',
|
||||
'4K',
|
||||
'原画',
|
||||
'蓝光',
|
||||
'超清',
|
||||
'高清',
|
||||
'流畅',
|
||||
];
|
||||
get description => _descList[index];
|
||||
}
|
130
lib/models/live/room_info_h5.dart
Normal file
130
lib/models/live/room_info_h5.dart
Normal file
@ -0,0 +1,130 @@
|
||||
class RoomInfoH5Model {
|
||||
RoomInfoH5Model({
|
||||
this.roomInfo,
|
||||
this.anchorInfo,
|
||||
this.isRoomFeed,
|
||||
this.watchedShow,
|
||||
this.likeInfoV3,
|
||||
this.blockInfo,
|
||||
});
|
||||
|
||||
RoomInfo? roomInfo;
|
||||
AnchorInfo? anchorInfo;
|
||||
int? isRoomFeed;
|
||||
Map? watchedShow;
|
||||
LikeInfoV3? likeInfoV3;
|
||||
Map? blockInfo;
|
||||
|
||||
RoomInfoH5Model.fromJson(Map<String, dynamic> json) {
|
||||
roomInfo = RoomInfo.fromJson(json['room_info']);
|
||||
anchorInfo = AnchorInfo.fromJson(json['anchor_info']);
|
||||
isRoomFeed = json['is_room_feed'];
|
||||
watchedShow = json['watched_show'];
|
||||
likeInfoV3 = LikeInfoV3.fromJson(json['like_info_v3']);
|
||||
blockInfo = json['block_info'];
|
||||
}
|
||||
}
|
||||
|
||||
class RoomInfo {
|
||||
RoomInfo({
|
||||
this.uid,
|
||||
this.roomId,
|
||||
this.title,
|
||||
this.cover,
|
||||
this.description,
|
||||
this.liveStatus,
|
||||
this.liveStartTime,
|
||||
this.areaId,
|
||||
this.areaName,
|
||||
this.parentAreaId,
|
||||
this.parentAreaName,
|
||||
this.online,
|
||||
this.background,
|
||||
this.appBackground,
|
||||
this.liveId,
|
||||
});
|
||||
|
||||
int? uid;
|
||||
int? roomId;
|
||||
String? title;
|
||||
String? cover;
|
||||
String? description;
|
||||
int? liveStatus;
|
||||
int? liveStartTime;
|
||||
int? areaId;
|
||||
String? areaName;
|
||||
int? parentAreaId;
|
||||
String? parentAreaName;
|
||||
int? online;
|
||||
String? background;
|
||||
String? appBackground;
|
||||
String? liveId;
|
||||
|
||||
RoomInfo.fromJson(Map<String, dynamic> json) {
|
||||
uid = json['uid'];
|
||||
roomId = json['room_id'];
|
||||
title = json['title'];
|
||||
cover = json['cover'];
|
||||
description = json['description'];
|
||||
liveStatus = json['liveS_satus'];
|
||||
liveStartTime = json['live_start_time'];
|
||||
areaId = json['area_id'];
|
||||
areaName = json['area_name'];
|
||||
parentAreaId = json['parent_area_id'];
|
||||
parentAreaName = json['parent_area_name'];
|
||||
online = json['online'];
|
||||
background = json['background'];
|
||||
appBackground = json['app_background'];
|
||||
liveId = json['live_id'];
|
||||
}
|
||||
}
|
||||
|
||||
class AnchorInfo {
|
||||
AnchorInfo({
|
||||
this.baseInfo,
|
||||
this.relationInfo,
|
||||
});
|
||||
|
||||
BaseInfo? baseInfo;
|
||||
RelationInfo? relationInfo;
|
||||
|
||||
AnchorInfo.fromJson(Map<String, dynamic> json) {
|
||||
baseInfo = BaseInfo.fromJson(json['base_info']);
|
||||
relationInfo = RelationInfo.fromJson(json['relation_info']);
|
||||
}
|
||||
}
|
||||
|
||||
class BaseInfo {
|
||||
BaseInfo({
|
||||
this.uname,
|
||||
this.face,
|
||||
});
|
||||
|
||||
String? uname;
|
||||
String? face;
|
||||
|
||||
BaseInfo.fromJson(Map<String, dynamic> json) {
|
||||
uname = json['uname'];
|
||||
face = json['face'];
|
||||
}
|
||||
}
|
||||
|
||||
class RelationInfo {
|
||||
RelationInfo({this.attention});
|
||||
|
||||
int? attention;
|
||||
|
||||
RelationInfo.fromJson(Map<String, dynamic> json) {
|
||||
attention = json['attention'];
|
||||
}
|
||||
}
|
||||
|
||||
class LikeInfoV3 {
|
||||
LikeInfoV3({this.totalLikes});
|
||||
|
||||
int? totalLikes;
|
||||
|
||||
LikeInfoV3.fromJson(Map<String, dynamic> json) {
|
||||
totalLikes = json['total_likes'];
|
||||
}
|
||||
}
|
@ -142,7 +142,7 @@ class Stat {
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = json["play"];
|
||||
danmaku = json['comment'];
|
||||
danmaku = json['video_review'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import './model_owner.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
@ -38,7 +36,7 @@ class RecVideoItemModel {
|
||||
@HiveField(6)
|
||||
String? title = '';
|
||||
@HiveField(7)
|
||||
String? duration = '';
|
||||
int? duration = -1;
|
||||
@HiveField(8)
|
||||
int? pubdate = -1;
|
||||
@HiveField(9)
|
||||
@ -58,7 +56,7 @@ class RecVideoItemModel {
|
||||
uri = json["uri"];
|
||||
pic = json["pic"];
|
||||
title = json["title"];
|
||||
duration = Utils.tampToSeektime(json["duration"]);
|
||||
duration = json["duration"];
|
||||
pubdate = json["pubdate"];
|
||||
owner = Owner.fromJson(json["owner"]);
|
||||
stat = Stat.fromJson(json["stat"]);
|
||||
@ -77,14 +75,15 @@ class Stat {
|
||||
this.danmu,
|
||||
});
|
||||
@HiveField(0)
|
||||
String? view;
|
||||
int? view;
|
||||
@HiveField(1)
|
||||
int? like;
|
||||
@HiveField(2)
|
||||
int? danmu;
|
||||
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
view = Utils.numFormat(json["view"]);
|
||||
// 无需在model中转换以保留原始数据,在view层处理即可
|
||||
view = json["view"];
|
||||
like = json["like"];
|
||||
danmu = json['danmaku'];
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
|
||||
uri: fields[4] as String?,
|
||||
pic: fields[5] as String?,
|
||||
title: fields[6] as String?,
|
||||
duration: fields[7] as String?,
|
||||
duration: fields[7] as int?,
|
||||
pubdate: fields[8] as int?,
|
||||
owner: fields[9] as Owner?,
|
||||
stat: fields[10] as Stat?,
|
||||
@ -87,7 +87,7 @@ class StatAdapter extends TypeAdapter<Stat> {
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Stat(
|
||||
view: fields[0] as String?,
|
||||
view: fields[0] as int?,
|
||||
like: fields[1] as int?,
|
||||
danmu: fields[2] as int?,
|
||||
);
|
||||
|
@ -8,7 +8,7 @@ class SessionDataModel {
|
||||
this.hasMore,
|
||||
});
|
||||
|
||||
List? sessionList;
|
||||
List<SessionList>? sessionList;
|
||||
int? hasMore;
|
||||
|
||||
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -121,35 +121,37 @@ class LastMsg {
|
||||
this.msgKey,
|
||||
this.msgStatus,
|
||||
this.notifyCode,
|
||||
this.newFaceVersion,
|
||||
// this.newFaceVersion,
|
||||
});
|
||||
|
||||
int? senderIid;
|
||||
int? receiverType;
|
||||
int? receiverId;
|
||||
int? msgType;
|
||||
Map? content;
|
||||
dynamic content;
|
||||
int? msgSeqno;
|
||||
int? timestamp;
|
||||
String? atUids;
|
||||
int? msgKey;
|
||||
int? msgStatus;
|
||||
String? notifyCode;
|
||||
int? newFaceVersion;
|
||||
// int? newFaceVersion;
|
||||
|
||||
LastMsg.fromJson(Map<String, dynamic> json) {
|
||||
senderIid = json['sender_uid'];
|
||||
receiverType = json['receiver_type'];
|
||||
receiverId = json['receiver_id'];
|
||||
msgType = json['msg_type'];
|
||||
content = jsonDecode(json['content']);
|
||||
content = json['content'] != null && json['content'] != ''
|
||||
? jsonDecode(json['content'])
|
||||
: '';
|
||||
msgSeqno = json['msg_seqno'];
|
||||
timestamp = json['timestamp'];
|
||||
atUids = json['at_uids'];
|
||||
msgKey = json['msg_key'];
|
||||
msgStatus = json['msg_status'];
|
||||
notifyCode = json['notify_code'];
|
||||
newFaceVersion = json['new_face_version'];
|
||||
// newFaceVersion = json['new_face_version'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +216,9 @@ class MessageItem {
|
||||
receiverId = json['receiver_id'];
|
||||
// 1 文本 2 图片 18 系统提示 10 系统通知 5 撤回的消息
|
||||
msgType = json['msg_type'];
|
||||
content = jsonDecode(json['content']);
|
||||
content = json['content'] != null && json['content'] != ''
|
||||
? jsonDecode(json['content'])
|
||||
: '';
|
||||
msgSeqno = json['msg_seqno'];
|
||||
timestamp = json['timestamp'];
|
||||
atUids = json['at_uids'];
|
||||
|
@ -85,7 +85,9 @@ class SearchVideoItemModel {
|
||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||
title = Em.regTitle(json['title']);
|
||||
description = json['description'];
|
||||
pic = 'https:${json['pic']}';
|
||||
pic = json['pic'] != null && json['pic'].startsWith('//')
|
||||
? 'https:${json['pic']}'
|
||||
: json['pic'] ?? '';
|
||||
videoReview = json['video_review'];
|
||||
pubdate = json['pubdate'];
|
||||
senddate = json['senddate'];
|
||||
|
@ -15,7 +15,7 @@ class FavFolderData {
|
||||
? json['list']
|
||||
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList()
|
||||
: [FavFolderItemData()];
|
||||
: <FavFolderItemData>[];
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
123
lib/models/user/sub_detail.dart
Normal file
123
lib/models/user/sub_detail.dart
Normal file
@ -0,0 +1,123 @@
|
||||
class SubDetailModelData {
|
||||
DetailInfo? info;
|
||||
List<SubDetailMediaItem>? medias;
|
||||
|
||||
SubDetailModelData({this.info, this.medias});
|
||||
|
||||
SubDetailModelData.fromJson(Map<String, dynamic> json) {
|
||||
info = DetailInfo.fromJson(json['info']);
|
||||
if (json['medias'] != null) {
|
||||
medias = <SubDetailMediaItem>[];
|
||||
json['medias'].forEach((v) {
|
||||
medias!.add(SubDetailMediaItem.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubDetailMediaItem {
|
||||
int? id;
|
||||
String? title;
|
||||
String? cover;
|
||||
String? pic;
|
||||
int? duration;
|
||||
int? pubtime;
|
||||
String? bvid;
|
||||
Map? upper;
|
||||
Map? cntInfo;
|
||||
int? enableVt;
|
||||
String? vtDisplay;
|
||||
|
||||
SubDetailMediaItem({
|
||||
this.id,
|
||||
this.title,
|
||||
this.cover,
|
||||
this.pic,
|
||||
this.duration,
|
||||
this.pubtime,
|
||||
this.bvid,
|
||||
this.upper,
|
||||
this.cntInfo,
|
||||
this.enableVt,
|
||||
this.vtDisplay,
|
||||
});
|
||||
|
||||
SubDetailMediaItem.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
title = json['title'];
|
||||
cover = json['cover'];
|
||||
pic = json['cover'];
|
||||
duration = json['duration'];
|
||||
pubtime = json['pubtime'];
|
||||
bvid = json['bvid'];
|
||||
upper = json['upper'];
|
||||
cntInfo = json['cnt_info'];
|
||||
enableVt = json['enable_vt'];
|
||||
vtDisplay = json['vt_display'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['title'] = title;
|
||||
data['cover'] = cover;
|
||||
data['duration'] = duration;
|
||||
data['pubtime'] = pubtime;
|
||||
data['bvid'] = bvid;
|
||||
data['upper'] = upper;
|
||||
data['cnt_info'] = cntInfo;
|
||||
data['enable_vt'] = enableVt;
|
||||
data['vt_display'] = vtDisplay;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class DetailInfo {
|
||||
int? id;
|
||||
int? seasonType;
|
||||
String? title;
|
||||
String? cover;
|
||||
Map? upper;
|
||||
Map? cntInfo;
|
||||
int? mediaCount;
|
||||
String? intro;
|
||||
int? enableVt;
|
||||
|
||||
DetailInfo({
|
||||
this.id,
|
||||
this.seasonType,
|
||||
this.title,
|
||||
this.cover,
|
||||
this.upper,
|
||||
this.cntInfo,
|
||||
this.mediaCount,
|
||||
this.intro,
|
||||
this.enableVt,
|
||||
});
|
||||
|
||||
DetailInfo.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
seasonType = json['season_type'];
|
||||
title = json['title'];
|
||||
cover = json['cover'];
|
||||
upper = json['upper'];
|
||||
cntInfo = json['cnt_info'];
|
||||
mediaCount = json['media_count'];
|
||||
intro = json['intro'];
|
||||
enableVt = json['enable_vt'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['season_type'] = seasonType;
|
||||
data['title'] = title;
|
||||
data['cover'] = cover;
|
||||
data['upper'] = upper;
|
||||
data['cnt_info'] = cntInfo;
|
||||
data['media_count'] = mediaCount;
|
||||
data['intro'] = intro;
|
||||
data['enable_vt'] = enableVt;
|
||||
return data;
|
||||
}
|
||||
}
|
111
lib/models/user/sub_folder.dart
Normal file
111
lib/models/user/sub_folder.dart
Normal file
@ -0,0 +1,111 @@
|
||||
class SubFolderModelData {
|
||||
final int? count;
|
||||
final List<SubFolderItemData>? list;
|
||||
|
||||
SubFolderModelData({
|
||||
this.count,
|
||||
this.list,
|
||||
});
|
||||
|
||||
factory SubFolderModelData.fromJson(Map<String, dynamic> json) {
|
||||
return SubFolderModelData(
|
||||
count: json['count'],
|
||||
list: json['list'] != null
|
||||
? (json['list'] as List)
|
||||
.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
|
||||
.toList()
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SubFolderItemData {
|
||||
final int? id;
|
||||
final int? fid;
|
||||
final int? mid;
|
||||
final int? attr;
|
||||
final String? title;
|
||||
final String? cover;
|
||||
final Upper? upper;
|
||||
final int? coverType;
|
||||
final String? intro;
|
||||
final int? ctime;
|
||||
final int? mtime;
|
||||
final int? state;
|
||||
final int? favState;
|
||||
final int? mediaCount;
|
||||
final int? viewCount;
|
||||
final int? vt;
|
||||
final int? playSwitch;
|
||||
final int? type;
|
||||
final String? link;
|
||||
final String? bvid;
|
||||
|
||||
SubFolderItemData({
|
||||
this.id,
|
||||
this.fid,
|
||||
this.mid,
|
||||
this.attr,
|
||||
this.title,
|
||||
this.cover,
|
||||
this.upper,
|
||||
this.coverType,
|
||||
this.intro,
|
||||
this.ctime,
|
||||
this.mtime,
|
||||
this.state,
|
||||
this.favState,
|
||||
this.mediaCount,
|
||||
this.viewCount,
|
||||
this.vt,
|
||||
this.playSwitch,
|
||||
this.type,
|
||||
this.link,
|
||||
this.bvid,
|
||||
});
|
||||
|
||||
factory SubFolderItemData.fromJson(Map<String, dynamic> json) {
|
||||
return SubFolderItemData(
|
||||
id: json['id'],
|
||||
fid: json['fid'],
|
||||
mid: json['mid'],
|
||||
attr: json['attr'],
|
||||
title: json['title'],
|
||||
cover: json['cover'],
|
||||
upper: json['upper'] != null ? Upper.fromJson(json['upper']) : null,
|
||||
coverType: json['cover_type'],
|
||||
intro: json['intro'],
|
||||
ctime: json['ctime'],
|
||||
mtime: json['mtime'],
|
||||
state: json['state'],
|
||||
favState: json['fav_state'],
|
||||
mediaCount: json['media_count'],
|
||||
viewCount: json['view_count'],
|
||||
vt: json['vt'],
|
||||
playSwitch: json['play_switch'],
|
||||
type: json['type'],
|
||||
link: json['link'],
|
||||
bvid: json['bvid'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Upper {
|
||||
final int? mid;
|
||||
final String? name;
|
||||
final String? face;
|
||||
|
||||
Upper({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
|
||||
factory Upper.fromJson(Map<String, dynamic> json) {
|
||||
return Upper(
|
||||
mid: json['mid'],
|
||||
name: json['name'],
|
||||
face: json['face'],
|
||||
);
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ class PlayUrlModel {
|
||||
String? seekParam;
|
||||
String? seekType;
|
||||
Dash? dash;
|
||||
List<Durl>? durl;
|
||||
List<FormatItem>? supportFormats;
|
||||
// String? highFormat;
|
||||
int? lastPlayTime;
|
||||
@ -52,7 +53,8 @@ class PlayUrlModel {
|
||||
videoCodecid = json['video_codecid'];
|
||||
seekParam = json['seek_param'];
|
||||
seekType = json['seek_type'];
|
||||
dash = Dash.fromJson(json['dash']);
|
||||
dash = json['dash'] != null ? Dash.fromJson(json['dash']) : null;
|
||||
durl = json['durl']?.map<Durl>((e) => Durl.fromJson(e)).toList();
|
||||
supportFormats = json['support_formats'] != null
|
||||
? json['support_formats']
|
||||
.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
@ -250,3 +252,30 @@ class Flac {
|
||||
audio = json['audio'] != null ? AudioItem.fromJson(json['audio']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
class Durl {
|
||||
Durl({
|
||||
this.order,
|
||||
this.length,
|
||||
this.size,
|
||||
this.ahead,
|
||||
this.vhead,
|
||||
this.url,
|
||||
});
|
||||
|
||||
int? order;
|
||||
int? length;
|
||||
int? size;
|
||||
String? ahead;
|
||||
String? vhead;
|
||||
String? url;
|
||||
|
||||
Durl.fromJson(Map<String, dynamic> json) {
|
||||
order = json['order'];
|
||||
length = json['length'];
|
||||
size = json['size'];
|
||||
ahead = json['ahead'];
|
||||
vhead = json['vhead'];
|
||||
url = json['url'];
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ class ReplyContent {
|
||||
this.vote,
|
||||
this.richText,
|
||||
this.isText,
|
||||
this.topicsMeta,
|
||||
});
|
||||
|
||||
String? message;
|
||||
@ -20,6 +21,7 @@ class ReplyContent {
|
||||
Map? vote;
|
||||
Map? richText;
|
||||
bool? isText;
|
||||
Map? topicsMeta;
|
||||
|
||||
ReplyContent.fromJson(Map<String, dynamic> json) {
|
||||
message = json['message']
|
||||
@ -39,6 +41,7 @@ class ReplyContent {
|
||||
richText = json['rich_text'] ?? {};
|
||||
// 不包含@ 笔记 图片的时候,文字可折叠
|
||||
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
|
||||
topicsMeta = json['topics_meta'] ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
|
120
lib/models/video/reply/emote.dart
Normal file
120
lib/models/video/reply/emote.dart
Normal file
@ -0,0 +1,120 @@
|
||||
class EmoteModelData {
|
||||
final List<PackageItem>? packages;
|
||||
|
||||
EmoteModelData({
|
||||
required this.packages,
|
||||
});
|
||||
|
||||
factory EmoteModelData.fromJson(Map<String, dynamic> jsonRes) {
|
||||
final List<PackageItem>? packages =
|
||||
jsonRes['packages'] is List ? <PackageItem>[] : null;
|
||||
if (packages != null) {
|
||||
for (final dynamic item in jsonRes['packages']!) {
|
||||
if (item != null) {
|
||||
try {
|
||||
packages.add(PackageItem.fromJson(item));
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return EmoteModelData(
|
||||
packages: packages,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PackageItem {
|
||||
final int? id;
|
||||
final String? text;
|
||||
final String? url;
|
||||
final int? mtime;
|
||||
final int? type;
|
||||
final int? attr;
|
||||
final Meta? meta;
|
||||
final List<Emote>? emote;
|
||||
|
||||
PackageItem({
|
||||
required this.id,
|
||||
required this.text,
|
||||
required this.url,
|
||||
required this.mtime,
|
||||
required this.type,
|
||||
required this.attr,
|
||||
required this.meta,
|
||||
required this.emote,
|
||||
});
|
||||
|
||||
factory PackageItem.fromJson(Map<String, dynamic> jsonRes) {
|
||||
final List<Emote>? emote = jsonRes['emote'] is List ? <Emote>[] : null;
|
||||
if (emote != null) {
|
||||
for (final dynamic item in jsonRes['emote']!) {
|
||||
if (item != null) {
|
||||
try {
|
||||
emote.add(Emote.fromJson(item));
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return PackageItem(
|
||||
id: jsonRes['id'],
|
||||
text: jsonRes['text'],
|
||||
url: jsonRes['url'],
|
||||
mtime: jsonRes['mtime'],
|
||||
type: jsonRes['type'],
|
||||
attr: jsonRes['attr'],
|
||||
meta: Meta.fromJson(jsonRes['meta']),
|
||||
emote: emote,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Meta {
|
||||
final int? size;
|
||||
final List<String>? suggest;
|
||||
|
||||
Meta({
|
||||
required this.size,
|
||||
required this.suggest,
|
||||
});
|
||||
|
||||
factory Meta.fromJson(Map<String, dynamic> jsonRes) => Meta(
|
||||
size: jsonRes['size'],
|
||||
suggest: jsonRes['suggest'] is List ? <String>[] : null,
|
||||
);
|
||||
}
|
||||
|
||||
class Emote {
|
||||
final int? id;
|
||||
final int? packageId;
|
||||
final String? text;
|
||||
final String? url;
|
||||
final int? mtime;
|
||||
final int? type;
|
||||
final int? attr;
|
||||
final Meta? meta;
|
||||
final dynamic activity;
|
||||
|
||||
Emote({
|
||||
required this.id,
|
||||
required this.packageId,
|
||||
required this.text,
|
||||
required this.url,
|
||||
required this.mtime,
|
||||
required this.type,
|
||||
required this.attr,
|
||||
required this.meta,
|
||||
required this.activity,
|
||||
});
|
||||
|
||||
factory Emote.fromJson(Map<String, dynamic> jsonRes) => Emote(
|
||||
id: jsonRes['id'],
|
||||
packageId: jsonRes['package_id'],
|
||||
text: jsonRes['text'],
|
||||
url: jsonRes['url'],
|
||||
mtime: jsonRes['mtime'],
|
||||
type: jsonRes['type'],
|
||||
attr: jsonRes['attr'],
|
||||
meta: Meta.fromJson(jsonRes['meta']),
|
||||
activity: jsonRes['activity'],
|
||||
);
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/models/github/latest.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../utils/cache_manage.dart';
|
||||
|
||||
class AboutPage extends StatefulWidget {
|
||||
const AboutPage({super.key});
|
||||
@ -17,6 +18,19 @@ class AboutPage extends StatefulWidget {
|
||||
|
||||
class _AboutPageState extends State<AboutPage> {
|
||||
final AboutController _aboutController = Get.put(AboutController());
|
||||
String cacheSize = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// 读取缓存占用
|
||||
getCacheSize();
|
||||
}
|
||||
|
||||
Future<void> getCacheSize() async {
|
||||
final res = await CacheManage().loadApplicationCache();
|
||||
setState(() => cacheSize = res);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -39,29 +53,54 @@ class _AboutPageState extends State<AboutPage> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'使用Flutter开发的哔哩哔哩第三方客户端',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
title: const Text('当前版本'),
|
||||
trailing: Text(_aboutController.currentVersion.value,
|
||||
style: subTitleStyle),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
onTap: () => _aboutController.onUpdate(),
|
||||
title: const Text('最新版本'),
|
||||
trailing: Text(
|
||||
_aboutController.isLoading.value
|
||||
? '正在获取'
|
||||
: _aboutController.isUpdate.value
|
||||
? '有新版本 ❤️${_aboutController.remoteVersion.value}'
|
||||
: '当前已是最新版',
|
||||
style: subTitleStyle,
|
||||
() => Badge(
|
||||
isLabelVisible: _aboutController.isLoading.value
|
||||
? false
|
||||
: _aboutController.isUpdate.value,
|
||||
label: const Text('New'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 30),
|
||||
child: FilledButton.tonal(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => _aboutController.githubRelease(),
|
||||
title: const Text('Github下载'),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.panDownload(),
|
||||
title: const Text('网盘下载'),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.webSiteUrl(),
|
||||
title: const Text('官网下载'),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.qimiao(),
|
||||
title: const Text('奇妙应用'),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom +
|
||||
20)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'V${_aboutController.currentVersion.value}',
|
||||
style: subTitleStyle.copyWith(
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -73,14 +112,9 @@ class _AboutPageState extends State<AboutPage> {
|
||||
// size: 16,
|
||||
// ),
|
||||
// ),
|
||||
Divider(
|
||||
thickness: 1,
|
||||
height: 30,
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.githubUrl(),
|
||||
title: const Text('Github'),
|
||||
title: const Text('开源地址'),
|
||||
trailing: Text(
|
||||
'github.com/guozhigq/pilipala',
|
||||
style: subTitleStyle,
|
||||
@ -115,19 +149,43 @@ class _AboutPageState extends State<AboutPage> {
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.qqChanel(),
|
||||
title: const Text('QQ群'),
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
onTap: () => _aboutController.qqChanel(),
|
||||
title: const Text('QQ群'),
|
||||
trailing: Text(
|
||||
'616150809',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.tgChanel(),
|
||||
title: const Text('TG频道'),
|
||||
trailing: Text(
|
||||
'https://t.me/+lm_oOVmF0RJiODk1',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 20)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
title: const Text('交流社区'),
|
||||
trailing: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.tgChanel(),
|
||||
title: const Text('TG频道'),
|
||||
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.aPay(),
|
||||
title: const Text('赞助'),
|
||||
@ -138,6 +196,17 @@ class _AboutPageState extends State<AboutPage> {
|
||||
title: const Text('错误日志'),
|
||||
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
var cleanStatus = await CacheManage().clearCacheAll();
|
||||
if (cleanStatus) {
|
||||
getCacheSize();
|
||||
}
|
||||
},
|
||||
title: const Text('清除缓存'),
|
||||
subtitle: Text('图片及网络缓存 $cacheSize', style: subTitleStyle),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom + 20)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -184,12 +253,16 @@ class AboutController extends GetxController {
|
||||
// 获取远程版本
|
||||
Future getRemoteApp() async {
|
||||
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
|
||||
isLoading.value = false;
|
||||
if (result.data == null || result.data.isEmpty) {
|
||||
SmartDialog.showToast('获取远程版本失败,请检查网络');
|
||||
return;
|
||||
}
|
||||
data = LatestDataModel.fromJson(result.data);
|
||||
remoteAppInfo = data;
|
||||
remoteVersion.value = data.tagName!;
|
||||
isUpdate.value =
|
||||
Utils.needUpdate(currentVersion.value, remoteVersion.value);
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
// 跳转下载/本地更新
|
||||
@ -205,11 +278,26 @@ class AboutController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
githubRelease() {
|
||||
launchUrl(
|
||||
Uri.parse('https://github.com/guozhigq/pilipala/releases'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
|
||||
// 从网盘下载
|
||||
panDownload() {
|
||||
launchUrl(
|
||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
Clipboard.setData(
|
||||
const ClipboardData(text: 'pili'),
|
||||
);
|
||||
SmartDialog.showToast(
|
||||
'已复制提取码:pili',
|
||||
displayTime: const Duration(milliseconds: 500),
|
||||
).then(
|
||||
(value) => launchUrl(
|
||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -225,7 +313,7 @@ class AboutController extends GetxController {
|
||||
// qq频道
|
||||
qqChanel() {
|
||||
Clipboard.setData(
|
||||
const ClipboardData(text: '489981949'),
|
||||
const ClipboardData(text: '616150809'),
|
||||
);
|
||||
SmartDialog.showToast('已复制QQ群号');
|
||||
}
|
||||
@ -266,6 +354,13 @@ class AboutController extends GetxController {
|
||||
);
|
||||
}
|
||||
|
||||
qimiao() {
|
||||
launchUrl(
|
||||
Uri.parse('https://www.magicalapk.com/home'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
|
||||
// 日志
|
||||
logs() {
|
||||
Get.toNamed('/logs');
|
||||
|
@ -7,8 +7,8 @@ import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
class BangumiController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
RxList<BangumiListItemModel> bangumiList = [BangumiListItemModel()].obs;
|
||||
RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
|
||||
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs;
|
||||
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs;
|
||||
int _currentPage = 1;
|
||||
bool isLoadingMore = true;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
|
@ -25,13 +25,6 @@ class BangumiIntroController extends GetxController {
|
||||
? int.tryParse(Get.parameters['epId']!)
|
||||
: null;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
// 视频详情 上个页面传入
|
||||
Map? videoItem = {};
|
||||
BangumiInfoModel? bangumiItem;
|
||||
|
||||
// 请求状态
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
@ -63,27 +56,6 @@ class BangumiIntroController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments.isNotEmpty as bool) {
|
||||
if (Get.arguments.containsKey('bangumiItem') as bool) {
|
||||
preRender = true;
|
||||
bangumiItem = Get.arguments['bangumiItem'];
|
||||
// bangumiItem!['pic'] = args.pic;
|
||||
// if (args.title is String) {
|
||||
// videoItem!['title'] = args.title;
|
||||
// } else {
|
||||
// String str = '';
|
||||
// for (Map map in args.title) {
|
||||
// str += map['text'];
|
||||
// }
|
||||
// videoItem!['title'] = str;
|
||||
// }
|
||||
// if (args.stat != null) {
|
||||
// videoItem!['stat'] = args.stat;
|
||||
// }
|
||||
// videoItem!['pubdate'] = args.pubdate;
|
||||
// videoItem!['owner'] = args.owner;
|
||||
}
|
||||
}
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin = userInfo != null;
|
||||
}
|
||||
@ -183,20 +155,21 @@ class BangumiIntroController extends GetxController {
|
||||
actions: [
|
||||
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
bangumiDetail.value.stat!['coins'] =
|
||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'))
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
bangumiDetail.value.stat!['coins'] =
|
||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
@ -218,14 +191,12 @@ class BangumiIntroController extends GetxController {
|
||||
addIds: addMediaIdsNew.join(','),
|
||||
delIds: delMediaIdsNew.join(','));
|
||||
if (result['status']) {
|
||||
if (result['data']['prompt']) {
|
||||
addMediaIdsNew = [];
|
||||
delMediaIdsNew = [];
|
||||
Get.back();
|
||||
// 重新获取收藏状态
|
||||
queryHasFavVideo();
|
||||
SmartDialog.showToast('✅ 操作成功');
|
||||
}
|
||||
addMediaIdsNew = [];
|
||||
delMediaIdsNew = [];
|
||||
// 重新获取收藏状态
|
||||
queryHasFavVideo();
|
||||
SmartDialog.showToast('✅ 操作成功');
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,10 @@ import 'package:pilipala/models/bangumi/info.dart';
|
||||
import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../../../common/widgets/http_error.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
|
||||
@ -51,9 +50,6 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
cid = widget.cid!;
|
||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
bangumiIntroController.bangumiDetail.listen((BangumiInfoModel value) {
|
||||
bangumiDetail = value;
|
||||
});
|
||||
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
||||
videoDetailCtr.cid.listen((int p0) {
|
||||
cid = p0;
|
||||
@ -68,27 +64,32 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
// 请求成功
|
||||
|
||||
return BangumiInfo(
|
||||
loadingStatus: false,
|
||||
bangumiDetail: bangumiDetail,
|
||||
cid: cid,
|
||||
return Obx(
|
||||
() => BangumiInfo(
|
||||
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
||||
cid: cid,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
// return HttpError(
|
||||
// errMsg: snapshot.data['msg'],
|
||||
// fn: () => Get.back(),
|
||||
// );
|
||||
return const SizedBox();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () => Get.back(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return BangumiInfo(
|
||||
loadingStatus: true,
|
||||
bangumiDetail: bangumiDetail,
|
||||
cid: cid,
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -99,12 +100,10 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
class BangumiInfo extends StatefulWidget {
|
||||
const BangumiInfo({
|
||||
super.key,
|
||||
this.loadingStatus = false,
|
||||
this.bangumiDetail,
|
||||
this.cid,
|
||||
});
|
||||
|
||||
final bool loadingStatus;
|
||||
final BangumiInfoModel? bangumiDetail;
|
||||
final int? cid;
|
||||
|
||||
@ -117,7 +116,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
late final BangumiIntroController bangumiIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late final BangumiInfoModel? bangumiItem;
|
||||
late double sheetHeight;
|
||||
int? cid;
|
||||
bool isProcessing = false;
|
||||
@ -136,13 +134,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
super.initState();
|
||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
bangumiItem = bangumiIntroController.bangumiItem;
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
cid = widget.cid!;
|
||||
print('cid: $cid');
|
||||
videoDetailCtr.cid.listen((p0) {
|
||||
cid = p0;
|
||||
print('cid: $cid');
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
@ -182,207 +177,155 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || bangumiItem != null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 105,
|
||||
height: 160,
|
||||
src: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
),
|
||||
if (bangumiItem != null &&
|
||||
bangumiItem!.rating != null)
|
||||
PBadge(
|
||||
text:
|
||||
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
|
||||
top: null,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
left: null,
|
||||
NetworkImgLayer(
|
||||
width: 105,
|
||||
height: 160,
|
||||
src: widget.bangumiDetail!.cover!,
|
||||
),
|
||||
PBadge(
|
||||
text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
|
||||
top: null,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
left: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => showIntroDetail(),
|
||||
child: SizedBox(
|
||||
height: 158,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.bangumiDetail!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => showIntroDetail(),
|
||||
child: SizedBox(
|
||||
height: 158,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.title!
|
||||
: bangumiItem!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
(Set<MaterialState> states) {
|
||||
return t
|
||||
.colorScheme.primaryContainer
|
||||
.withOpacity(0.7);
|
||||
}),
|
||||
),
|
||||
onPressed: () =>
|
||||
bangumiIntroController.bangumiAdd(),
|
||||
icon: Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
color: t.colorScheme.primary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
(Set<MaterialState> states) {
|
||||
return t.colorScheme.primaryContainer
|
||||
.withOpacity(0.7);
|
||||
}),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['views']
|
||||
: bangumiItem!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: !widget.loadingStatus
|
||||
? widget
|
||||
.bangumiDetail!.stat!['danmakus']
|
||||
: bangumiItem!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
],
|
||||
onPressed: () =>
|
||||
bangumiIntroController.bangumiAdd(),
|
||||
icon: Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
color: t.colorScheme.primary,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? (widget.bangumiDetail!.areas!
|
||||
.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!
|
||||
.first['name']
|
||||
: '')
|
||||
: (bangumiItem!.areas!.isNotEmpty
|
||||
? bangumiItem!
|
||||
.areas!.first['name']
|
||||
: ''),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!
|
||||
.publish!['pub_time_show']
|
||||
: bangumiItem!
|
||||
.publish!['pub_time_show'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 4),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.newEp!['desc']
|
||||
: bangumiItem!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 10),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: widget.bangumiDetail!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: widget.bangumiDetail!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(widget.bangumiDetail!.areas!.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!.first['name']
|
||||
: ''),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
widget.bangumiDetail!.publish!['pub_time_show'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget.bangumiDetail!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${widget.bangumiDetail!.evaluate!}',
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
// 点赞收藏转发 布局样式1
|
||||
// SingleChildScrollView(
|
||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: actionRow(
|
||||
// context,
|
||||
// bangumiIntroController,
|
||||
// videoDetailCtr,
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
actionGrid(context, bangumiIntroController),
|
||||
// 番剧分p
|
||||
if ((!widget.loadingStatus &&
|
||||
widget.bangumiDetail!.episodes!.isNotEmpty) ||
|
||||
bangumiItem != null &&
|
||||
bangumiItem!.episodes!.isNotEmpty) ...[
|
||||
BangumiPanel(
|
||||
pages: bangumiItem != null
|
||||
? bangumiItem!.episodes!
|
||||
: widget.bangumiDetail!.episodes!,
|
||||
cid: cid ??
|
||||
(bangumiItem != null
|
||||
? bangumiItem!.episodes!.first.cid
|
||||
: widget.bangumiDetail!.episodes!.first.cid),
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) => bangumiIntroController
|
||||
.changeSeasonOrbangu(bvid, cid, aid),
|
||||
)
|
||||
],
|
||||
],
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
/// 点赞收藏转发
|
||||
actionGrid(context, bangumiIntroController),
|
||||
// 番剧分p
|
||||
if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[
|
||||
BangumiPanel(
|
||||
pages: widget.bangumiDetail!.episodes!,
|
||||
cid: cid ?? widget.bangumiDetail!.episodes!.first.cid,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) =>
|
||||
bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
|
||||
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
||||
)
|
||||
],
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -402,57 +345,44 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap:
|
||||
handleState(bangumiIntroController.actionLikeVideo),
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: bangumiItem!.stat!['likes']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: handleState(bangumiIntroController.actionLikeVideo),
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
text: widget.bangumiDetail!.stat!['likes']!.toString(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap:
|
||||
handleState(bangumiIntroController.actionCoinVideo),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: bangumiItem!.stat!['coins']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(bangumiIntroController.actionCoinVideo),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
text: widget.bangumiDetail!.stat!['coins']!.toString(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: bangumiItem!.stat!['favorite']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
text: widget.bangumiDetail!.stat!['favorite']!.toString(),
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
selectIcon: const Icon(FontAwesomeIcons.reply),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: bangumiItem!.stat!['reply']!.toString(),
|
||||
text: widget.bangumiDetail!.stat!['reply']!.toString(),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['share']!.toString()
|
||||
: bangumiItem!.stat!['share']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
text: widget.bangumiDetail!.stat!['share']!.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -460,63 +390,4 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||
return Row(children: [
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: '-',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/view.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/bangumu_card_v.dart';
|
||||
@ -199,7 +198,10 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
fn: () {
|
||||
_futureBuilderFuture =
|
||||
_bangumidController.queryBangumiListFeed();
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
@ -208,7 +210,6 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
},
|
||||
),
|
||||
),
|
||||
const LoadingMore()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -14,12 +14,14 @@ class BangumiPanel extends StatefulWidget {
|
||||
this.cid,
|
||||
this.sheetHeight,
|
||||
this.changeFuc,
|
||||
this.bangumiDetail,
|
||||
});
|
||||
|
||||
final List<EpisodeItem> pages;
|
||||
final int? cid;
|
||||
final double? sheetHeight;
|
||||
final Function? changeFuc;
|
||||
final BangumiInfoModel? bangumiDetail;
|
||||
|
||||
@override
|
||||
State<BangumiPanel> createState() => _BangumiPanelState();
|
||||
@ -65,6 +67,47 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget buildPageListItem(
|
||||
EpisodeItem page,
|
||||
int index,
|
||||
bool isCurrentIndex,
|
||||
) {
|
||||
Color primary = Theme.of(context).colorScheme.primary;
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
setState(() {
|
||||
changeFucCall(page, index);
|
||||
});
|
||||
},
|
||||
dense: false,
|
||||
leading: isCurrentIndex
|
||||
? Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: primary,
|
||||
height: 12,
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
'第${page.title}话 ${page.longTitle!}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isCurrentIndex
|
||||
? primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
trailing: page.badge != null
|
||||
? Text(
|
||||
page.badge!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
}
|
||||
|
||||
void showBangumiPanel() {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
@ -105,38 +148,22 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ScrollablePositionedList.builder(
|
||||
itemCount: widget.pages.length,
|
||||
itemBuilder: (BuildContext context, int index) =>
|
||||
ListTile(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
changeFucCall(widget.pages[index], index);
|
||||
});
|
||||
},
|
||||
dense: false,
|
||||
leading: index == currentIndex
|
||||
? Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 12,
|
||||
itemCount: widget.pages.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
bool isLastItem = index == widget.pages.length;
|
||||
bool isCurrentIndex = currentIndex == index;
|
||||
return isLastItem
|
||||
? SizedBox(
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom +
|
||||
20,
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
'第${index + 1}话 ${widget.pages[index].longTitle!}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: index == currentIndex
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
trailing: widget.pages[index].badge != null
|
||||
? Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 20,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
: buildPageListItem(
|
||||
widget.pages[index],
|
||||
index,
|
||||
isCurrentIndex,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController,
|
||||
),
|
||||
),
|
||||
@ -151,7 +178,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
}
|
||||
|
||||
void changeFucCall(item, i) async {
|
||||
if (item.badge != null && vipStatus != 1) {
|
||||
if (item.badge != null && item.badge == '会员' && vipStatus != 1) {
|
||||
SmartDialog.showToast('需要大会员');
|
||||
return;
|
||||
}
|
||||
@ -178,11 +205,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 6),
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('合集 '),
|
||||
const Text('选集 '),
|
||||
Expanded(
|
||||
child: Text(
|
||||
' 正在播放:${widget.pages[currentIndex].longTitle}',
|
||||
@ -202,7 +229,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
onPressed: () => showBangumiPanel(),
|
||||
child: Text(
|
||||
'全${widget.pages.length}话',
|
||||
'${widget.bangumiDetail!.newEp!['desc']}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
@ -255,11 +282,16 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
if (widget.pages[i].badge != null) ...[
|
||||
Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 16,
|
||||
const Spacer(),
|
||||
Text(
|
||||
widget.pages[i].badge!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
]
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
|
@ -139,7 +139,7 @@ class BlackListController extends GetxController {
|
||||
int currentPage = 1;
|
||||
int pageSize = 50;
|
||||
RxInt total = 0.obs;
|
||||
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||
RxList<BlackListItem> blackList = <BlackListItem>[].obs;
|
||||
|
||||
Future queryBlacklist({type = 'init'}) async {
|
||||
if (type == 'init') {
|
||||
|
@ -20,7 +20,7 @@ import 'package:pilipala/utils/utils.dart';
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
String? offset = '';
|
||||
RxList<DynamicItemModel> dynamicsList = [DynamicItemModel()].obs;
|
||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
||||
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
|
||||
RxString dynamicsTypeLabel = '全部'.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
@ -105,7 +105,7 @@ class DynamicsController extends GetxController {
|
||||
|
||||
onSelectType(value) async {
|
||||
dynamicsType.value = filterTypeList[value]['value'];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
dynamicsList.value = <DynamicItemModel>[];
|
||||
page = 1;
|
||||
initialValue.value = value;
|
||||
await queryFollowDynamic();
|
||||
@ -249,8 +249,8 @@ class DynamicsController extends GetxController {
|
||||
return {'status': false, 'msg': '账号未登录'};
|
||||
}
|
||||
if (type == 'init') {
|
||||
upData.value.upList = [];
|
||||
upData.value.liveUsers = LiveUsers();
|
||||
upData.value.upList = <UpItem>[];
|
||||
upData.value.liveList = <LiveUserItem>[];
|
||||
}
|
||||
var res = await DynamicsHttp.followUp();
|
||||
if (res['status']) {
|
||||
@ -258,20 +258,23 @@ class DynamicsController extends GetxController {
|
||||
if (upData.value.upList!.isEmpty) {
|
||||
mid.value = -1;
|
||||
}
|
||||
upData.value.upList!.insertAll(0, [
|
||||
UpItem(face: '', uname: '全部动态', mid: -1),
|
||||
UpItem(face: userInfo.face, uname: '我', mid: userInfo.mid),
|
||||
]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
onSelectUp(mid) async {
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
dynamicsList.value = <DynamicItemModel>[];
|
||||
page = 1;
|
||||
queryFollowDynamic();
|
||||
}
|
||||
|
||||
onRefresh() async {
|
||||
page = 1;
|
||||
print('onRefresh');
|
||||
await queryFollowUp();
|
||||
await queryFollowDynamic();
|
||||
}
|
||||
@ -293,7 +296,7 @@ class DynamicsController extends GetxController {
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
initialValue.value = 0;
|
||||
SmartDialog.showToast('还原默认加载');
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
dynamicsList.value = <DynamicItemModel>[];
|
||||
queryFollowDynamic();
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class DynamicDetailController extends GetxController {
|
||||
int currentPage = 0;
|
||||
bool isLoadingMore = false;
|
||||
RxString noMore = ''.obs;
|
||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||
RxInt acount = 0.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@ -37,6 +37,10 @@ class DynamicDetailController extends GetxController {
|
||||
}
|
||||
int deaultReplySortIndex =
|
||||
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
|
||||
if (deaultReplySortIndex == 2) {
|
||||
setting.put(SettingBoxKey.replySortType, 0);
|
||||
deaultReplySortIndex = 0;
|
||||
}
|
||||
_sortType = ReplySortType.values[deaultReplySortIndex];
|
||||
sortTypeTitle.value = _sortType.titles;
|
||||
sortTypeLabel.value = _sortType.labels;
|
||||
@ -92,9 +96,6 @@ class DynamicDetailController extends GetxController {
|
||||
_sortType = ReplySortType.like;
|
||||
break;
|
||||
case ReplySortType.like:
|
||||
_sortType = ReplySortType.reply;
|
||||
break;
|
||||
case ReplySortType.reply:
|
||||
_sortType = ReplySortType.time;
|
||||
break;
|
||||
default:
|
||||
|
@ -14,6 +14,7 @@ import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../mine/controller.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
@ -28,6 +29,7 @@ class DynamicsPage extends StatefulWidget {
|
||||
class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
final MineController mineController = Get.put(MineController());
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _futureBuilderFutureUp;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
@ -192,22 +194,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
)
|
||||
],
|
||||
),
|
||||
// Obx(
|
||||
// () => Visibility(
|
||||
// visible: _dynamicsController.userLogin.value,
|
||||
// child: Positioned(
|
||||
// right: 4,
|
||||
// top: 0,
|
||||
// bottom: 0,
|
||||
// child: IconButton(
|
||||
// padding: EdgeInsets.zero,
|
||||
// onPressed: () =>
|
||||
// {feedBack(), _dynamicsController.resetSearch()},
|
||||
// icon: const Icon(Icons.history, size: 21),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -229,7 +215,8 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80));
|
||||
child: SizedBox(height: 80),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
@ -240,15 +227,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: Container(
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@ -280,6 +258,14 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (data['msg'] == "账号未登录") {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
btnText: "去登录",
|
||||
fn: () {
|
||||
mineController.onLogin();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
|
@ -34,25 +34,25 @@ Widget articlePanel(item, context, {floor = 1}) {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Text(
|
||||
item.modules.moduleDynamic.major.opus.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (item.modules.moduleDynamic.major.opus.summary.text !=
|
||||
'undefined') ...[
|
||||
Text(
|
||||
item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
|
||||
.text,
|
||||
maxLines: 4,
|
||||
style: const TextStyle(height: 1.55),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
],
|
||||
// Text(
|
||||
// item.modules.moduleDynamic.major.opus.title,
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .titleMedium!
|
||||
// .copyWith(fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// const SizedBox(height: 2),
|
||||
// if (item.modules.moduleDynamic.major.opus.summary.text !=
|
||||
// 'undefined') ...[
|
||||
// Text(
|
||||
// item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
|
||||
// .text,
|
||||
// maxLines: 4,
|
||||
// style: const TextStyle(height: 1.55),
|
||||
// overflow: TextOverflow.ellipsis,
|
||||
// ),
|
||||
// const SizedBox(height: 2),
|
||||
// ],
|
||||
picWidget(item, context)
|
||||
],
|
||||
),
|
||||
|
@ -45,7 +45,9 @@ class _ContentState extends State<Content> {
|
||||
if (len == 1) {
|
||||
OpusPicsModel pictureItem = pics.first;
|
||||
picList.add(pictureItem.url!);
|
||||
spanChilds.add(const TextSpan(text: '\n'));
|
||||
|
||||
/// 图片上方的空白间隔
|
||||
// spanChilds.add(const TextSpan(text: '\n'));
|
||||
spanChilds.add(
|
||||
WidgetSpan(
|
||||
child: LayoutBuilder(
|
||||
|
@ -19,6 +19,17 @@ InlineSpan richNode(item, context) {
|
||||
// 动态页面 richTextNodes 层级可能与主页动态层级不同
|
||||
richTextNodes =
|
||||
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
|
||||
if (item.modules.moduleDynamic.major.opus.title != null) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: item.modules.moduleDynamic.major.opus.title + '\n',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (richTextNodes == null || richTextNodes.isEmpty) {
|
||||
return spacer;
|
||||
|
@ -1,16 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/pages/dynamics/controller.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
final FollowUpModel? upData;
|
||||
final FollowUpModel upData;
|
||||
const UpPanel(this.upData, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -24,39 +22,22 @@ class _UpPanelState extends State<UpPanel> {
|
||||
List<UpItem> upList = [];
|
||||
List<LiveUserItem> liveList = [];
|
||||
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
var userInfo;
|
||||
late MyInfo userInfo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
upList = widget.upData!.upList!;
|
||||
if (widget.upData!.liveUsers != null) {
|
||||
liveList = widget.upData!.liveUsers!.items!;
|
||||
}
|
||||
upList.insert(
|
||||
0,
|
||||
UpItem(
|
||||
face: 'https://files.catbox.moe/8uc48f.png', uname: '全部动态', mid: -1),
|
||||
);
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
upList.insert(
|
||||
1,
|
||||
UpItem(
|
||||
face: userInfo.face,
|
||||
uname: '我',
|
||||
mid: userInfo.mid,
|
||||
),
|
||||
);
|
||||
void listFormat() {
|
||||
userInfo = widget.upData.myInfo!;
|
||||
upList = widget.upData.upList!;
|
||||
liveList = widget.upData.liveList!;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
listFormat();
|
||||
return SliverPersistentHeader(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
delegate: _SliverHeaderDelegate(
|
||||
height: 124,
|
||||
height: liveList.isNotEmpty || upList.isNotEmpty ? 126 : 0,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -91,7 +72,7 @@ class _UpPanelState extends State<UpPanel> {
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
Flexible(
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: scrollController,
|
||||
@ -121,6 +102,13 @@ class _UpPanelState extends State<UpPanel> {
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
@ -171,6 +159,9 @@ class _UpPanelState extends State<UpPanel> {
|
||||
},
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
if (data.mid == -1) {
|
||||
return;
|
||||
}
|
||||
String heroTag = Utils.makeHeroTag(data.mid);
|
||||
Get.toNamed('/member?mid=${data.mid}',
|
||||
arguments: {'face': data.face, 'heroTag': heroTag});
|
||||
@ -198,12 +189,19 @@ class _UpPanelState extends State<UpPanel> {
|
||||
backgroundColor: data.type == 'live'
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
child: NetworkImgLayer(
|
||||
width: 49,
|
||||
height: 49,
|
||||
src: data.face,
|
||||
type: 'avatar',
|
||||
),
|
||||
child: data.face != ''
|
||||
? NetworkImgLayer(
|
||||
width: 50,
|
||||
height: 50,
|
||||
src: data.face,
|
||||
type: 'avatar',
|
||||
)
|
||||
: const CircleAvatar(
|
||||
radius: 25,
|
||||
backgroundImage: AssetImage(
|
||||
'assets/images/noface.jpeg',
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
@ -271,13 +269,11 @@ class UpPanelSkeleton extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 49,
|
||||
height: 49,
|
||||
width: 50,
|
||||
height: 50,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(24),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
|
20
lib/pages/emote/controller.dart
Normal file
20
lib/pages/emote/controller.dart
Normal file
@ -0,0 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../http/reply.dart';
|
||||
import '../../models/video/reply/emote.dart';
|
||||
|
||||
class EmotePanelController extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
late List<PackageItem> emotePackage;
|
||||
late TabController tabController;
|
||||
|
||||
Future getEmote() async {
|
||||
var res = await ReplyHttp.getEmoteList(business: 'reply');
|
||||
if (res['status']) {
|
||||
emotePackage = res['data'].packages;
|
||||
tabController = TabController(length: emotePackage.length, vsync: this);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
4
lib/pages/emote/index.dart
Normal file
4
lib/pages/emote/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library emote;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
116
lib/pages/emote/view.dart
Normal file
116
lib/pages/emote/view.dart
Normal file
@ -0,0 +1,116 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../models/video/reply/emote.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class EmotePanel extends StatefulWidget {
|
||||
final Function onChoose;
|
||||
const EmotePanel({super.key, required this.onChoose});
|
||||
|
||||
@override
|
||||
State<EmotePanel> createState() => _EmotePanelState();
|
||||
}
|
||||
|
||||
class _EmotePanelState extends State<EmotePanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final EmotePanelController _emotePanelController =
|
||||
Get.put(EmotePanelController());
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_futureBuilderFuture = _emotePanelController.getEmote();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
List<PackageItem> emotePackage =
|
||||
_emotePanelController.emotePackage;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _emotePanelController.tabController,
|
||||
children: emotePackage.map(
|
||||
(e) {
|
||||
int size = e.emote!.first.meta!.size!;
|
||||
int type = e.type!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 12, 0),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: size == 1 ? 40 : 60,
|
||||
crossAxisSpacing: 8,
|
||||
mainAxisSpacing: 8,
|
||||
),
|
||||
itemCount: e.emote!.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.onChoose(e, e.emote![index]);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: type == 4
|
||||
? Text(
|
||||
e.emote![index].text!,
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 1,
|
||||
)
|
||||
: Image.network(
|
||||
e.emote![index].url!,
|
||||
width: size * 38,
|
||||
height: size * 38,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
)),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
TabBar(
|
||||
controller: _emotePanelController.tabController,
|
||||
dividerColor: Colors.transparent,
|
||||
isScrollable: true,
|
||||
tabs: _emotePanelController.emotePackage
|
||||
.map((e) => Tab(text: e.text))
|
||||
.toList(),
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom + 20),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Center(child: Text(data['msg']));
|
||||
}
|
||||
} else {
|
||||
return const Center(child: Text('加载中...'));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ class FansController extends GetxController {
|
||||
int pn = 1;
|
||||
int ps = 20;
|
||||
int total = 0;
|
||||
RxList<FansItemModel> fansList = [FansItemModel()].obs;
|
||||
RxList<FansItemModel> fansList = <FansItemModel>[].obs;
|
||||
late int mid;
|
||||
late String name;
|
||||
var userInfo;
|
||||
|
@ -24,7 +24,7 @@ class FavController extends GetxController {
|
||||
if (!hasMore.value) {
|
||||
return;
|
||||
}
|
||||
var res = await await UserHttp.userfavFolder(
|
||||
var res = await UserHttp.userfavFolder(
|
||||
pn: currentPage,
|
||||
ps: pageSize,
|
||||
mid: userInfo!.mid!,
|
||||
|
@ -16,7 +16,7 @@ class FavDetailController extends GetxController {
|
||||
RxMap favInfo = {}.obs;
|
||||
RxList favList = [].obs;
|
||||
RxString loadingText = '加载中...'.obs;
|
||||
int mediaCount = 0;
|
||||
RxInt mediaCount = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -29,12 +29,12 @@ class FavDetailController extends GetxController {
|
||||
}
|
||||
|
||||
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
||||
if (type == 'onLoad' && favList.length >= mediaCount) {
|
||||
if (type == 'onLoad' && favList.length >= mediaCount.value) {
|
||||
loadingText.value = '没有更多了';
|
||||
return;
|
||||
}
|
||||
isLoadingMore = true;
|
||||
var res = await await UserHttp.userFavFolderDetail(
|
||||
var res = await UserHttp.userFavFolderDetail(
|
||||
pn: currentPage,
|
||||
ps: 20,
|
||||
mediaId: mediaId!,
|
||||
@ -43,11 +43,11 @@ class FavDetailController extends GetxController {
|
||||
favInfo.value = res['data'].info;
|
||||
if (currentPage == 1 && type == 'init') {
|
||||
favList.value = res['data'].medias;
|
||||
mediaCount = res['data'].info['media_count'];
|
||||
mediaCount.value = res['data'].info['media_count'];
|
||||
} else if (type == 'onLoad') {
|
||||
favList.addAll(res['data'].medias);
|
||||
}
|
||||
if (favList.length >= mediaCount) {
|
||||
if (favList.length >= mediaCount.value) {
|
||||
loadingText.value = '没有更多了';
|
||||
}
|
||||
}
|
||||
@ -60,16 +60,14 @@ class FavDetailController extends GetxController {
|
||||
var result = await VideoHttp.favVideo(
|
||||
aid: id, addIds: '', delIds: mediaId.toString());
|
||||
if (result['status']) {
|
||||
if (result['data']['prompt']) {
|
||||
List dataList = favList;
|
||||
for (var i in dataList) {
|
||||
if (i.id == id) {
|
||||
dataList.remove(i);
|
||||
break;
|
||||
}
|
||||
List dataList = favList;
|
||||
for (var i in dataList) {
|
||||
if (i.id == id) {
|
||||
dataList.remove(i);
|
||||
break;
|
||||
}
|
||||
SmartDialog.showToast('取消收藏');
|
||||
}
|
||||
SmartDialog.showToast('取消收藏');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,10 +24,12 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
Get.put(FavDetailController());
|
||||
late StreamController<bool> titleStreamC; // a
|
||||
Future? _futureBuilderFuture;
|
||||
late String mediaId;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
mediaId = Get.parameters['mediaId']!;
|
||||
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
|
||||
titleStreamC = StreamController<bool>();
|
||||
_controller.addListener(
|
||||
@ -82,7 +84,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
Text(
|
||||
'共${_favDetailController.item!.mediaCount!}条视频',
|
||||
'共${_favDetailController.mediaCount}条视频',
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
)
|
||||
],
|
||||
@ -94,8 +96,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'),
|
||||
onPressed: () =>
|
||||
Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
// IconButton(
|
||||
@ -173,7 +175,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'共${_favDetailController.favList.length}条视频',
|
||||
'共${_favDetailController.mediaCount}条视频',
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
|
@ -9,14 +9,20 @@ import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import '../../../common/widgets/badge.dart';
|
||||
|
||||
// 收藏视频卡片 - 水平布局
|
||||
class FavVideoCardH extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final Function? callFn;
|
||||
final int? searchType;
|
||||
|
||||
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
|
||||
: super(key: key);
|
||||
const FavVideoCardH({
|
||||
Key? key,
|
||||
required this.videoItem,
|
||||
this.callFn,
|
||||
this.searchType,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -27,7 +33,9 @@ class FavVideoCardH extends StatelessWidget {
|
||||
onTap: () async {
|
||||
// int? seasonId;
|
||||
String? epId;
|
||||
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
|
||||
if (videoItem.ogv != null &&
|
||||
(videoItem.ogv['type_name'] == '番剧' ||
|
||||
videoItem.ogv['type_name'] == '国创')) {
|
||||
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
||||
// seasonId = videoItem.ogv['season_id'];
|
||||
epId = videoItem.epId;
|
||||
@ -84,28 +92,31 @@ class FavVideoCardH extends StatelessWidget {
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1, horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Colors.black54.withOpacity(0.4)),
|
||||
child: Text(
|
||||
Utils.timeFormat(videoItem.duration!),
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Colors.white),
|
||||
),
|
||||
PBadge(
|
||||
text: Utils.timeFormat(videoItem.duration!),
|
||||
right: 6.0,
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (videoItem.ogv != null) ...[
|
||||
PBadge(
|
||||
text: videoItem.ogv['type_name'],
|
||||
top: 6.0,
|
||||
right: 6.0,
|
||||
bottom: null,
|
||||
left: null,
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
VideoContent(videoItem: videoItem, callFn: callFn)
|
||||
VideoContent(
|
||||
videoItem: videoItem,
|
||||
callFn: callFn,
|
||||
searchType: searchType,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -121,93 +132,123 @@ class FavVideoCardH extends StatelessWidget {
|
||||
class VideoContent extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final Function? callFn;
|
||||
const VideoContent({super.key, required this.videoItem, this.callFn});
|
||||
final int? searchType;
|
||||
const VideoContent({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.callFn,
|
||||
this.searchType,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Stack(
|
||||
children: [
|
||||
Text(
|
||||
videoItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
Utils.dateFormat(videoItem.ctime!),
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.cntInfo['play'],
|
||||
Text(
|
||||
videoItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 26,
|
||||
height: 26,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('要取消收藏吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await callFn!();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定取消'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.clear_outlined,
|
||||
if (videoItem.ogv != null) ...[
|
||||
Text(
|
||||
videoItem.intro,
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Text(
|
||||
Utils.dateFormat(videoItem.favTime),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
if (videoItem.owner.name != '') ...[
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.cntInfo['play'],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(
|
||||
theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
searchType != 1
|
||||
? Positioned(
|
||||
right: 0,
|
||||
bottom: -4,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('要取消收藏吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline),
|
||||
)),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await callFn!();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定取消'),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.clear_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,8 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/user/fav_detail.dart';
|
||||
|
||||
import '../../http/video.dart';
|
||||
|
||||
class FavSearchController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||
@ -72,4 +75,19 @@ class FavSearchController extends GetxController {
|
||||
if (!hasMore) return;
|
||||
searchFav(type: 'onLoad');
|
||||
}
|
||||
|
||||
onCancelFav(int id) async {
|
||||
var result = await VideoHttp.favVideo(
|
||||
aid: id, addIds: '', delIds: mediaId.toString());
|
||||
if (result['status']) {
|
||||
List dataList = favList;
|
||||
for (var i in dataList) {
|
||||
if (i.id == id) {
|
||||
dataList.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SmartDialog.showToast('取消收藏');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,9 +8,7 @@ import 'package:pilipala/pages/fav_detail/widget/fav_video_card.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class FavSearchPage extends StatefulWidget {
|
||||
final int? sourceType;
|
||||
final int? mediaId;
|
||||
const FavSearchPage({super.key, this.sourceType, this.mediaId});
|
||||
const FavSearchPage({super.key});
|
||||
|
||||
@override
|
||||
State<FavSearchPage> createState() => _FavSearchPageState();
|
||||
@ -19,11 +17,12 @@ class FavSearchPage extends StatefulWidget {
|
||||
class _FavSearchPageState extends State<FavSearchPage> {
|
||||
final FavSearchController _favSearchCtr = Get.put(FavSearchController());
|
||||
late ScrollController scrollController;
|
||||
late int searchType;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
searchType = int.parse(Get.parameters['searchType']!);
|
||||
scrollController = _favSearchCtr.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
@ -100,7 +99,11 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
||||
} else {
|
||||
return FavVideoCardH(
|
||||
videoItem: _favSearchCtr.favList[index],
|
||||
callFn: () => null,
|
||||
searchType: searchType,
|
||||
callFn: () => searchType != 1
|
||||
? _favSearchCtr
|
||||
.onCancelFav(_favSearchCtr.favList[index].id!)
|
||||
: {},
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -37,6 +37,29 @@ class _FollowPageState extends State<FollowPage> {
|
||||
: '${_followController.name}的关注',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed('/followSearch?mid=$mid'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
onTap: () => Get.toNamed('/blackListPage'),
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.block, size: 19),
|
||||
SizedBox(width: 10),
|
||||
Text('黑名单管理'),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
body: Obx(
|
||||
() => !_followController.isOwner.value
|
||||
@ -87,3 +110,22 @@ class _FollowPageState extends State<FollowPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FakeAPI {
|
||||
static const List<String> _kOptions = <String>[
|
||||
'aardvark',
|
||||
'bobcat',
|
||||
'chameleon',
|
||||
];
|
||||
// Searches the options, but injects a fake "network" delay.
|
||||
static Future<Iterable<String>> search(String query) async {
|
||||
await Future<void>.delayed(
|
||||
const Duration(seconds: 1)); // Fake 1 second delay.
|
||||
if (query == '') {
|
||||
return const Iterable<String>.empty();
|
||||
}
|
||||
return _kOptions.where((String option) {
|
||||
return option.contains(query.toLowerCase());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class FollowItem extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
dense: true,
|
||||
trailing: ctr!.isOwner.value
|
||||
trailing: ctr != null && ctr!.isOwner.value
|
||||
? SizedBox(
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
|
73
lib/pages/follow_search/controller.dart
Normal file
73
lib/pages/follow_search/controller.dart
Normal file
@ -0,0 +1,73 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
|
||||
import '../../models/follow/result.dart';
|
||||
|
||||
class FollowSearchController extends GetxController {
|
||||
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||
final FocusNode searchFocusNode = FocusNode();
|
||||
RxString searchKeyWord = ''.obs;
|
||||
String hintText = '搜索';
|
||||
RxString loadingStatus = 'init'.obs;
|
||||
late int mid = 1;
|
||||
RxString uname = ''.obs;
|
||||
int ps = 20;
|
||||
int pn = 1;
|
||||
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
|
||||
RxInt total = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
|
||||
// 清空搜索
|
||||
void onClear() {
|
||||
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||
controller.value.clear();
|
||||
searchKeyWord.value = '';
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
}
|
||||
|
||||
void onChange(value) {
|
||||
searchKeyWord.value = value;
|
||||
}
|
||||
|
||||
// 提交搜索内容
|
||||
void submit() {
|
||||
loadingStatus.value = 'loading';
|
||||
searchFollow();
|
||||
}
|
||||
|
||||
Future searchFollow({type = 'init'}) async {
|
||||
if (controller.value.text == '') {
|
||||
return {'status': true, 'data': <FollowItemModel>[].obs};
|
||||
}
|
||||
if (type == 'init') {
|
||||
ps = 1;
|
||||
}
|
||||
var res = await MemberHttp.getfollowSearch(
|
||||
mid: mid,
|
||||
ps: ps,
|
||||
pn: pn,
|
||||
name: controller.value.text,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
followList.value = res['data'].list;
|
||||
} else {
|
||||
followList.addAll(res['data'].list);
|
||||
}
|
||||
total.value = res['data'].total;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void onLoad() {
|
||||
searchFollow(type: 'onLoad');
|
||||
}
|
||||
}
|
4
lib/pages/follow_search/index.dart
Normal file
4
lib/pages/follow_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library follow_search;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
121
lib/pages/follow_search/view.dart
Normal file
121
lib/pages/follow_search/view.dart
Normal file
@ -0,0 +1,121 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/follow_search/index.dart';
|
||||
|
||||
import '../follow/widgets/follow_item.dart';
|
||||
|
||||
class FollowSearchPage extends StatefulWidget {
|
||||
const FollowSearchPage({super.key});
|
||||
|
||||
@override
|
||||
State<FollowSearchPage> createState() => _FollowSearchPageState();
|
||||
}
|
||||
|
||||
class _FollowSearchPageState extends State<FollowSearchPage> {
|
||||
final FollowSearchController _followSearchController =
|
||||
Get.put(FollowSearchController());
|
||||
late Future? _futureBuilder;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilder = _followSearchController.searchFollow();
|
||||
scrollController.addListener(
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'my-throttler', const Duration(milliseconds: 500), () {
|
||||
_followSearchController.onLoad();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void reRequest() {
|
||||
setState(() {
|
||||
_futureBuilder = _followSearchController.searchFollow();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: reRequest,
|
||||
icon: const Icon(CupertinoIcons.search, size: 22),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
title: TextField(
|
||||
autofocus: true,
|
||||
focusNode: _followSearchController.searchFocusNode,
|
||||
controller: _followSearchController.controller.value,
|
||||
textInputAction: TextInputAction.search,
|
||||
onChanged: (value) => _followSearchController.onChange(value),
|
||||
decoration: InputDecoration(
|
||||
hintText: _followSearchController.hintText,
|
||||
border: InputBorder.none,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
Icons.clear,
|
||||
size: 22,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
onPressed: () => _followSearchController.onClear(),
|
||||
),
|
||||
),
|
||||
onSubmitted: (String value) => reRequest(),
|
||||
),
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _futureBuilder,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
var data = snapshot.data;
|
||||
if (data == null) {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
|
||||
],
|
||||
);
|
||||
}
|
||||
if (data['status']) {
|
||||
RxList followList = _followSearchController.followList;
|
||||
return Obx(
|
||||
() => followList.isNotEmpty
|
||||
? ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: followList.length,
|
||||
itemBuilder: ((context, index) {
|
||||
return FollowItem(
|
||||
item: followList[index],
|
||||
);
|
||||
}),
|
||||
)
|
||||
: CustomScrollView(
|
||||
slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
@ -88,8 +88,10 @@ class HistoryController extends GetxController {
|
||||
// 观看历史暂停状态
|
||||
Future historyStatus() async {
|
||||
var res = await UserHttp.historyStatus();
|
||||
pauseStatus.value = res.data['data'];
|
||||
localCache.put(LocalCacheKey.historyPause, res.data['data']);
|
||||
if (res.data['code'] == 0) {
|
||||
pauseStatus.value = res.data['data'];
|
||||
localCache.put(LocalCacheKey.historyPause, res.data['data']);
|
||||
}
|
||||
}
|
||||
|
||||
// 清空观看历史
|
||||
|
@ -70,10 +70,6 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
child1: AppBar(
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
leading: IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.arrow_back_outlined),
|
||||
),
|
||||
title: Text(
|
||||
'观看记录',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
|
@ -26,6 +26,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
late List defaultTabs;
|
||||
late List<String> tabbarSort;
|
||||
RxString defaultSearch = ''.obs;
|
||||
late bool enableGradientBg;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -33,13 +34,15 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
userFace.value = userInfo != null ? userInfo.face : '';
|
||||
// 进行tabs配置
|
||||
setTabConfig();
|
||||
hideSearchBar =
|
||||
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
|
||||
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
|
||||
searchDefault();
|
||||
}
|
||||
enableGradientBg =
|
||||
setting.get(SettingBoxKey.enableGradientBg, defaultValue: true);
|
||||
// 进行tabs配置
|
||||
setTabConfig();
|
||||
}
|
||||
|
||||
void onRefresh() {
|
||||
@ -88,19 +91,21 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
vsync: this,
|
||||
);
|
||||
// 监听 tabController 切换
|
||||
tabController.animation!.addListener(() {
|
||||
if (tabController.indexIsChanging) {
|
||||
if (initialIndex.value != tabController.index) {
|
||||
initialIndex.value = tabController.index;
|
||||
if (enableGradientBg) {
|
||||
tabController.animation!.addListener(() {
|
||||
if (tabController.indexIsChanging) {
|
||||
if (initialIndex.value != tabController.index) {
|
||||
initialIndex.value = tabController.index;
|
||||
}
|
||||
} else {
|
||||
final int temp = tabController.animation!.value.round();
|
||||
if (initialIndex.value != temp) {
|
||||
initialIndex.value = temp;
|
||||
tabController.index = initialIndex.value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final int temp = tabController.animation!.value.round();
|
||||
if (initialIndex.value != temp) {
|
||||
initialIndex.value = temp;
|
||||
tabController.index = initialIndex.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void searchDefault() async {
|
||||
|
@ -48,38 +48,51 @@ class _HomePageState extends State<HomePage>
|
||||
super.build(context);
|
||||
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
|
||||
// 设置状态栏图标的亮度
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
));
|
||||
if (_homeController.enableGradientBg) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
));
|
||||
}
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: _homeController.enableGradientBg
|
||||
? null
|
||||
: AppBar(toolbarHeight: 0, elevation: 0),
|
||||
body: Stack(
|
||||
children: [
|
||||
// gradient background
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.9),
|
||||
Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
||||
Theme.of(context).colorScheme.surface
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.0034, 0.34]),
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.9),
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.5),
|
||||
Theme.of(context).colorScheme.surface
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.0034, 0.34]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Column(
|
||||
children: [
|
||||
CustomAppBar(
|
||||
@ -90,7 +103,37 @@ class _HomePageState extends State<HomePage>
|
||||
callback: showUserBottomSheet,
|
||||
),
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
const CustomTabs(),
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
const CustomTabs(),
|
||||
] else ...[
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs)
|
||||
Tab(text: i['label'])
|
||||
],
|
||||
isScrollable: true,
|
||||
dividerColor: Colors.transparent,
|
||||
enableFeedback: true,
|
||||
splashBorderRadius: BorderRadius.circular(10),
|
||||
tabAlignment: TabAlignment.center,
|
||||
onTap: (value) {
|
||||
feedBack();
|
||||
if (_homeController.initialIndex.value == value) {
|
||||
_homeController.tabsCtrList[value]().animateToTop();
|
||||
}
|
||||
_homeController.initialIndex.value = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
] else ...[
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
@ -372,13 +415,16 @@ class SearchBar extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
ctr!.defaultSearch.value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.outline),
|
||||
() => Expanded(
|
||||
child: Text(
|
||||
ctr!.defaultSearch.value,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: colorScheme.outline),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 15),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -7,7 +7,7 @@ class HotController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
final int _count = 20;
|
||||
int _currentPage = 1;
|
||||
RxList<HotVideoItemModel> videoList = [HotVideoItemModel()].obs;
|
||||
RxList<HotVideoItemModel> videoList = <HotVideoItemModel>[].obs;
|
||||
bool isLoadingMore = false;
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
|
@ -89,8 +89,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: _hotController.videoList[index],
|
||||
showPubdate: true,
|
||||
@ -110,7 +109,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_hotController.queryHotFeed('init');
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
@ -96,9 +96,6 @@ class HtmlRenderController extends GetxController {
|
||||
_sortType = ReplySortType.like;
|
||||
break;
|
||||
case ReplySortType.like:
|
||||
_sortType = ReplySortType.reply;
|
||||
break;
|
||||
case ReplySortType.reply:
|
||||
_sortType = ReplySortType.time;
|
||||
break;
|
||||
default:
|
||||
|
@ -10,8 +10,7 @@ class LiveController extends GetxController {
|
||||
int count = 12;
|
||||
int _currentPage = 1;
|
||||
RxInt crossAxisCount = 2.obs;
|
||||
RxList<LiveItemModel> liveList = [LiveItemModel()].obs;
|
||||
bool isLoadingMore = false;
|
||||
RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
Box setting = GStrorage.setting;
|
||||
@ -39,7 +38,6 @@ class LiveController extends GetxController {
|
||||
}
|
||||
_currentPage += 1;
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/pages/rcmd/index.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
import 'widgets/live_item.dart';
|
||||
@ -45,8 +44,8 @@ class _LivePageState extends State<LivePage>
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () {
|
||||
_liveController.isLoadingMore = true;
|
||||
EasyThrottle.throttle('liveList', const Duration(milliseconds: 200),
|
||||
() {
|
||||
_liveController.onLoad();
|
||||
});
|
||||
}
|
||||
@ -108,24 +107,20 @@ class _LivePageState extends State<LivePage>
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => {},
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_liveController.queryLiveList('init');
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 缓存数据
|
||||
if (_liveController.liveList.length > 1) {
|
||||
return contentGrid(
|
||||
_liveController, _liveController.liveList);
|
||||
}
|
||||
// 骨架屏
|
||||
else {
|
||||
return contentGrid(_liveController, []);
|
||||
}
|
||||
return contentGrid(_liveController, []);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
LoadingMore(ctr: _liveController)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -184,18 +184,32 @@ class VideoStat extends StatelessWidget {
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
),
|
||||
child: RichText(
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.justify,
|
||||
softWrap: false,
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
||||
children: [
|
||||
TextSpan(text: liveItem!.areaName!),
|
||||
TextSpan(text: liveItem!.watchedShow!['text_small']),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
liveItem!.areaName!,
|
||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
||||
),
|
||||
Text(
|
||||
liveItem!.watchedShow!['text_small'],
|
||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// child: RichText(
|
||||
// maxLines: 1,
|
||||
// textAlign: TextAlign.justify,
|
||||
// softWrap: false,
|
||||
// text: TextSpan(
|
||||
// style: const TextStyle(fontSize: 11, color: Colors.white),
|
||||
// children: [
|
||||
// TextSpan(text: liveItem!.areaName!),
|
||||
// TextSpan(text: liveItem!.watchedShow!['text_small']),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,13 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/live.dart';
|
||||
import 'package:pilipala/models/live/quality.dart';
|
||||
import 'package:pilipala/models/live/room_info.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import '../../models/live/room_info_h5.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../../utils/video_utils.dart';
|
||||
|
||||
class LiveRoomController extends GetxController {
|
||||
String cover = '';
|
||||
@ -14,17 +19,18 @@ class LiveRoomController extends GetxController {
|
||||
RxBool volumeOff = false.obs;
|
||||
PlPlayerController plPlayerController =
|
||||
PlPlayerController.getInstance(videoType: 'live');
|
||||
|
||||
// MeeduPlayerController meeduPlayerController = MeeduPlayerController(
|
||||
// colorTheme: Theme.of(Get.context!).colorScheme.primary,
|
||||
// pipEnabled: true,
|
||||
// controlsStyle: ControlsStyle.live,
|
||||
// enabledButtons: const EnabledButtons(pip: true),
|
||||
// );
|
||||
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
|
||||
late bool enableCDN;
|
||||
late int currentQn;
|
||||
int? tempCurrentQn;
|
||||
late List<Map<String, dynamic>> acceptQnList;
|
||||
RxString currentQnDesc = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
currentQn = setting.get(SettingBoxKey.defaultLiveQa,
|
||||
defaultValue: LiveQuality.values.last.code);
|
||||
roomId = int.parse(Get.parameters['roomid']!);
|
||||
if (Get.arguments != null) {
|
||||
liveItem = Get.arguments['liveItem'];
|
||||
@ -36,11 +42,12 @@ class LiveRoomController extends GetxController {
|
||||
cover = liveItem.cover;
|
||||
}
|
||||
}
|
||||
queryLiveInfo();
|
||||
// CDN优化
|
||||
enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);
|
||||
}
|
||||
|
||||
playerInit(source) {
|
||||
plPlayerController.setDataSource(
|
||||
playerInit(source) async {
|
||||
await plPlayerController.setDataSource(
|
||||
DataSource(
|
||||
videoSource: source,
|
||||
audioSource: null,
|
||||
@ -58,15 +65,35 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
|
||||
Future queryLiveInfo() async {
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: 10000);
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: currentQn);
|
||||
if (res['status']) {
|
||||
List<CodecItem> codec =
|
||||
res['data'].playurlInfo.playurl.stream.first.format.first.codec;
|
||||
CodecItem item = codec.first;
|
||||
String videoUrl = (item.urlInfo?.first.host)! +
|
||||
item.baseUrl! +
|
||||
item.urlInfo!.first.extra!;
|
||||
playerInit(videoUrl);
|
||||
// 以服务端返回的码率为准
|
||||
currentQn = item.currentQn!;
|
||||
if (tempCurrentQn != null && tempCurrentQn == currentQn) {
|
||||
SmartDialog.showToast('画质切换失败,请检查登录状态');
|
||||
}
|
||||
List acceptQn = item.acceptQn!;
|
||||
acceptQnList = acceptQn.map((e) {
|
||||
return {
|
||||
'code': e,
|
||||
'desc': LiveQuality.values
|
||||
.firstWhere((element) => element.code == e)
|
||||
.description,
|
||||
};
|
||||
}).toList();
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
String videoUrl = enableCDN
|
||||
? VideoUtils.getCdnUrl(item)
|
||||
: (item.urlInfo?.first.host)! +
|
||||
item.baseUrl! +
|
||||
item.urlInfo!.first.extra!;
|
||||
await playerInit(videoUrl);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,4 +107,25 @@ class LiveRoomController extends GetxController {
|
||||
volumeOff.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
Future queryLiveInfoH5() async {
|
||||
var res = await LiveHttp.liveRoomInfoH5(roomId: roomId);
|
||||
if (res['status']) {
|
||||
roomInfoH5.value = res['data'];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 修改画质
|
||||
void changeQn(int qn) async {
|
||||
tempCurrentQn = currentQn;
|
||||
if (currentQn == qn) {
|
||||
return;
|
||||
}
|
||||
currentQn = qn;
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
await queryLiveInfo();
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ class LiveRoomPage extends StatefulWidget {
|
||||
class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
final LiveRoomController _liveRoomController = Get.put(LiveRoomController());
|
||||
PlPlayerController? plPlayerController;
|
||||
late Future? _futureBuilder;
|
||||
late Future? _futureBuilderFuture;
|
||||
|
||||
bool isShowCover = true;
|
||||
bool isPlay = true;
|
||||
@ -27,18 +29,16 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
plPlayerController = _liveRoomController.plPlayerController;
|
||||
plPlayerController!.onPlayerStatusChanged.listen(
|
||||
(PlayerStatus status) {
|
||||
if (status == PlayerStatus.playing) {
|
||||
isShowCover = false;
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
floating = Floating();
|
||||
}
|
||||
videoSourceInit();
|
||||
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
|
||||
}
|
||||
|
||||
Future<void> videoSourceInit() async {
|
||||
_futureBuilder = _liveRoomController.queryLiveInfoH5();
|
||||
plPlayerController = _liveRoomController.plPlayerController;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -52,57 +52,127 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget videoPlayerPanel = FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.hasData && snapshot.data['status']) {
|
||||
return PLVideoPlayer(
|
||||
controller: plPlayerController!,
|
||||
bottomControl: BottomControl(
|
||||
controller: plPlayerController,
|
||||
liveRoomCtr: _liveRoomController,
|
||||
floating: floating,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Widget childWhenDisabled = Scaffold(
|
||||
primary: true,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: Size.fromHeight(
|
||||
MediaQuery.of(context).orientation == Orientation.portrait ? 56 : 0,
|
||||
),
|
||||
child: AppBar(
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: _liveRoomController.liveItem != null
|
||||
? Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
src: _liveRoomController.liveItem.face,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_liveRoomController.liveItem.uname,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
if (_liveRoomController.liveItem.watchedShow != null)
|
||||
Text(
|
||||
_liveRoomController
|
||||
.liveItem.watchedShow['text_large'] ??
|
||||
'',
|
||||
style: const TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(),
|
||||
// actions: [
|
||||
// SizedBox(
|
||||
// height: 34,
|
||||
// child: ElevatedButton(onPressed: () {}, child: const Text('关注')),
|
||||
// ),
|
||||
// const SizedBox(width: 12),
|
||||
// ],
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
Stack(
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Opacity(
|
||||
opacity: 0.8,
|
||||
child: Image.asset(
|
||||
'assets/images/live/default_bg.webp',
|
||||
fit: BoxFit.cover,
|
||||
// width: Get.width,
|
||||
// height: Get.height,
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: _liveRoomController
|
||||
.roomInfoH5.value.roomInfo?.appBackground !=
|
||||
'' &&
|
||||
_liveRoomController
|
||||
.roomInfoH5.value.roomInfo?.appBackground !=
|
||||
null
|
||||
? Opacity(
|
||||
opacity: 0.8,
|
||||
child: NetworkImgLayer(
|
||||
width: Get.width,
|
||||
height: Get.height,
|
||||
type: 'bg',
|
||||
src: _liveRoomController
|
||||
.roomInfoH5.value.roomInfo?.appBackground ??
|
||||
'',
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
AppBar(
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
foregroundColor: Colors.white,
|
||||
toolbarHeight:
|
||||
MediaQuery.of(context).orientation == Orientation.portrait
|
||||
? 56
|
||||
: 0,
|
||||
title: FutureBuilder(
|
||||
future: _futureBuilder,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 34,
|
||||
height: 34,
|
||||
type: 'avatar',
|
||||
src: _liveRoomController
|
||||
.roomInfoH5.value.anchorInfo!.baseInfo!.face,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_liveRoomController.roomInfoH5.value
|
||||
.anchorInfo!.baseInfo!.uname!,
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
if (_liveRoomController
|
||||
.roomInfoH5.value.watchedShow !=
|
||||
null)
|
||||
Text(
|
||||
_liveRoomController.roomInfoH5.value
|
||||
.watchedShow!['text_large'] ??
|
||||
'',
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
PopScope(
|
||||
canPop: plPlayerController?.isFullScreen.value != true,
|
||||
onPopInvoked: (bool didPop) {
|
||||
@ -120,55 +190,19 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
||||
Orientation.landscape
|
||||
? Get.size.height
|
||||
: Get.size.width * 9 / 16,
|
||||
child: plPlayerController!.videoPlayerController != null
|
||||
? PLVideoPlayer(
|
||||
controller: plPlayerController!,
|
||||
bottomControl: BottomControl(
|
||||
controller: plPlayerController,
|
||||
liveRoomCtr: _liveRoomController,
|
||||
floating: floating,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
child: videoPlayerPanel,
|
||||
),
|
||||
),
|
||||
// if (_liveRoomController.liveItem != null &&
|
||||
// _liveRoomController.liveItem.cover != null)
|
||||
// Visibility(
|
||||
// visible: isShowCover,
|
||||
// child: Positioned(
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: NetworkImgLayer(
|
||||
// type: 'emote',
|
||||
// src: _liveRoomController.liveItem.cover,
|
||||
// width: Get.size.width,
|
||||
// height: videoHeight,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
Widget childWhenEnabled = AspectRatio(
|
||||
aspectRatio: 16 / 9,
|
||||
child: plPlayerController!.videoPlayerController != null
|
||||
? PLVideoPlayer(
|
||||
controller: plPlayerController!,
|
||||
bottomControl: BottomControl(
|
||||
controller: plPlayerController,
|
||||
liveRoomCtr: _liveRoomController,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
return PiPSwitcher(
|
||||
childWhenDisabled: childWhenDisabled,
|
||||
childWhenEnabled: childWhenEnabled,
|
||||
childWhenEnabled: videoPlayerPanel,
|
||||
floating: floating,
|
||||
);
|
||||
} else {
|
||||
return childWhenDisabled;
|
||||
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/pages/live_room/index.dart';
|
||||
@ -29,7 +30,6 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget {
|
||||
|
||||
class _BottomControlState extends State<BottomControl> {
|
||||
late PlayUrlModel videoInfo;
|
||||
List<PlaySpeed> playSpeed = PlaySpeed.values;
|
||||
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
@ -84,6 +84,30 @@ class _BottomControlState extends State<BottomControl> {
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: PopupMenuButton<int>(
|
||||
padding: EdgeInsets.zero,
|
||||
onSelected: (value) {
|
||||
widget.liveRoomCtr!.changeQn(value);
|
||||
},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
widget.liveRoomCtr!.currentQnDesc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return widget.liveRoomCtr!.acceptQnList.map((e) {
|
||||
return PopupMenuItem<int>(
|
||||
value: e['code'],
|
||||
child: Text(e['desc']),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (Platform.isAndroid) ...[
|
||||
SizedBox(
|
||||
width: 34,
|
||||
@ -111,7 +135,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
|
@ -9,53 +9,20 @@ import 'package:pilipala/http/common.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/view.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
import 'package:pilipala/pages/rank/index.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import '../../models/common/dynamic_badge_mode.dart';
|
||||
import '../../models/common/nav_bar_config.dart';
|
||||
|
||||
class MainController extends GetxController {
|
||||
List<Widget> pages = <Widget>[
|
||||
const HomePage(),
|
||||
const RankPage(),
|
||||
const DynamicsPage(),
|
||||
const MediaPage(),
|
||||
];
|
||||
RxList navigationBars = [
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.home_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.home,
|
||||
size: 21,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.motion_photos_on_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.motion_photos_on,
|
||||
size: 21,
|
||||
),
|
||||
'label': "动态",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.video_collection_outlined,
|
||||
size: 20,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.video_collection,
|
||||
size: 21,
|
||||
),
|
||||
'label': "媒体库",
|
||||
'count': 0,
|
||||
}
|
||||
].obs;
|
||||
RxList navigationBars = defaultNavigationBars.obs;
|
||||
final StreamController<bool> bottomBarStream =
|
||||
StreamController<bool>.broadcast();
|
||||
Box setting = GStrorage.setting;
|
||||
@ -65,6 +32,7 @@ class MainController extends GetxController {
|
||||
int selectedIndex = 0;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
RxBool userLogin = false.obs;
|
||||
late Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -73,9 +41,18 @@ class MainController extends GetxController {
|
||||
Utils.checkUpdata();
|
||||
}
|
||||
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
||||
int defaultHomePage =
|
||||
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
|
||||
selectedIndex = defaultNavigationBars
|
||||
.indexWhere((item) => item['id'] == defaultHomePage);
|
||||
var userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
getUnreadDynamic();
|
||||
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
|
||||
SettingBoxKey.dynamicBadgeMode,
|
||||
defaultValue: DynamicBadgeMode.number.code)];
|
||||
if (dynamicBadgeType.value != DynamicBadgeMode.hidden) {
|
||||
getUnreadDynamic();
|
||||
}
|
||||
}
|
||||
|
||||
void onBackPressed(BuildContext context) {
|
||||
|
@ -3,9 +3,11 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/common/dynamic_badge_mode.dart';
|
||||
import 'package:pilipala/pages/dynamics/index.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/media/index.dart';
|
||||
import 'package:pilipala/pages/rank/index.dart';
|
||||
import 'package:pilipala/utils/event_bus.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
@ -21,6 +23,7 @@ class MainApp extends StatefulWidget {
|
||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final MainController _mainController = Get.put(MainController());
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
final RankController _rankController = Get.put(RankController());
|
||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||
final MediaController _mediaController = Get.put(MediaController());
|
||||
|
||||
@ -56,6 +59,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_homeController.flag = false;
|
||||
}
|
||||
|
||||
if (currentPage is RankPage) {
|
||||
if (_rankController.flag) {
|
||||
// 单击返回顶部 双击并刷新
|
||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||
_rankController.onRefresh();
|
||||
} else {
|
||||
_rankController.animateToTop();
|
||||
}
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
_rankController.flag = true;
|
||||
} else {
|
||||
_rankController.flag = false;
|
||||
}
|
||||
|
||||
if (currentPage is DynamicsPage) {
|
||||
if (_dynamicController.flag) {
|
||||
// 单击返回顶部 双击并刷新
|
||||
@ -127,11 +145,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
destinations: <Widget>[
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return NavigationDestination(
|
||||
icon: Badge(
|
||||
label: Text(e['count'].toString()),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible: e['count'] > 0,
|
||||
child: e['icon'],
|
||||
icon: Obx(
|
||||
() => Badge(
|
||||
label:
|
||||
_mainController.dynamicBadgeType.value ==
|
||||
DynamicBadgeMode.number
|
||||
? Text(e['count'].toString())
|
||||
: null,
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible:
|
||||
_mainController.dynamicBadgeType.value !=
|
||||
DynamicBadgeMode.hidden &&
|
||||
e['count'] > 0,
|
||||
child: e['icon'],
|
||||
),
|
||||
),
|
||||
selectedIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
@ -148,11 +176,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
items: [
|
||||
..._mainController.navigationBars.map((e) {
|
||||
return BottomNavigationBarItem(
|
||||
icon: Badge(
|
||||
label: Text(e['count'].toString()),
|
||||
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible: e['count'] > 0,
|
||||
child: e['icon'],
|
||||
icon: Obx(
|
||||
() => Badge(
|
||||
label:
|
||||
_mainController.dynamicBadgeType.value ==
|
||||
DynamicBadgeMode.number
|
||||
? Text(e['count'].toString())
|
||||
: null,
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(6, 0, 6, 0),
|
||||
isLabelVisible:
|
||||
_mainController.dynamicBadgeType.value !=
|
||||
DynamicBadgeMode.hidden &&
|
||||
e['count'] > 0,
|
||||
child: e['icon'],
|
||||
),
|
||||
),
|
||||
activeIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
|
@ -28,6 +28,11 @@ class MediaController extends GetxController {
|
||||
'title': '我的收藏',
|
||||
'onTap': () => Get.toNamed('/fav'),
|
||||
},
|
||||
{
|
||||
'icon': Icons.subscriptions_outlined,
|
||||
'title': '我的订阅',
|
||||
'onTap': () => Get.toNamed('/subscription'),
|
||||
},
|
||||
{
|
||||
'icon': Icons.watch_later_outlined,
|
||||
'title': '稍后再看',
|
||||
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
import 'package:pilipala/pages/main/index.dart';
|
||||
@ -102,7 +103,11 @@ class _MediaPageState extends State<MediaPage>
|
||||
],
|
||||
Obx(() => mediaController.userLogin.value
|
||||
? favFolder(mediaController, context)
|
||||
: const SizedBox())
|
||||
: const SizedBox()),
|
||||
SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom +
|
||||
kBottomNavigationBarHeight,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -20,7 +20,7 @@ class MemberController extends GetxController {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
late int ownerMid;
|
||||
// 投稿列表
|
||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||
RxList<VListItemModel>? archiveList = <VListItemModel>[].obs;
|
||||
dynamic userInfo;
|
||||
RxInt attribute = (-1).obs;
|
||||
RxString attributeText = '关注'.obs;
|
||||
|
@ -105,7 +105,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton(
|
||||
|
@ -25,7 +25,7 @@ class MemberArchiveController extends GetxController {
|
||||
|
||||
// 获取用户投稿
|
||||
Future getMemberArchive(type) async {
|
||||
if (type == 'onRefresh') {
|
||||
if (type == 'init') {
|
||||
pn = 1;
|
||||
}
|
||||
var res = await MemberHttp.memberArchive(
|
||||
@ -34,7 +34,12 @@ class MemberArchiveController extends GetxController {
|
||||
order: currentOrder['type']!,
|
||||
);
|
||||
if (res['status']) {
|
||||
archivesList.addAll(res['data'].list.vlist);
|
||||
if (type == 'init') {
|
||||
archivesList.value = res['data'].list.vlist;
|
||||
}
|
||||
if (type == 'onLoad') {
|
||||
archivesList.addAll(res['data'].list.vlist);
|
||||
}
|
||||
count = res['data'].page['count'];
|
||||
pn += 1;
|
||||
}
|
||||
@ -42,13 +47,14 @@ class MemberArchiveController extends GetxController {
|
||||
}
|
||||
|
||||
toggleSort() async {
|
||||
pn = 1;
|
||||
int index = orderList.indexOf(currentOrder);
|
||||
List<String> typeList = orderList.map((e) => e['type']!).toList();
|
||||
int index = typeList.indexOf(currentOrder['type']!);
|
||||
if (index == orderList.length - 1) {
|
||||
currentOrder.value = orderList.first;
|
||||
} else {
|
||||
currentOrder.value = orderList[index + 1];
|
||||
}
|
||||
getMemberArchive('init');
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import '../../common/widgets/http_error.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class MemberArchivePage extends StatefulWidget {
|
||||
@ -25,8 +26,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
final String heroTag = Utils.makeHeroTag(mid);
|
||||
_memberArchivesController =
|
||||
Get.put(MemberArchiveController(), tag: heroTag);
|
||||
_futureBuilderFuture =
|
||||
_memberArchivesController.getMemberArchive('onRefresh');
|
||||
_futureBuilderFuture = _memberArchivesController.getMemberArchive('init');
|
||||
scrollController = _memberArchivesController.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
@ -48,39 +48,16 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
titleSpacing: 0,
|
||||
centerTitle: false,
|
||||
title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium),
|
||||
// actions: [
|
||||
// Obx(
|
||||
// () => PopupMenuButton<String>(
|
||||
// padding: EdgeInsets.zero,
|
||||
// tooltip: '投稿排序',
|
||||
// icon: Icon(
|
||||
// Icons.more_vert_outlined,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// position: PopupMenuPosition.under,
|
||||
// onSelected: (String type) {},
|
||||
// itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
// for (var i in _memberArchivesController.orderList) ...[
|
||||
// PopupMenuItem<String>(
|
||||
// onTap: () {},
|
||||
// value: _memberArchivesController.currentOrder['label'],
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// Text(i['label']!),
|
||||
// if (_memberArchivesController.currentOrder['label'] ==
|
||||
// i['label']) ...[
|
||||
// const SizedBox(width: 10),
|
||||
// const Icon(Icons.done, size: 20),
|
||||
// ],
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ]
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
actions: [
|
||||
Obx(
|
||||
() => TextButton.icon(
|
||||
icon: const Icon(Icons.sort, size: 20),
|
||||
onPressed: _memberArchivesController.toggleSort,
|
||||
label: Text(_memberArchivesController.currentOrder['label']!),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
),
|
||||
body: CustomScrollView(
|
||||
controller: _memberArchivesController.scrollController,
|
||||
@ -110,10 +87,16 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
: const SliverToBoxAdapter(),
|
||||
);
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
|
@ -4,6 +4,7 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/member_dynamics/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import '../../common/widgets/http_error.dart';
|
||||
import '../dynamics/widgets/dynamic_panel.dart';
|
||||
|
||||
class MemberDynamicsPage extends StatefulWidget {
|
||||
@ -80,10 +81,16 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
: const SliverToBoxAdapter(),
|
||||
);
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user