Compare commits
202 Commits
v1.0.0.081
...
feature-bu
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ece96df21 | |||
| c11c5695a2 | |||
| 93581c2932 | |||
| fd43a8cb31 | |||
| 6f34bacb64 | |||
| 90314f89ed | |||
| 45c53de2c2 | |||
| 5c07cb4545 | |||
| 844053b138 | |||
| 6af8b91f63 | |||
| 8aa02f7450 | |||
| 38a1f2e1f7 | |||
| b2e1d98f51 | |||
| e19cf92992 | |||
| 80e10aeaad | |||
| b1a9152a49 | |||
| 935b7577b3 | |||
| fd5c4463d2 | |||
| 7fcbe4dd9d | |||
| 9a0c9f4021 | |||
| 6b3773a074 | |||
| 7be8ebaa7e | |||
| 092b1cee3d | |||
| 252f39e8c7 | |||
| e631ca04a0 | |||
| 3665d6a0f6 | |||
| e3c9e8c13b | |||
| 39995bae23 | |||
| f42d0d01ea | |||
| 0e39453558 | |||
| 8ff4259972 | |||
| 5082dc6d59 | |||
| 627df8e6ad | |||
| 2467fd0dea | |||
| c6f6af4628 | |||
| 9e907f9151 | |||
| 22e17d437b | |||
| 8a06ce65a5 | |||
| 72ff3fdab0 | |||
| 517ca032d2 | |||
| 396f9fbbac | |||
| c0332c74d7 | |||
| 2669b41ede | |||
| 81dace96d7 | |||
| d693d7ad6c | |||
| 8c02a566f6 | |||
| a2420d0bef | |||
| 29d3f78da9 | |||
| a864bea3f4 | |||
| 070156da86 | |||
| 69f846760d | |||
| 2ca79003bf | |||
| 18af065a1e | |||
| 0ad54d8c0b | |||
| 0dfcd4ed40 | |||
| 5b953ae0be | |||
| 392980f0e8 | |||
| 4c938ed8aa | |||
| 7f961e998c | |||
| e6b307ddd7 | |||
| f5b4ad33c6 | |||
| a2d4613293 | |||
| 1bebb32a0d | |||
| 217b036ee3 | |||
| fa95ae0cce | |||
| 977bac84c3 | |||
| 0f134b8dca | |||
| daec283bdf | |||
| 6f84eefbe4 | |||
| 4a7f2f027f | |||
| a39f81ac2a | |||
| 0cb580ba8e | |||
| c7187f2456 | |||
| cd38c0799d | |||
| aa63007c8a | |||
| b9b1ac7ec5 | |||
| 4036262bed | |||
| e35f39e353 | |||
| ff47aff7cf | |||
| 72b1116b57 | |||
| a0d71491be | |||
| b000a400c4 | |||
| be11598753 | |||
| 2d122374f6 | |||
| c88b89110b | |||
| 9b1bb8c566 | |||
| 01df8622e0 | |||
| ab8c88c696 | |||
| 08e4e64764 | |||
| 6f2ffcf2a1 | |||
| 3c1fa82010 | |||
| 4f6dd68954 | |||
| f06507925f | |||
| a49c400a8e | |||
| 1a60f74863 | |||
| a304df2670 | |||
| 1e1e2e5a77 | |||
| e1c69ac550 | |||
| f6c7143d2d | |||
| b485399517 | |||
| 108c37f0d7 | |||
| fceb55aaa3 | |||
| 52e44fb95b | |||
| dfbe3b1f6c | |||
| e90a9f0323 | |||
| 184088f96d | |||
| 73e8972a76 | |||
| c15646867c | |||
| f7e9fbaea7 | |||
| 5f730af347 | |||
| ce1daec3c5 | |||
| 67a7113bd7 | |||
| 4e2808fab7 | |||
| a1900c7362 | |||
| e8cd0d5330 | |||
| 9528a6f462 | |||
| b9e78bf2ec | |||
| d8abfde52d | |||
| 1d0b91f80b | |||
| 2dd967da4d | |||
| 53d4379bb9 | |||
| a928c575ef | |||
| a0e51c86fc | |||
| d670b8123a | |||
| 2621b096ac | |||
| 05631f7803 | |||
| 495ba57ca8 | |||
| 8990c4ae92 | |||
| 8bc6a32b06 | |||
| 6083578f93 | |||
| aa7419f352 | |||
| c90a6cd86c | |||
| 2e04c27292 | |||
| 5741f80536 | |||
| 161ba1c313 | |||
| 5d9dc6c1a9 | |||
| 1abe70d4d4 | |||
| 5fc959eb59 | |||
| 6322b29aef | |||
| 6461f72b5e | |||
| e3d561bffd | |||
| 535cf69967 | |||
| 4314b0fc3c | |||
| 9da113726b | |||
| 201422c150 | |||
| b67127123a | |||
| 3ce7578183 | |||
| 7b60cc2666 | |||
| ead24e90fb | |||
| 3ad3ca9d48 | |||
| 8a8e99f30b | |||
| 0fe6d6c8e2 | |||
| 5a03bee410 | |||
| cafde6cc04 | |||
| b6023e35bc | |||
| 6a1c89f885 | |||
| b7c0ef8341 | |||
| 9e44995082 | |||
| 8703d9f576 | |||
| 5812b5cff1 | |||
| 1884801ed2 | |||
| a19ab8d17f | |||
| a4078c0a8e | |||
| 90e811489b | |||
| 706bb0f924 | |||
| 01d6308350 | |||
| 332d5dc38c | |||
| 8f84c6a6f9 | |||
| cc8753e8de | |||
| 8c8ddc9d93 | |||
| 5f03244085 | |||
| 50a5653516 | |||
| 7bb7159d48 | |||
| 83341cd62b | |||
| 8627869309 | |||
| 6bbbdd7710 | |||
| 599d6983fc | |||
| b7eed8578a | |||
| ec9d9739fe | |||
| 740116e873 | |||
| c8810b458f | |||
| 2b0dc9d285 | |||
| 7c2518bcd2 | |||
| 9db76e99db | |||
| 40849cb68d | |||
| b435023c99 | |||
| b55568ef2a | |||
| 47a3c964c0 | |||
| 85ff7c7a92 | |||
| 083d05ed7e | |||
| 08862e7f72 | |||
| 04d953a3a2 | |||
| be1cc25d12 | |||
| adff2f2828 | |||
| 592b32fc7a | |||
| d9a758464c | |||
| 0c067f7ca6 | |||
| b6aa144205 | |||
| 438a94e298 | |||
| b800f1b83b | |||
| ddceda1184 | |||
| 0003c057cd |
23
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Bug 反馈
|
||||||
|
about: 描述你所遇到的bug
|
||||||
|
title: ''
|
||||||
|
labels: 问题反馈
|
||||||
|
assignees: guozhigq
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题描述
|
||||||
|
请提供一个清晰而简明的问题描述。
|
||||||
|
|
||||||
|
### 复现步骤
|
||||||
|
请提供复现该问题所需的具体步骤。
|
||||||
|
|
||||||
|
### 预期行为
|
||||||
|
请描述你期望的正确行为或结果。
|
||||||
|
|
||||||
|
### 系统信息
|
||||||
|
请提供关于您的环境的详细信息,包括操作系统、浏览器版本等。
|
||||||
|
|
||||||
|
### 相关截图或日志
|
||||||
|
如果有的话,请提供相关的截图、错误日志或其他有助于解决问题的信息。
|
||||||
20
.github/ISSUE_TEMPLATE/功能请求.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/功能请求.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: 功能请求
|
||||||
|
about: 对于功能的一些建议
|
||||||
|
title: ''
|
||||||
|
labels: 功能
|
||||||
|
assignees: guozhigq
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
请提供对所请求功能的清晰描述。
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
请描述你希望通过这个功能实现的目标。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
请提供已实现该功能或类似功能的应用
|
||||||
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -59,16 +59,16 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
|
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: 获取当前日期
|
# - name: 获取当前日期
|
||||||
id: date
|
# id: date
|
||||||
run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
|
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk
|
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk
|
||||||
run: |
|
run: |
|
||||||
DATE=${{ steps.date.outputs.date }}
|
# DATE=${{ steps.date.outputs.date }}
|
||||||
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
|
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
|
||||||
if [[ $file =~ app-(.*)-release.apk ]]; then
|
if [[ $file =~ app-(.*)-release.apk ]]; then
|
||||||
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}-${{ steps.version.outputs.version }}($DATE).apk"
|
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}-${{ steps.version.outputs.version }}.apk"
|
||||||
mv "$file" "$new_file_name"
|
mv "$file" "$new_file_name"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
24
README.md
24
README.md
@ -11,10 +11,11 @@
|
|||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/main_screen.png" width="96%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### 开发环境
|
## 开发环境
|
||||||
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -30,7 +31,15 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 功能
|
|
||||||
|
## 技术交流
|
||||||
|
|
||||||
|
Telegram: https://t.me/+lm_oOVmF0RJiODk1
|
||||||
|
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
||||||
|
|
||||||
@ -74,12 +83,14 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
- [ ] 弹幕
|
- [ ] 弹幕
|
||||||
- [ ] 字幕
|
- [ ] 字幕
|
||||||
- [x] 记忆播放
|
- [x] 记忆播放
|
||||||
|
- [x] 视频比例:高度/宽度适应、填充、包含等
|
||||||
|
|
||||||
- [x] 搜索相关
|
- [x] 搜索相关
|
||||||
- [x] 热搜
|
- [x] 热搜
|
||||||
- [x] 搜索历史
|
- [x] 搜索历史
|
||||||
- [x] 默认搜索词
|
- [x] 默认搜索词
|
||||||
- [x] 投稿、番剧、直播间、用户搜索
|
- [x] 投稿、番剧、直播间、用户搜索
|
||||||
|
- [x] 视频搜索排序、按时长筛选
|
||||||
|
|
||||||
- [x] 视频详情页相关
|
- [x] 视频详情页相关
|
||||||
- [x] 视频选集(分p)切换
|
- [x] 视频选集(分p)切换
|
||||||
@ -96,26 +107,29 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
- [x] 图片质量设定
|
- [x] 图片质量设定
|
||||||
- [x] 主题模式:亮色/暗色/跟随系统
|
- [x] 主题模式:亮色/暗色/跟随系统
|
||||||
- [x] 震动反馈(可选)
|
- [x] 震动反馈(可选)
|
||||||
|
- [x] 高帧率
|
||||||
|
- [x] 自动全屏
|
||||||
- [ ] 等等
|
- [ ] 等等
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 下载
|
## 下载
|
||||||
|
|
||||||
可以通过右侧release进行下载或拉取代码到本地进行编译
|
可以通过右侧release进行下载或拉取代码到本地进行编译
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 声明
|
## 声明
|
||||||
|
|
||||||
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
||||||
所用API皆从官方网站收集, 不提供任何破解内容。
|
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||||
|
|
||||||
感谢使用
|
感谢使用
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
### 致谢
|
## 致谢
|
||||||
|
|
||||||
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
||||||
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)
|
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)
|
||||||
|
|||||||
@ -20,7 +20,22 @@
|
|||||||
"android.support.customtabs.action.CustomTabsService" />
|
"android.support.customtabs.action.CustomTabsService" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
<application
|
|
||||||
|
<queries>
|
||||||
|
<!-- If your app checks for http support -->
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="http" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
<application
|
||||||
android:label="PiliPala"
|
android:label="PiliPala"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
@ -48,6 +63,164 @@
|
|||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="bilibili" android:host="forward" />
|
||||||
|
<data android:scheme="bilibili" android:host="comment"
|
||||||
|
android:pathPattern="/detail/.*/.*/.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="uper" />
|
||||||
|
<data android:scheme="bilibili" android:host="article"
|
||||||
|
android:pathPattern="/readlist" />
|
||||||
|
<data android:scheme="bilibili" android:host="advertise" android:path="/home" />
|
||||||
|
<data android:scheme="bilibili" android:host="clip" />
|
||||||
|
<data android:scheme="bilibili" android:host="search" />
|
||||||
|
<data android:scheme="bilibili" android:host="stardust-search" />
|
||||||
|
<data android:scheme="bilibili" android:host="music" />
|
||||||
|
<data android:scheme="bilibili" android:host="bangumi"
|
||||||
|
android:pathPattern="/season.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="bangumi" android:pathPattern="/.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="pictureshow"
|
||||||
|
android:pathPrefix="/creative_center" />
|
||||||
|
<data android:scheme="bilibili" android:host="cliparea" />
|
||||||
|
<data android:scheme="bilibili" android:host="im" />
|
||||||
|
<data android:scheme="bilibili" android:host="im" android:path="/notifications" />
|
||||||
|
<data android:scheme="bilibili" android:host="following" />
|
||||||
|
<data android:scheme="bilibili" android:host="following"
|
||||||
|
android:pathPattern="/detail/.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="following"
|
||||||
|
android:path="/publishInfo/" />
|
||||||
|
<data android:scheme="bilibili" android:host="laser" android:pathPattern="/.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="livearea" />
|
||||||
|
<data android:scheme="bilibili" android:host="live" />
|
||||||
|
<data android:scheme="bilibili" android:host="catalog" />
|
||||||
|
<data android:scheme="bilibili" android:host="browser" />
|
||||||
|
<data android:scheme="bilibili" android:host="user_center" />
|
||||||
|
<data android:scheme="bilibili" android:host="login" />
|
||||||
|
<data android:scheme="bilibili" android:host="space" />
|
||||||
|
<data android:scheme="bilibili" android:host="author" />
|
||||||
|
<data android:scheme="bilibili" android:host="tag" />
|
||||||
|
<data android:scheme="bilibili" android:host="rank" />
|
||||||
|
<data android:scheme="bilibili" android:host="external" />
|
||||||
|
<data android:scheme="bilibili" android:host="blank" />
|
||||||
|
<data android:scheme="bilibili" android:host="home" />
|
||||||
|
<data android:scheme="bilibili" android:host="root" />
|
||||||
|
<data android:scheme="bilibili" android:host="video" />
|
||||||
|
<data android:scheme="bilibili" android:host="story" />
|
||||||
|
<data android:scheme="bilibili" android:host="podcast" />
|
||||||
|
<data android:scheme="bilibili" android:host="search" />
|
||||||
|
<data android:scheme="bilibili" android:host="main" android:path="/favorite" />
|
||||||
|
<data android:scheme="bilibili" android:host="pgc" android:path="/theater/match" />
|
||||||
|
<data android:scheme="bilibili" android:host="pgc" android:path="/theater/square" />
|
||||||
|
<data android:scheme="bilibili" android:host="m.bilibili.com"
|
||||||
|
android:path="/topic-detail" />
|
||||||
|
<data android:scheme="bilibili" android:host="article" />
|
||||||
|
<data android:scheme="bilibili" android:host="pegasus"
|
||||||
|
android:pathPattern="/channel/v2/.*" />
|
||||||
|
<data android:scheme="bilibili" android:host="feed" android:pathPattern="/channel" />
|
||||||
|
<data android:scheme="bilibili" android:host="vip" />
|
||||||
|
<data android:scheme="bilibili" android:host="user_center" android:path="/vip" />
|
||||||
|
<data android:scheme="bilibili" android:host="history" />
|
||||||
|
<data android:scheme="bilibili" android:host="charge" android:path="/rank" />
|
||||||
|
<data android:scheme="bilibili" android:host="assistant" />
|
||||||
|
<data android:scheme="bilibili" android:host="assistant" />
|
||||||
|
<data android:scheme="bilibili" android:host="feedback" />
|
||||||
|
<data android:scheme="bilibili" android:host="auth" android:path="/launch" />
|
||||||
|
|
||||||
|
<data android:scheme="http" android:host="live.bilibili.com"
|
||||||
|
android:pathPattern="/live/.*" />
|
||||||
|
<data android:scheme="https" android:host="live.bilibili.com"
|
||||||
|
android:pathPattern="/live/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.tv"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.tv"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/story/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/story/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/i/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/i/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/bangumi/i/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/bangumi/i/.*" />
|
||||||
|
<data android:scheme="http" android:host="bangumi.bilibili.com"
|
||||||
|
android:pathPattern="/.*" />
|
||||||
|
<data android:scheme="https" android:host="bangumi.bilibili.com"
|
||||||
|
android:pathPattern="/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/.*" />
|
||||||
|
<data android:scheme="http" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/.*" />
|
||||||
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ss.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ss.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
|
<data android:scheme="http" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/bangumi/play/ss.*" />
|
||||||
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ss.*" />
|
||||||
|
<data android:scheme="http" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/read/cv.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/read/cv.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com" android:path="/review/" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com" android:path="/review/" />
|
||||||
|
<data android:scheme="http" android:host="bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.cn"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/video/.*" />
|
||||||
|
<data android:scheme="http" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/video/.*" />
|
||||||
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
|
android:pathPattern="/mobile/video/.*" />
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
@ -56,7 +229,6 @@
|
|||||||
android:value="2" />
|
android:value="2" />
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
@ -14,5 +14,6 @@
|
|||||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode" tools:targetApi="o_mr1">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
assets/images/error.svg
Normal file
1
assets/images/error.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/sreenshot/main_screen.png
Normal file
BIN
assets/sreenshot/main_screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
7
change_log/1.0.1.0817.md
Normal file
7
change_log/1.0.1.0817.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 升级播放器依赖
|
||||||
|
+ android平台 AV1格式视频支持
|
||||||
|
+ 视频全屏功能
|
||||||
|
|
||||||
19
change_log/1.0.2.0819.md
Normal file
19
change_log/1.0.2.0819.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## 1.0.2
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 自动检查更新
|
||||||
|
+ 封面图片保存
|
||||||
|
+ 动态跳转番剧
|
||||||
|
+ 历史记录番剧记忆播放
|
||||||
|
+ 一键清空稍后再看
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 切换分P cid未切换
|
||||||
|
+ cookie存储问题
|
||||||
|
+ 登录/退出登录问题
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 页面空/异常状态样式
|
||||||
|
+ 退出登录提示
|
||||||
|
+ 请求节流
|
||||||
|
+ 全屏播放
|
||||||
19
change_log/1.0.3.0821.md
Normal file
19
change_log/1.0.3.0821.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## 1.0.3
|
||||||
|
|
||||||
|
建议卸载1.0.2版本,重新安装
|
||||||
|
### 新功能
|
||||||
|
+ 底部播放进度条设置
|
||||||
|
+ 复制图片链接
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 用户数据格式修改
|
||||||
|
+ video Fit
|
||||||
|
+ 没有audio 资源的视频异常
|
||||||
|
+ 评论区域图片无法点击
|
||||||
|
+ 视频进度条拖动问题
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 页面空/异常状态样式
|
||||||
|
+ 部分页面样式
|
||||||
|
+ 图片预览页面样式
|
||||||
21
change_log/1.0.4.0822.md
Normal file
21
change_log/1.0.4.0822.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## 1.0.4
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 热搜刷新
|
||||||
|
+ 视频搜索排序、筛选
|
||||||
|
+ app字体大小自定义
|
||||||
|
+ app主题色自定义
|
||||||
|
+ 「课堂」类动态渲染
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 搜索词联想richText渲染异常
|
||||||
|
+ 部分动态点赞异常
|
||||||
|
+ 默认视频解码格式
|
||||||
|
+ 搜索页面返回搜索词未清空
|
||||||
|
+ 动态详情评论加载异常
|
||||||
|
+ 动态页面下拉刷新数据异常
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 一些样式修改
|
||||||
|
+ 取消热搜词缓存
|
||||||
30
change_log/1.0.5.0826.md
Normal file
30
change_log/1.0.5.0826.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
## 1.0.5
|
||||||
|
|
||||||
|
主要是bug修复跟一部分小功能,弹幕功能需要下一版。
|
||||||
|
问题反馈请前往QQ频道或提交issues。
|
||||||
|
感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 高帧率支持
|
||||||
|
+ 默认评论排序设置
|
||||||
|
+ 默认动态类别设置
|
||||||
|
+ 动态合集查看
|
||||||
|
+ 同时观看人数
|
||||||
|
+ iOS路由切换效果
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 收藏夹翻页
|
||||||
|
+ 首页搜索框频繁点击消失
|
||||||
|
+ 评论排序切换空白
|
||||||
|
+ 快速返回首页
|
||||||
|
+ 重复进入个人中心页面数据未刷新
|
||||||
|
+ 动态goods数据异常
|
||||||
|
+ 大会员切换番剧
|
||||||
|
+ 高画质codes匹配
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 倍速选择
|
||||||
|
+ 播放器亮度记忆
|
||||||
|
+ 下载对应版本apk
|
||||||
34
change_log/1.0.6.0902.md
Normal file
34
change_log/1.0.6.0902.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
## 1.0.6
|
||||||
|
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 首页单列布局
|
||||||
|
+ 首页推荐展示播放量、弹幕数
|
||||||
|
+ 简单弹幕功能实现(持续开发中...)
|
||||||
|
+ 评论区搜索关键词开关 issues#46
|
||||||
|
+ 热搜榜隐藏功能 issues#35
|
||||||
|
+ 自动全屏 issues#37
|
||||||
|
+ 快速收藏功能
|
||||||
|
+ 双击快进/快退开关
|
||||||
|
+ 评论链接跳转视频
|
||||||
|
+ 支持移除单个稍后再看
|
||||||
|
+ app scheme外链跳转
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 杜比、无损音频切换
|
||||||
|
+ 收藏夹展示 issues#42
|
||||||
|
+ 搜索建议次 issues#47
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 倍速选择优化
|
||||||
|
+ 导航条沉浸
|
||||||
|
+ 取消Hero动画
|
||||||
|
+ 视频锁定逻辑
|
||||||
|
+ 登录逻辑优化
|
||||||
|
+ 图片预览样式
|
||||||
|
+ +评论区用户点击范围
|
||||||
|
+ 关注、粉丝页面优化
|
||||||
|
+ 关闭自动播放时播放器初始化逻辑
|
||||||
22
change_log/1.0.7.0908.md
Normal file
22
change_log/1.0.7.0908.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
## 1.0.7
|
||||||
|
|
||||||
|
默认倍速、直播弹幕、专栏等功能开发中
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 弹幕设置、屏蔽功能
|
||||||
|
+ 不是很完美的后台播放功能
|
||||||
|
+ 不是很完美的画中画(pip)功能(Android端)
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 动态页面加载异常
|
||||||
|
+ 网络异常时页面空白
|
||||||
|
+ 竖屏全屏状态栏问题
|
||||||
|
+ iOS端代理请求异常
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 图片预览
|
||||||
|
+ 全屏播放时自动旋转
|
||||||
|
+ 转发内容增加视频标题
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -1,10 +1,14 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- appscheme (1.0.4):
|
||||||
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_volume_controller (0.0.1):
|
||||||
|
- Flutter
|
||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
@ -31,6 +35,10 @@ PODS:
|
|||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
|
- status_bar_control (3.2.1):
|
||||||
|
- Flutter
|
||||||
|
- system_proxy (0.0.1):
|
||||||
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
@ -43,9 +51,11 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
@ -56,6 +66,8 @@ DEPENDENCIES:
|
|||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
|
- status_bar_control (from `.symlinks/plugins/status_bar_control/ios`)
|
||||||
|
- system_proxy (from `.symlinks/plugins/system_proxy/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
@ -68,12 +80,16 @@ SPEC REPOS:
|
|||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
appscheme:
|
||||||
|
:path: ".symlinks/plugins/appscheme/ios"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_volume_controller:
|
||||||
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
image_gallery_saver:
|
image_gallery_saver:
|
||||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
@ -94,6 +110,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
|
status_bar_control:
|
||||||
|
:path: ".symlinks/plugins/status_bar_control/ios"
|
||||||
|
system_proxy:
|
||||||
|
:path: ".symlinks/plugins/system_proxy/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
volume_controller:
|
volume_controller:
|
||||||
@ -106,9 +126,11 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
@ -121,6 +143,8 @@ SPEC CHECKSUMS:
|
|||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
|
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
||||||
|
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
|
|||||||
@ -1,62 +1,107 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>PiliPala</string>
|
<string>PiliPala</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>pilipala</string>
|
<string>PiliPala</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>请允许APP保存图片到相册</string>
|
<string>请允许APP保存图片到相册</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>App需要您的同意,才能访问相册</string>
|
<string>App需要您的同意,才能访问相册</string>
|
||||||
<key>NSAppleMusicUsageDescription</key>
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
<string>App需要您的同意,才能访问媒体资料库</string>
|
<string>App需要您的同意,才能访问媒体资料库</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
<string>http</string>
|
<string>http</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
<!-- Add Scheme related information -->
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>http</string>
|
||||||
|
<string>https</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string></string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>m.bilibili.com</string>
|
||||||
|
<string>bilibili.com</string>
|
||||||
|
<string>www.bilibili.com</string>
|
||||||
|
<string>bangumi.bilibili.com</string>
|
||||||
|
<string>bilibili.cn</string>
|
||||||
|
<string>www.bilibili.cn</string>
|
||||||
|
<string>bangumi.bilibili.cn</string>
|
||||||
|
<string>bilibili.tv</string>
|
||||||
|
<string>www.bilibili.tv</string>
|
||||||
|
<string>bangumi.bilibili.tv</string>
|
||||||
|
<string>miniapp.bilibili.com</string>
|
||||||
|
<string>live.bilibili.com</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
|
||||||
|
<!-- 当其他应用程序或系统通过 bilibili -->
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleURLName</key>
|
||||||
|
<string>bilibili</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>bilibili</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -45,11 +45,6 @@ class VideoCardVSkeleton extends StatelessWidget {
|
|||||||
margin: const EdgeInsets.only(bottom: 12),
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
),
|
),
|
||||||
Container(
|
|
||||||
width: 80,
|
|
||||||
height: 12,
|
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AnimatedDialog extends StatefulWidget {
|
class AnimatedDialog extends StatefulWidget {
|
||||||
const AnimatedDialog({Key? key, required this.child}) : super(key: key);
|
const AnimatedDialog({Key? key, required this.child, this.closeFn})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final Function? closeFn;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => AnimatedDialogState();
|
State<StatefulWidget> createState() => AnimatedDialogState();
|
||||||
@ -39,12 +41,16 @@ class AnimatedDialogState extends State<AnimatedDialog>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.black.withOpacity(opacityAnimation!.value),
|
color: Colors.black.withOpacity(opacityAnimation!.value),
|
||||||
child: Center(
|
child: InkWell(
|
||||||
child: FadeTransition(
|
splashColor: Colors.transparent,
|
||||||
opacity: scaleAnimation!,
|
onTap: () => widget.closeFn!(),
|
||||||
child: ScaleTransition(
|
child: Center(
|
||||||
scale: scaleAnimation!,
|
child: FadeTransition(
|
||||||
child: widget.child,
|
opacity: scaleAnimation!,
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: scaleAnimation!,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
351
lib/common/widgets/app_expansion_panel_list.dart
Normal file
351
lib/common/widgets/app_expansion_panel_list.dart
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const double _kPanelHeaderCollapsedHeight = kMinInteractiveDimension;
|
||||||
|
|
||||||
|
class _SaltedKey<S, V> extends LocalKey {
|
||||||
|
const _SaltedKey(this.salt, this.value);
|
||||||
|
|
||||||
|
final S salt;
|
||||||
|
final V value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other.runtimeType != runtimeType) return false;
|
||||||
|
return other is _SaltedKey<S, V> &&
|
||||||
|
other.salt == salt &&
|
||||||
|
other.value == value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, salt, value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
final String saltString = S == String ? "<'$salt'>" : '<$salt>';
|
||||||
|
final String valueString = V == String ? "<'$value'>" : '<$value>';
|
||||||
|
return '[$saltString $valueString]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppExpansionPanelList extends StatefulWidget {
|
||||||
|
/// Creates an expansion panel list widget. The [expansionCallback] is
|
||||||
|
/// triggered when an expansion panel expand/collapse button is pushed.
|
||||||
|
///
|
||||||
|
/// The [children] and [animationDuration] arguments must not be null.
|
||||||
|
const AppExpansionPanelList({
|
||||||
|
super.key,
|
||||||
|
required this.children,
|
||||||
|
this.expansionCallback,
|
||||||
|
this.animationDuration = kThemeAnimationDuration,
|
||||||
|
this.expandedHeaderPadding = EdgeInsets.zero,
|
||||||
|
this.dividerColor,
|
||||||
|
this.elevation = 2,
|
||||||
|
}) : _allowOnlyOnePanelOpen = false,
|
||||||
|
initialOpenPanelValue = null;
|
||||||
|
|
||||||
|
/// The children of the expansion panel list. They are laid out in a similar
|
||||||
|
/// fashion to [ListBody].
|
||||||
|
final List<AppExpansionPanel> children;
|
||||||
|
|
||||||
|
/// The callback that gets called whenever one of the expand/collapse buttons
|
||||||
|
/// is pressed. The arguments passed to the callback are the index of the
|
||||||
|
/// pressed panel and whether the panel is currently expanded or not.
|
||||||
|
///
|
||||||
|
/// If AppExpansionPanelList.radio is used, the callback may be called a
|
||||||
|
/// second time if a different panel was previously open. The arguments
|
||||||
|
/// passed to the second callback are the index of the panel that will close
|
||||||
|
/// and false, marking that it will be closed.
|
||||||
|
///
|
||||||
|
/// For AppExpansionPanelList, the callback needs to setState when it's notified
|
||||||
|
/// about the closing/opening panel. On the other hand, the callback for
|
||||||
|
/// AppExpansionPanelList.radio is simply meant to inform the parent widget of
|
||||||
|
/// changes, as the radio panels' open/close states are managed internally.
|
||||||
|
///
|
||||||
|
/// This callback is useful in order to keep track of the expanded/collapsed
|
||||||
|
/// panels in a parent widget that may need to react to these changes.
|
||||||
|
final ExpansionPanelCallback? expansionCallback;
|
||||||
|
|
||||||
|
/// The duration of the expansion animation.
|
||||||
|
final Duration animationDuration;
|
||||||
|
|
||||||
|
// Whether multiple panels can be open simultaneously
|
||||||
|
final bool _allowOnlyOnePanelOpen;
|
||||||
|
|
||||||
|
/// The value of the panel that initially begins open. (This value is
|
||||||
|
/// only used when initializing with the [AppExpansionPanelList.radio]
|
||||||
|
/// constructor.)
|
||||||
|
final Object? initialOpenPanelValue;
|
||||||
|
|
||||||
|
/// The padding that surrounds the panel header when expanded.
|
||||||
|
///
|
||||||
|
/// By default, 16px of space is added to the header vertically (above and below)
|
||||||
|
/// during expansion.
|
||||||
|
final EdgeInsets expandedHeaderPadding;
|
||||||
|
|
||||||
|
/// Defines color for the divider when [AppExpansionPanel.isExpanded] is false.
|
||||||
|
///
|
||||||
|
/// If `dividerColor` is null, then [DividerThemeData.color] is used. If that
|
||||||
|
/// is null, then [ThemeData.dividerColor] is used.
|
||||||
|
final Color? dividerColor;
|
||||||
|
|
||||||
|
/// Defines elevation for the [AppExpansionPanel] while it's expanded.
|
||||||
|
///
|
||||||
|
/// By default, the value of elevation is 2.
|
||||||
|
final double elevation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AppExpansionPanelList> createState() => _AppExpansionPanelListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppExpansionPanelListState extends State<AppExpansionPanelList> {
|
||||||
|
ExpansionPanelRadio? _currentOpenPanel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget._allowOnlyOnePanelOpen) {
|
||||||
|
assert(_allIdentifiersUnique(),
|
||||||
|
'All ExpansionPanelRadio identifier values must be unique.');
|
||||||
|
if (widget.initialOpenPanelValue != null) {
|
||||||
|
_currentOpenPanel = searchPanelByValue(
|
||||||
|
widget.children.cast<ExpansionPanelRadio>(),
|
||||||
|
widget.initialOpenPanelValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(AppExpansionPanelList oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (widget._allowOnlyOnePanelOpen) {
|
||||||
|
assert(_allIdentifiersUnique(),
|
||||||
|
'All ExpansionPanelRadio identifier values must be unique.');
|
||||||
|
// If the previous widget was non-radio AppExpansionPanelList, initialize the
|
||||||
|
// open panel to widget.initialOpenPanelValue
|
||||||
|
if (!oldWidget._allowOnlyOnePanelOpen) {
|
||||||
|
_currentOpenPanel = searchPanelByValue(
|
||||||
|
widget.children.cast<ExpansionPanelRadio>(),
|
||||||
|
widget.initialOpenPanelValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_currentOpenPanel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _allIdentifiersUnique() {
|
||||||
|
final Map<Object, bool> identifierMap = <Object, bool>{};
|
||||||
|
for (final ExpansionPanelRadio child
|
||||||
|
in widget.children.cast<ExpansionPanelRadio>()) {
|
||||||
|
identifierMap[child.value] = true;
|
||||||
|
}
|
||||||
|
return identifierMap.length == widget.children.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isChildExpanded(int index) {
|
||||||
|
if (widget._allowOnlyOnePanelOpen) {
|
||||||
|
final ExpansionPanelRadio radioWidget =
|
||||||
|
widget.children[index] as ExpansionPanelRadio;
|
||||||
|
return _currentOpenPanel?.value == radioWidget.value;
|
||||||
|
}
|
||||||
|
return widget.children[index].isExpanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handlePressed(bool isExpanded, int index) {
|
||||||
|
widget.expansionCallback?.call(index, isExpanded);
|
||||||
|
|
||||||
|
if (widget._allowOnlyOnePanelOpen) {
|
||||||
|
final ExpansionPanelRadio pressedChild =
|
||||||
|
widget.children[index] as ExpansionPanelRadio;
|
||||||
|
|
||||||
|
// If another ExpansionPanelRadio was already open, apply its
|
||||||
|
// expansionCallback (if any) to false, because it's closing.
|
||||||
|
for (int childIndex = 0;
|
||||||
|
childIndex < widget.children.length;
|
||||||
|
childIndex += 1) {
|
||||||
|
final ExpansionPanelRadio child =
|
||||||
|
widget.children[childIndex] as ExpansionPanelRadio;
|
||||||
|
if (widget.expansionCallback != null &&
|
||||||
|
childIndex != index &&
|
||||||
|
child.value == _currentOpenPanel?.value) {
|
||||||
|
widget.expansionCallback!(childIndex, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_currentOpenPanel = isExpanded ? null : pressedChild;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpansionPanelRadio? searchPanelByValue(
|
||||||
|
List<ExpansionPanelRadio> panels, Object? value) {
|
||||||
|
for (final ExpansionPanelRadio panel in panels) {
|
||||||
|
if (panel.value == value) return panel;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
assert(
|
||||||
|
kElevationToShadow.containsKey(widget.elevation),
|
||||||
|
'Invalid value for elevation. See the kElevationToShadow constant for'
|
||||||
|
' possible elevation values.',
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<MergeableMaterialItem> items = <MergeableMaterialItem>[];
|
||||||
|
|
||||||
|
for (int index = 0; index < widget.children.length; index += 1) {
|
||||||
|
//todo: Uncomment to add gap between selected panels
|
||||||
|
/*if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
|
||||||
|
items.add(MaterialGap(key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));*/
|
||||||
|
|
||||||
|
final AppExpansionPanel child = widget.children[index];
|
||||||
|
final Widget headerWidget = child.headerBuilder(
|
||||||
|
context,
|
||||||
|
_isChildExpanded(index),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget? expandIconContainer = ExpandIcon(
|
||||||
|
isExpanded: _isChildExpanded(index),
|
||||||
|
onPressed: !child.canTapOnHeader
|
||||||
|
? (bool isExpanded) => _handlePressed(isExpanded, index)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
if (!child.canTapOnHeader) {
|
||||||
|
final MaterialLocalizations localizations =
|
||||||
|
MaterialLocalizations.of(context);
|
||||||
|
expandIconContainer = Semantics(
|
||||||
|
label: _isChildExpanded(index)
|
||||||
|
? localizations.expandedIconTapHint
|
||||||
|
: localizations.collapsedIconTapHint,
|
||||||
|
container: true,
|
||||||
|
child: expandIconContainer,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final iconContainer = child.iconBuilder;
|
||||||
|
if (iconContainer != null) {
|
||||||
|
expandIconContainer = iconContainer(
|
||||||
|
expandIconContainer,
|
||||||
|
_isChildExpanded(index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget header = Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: widget.animationDuration,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
margin: _isChildExpanded(index)
|
||||||
|
? widget.expandedHeaderPadding
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
minHeight: _kPanelHeaderCollapsedHeight),
|
||||||
|
child: headerWidget,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (expandIconContainer != null) expandIconContainer,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (child.canTapOnHeader) {
|
||||||
|
header = MergeSemantics(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _handlePressed(_isChildExpanded(index), index),
|
||||||
|
child: header,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
items.add(
|
||||||
|
MaterialSlice(
|
||||||
|
key: _SaltedKey<BuildContext, int>(context, index * 2),
|
||||||
|
color: child.backgroundColor,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
header,
|
||||||
|
AnimatedCrossFade(
|
||||||
|
firstChild: Container(height: 0.0),
|
||||||
|
secondChild: child.body,
|
||||||
|
firstCurve:
|
||||||
|
const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
|
||||||
|
secondCurve:
|
||||||
|
const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
|
||||||
|
sizeCurve: Curves.fastOutSlowIn,
|
||||||
|
crossFadeState: _isChildExpanded(index)
|
||||||
|
? CrossFadeState.showSecond
|
||||||
|
: CrossFadeState.showFirst,
|
||||||
|
duration: widget.animationDuration,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (_isChildExpanded(index) && index != widget.children.length - 1) {
|
||||||
|
items.add(MaterialGap(
|
||||||
|
key: _SaltedKey<BuildContext, int>(context, index * 2 + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MergeableMaterial(
|
||||||
|
hasDividers: true,
|
||||||
|
dividerColor: widget.dividerColor,
|
||||||
|
elevation: widget.elevation,
|
||||||
|
children: items,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef ExpansionPanelIconBuilder = Widget? Function(
|
||||||
|
Widget child,
|
||||||
|
bool isExpanded,
|
||||||
|
);
|
||||||
|
|
||||||
|
class AppExpansionPanel {
|
||||||
|
/// Creates an expansion panel to be used as a child for [ExpansionPanelList].
|
||||||
|
/// See [ExpansionPanelList] for an example on how to use this widget.
|
||||||
|
///
|
||||||
|
/// The [headerBuilder], [body], and [isExpanded] arguments must not be null.
|
||||||
|
AppExpansionPanel({
|
||||||
|
required this.headerBuilder,
|
||||||
|
required this.body,
|
||||||
|
this.iconBuilder,
|
||||||
|
this.isExpanded = false,
|
||||||
|
this.canTapOnHeader = false,
|
||||||
|
this.backgroundColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The widget builder that builds the expansion panels' header.
|
||||||
|
final ExpansionPanelHeaderBuilder headerBuilder;
|
||||||
|
|
||||||
|
/// The widget builder that builds the expansion panels' icon.
|
||||||
|
///
|
||||||
|
/// If not pass any function, then default icon will be displayed.
|
||||||
|
///
|
||||||
|
/// If builder function return null, then icon will not displayed.
|
||||||
|
final ExpansionPanelIconBuilder? iconBuilder;
|
||||||
|
|
||||||
|
/// The body of the expansion panel that's displayed below the header.
|
||||||
|
///
|
||||||
|
/// This widget is visible only when the panel is expanded.
|
||||||
|
final Widget body;
|
||||||
|
|
||||||
|
/// Whether the panel is expanded.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool isExpanded;
|
||||||
|
|
||||||
|
/// Whether tapping on the panel's header will expand/collapse it.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
final bool canTapOnHeader;
|
||||||
|
|
||||||
|
/// Defines the background color of the panel.
|
||||||
|
///
|
||||||
|
/// Defaults to [ThemeData.cardColor].
|
||||||
|
final Color? backgroundColor;
|
||||||
|
}
|
||||||
136
lib/common/widgets/html_render.dart
Normal file
136
lib/common/widgets/html_render.dart
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
|
|
||||||
|
// ignore: must_be_immutable
|
||||||
|
class HtmlRender extends StatelessWidget {
|
||||||
|
String? htmlContent;
|
||||||
|
final int? imgCount;
|
||||||
|
final List? imgList;
|
||||||
|
|
||||||
|
HtmlRender({
|
||||||
|
this.htmlContent,
|
||||||
|
this.imgCount,
|
||||||
|
this.imgList,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Html(
|
||||||
|
data: htmlContent,
|
||||||
|
// tagsList: Html.tags..addAll(["form", "label", "input"]),
|
||||||
|
onLinkTap: (url, buildContext, attributes) => {},
|
||||||
|
extensions: [
|
||||||
|
TagExtension(
|
||||||
|
tagsToExtend: {"img"},
|
||||||
|
builder: (extensionContext) {
|
||||||
|
String? imgUrl = extensionContext.attributes['src'];
|
||||||
|
if (imgUrl!.startsWith('//')) {
|
||||||
|
imgUrl = 'https:$imgUrl';
|
||||||
|
}
|
||||||
|
if (imgUrl.startsWith('http://')) {
|
||||||
|
imgUrl = imgUrl.replaceAll('http://', 'https://');
|
||||||
|
}
|
||||||
|
|
||||||
|
print(imgUrl);
|
||||||
|
bool isEmote = imgUrl.contains('/emote/');
|
||||||
|
bool isMall = imgUrl.contains('/mall/');
|
||||||
|
if (isMall) {
|
||||||
|
return SizedBox();
|
||||||
|
}
|
||||||
|
// bool inTable =
|
||||||
|
// extensionContext.element!.previousElementSibling == null ||
|
||||||
|
// extensionContext.element!.nextElementSibling == null;
|
||||||
|
// imgUrl = Utils().imageUrl(imgUrl!);
|
||||||
|
return Image.network(
|
||||||
|
imgUrl,
|
||||||
|
width: isEmote ? 22 : null,
|
||||||
|
height: isEmote ? 22 : null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: {
|
||||||
|
"html": Style(
|
||||||
|
fontSize: FontSize.medium,
|
||||||
|
lineHeight: LineHeight.percent(140),
|
||||||
|
),
|
||||||
|
"body": Style(margin: Margins.zero, padding: HtmlPaddings.zero),
|
||||||
|
"a": Style(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
textDecoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
"p": Style(
|
||||||
|
margin: Margins.only(bottom: 0),
|
||||||
|
),
|
||||||
|
"span": Style(
|
||||||
|
fontSize: FontSize.medium,
|
||||||
|
),
|
||||||
|
"li > p": Style(
|
||||||
|
display: Display.inline,
|
||||||
|
),
|
||||||
|
"li": Style(
|
||||||
|
padding: HtmlPaddings.only(bottom: 4),
|
||||||
|
textAlign: TextAlign.justify,
|
||||||
|
),
|
||||||
|
"image": Style(margin: Margins.only(top: 4, bottom: 4)),
|
||||||
|
"p > img": Style(margin: Margins.only(top: 4, bottom: 4)),
|
||||||
|
"code": Style(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.onInverseSurface),
|
||||||
|
"code > span": Style(textAlign: TextAlign.start),
|
||||||
|
"hr": Style(
|
||||||
|
margin: Margins.zero,
|
||||||
|
padding: HtmlPaddings.zero,
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'table': Style(
|
||||||
|
border: Border(
|
||||||
|
right: BorderSide(
|
||||||
|
width: 0.5,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
bottom: BorderSide(
|
||||||
|
width: 0.5,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'tr': Style(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
left: BorderSide(
|
||||||
|
width: 1.0,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'thead': Style(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
),
|
||||||
|
'th': Style(
|
||||||
|
padding: HtmlPaddings.only(left: 3, right: 3),
|
||||||
|
),
|
||||||
|
'td': Style(
|
||||||
|
padding: HtmlPaddings.all(4.0),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,31 +1,41 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
class HttpError extends StatelessWidget {
|
class HttpError extends StatelessWidget {
|
||||||
const HttpError({required this.errMsg, required this.fn, super.key});
|
const HttpError(
|
||||||
|
{required this.errMsg, required this.fn, this.btnText, super.key});
|
||||||
|
|
||||||
final String? errMsg;
|
final String? errMsg;
|
||||||
final Function()? fn;
|
final Function()? fn;
|
||||||
|
final String? btnText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 150,
|
height: 400,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
"assets/images/error.svg",
|
||||||
|
height: 200,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
Text(
|
Text(
|
||||||
errMsg ?? '请求异常',
|
errMsg ?? '请求异常',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 30),
|
||||||
ElevatedButton(
|
OutlinedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
fn!();
|
fn!();
|
||||||
},
|
},
|
||||||
child: const Text('点击重试'))
|
icon: const Icon(Icons.arrow_forward_outlined, size: 20),
|
||||||
|
label: Text(btnText ?? '点击重试'),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/pages/rcmd/controller.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class LiveCard extends StatelessWidget {
|
class LiveCard extends StatelessWidget {
|
||||||
@ -95,7 +93,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
liveItem.title,
|
liveItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@ -70,9 +70,17 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
|
|
||||||
Widget placeholder(context) {
|
Widget placeholder(context) {
|
||||||
return Container(
|
return Container(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
|
|
||||||
width: width ?? double.infinity,
|
width: width ?? double.infinity,
|
||||||
height: height ?? double.infinity,
|
height: height ?? double.infinity,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
|
||||||
|
borderRadius: BorderRadius.circular(type == 'avatar'
|
||||||
|
? 50
|
||||||
|
: type == 'emote'
|
||||||
|
? 0
|
||||||
|
: StyleString.imgRadius.x),
|
||||||
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
type == 'avatar'
|
type == 'avatar'
|
||||||
|
|||||||
31
lib/common/widgets/no_data.dart
Normal file
31
lib/common/widgets/no_data.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
|
class NoData extends StatelessWidget {
|
||||||
|
const NoData({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 400,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
"assets/images/error.svg",
|
||||||
|
height: 200,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Text(
|
||||||
|
'没有数据',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,42 +1,82 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/download.dart';
|
||||||
|
|
||||||
class OverlayPop extends StatelessWidget {
|
class OverlayPop extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const OverlayPop({super.key, this.videoItem});
|
final Function? closeFn;
|
||||||
|
const OverlayPop({super.key, this.videoItem, this.closeFn});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
double imgWidth = MediaQuery.of(context).size.width - 8 * 2;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: Column(
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: (MediaQuery.of(context).size.width - 16),
|
width: imgWidth,
|
||||||
height: (MediaQuery.of(context).size.width - 16) /
|
height: imgWidth / StyleString.aspectRatio,
|
||||||
StyleString.aspectRatio,
|
src: videoItem.pic!,
|
||||||
src: videoItem.pic!,
|
quality: 100,
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 15, 10, 15),
|
|
||||||
child: Text(
|
|
||||||
videoItem.title!,
|
|
||||||
// maxLines: 1,
|
|
||||||
// overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
right: 8,
|
||||||
),
|
top: 8,
|
||||||
|
child: Container(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(20))),
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => closeFn!(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
videoItem.title!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
IconButton(
|
||||||
|
tooltip: '保存封面图',
|
||||||
|
onPressed: () async {
|
||||||
|
await DownloadUtils.downloadImg(
|
||||||
|
videoItem.pic ?? videoItem.cover);
|
||||||
|
// closeFn!();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.download, size: 20),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
|
|||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
@ -33,15 +34,16 @@ class VideoCardH extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(aid);
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (longPress != null) {
|
// if (longPress != null) {
|
||||||
longPress!();
|
// longPress!();
|
||||||
}
|
// }
|
||||||
},
|
MemberController().blockUser(videoItem.mid);
|
||||||
onLongPressEnd: (details) {
|
|
||||||
if (longPressEnd != null) {
|
|
||||||
longPressEnd!();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
// onLongPressEnd: (details) {
|
||||||
|
// if (longPressEnd != null) {
|
||||||
|
// longPressEnd!();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
@ -55,11 +57,14 @@ class VideoCardH extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double width =
|
double width = (boxConstraints.maxWidth -
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
StyleString.cardSpace *
|
||||||
|
6 /
|
||||||
|
MediaQuery.of(context).textScaleFactor) /
|
||||||
|
2;
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(minHeight: 88),
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
@ -123,7 +128,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -132,7 +137,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
videoItem.title,
|
videoItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
@ -147,7 +151,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
color: i['type'] == 'em'
|
color: i['type'] == 'em'
|
||||||
@ -177,7 +180,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
// color: Theme.of(context).colorScheme.surfaceTint),
|
// color: Theme.of(context).colorScheme.surfaceTint),
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
@ -187,46 +190,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatView(
|
|
||||||
theme: 'gray',
|
|
||||||
view: videoItem.stat.view,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
StatDanMu(
|
|
||||||
theme: 'gray',
|
|
||||||
danmu: videoItem.stat.danmaku,
|
|
||||||
),
|
|
||||||
// Text(
|
|
||||||
// Utils.dateFormat(videoItem.pubdate!),
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 11,
|
|
||||||
// color: Theme.of(context).colorScheme.outline),
|
|
||||||
// )
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
// SizedBox(
|
|
||||||
// width: 20,
|
|
||||||
// height: 20,
|
|
||||||
// child: IconButton(
|
|
||||||
// tooltip: '稍后再看',
|
|
||||||
// style: ButtonStyle(
|
|
||||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
// ),
|
|
||||||
// onPressed: () async {
|
|
||||||
// var res =
|
|
||||||
// await UserHttp.toViewLater(bvid: videoItem.bvid);
|
|
||||||
// SmartDialog.showToast(res['msg']);
|
|
||||||
// },
|
|
||||||
// icon: Icon(
|
|
||||||
// Icons.more_vert_outlined,
|
|
||||||
// color: Theme.of(context).colorScheme.outline,
|
|
||||||
// size: 14,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
if (source == 'normal')
|
if (source == 'normal')
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
@ -260,6 +224,20 @@ class VideoContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// PopupMenuItem<String>(
|
||||||
|
// onTap: () async {
|
||||||
|
// MemberController().blockUser(videoItem.mid);
|
||||||
|
// },
|
||||||
|
// value: 'block',
|
||||||
|
// height: 35,
|
||||||
|
// child: const Row(
|
||||||
|
// children: [
|
||||||
|
// Icon(Icons.block, size: 16),
|
||||||
|
// SizedBox(width: 6),
|
||||||
|
// Text('拉黑up', style: TextStyle(fontSize: 13))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,14 +14,15 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
|
|
||||||
// 视频卡片 - 垂直布局
|
// 视频卡片 - 垂直布局
|
||||||
class VideoCardV extends StatelessWidget {
|
class VideoCardV extends StatelessWidget {
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
final dynamic videoItem;
|
||||||
final videoItem;
|
final int crossAxisCount;
|
||||||
final Function()? longPress;
|
final Function()? longPress;
|
||||||
final Function()? longPressEnd;
|
final Function()? longPressEnd;
|
||||||
|
|
||||||
const VideoCardV({
|
const VideoCardV({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.videoItem,
|
required this.videoItem,
|
||||||
|
required this.crossAxisCount,
|
||||||
this.longPress,
|
this.longPress,
|
||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -47,7 +48,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': videoItem.pic,
|
'pic': videoItem.pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -61,6 +62,16 @@ class VideoCardV extends StatelessWidget {
|
|||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
SmartDialog.showToast(videoItem.goto);
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': videoItem.uri,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': videoItem.title,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,9 +81,6 @@ class VideoCardV extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
@ -80,59 +88,51 @@ class VideoCardV extends StatelessWidget {
|
|||||||
longPress!();
|
longPress!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPressEnd: (details) {
|
// onLongPressEnd: (details) {
|
||||||
if (longPressEnd != null) {
|
// if (longPressEnd != null) {
|
||||||
longPressEnd!();
|
// longPressEnd!();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async => onPushDetail(heroTag),
|
onTap: () async => onPushDetail(heroTag),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
AspectRatio(
|
||||||
borderRadius: const BorderRadius.only(
|
aspectRatio: StyleString.aspectRatio,
|
||||||
topLeft: StyleString.imgRadius,
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
topRight: StyleString.imgRadius,
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
bottomLeft: StyleString.imgRadius,
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
bottomRight: StyleString.imgRadius,
|
return Stack(
|
||||||
),
|
children: [
|
||||||
child: AspectRatio(
|
Hero(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
tag: heroTag,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: NetworkImgLayer(
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
src: videoItem.pic,
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
width: maxWidth,
|
||||||
return Stack(
|
height: maxHeight,
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: videoItem.pic,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// if (videoItem.stat.view is int &&
|
),
|
||||||
// videoItem.stat.danmaku is int)
|
if (videoItem.duration != null)
|
||||||
// Positioned(
|
if (crossAxisCount == 1) ...[
|
||||||
// left: 0,
|
PBadge(
|
||||||
// right: 0,
|
bottom: 10,
|
||||||
// bottom: 0,
|
right: 10,
|
||||||
// child: AnimatedOpacity(
|
text: videoItem.duration,
|
||||||
// opacity: 1,
|
)
|
||||||
// duration: const Duration(milliseconds: 200),
|
] else ...[
|
||||||
// child: VideoStat(
|
PBadge(
|
||||||
// view: videoItem.stat.view,
|
bottom: 6,
|
||||||
// danmaku: videoItem.stat.danmaku,
|
right: 7,
|
||||||
// duration: videoItem.duration,
|
size: 'small',
|
||||||
// ),
|
type: 'gray',
|
||||||
// ),
|
text: videoItem.duration,
|
||||||
// ),
|
)
|
||||||
],
|
],
|
||||||
);
|
],
|
||||||
}),
|
);
|
||||||
),
|
}),
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
VideoContent(videoItem: videoItem, crossAxisCount: crossAxisCount)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -143,24 +143,53 @@ class VideoCardV extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const VideoContent({Key? key, required this.videoItem}) : super(key: key);
|
final int crossAxisCount;
|
||||||
|
const VideoContent(
|
||||||
|
{Key? key, required this.videoItem, required this.crossAxisCount})
|
||||||
|
: super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
|
flex: crossAxisCount == 1 ? 0 : 1,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(4, 8, 0, 3),
|
padding: crossAxisCount == 1
|
||||||
|
? const EdgeInsets.fromLTRB(9, 9, 9, 4)
|
||||||
|
: const EdgeInsets.fromLTRB(5, 8, 5, 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Row(
|
||||||
videoItem.title,
|
children: [
|
||||||
textAlign: TextAlign.start,
|
Expanded(
|
||||||
style: const TextStyle(fontSize: 13),
|
child: Text(
|
||||||
maxLines: 2,
|
videoItem.title,
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (videoItem.goto == 'av' && crossAxisCount == 1) ...[
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
WatchLater(
|
||||||
|
size: 32,
|
||||||
|
iconSize: 18,
|
||||||
|
callFn: () async {
|
||||||
|
int aid = videoItem.param;
|
||||||
|
var res =
|
||||||
|
await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
if (crossAxisCount > 1) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
VideoStat(
|
||||||
|
videoItem: videoItem,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (crossAxisCount == 1) const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (videoItem.goto == 'bangumi') ...[
|
if (videoItem.goto == 'bangumi') ...[
|
||||||
@ -181,112 +210,57 @@ class VideoContent extends StatelessWidget {
|
|||||||
type: 'color',
|
type: 'color',
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
if (videoItem.goto == 'picture') ...[
|
||||||
|
const PBadge(
|
||||||
|
text: '动态',
|
||||||
|
stack: 'normal',
|
||||||
|
size: 'small',
|
||||||
|
type: 'line',
|
||||||
|
fs: 9,
|
||||||
|
)
|
||||||
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(builder:
|
flex: crossAxisCount == 1 ? 0 : 1,
|
||||||
(BuildContext context, BoxConstraints constraints) {
|
child: Text(
|
||||||
return SizedBox(
|
videoItem.owner.name,
|
||||||
width: constraints.maxWidth,
|
maxLines: 1,
|
||||||
child: Text(
|
style: TextStyle(
|
||||||
videoItem.owner.name,
|
fontSize:
|
||||||
maxLines: 1,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
style: TextStyle(
|
color: Theme.of(context).colorScheme.outline,
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
if (videoItem.goto == 'av')
|
|
||||||
SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: PopupMenuButton<String>(
|
|
||||||
padding: EdgeInsets.zero,
|
|
||||||
tooltip: '稍后再看',
|
|
||||||
icon: Icon(
|
|
||||||
Icons.more_vert_outlined,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
size: 14,
|
|
||||||
),
|
|
||||||
position: PopupMenuPosition.under,
|
|
||||||
// constraints: const BoxConstraints(maxHeight: 35),
|
|
||||||
onSelected: (String type) {},
|
|
||||||
itemBuilder: (BuildContext context) =>
|
|
||||||
<PopupMenuEntry<String>>[
|
|
||||||
PopupMenuItem<String>(
|
|
||||||
onTap: () async {
|
|
||||||
int aid = videoItem.param;
|
|
||||||
var res = await UserHttp.toViewLater(
|
|
||||||
bvid: IdUtils.av2bv(aid));
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
},
|
|
||||||
value: 'pause',
|
|
||||||
height: 35,
|
|
||||||
child: const Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.watch_later_outlined, size: 16),
|
|
||||||
SizedBox(width: 6),
|
|
||||||
Text('稍后再看', style: TextStyle(fontSize: 13))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if (crossAxisCount == 1) ...[
|
||||||
|
Text(
|
||||||
|
' • ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
VideoStat(
|
||||||
|
videoItem: videoItem,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
if (videoItem.goto == 'av' && crossAxisCount != 1) ...[
|
||||||
|
WatchLater(
|
||||||
|
size: 24,
|
||||||
|
iconSize: 14,
|
||||||
|
callFn: () async {
|
||||||
|
int aid = videoItem.param;
|
||||||
|
var res =
|
||||||
|
await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
] else ...[
|
||||||
|
const SizedBox(height: 24)
|
||||||
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Row(
|
|
||||||
// children: [
|
|
||||||
// const SizedBox(width: 1),
|
|
||||||
// StatView(
|
|
||||||
// theme: 'gray',
|
|
||||||
// view: videoItem.stat.view,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 10),
|
|
||||||
// StatDanMu(
|
|
||||||
// theme: 'gray',
|
|
||||||
// danmu: videoItem.stat.danmaku,
|
|
||||||
// ),
|
|
||||||
// const Spacer(),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 24,
|
|
||||||
// height: 24,
|
|
||||||
// child: PopupMenuButton<String>(
|
|
||||||
// padding: EdgeInsets.zero,
|
|
||||||
// tooltip: '稍后再看',
|
|
||||||
// icon: Icon(
|
|
||||||
// Icons.more_vert_outlined,
|
|
||||||
// color: Theme.of(context).colorScheme.outline,
|
|
||||||
// size: 14,
|
|
||||||
// ),
|
|
||||||
// position: PopupMenuPosition.under,
|
|
||||||
// // constraints: const BoxConstraints(maxHeight: 35),
|
|
||||||
// onSelected: (String type) {},
|
|
||||||
// itemBuilder: (BuildContext context) =>
|
|
||||||
// <PopupMenuEntry<String>>[
|
|
||||||
// PopupMenuItem<String>(
|
|
||||||
// onTap: () async {
|
|
||||||
// var res =
|
|
||||||
// await UserHttp.toViewLater(bvid: videoItem.bvid);
|
|
||||||
// SmartDialog.showToast(res['msg']);
|
|
||||||
// },
|
|
||||||
// value: 'pause',
|
|
||||||
// height: 35,
|
|
||||||
// child: const Row(
|
|
||||||
// children: [
|
|
||||||
// Icon(Icons.watch_later_outlined, size: 16),
|
|
||||||
// SizedBox(width: 6),
|
|
||||||
// Text('稍后再看', style: TextStyle(fontSize: 13))
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -295,53 +269,77 @@ class VideoContent extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class VideoStat extends StatelessWidget {
|
class VideoStat extends StatelessWidget {
|
||||||
final int? view;
|
final dynamic videoItem;
|
||||||
final int? danmaku;
|
|
||||||
final int? duration;
|
|
||||||
|
|
||||||
const VideoStat(
|
const VideoStat({
|
||||||
{Key? key,
|
Key? key,
|
||||||
required this.view,
|
required this.videoItem,
|
||||||
required this.danmaku,
|
}) : super(key: key);
|
||||||
required this.duration})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Row(
|
||||||
height: 48,
|
children: [
|
||||||
padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
|
Text(
|
||||||
decoration: const BoxDecoration(
|
'${videoItem.stat.view}观看',
|
||||||
gradient: LinearGradient(
|
style: TextStyle(
|
||||||
begin: Alignment.topCenter,
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
end: Alignment.bottomCenter,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
colors: <Color>[
|
),
|
||||||
Colors.transparent,
|
),
|
||||||
Colors.black54,
|
Text(
|
||||||
],
|
' • ${videoItem.stat.danmu}弹幕',
|
||||||
tileMode: TileMode.mirror,
|
style: TextStyle(
|
||||||
),
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
),
|
color: Theme.of(context).colorScheme.outline,
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
),
|
||||||
children: [
|
],
|
||||||
Row(
|
);
|
||||||
children: [
|
}
|
||||||
StatView(
|
}
|
||||||
theme: 'white',
|
|
||||||
view: view,
|
class WatchLater extends StatelessWidget {
|
||||||
),
|
final double? size;
|
||||||
const SizedBox(width: 6),
|
final double? iconSize;
|
||||||
StatDanMu(
|
final Function? callFn;
|
||||||
theme: 'white',
|
|
||||||
danmu: danmaku,
|
const WatchLater({
|
||||||
),
|
Key? key,
|
||||||
],
|
required this.size,
|
||||||
|
required this.iconSize,
|
||||||
|
this.callFn,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: '稍后再看',
|
||||||
|
icon: Icon(
|
||||||
|
Icons.more_vert_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: iconSize,
|
||||||
|
),
|
||||||
|
position: PopupMenuPosition.under,
|
||||||
|
// constraints: const BoxConstraints(maxHeight: 35),
|
||||||
|
onSelected: (String type) {},
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
onTap: () => callFn!(),
|
||||||
|
value: 'pause',
|
||||||
|
height: 35,
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.watch_later_outlined, size: 16),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
|
||||||
Utils.timeFormat(duration!),
|
|
||||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -164,6 +164,9 @@ class Api {
|
|||||||
// 清空历史记录
|
// 清空历史记录
|
||||||
static const String clearHistory = '/x/v2/history/clear';
|
static const String clearHistory = '/x/v2/history/clear';
|
||||||
|
|
||||||
|
// 删除某条历史记录
|
||||||
|
static const String delHistory = '/x/v2/history/delete';
|
||||||
|
|
||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
'https://s.search.bilibili.com/main/hotword';
|
'https://s.search.bilibili.com/main/hotword';
|
||||||
@ -239,6 +242,9 @@ class Api {
|
|||||||
// wts=1689767832
|
// wts=1689767832
|
||||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||||
|
|
||||||
|
// 用户动态搜索
|
||||||
|
static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||||
|
|
||||||
// 用户动态
|
// 用户动态
|
||||||
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
||||||
|
|
||||||
@ -248,6 +254,9 @@ class Api {
|
|||||||
// 移除已观看
|
// 移除已观看
|
||||||
static const String toViewDel = '/x/v2/history/toview/del';
|
static const String toViewDel = '/x/v2/history/toview/del';
|
||||||
|
|
||||||
|
// 清空稍后再看
|
||||||
|
static const String toViewClear = '/x/v2/history/toview/clear';
|
||||||
|
|
||||||
// 追番
|
// 追番
|
||||||
static const String bangumiAdd = '/pgc/web/follow/add';
|
static const String bangumiAdd = '/pgc/web/follow/add';
|
||||||
|
|
||||||
@ -282,7 +291,19 @@ class Api {
|
|||||||
// 黑名单
|
// 黑名单
|
||||||
static const String blackLst = '/x/relation/blacks';
|
static const String blackLst = '/x/relation/blacks';
|
||||||
|
|
||||||
|
// 移除黑名单
|
||||||
|
static const String removeBlack = '/x/relation/modify';
|
||||||
|
|
||||||
// github 获取最新版
|
// github 获取最新版
|
||||||
static const String latestApp =
|
static const String latestApp =
|
||||||
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
||||||
|
|
||||||
|
// 多少人在看
|
||||||
|
// https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
|
||||||
|
static const String onlineTotal = '/x/player/online/total';
|
||||||
|
|
||||||
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static const String searchHistory = '/x/web-goblin/history/search';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,4 +23,31 @@ class BlackHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移除黑名单
|
||||||
|
static Future removeBlack({required int fid}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.removeBlack,
|
||||||
|
queryParameters: {
|
||||||
|
'act': 6,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
'fid': fid,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
're_src': 116,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': [],
|
||||||
|
'msg': '操作成功',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
lib/http/danmaku.dart
Normal file
33
lib/http/danmaku.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/models/danmaku/dm.pb.dart';
|
||||||
|
|
||||||
|
import 'constants.dart';
|
||||||
|
|
||||||
|
class DanmakaHttp {
|
||||||
|
// 获取视频弹幕
|
||||||
|
static Future queryDanmaku({
|
||||||
|
required int cid,
|
||||||
|
required int segmentIndex,
|
||||||
|
}) async {
|
||||||
|
// 构建参数对象
|
||||||
|
Map<String, int> params = {
|
||||||
|
'type': 1,
|
||||||
|
'oid': cid,
|
||||||
|
'segment_index': segmentIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算函数
|
||||||
|
Future<DmSegMobileReply> computeTask(Map<String, int> params) async {
|
||||||
|
var response = await Request().get(
|
||||||
|
Api.webDanmaku,
|
||||||
|
data: params,
|
||||||
|
extra: {'resType': ResponseType.bytes},
|
||||||
|
);
|
||||||
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await compute(computeTask, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,10 +22,18 @@ class DynamicsHttp {
|
|||||||
}
|
}
|
||||||
var res = await Request().get(Api.followDynamic, data: data);
|
var res = await Request().get(Api.followDynamic, data: data);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
try {
|
||||||
'status': true,
|
return {
|
||||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
'status': true,
|
||||||
};
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': err.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
|
|||||||
40
lib/http/html.dart
Normal file
40
lib/http/html.dart
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:html/dom.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
|
||||||
|
class HtmlHttp {
|
||||||
|
static Future reqHtml(id) async {
|
||||||
|
var response = await Request().get("https://www.bilibili.com/opus/$id");
|
||||||
|
Document rootTree = parse(response.data);
|
||||||
|
Element body = rootTree.body!;
|
||||||
|
Element appDom = body.querySelector('#app')!;
|
||||||
|
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
||||||
|
// 头像
|
||||||
|
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
|
||||||
|
avatar = 'https:${avatar.split('@')[0]}';
|
||||||
|
String uname =
|
||||||
|
authorHeader.querySelector('.fixed-author-header__author__name')!.text;
|
||||||
|
// 动态详情
|
||||||
|
Element opusDetail = appDom.querySelector('.opus-detail')!;
|
||||||
|
// 发布时间
|
||||||
|
String updateTime =
|
||||||
|
opusDetail.querySelector('.opus-module-author__pub__text')!.text;
|
||||||
|
//
|
||||||
|
String opusContent =
|
||||||
|
opusDetail.querySelector('.opus-module-content')!.innerHtml;
|
||||||
|
String commentId = opusDetail
|
||||||
|
.querySelector('.bili-comment-container')!
|
||||||
|
.className
|
||||||
|
.split(' ')[1]
|
||||||
|
.split('-')[2];
|
||||||
|
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'avatar': avatar,
|
||||||
|
'uname': uname,
|
||||||
|
'updateTime': updateTime,
|
||||||
|
'content': opusContent,
|
||||||
|
'commentId': commentId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,27 +9,17 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
import 'package:pilipala/http/interceptor.dart';
|
import 'package:pilipala/http/interceptor.dart';
|
||||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
|
||||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
static final Request _instance = Request._internal();
|
static final Request _instance = Request._internal();
|
||||||
static late CookieManager cookieManager;
|
static late CookieManager cookieManager;
|
||||||
|
static late final Dio dio;
|
||||||
factory Request() => _instance;
|
factory Request() => _instance;
|
||||||
|
|
||||||
static Dio dio = Dio()
|
|
||||||
..httpClientAdapter = Http2Adapter(
|
|
||||||
ConnectionManager(
|
|
||||||
idleTimeout: const Duration(milliseconds: 10000),
|
|
||||||
// Ignore bad certificate
|
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// 设置cookie
|
/// 设置cookie
|
||||||
static setCookie() async {
|
static setCookie() async {
|
||||||
Box user = GStrorage.user;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
var cookiePath = await Utils.getCookiePath();
|
var cookiePath = await Utils.getCookiePath();
|
||||||
var cookieJar = PersistCookieJar(
|
var cookieJar = PersistCookieJar(
|
||||||
ignoreExpires: true,
|
ignoreExpires: true,
|
||||||
@ -39,7 +29,8 @@ class Request {
|
|||||||
dio.interceptors.add(cookieManager);
|
dio.interceptors.add(cookieManager);
|
||||||
var cookie = await cookieManager.cookieJar
|
var cookie = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||||
if (user.get(UserBoxKey.userMid) != null) {
|
var userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
if (userInfo != null && userInfo.mid != null) {
|
||||||
var cookie2 = await cookieManager.cookieJar
|
var cookie2 = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||||
if (cookie2.isEmpty) {
|
if (cookie2.isEmpty) {
|
||||||
@ -49,6 +40,7 @@ class Request {
|
|||||||
log("setCookie, ${e.toString()}");
|
log("setCookie, ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setOptionsHeaders(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cookie.isEmpty) {
|
if (cookie.isEmpty) {
|
||||||
@ -63,16 +55,6 @@ class Request {
|
|||||||
dio.options.headers['cookie'] = cookieString;
|
dio.options.headers['cookie'] = cookieString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移除cookie
|
|
||||||
static removeCookie() async {
|
|
||||||
await cookieManager.cookieJar
|
|
||||||
.saveFromResponse(Uri.parse(HttpString.baseUrl), []);
|
|
||||||
await cookieManager.cookieJar
|
|
||||||
.saveFromResponse(Uri.parse(HttpString.baseApiUrl), []);
|
|
||||||
cookieManager.cookieJar.deleteAll();
|
|
||||||
dio.interceptors.add(cookieManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从cookie中获取 csrf token
|
// 从cookie中获取 csrf token
|
||||||
static Future<String> getCsrf() async {
|
static Future<String> getCsrf() async {
|
||||||
var cookies = await cookieManager.cookieJar
|
var cookies = await cookieManager.cookieJar
|
||||||
@ -87,6 +69,15 @@ class Request {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setOptionsHeaders(userInfo) {
|
||||||
|
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||||
|
dio.options.headers['env'] = 'prod';
|
||||||
|
dio.options.headers['app-key'] = 'android64';
|
||||||
|
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
||||||
|
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
|
||||||
|
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* config it and create
|
* config it and create
|
||||||
*/
|
*/
|
||||||
@ -101,20 +92,15 @@ class Request {
|
|||||||
receiveTimeout: const Duration(milliseconds: 12000),
|
receiveTimeout: const Duration(milliseconds: 12000),
|
||||||
//Http请求头.
|
//Http请求头.
|
||||||
headers: {
|
headers: {
|
||||||
// 'cookie': '',
|
'keep-alive': true,
|
||||||
|
'user-agent': headerUa('pc'),
|
||||||
|
'Accept-Encoding': 'gzip'
|
||||||
},
|
},
|
||||||
|
persistentConnection: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
Box user = GStrorage.user;
|
dio = Dio(options);
|
||||||
if (user.get(UserBoxKey.userMid) != null) {
|
|
||||||
options.headers['x-bili-mid'] = user.get(UserBoxKey.userMid).toString();
|
|
||||||
options.headers['env'] = 'prod';
|
|
||||||
options.headers['app-key'] = 'android64';
|
|
||||||
options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
|
||||||
options.headers['x-bili-aurora-zone'] = 'sh001';
|
|
||||||
options.headers['referer'] = 'https://www.bilibili.com/';
|
|
||||||
}
|
|
||||||
dio.options = options;
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
dio.interceptors.add(ApiInterceptor());
|
dio.interceptors.add(ApiInterceptor());
|
||||||
|
|
||||||
@ -225,7 +211,7 @@ class Request {
|
|||||||
: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
|
: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
|
||||||
} else {
|
} else {
|
||||||
headerUa =
|
headerUa =
|
||||||
'Mozilla/5.0 (MaciMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36';
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15';
|
||||||
}
|
}
|
||||||
return headerUa;
|
return headerUa;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,6 @@ class ApiInterceptor extends Interceptor {
|
|||||||
handler.next(options);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
try {
|
try {
|
||||||
@ -29,7 +27,11 @@ class ApiInterceptor extends Interceptor {
|
|||||||
final uri = Uri.parse(locations.first);
|
final uri = Uri.parse(locations.first);
|
||||||
final accessKey = uri.queryParameters['access_key'];
|
final accessKey = uri.queryParameters['access_key'];
|
||||||
final mid = uri.queryParameters['mid'];
|
final mid = uri.queryParameters['mid'];
|
||||||
setting.put(UserBoxKey.accessKey, {'mid': mid, 'value': accessKey});
|
try {
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
localCache.put(
|
||||||
|
LocalCacheKey.accessKey, {'mid': mid, 'value': accessKey});
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class MemberHttp {
|
|||||||
int ps = 30,
|
int ps = 30,
|
||||||
int tid = 0,
|
int tid = 0,
|
||||||
int? pn,
|
int? pn,
|
||||||
String keyword = '',
|
String? keyword,
|
||||||
String order = 'pubdate',
|
String order = 'pubdate',
|
||||||
bool orderAvoided = true,
|
bool orderAvoided = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -74,7 +74,7 @@ class MemberHttp {
|
|||||||
'ps': ps,
|
'ps': ps,
|
||||||
'tid': tid,
|
'tid': tid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'keyword': keyword,
|
'keyword': keyword ?? '',
|
||||||
'order': order,
|
'order': order,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
@ -119,4 +119,27 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索用户动态
|
||||||
|
static Future memberDynamicSearch({int? pn, int? ps, int? mid}) async {
|
||||||
|
var res = await Request().get(Api.memberDynamic, data: {
|
||||||
|
'keyword': '海拔',
|
||||||
|
'mid': mid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
'platform': 'web'
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,37 +1,63 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/search/hot.dart';
|
import 'package:pilipala/models/search/hot.dart';
|
||||||
import 'package:pilipala/models/search/result.dart';
|
import 'package:pilipala/models/search/result.dart';
|
||||||
import 'package:pilipala/models/search/suggest.dart';
|
import 'package:pilipala/models/search/suggest.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class SearchHttp {
|
class SearchHttp {
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
static Future hotSearchList() async {
|
static Future hotSearchList() async {
|
||||||
var res = await Request().get(Api.hotSearchList);
|
var res = await Request().get(Api.hotSearchList);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
|
if (resultMap['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': HotSearchModel.fromJson(resultMap),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (res.data is Map<String, dynamic> && res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': HotSearchModel.fromJson(res.data),
|
'data': HotSearchModel.fromJson(res.data),
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误 🙅',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取搜索建议
|
// 获取搜索建议
|
||||||
static Future searchSuggest({required term}) async {
|
static Future searchSuggest({required term}) async {
|
||||||
var res = await Request().get(Api.serachSuggest,
|
var res = await Request().get(Api.serachSuggest,
|
||||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
res.data['result']['term'] = term;
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
return {
|
if (resultMap['code'] == 0) {
|
||||||
'status': true,
|
if (resultMap['result'] is Map) {
|
||||||
'data': SearchSuggestModel.fromJson(res.data['result']),
|
resultMap['result']['term'] = term;
|
||||||
};
|
}
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': resultMap['result'] is Map
|
||||||
|
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
@ -46,39 +72,59 @@ class SearchHttp {
|
|||||||
required SearchType searchType,
|
required SearchType searchType,
|
||||||
required String keyword,
|
required String keyword,
|
||||||
required page,
|
required page,
|
||||||
|
String? order,
|
||||||
|
int? duration,
|
||||||
}) async {
|
}) async {
|
||||||
var res = await Request().get(Api.searchByType, data: {
|
var reqData = {
|
||||||
'search_type': searchType.type,
|
'search_type': searchType.type,
|
||||||
'keyword': keyword,
|
'keyword': keyword,
|
||||||
// 'order_sort': 0,
|
// 'order_sort': 0,
|
||||||
// 'user_type': 0,
|
// 'user_type': 0,
|
||||||
'page': page
|
'page': page,
|
||||||
});
|
if (order != null) 'order': order,
|
||||||
|
if (duration != null) 'duration': duration,
|
||||||
|
};
|
||||||
|
var res = await Request().get(Api.searchByType, data: reqData);
|
||||||
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
||||||
Object data;
|
Object data;
|
||||||
switch (searchType) {
|
try {
|
||||||
case SearchType.video:
|
switch (searchType) {
|
||||||
data = SearchVideoModel.fromJson(res.data['data']);
|
case SearchType.video:
|
||||||
break;
|
List<int> blackMidsList =
|
||||||
case SearchType.live_room:
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
data = SearchLiveModel.fromJson(res.data['data']);
|
for (var i in res.data['data']['result']) {
|
||||||
break;
|
// 屏蔽推广和拉黑用户
|
||||||
case SearchType.bili_user:
|
i['available'] = !blackMidsList.contains(i['mid']);
|
||||||
data = SearchUserModel.fromJson(res.data['data']);
|
}
|
||||||
break;
|
data = SearchVideoModel.fromJson(res.data['data']);
|
||||||
case SearchType.media_bangumi:
|
break;
|
||||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
case SearchType.live_room:
|
||||||
break;
|
data = SearchLiveModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.bili_user:
|
||||||
|
data = SearchUserModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.media_bangumi:
|
||||||
|
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.article:
|
||||||
|
data = SearchArticleModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': data,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': data,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
'data': [],
|
||||||
'msg': res.data['data']['numPages'] == 0 ? '没有相关数据' : '请求错误 🙅',
|
'msg': res.data['data'] != null && res.data['data']['numPages'] == 0
|
||||||
|
? '没有相关数据'
|
||||||
|
: res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/api.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
@ -50,10 +51,17 @@ class UserHttp {
|
|||||||
'up_mid': mid,
|
'up_mid': mid,
|
||||||
});
|
});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
FavFolderData data = FavFolderData.fromJson(res.data['data']);
|
late FavFolderData data;
|
||||||
return {'status': true, 'data': data};
|
if (res.data['data'] != null) {
|
||||||
|
data = FavFolderData.fromJson(res.data['data']);
|
||||||
|
return {'status': true, 'data': data};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': '账号未登录'};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'] ?? '账号未登录'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +93,12 @@ class UserHttp {
|
|||||||
static Future<dynamic> seeYouLater() async {
|
static Future<dynamic> seeYouLater() async {
|
||||||
var res = await Request().get(Api.seeYouLater);
|
var res = await Request().get(Api.seeYouLater);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
|
if (res.data['data']['count'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': {'list': [], 'count': 0}
|
||||||
|
};
|
||||||
|
}
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = [];
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
list.add(HotVideoItemModel.fromJson(i));
|
||||||
@ -165,14 +179,16 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 移除已观看
|
// 移除已观看
|
||||||
static Future toViewDel() async {
|
static Future toViewDel({int? aid}) async {
|
||||||
|
final Map<String, dynamic> params = {
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
};
|
||||||
|
|
||||||
|
params[aid != null ? 'aid' : 'viewed'] = aid ?? true;
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.toViewDel,
|
Api.toViewDel,
|
||||||
queryParameters: {
|
queryParameters: params,
|
||||||
'jsonp': 'jsonp',
|
|
||||||
'viewed': true,
|
|
||||||
'csrf': await Request.getCsrf(),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'msg': 'yeah!成功移除'};
|
return {'status': true, 'msg': 'yeah!成功移除'};
|
||||||
@ -191,8 +207,63 @@ class UserHttp {
|
|||||||
'sign': Constants.thirdSign,
|
'sign': Constants.thirdSign,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
|
try {
|
||||||
Request().get(res.data['data']['confirm_uri']);
|
if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
|
||||||
|
Request().get(res.data['data']['confirm_uri']);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showNotify(msg: '获取用户凭证: $err', notifyType: NotifyType.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空稍后再看
|
||||||
|
static Future toViewClear() async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.toViewClear,
|
||||||
|
queryParameters: {
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '操作完成'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除历史记录
|
||||||
|
static Future delHistory(kid) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.delHistory,
|
||||||
|
queryParameters: {
|
||||||
|
'kid': kid,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '已删除'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static Future searchHistory(
|
||||||
|
{required int pn, required String keyword}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.searchHistory,
|
||||||
|
data: {
|
||||||
|
'pn': pn,
|
||||||
|
'keyword': keyword,
|
||||||
|
'business': 'all',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,10 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
/// 返回{'status': bool, 'data': List}
|
/// 返回{'status': bool, 'data': List}
|
||||||
/// view层根据 status 判断渲染逻辑
|
/// view层根据 status 判断渲染逻辑
|
||||||
class VideoHttp {
|
class VideoHttp {
|
||||||
|
static Box localCache = GStrorage.localCache;
|
||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStrorage.setting;
|
||||||
|
static bool enableRcmdDynamic =
|
||||||
|
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||||
|
|
||||||
// 首页推荐视频
|
// 首页推荐视频
|
||||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||||
@ -60,8 +63,9 @@ class VideoHttp {
|
|||||||
'device_name': 'vivo',
|
'device_name': 'vivo',
|
||||||
'pull': freshIdx == 0 ? 'true' : 'false',
|
'pull': freshIdx == 0 ? 'true' : 'false',
|
||||||
'appkey': Constants.appKey,
|
'appkey': Constants.appKey,
|
||||||
'access_key':
|
'access_key': localCache
|
||||||
setting.get(UserBoxKey.accessKey, defaultValue: {})['value'] ?? ''
|
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||||
|
''
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
@ -71,6 +75,7 @@ class VideoHttp {
|
|||||||
for (var i in res.data['data']['items']) {
|
for (var i in res.data['data']['items']) {
|
||||||
// 屏蔽推广和拉黑用户
|
// 屏蔽推广和拉黑用户
|
||||||
if (i['card_goto'] != 'ad_av' &&
|
if (i['card_goto'] != 'ad_av' &&
|
||||||
|
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||||
(i['args'] != null &&
|
(i['args'] != null &&
|
||||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||||
list.add(RecVideoItemAppModel.fromJson(i));
|
list.add(RecVideoItemAppModel.fromJson(i));
|
||||||
@ -136,7 +141,12 @@ class VideoHttp {
|
|||||||
'data': PlayUrlModel.fromJson(res.data['data'])
|
'data': PlayUrlModel.fromJson(res.data['data'])
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': []};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'code': res.data['code'],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {'status': false, 'data': [], 'msg': err};
|
return {'status': false, 'data': [], 'msg': err};
|
||||||
@ -153,13 +163,14 @@ class VideoHttp {
|
|||||||
Map errMap = {
|
Map errMap = {
|
||||||
-400: '请求错误',
|
-400: '请求错误',
|
||||||
-403: '权限不足',
|
-403: '权限不足',
|
||||||
-404: '无视频',
|
-404: '视频资源失效',
|
||||||
62002: '稿件不可见',
|
62002: '稿件不可见',
|
||||||
62004: '稿件审核中',
|
62004: '稿件审核中',
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': null,
|
'data': null,
|
||||||
|
'code': result.code,
|
||||||
'msg': errMap[result.code] ?? '请求异常',
|
'msg': errMap[result.code] ?? '请求异常',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -391,4 +402,16 @@ class VideoHttp {
|
|||||||
return {'status': false, 'msg': res.data['result']['toast']};
|
return {'status': false, 'msg': res.data['result']['toast']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看视频同时在看人数
|
||||||
|
static Future onlineTotal({int? aid, String? bvid, int? cid}) async {
|
||||||
|
var res = await Request().get(Api.onlineTotal, data: {
|
||||||
|
'aid': aid,
|
||||||
|
'bvid': bvid,
|
||||||
|
'cid': cid,
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,11 +7,13 @@ import 'package:dynamic_color/dynamic_color.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/custom_toast.dart';
|
import 'package:pilipala/common/widgets/custom_toast.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
|
import 'package:pilipala/models/common/color_type.dart';
|
||||||
import 'package:pilipala/models/common/theme_type.dart';
|
import 'package:pilipala/models/common/theme_type.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/router/app_pages.dart';
|
import 'package:pilipala/router/app_pages.dart';
|
||||||
import 'package:pilipala/pages/main/view.dart';
|
import 'package:pilipala/pages/main/view.dart';
|
||||||
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
import 'package:pilipala/utils/data.dart';
|
import 'package:pilipala/utils/data.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
|
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
|
||||||
@ -24,9 +26,17 @@ void main() async {
|
|||||||
.then((_) async {
|
.then((_) async {
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
|
// 小白条、导航栏沉浸
|
||||||
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
|
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
|
statusBarColor: Colors.transparent,
|
||||||
|
));
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
await Data.init();
|
Data.init();
|
||||||
await GStrorage.lazyInit();
|
GStrorage.lazyInit();
|
||||||
|
PiliSchame.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,15 +45,27 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Color brandColor = const Color.fromARGB(255, 92, 182, 123);
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
|
// 主题色
|
||||||
|
Color defaultColor =
|
||||||
|
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
||||||
|
['color'];
|
||||||
|
Color brandColor = defaultColor;
|
||||||
|
// 主题模式
|
||||||
ThemeType currentThemeValue = ThemeType.values[setting
|
ThemeType currentThemeValue = ThemeType.values[setting
|
||||||
.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)];
|
.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)];
|
||||||
|
// 是否动态取色
|
||||||
|
bool isDynamicColor =
|
||||||
|
setting.get(SettingBoxKey.dynamicColor, defaultValue: true);
|
||||||
|
// 字体缩放大小
|
||||||
|
double textScale =
|
||||||
|
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||||
ColorScheme? lightColorScheme;
|
ColorScheme? lightColorScheme;
|
||||||
ColorScheme? darkColorScheme;
|
ColorScheme? darkColorScheme;
|
||||||
if (lightDynamic != null && darkDynamic != null) {
|
if (lightDynamic != null && darkDynamic != null && isDynamicColor) {
|
||||||
// dynamic取色成功
|
// dynamic取色成功
|
||||||
lightColorScheme = lightDynamic.harmonized();
|
lightColorScheme = lightDynamic.harmonized();
|
||||||
darkColorScheme = darkDynamic.harmonized();
|
darkColorScheme = darkDynamic.harmonized();
|
||||||
@ -93,9 +115,17 @@ class MyApp extends StatelessWidget {
|
|||||||
fallbackLocale: const Locale("zh", "CN"),
|
fallbackLocale: const Locale("zh", "CN"),
|
||||||
getPages: Routes.getPages,
|
getPages: Routes.getPages,
|
||||||
home: const MainApp(),
|
home: const MainApp(),
|
||||||
builder: FlutterSmartDialog.init(
|
builder: (BuildContext context, Widget? child) {
|
||||||
toastBuilder: (String msg) => CustomToast(msg: msg),
|
return FlutterSmartDialog(
|
||||||
),
|
toastBuilder: (String msg) => CustomToast(msg: msg),
|
||||||
|
child: MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(
|
||||||
|
textScaleFactor:
|
||||||
|
MediaQuery.of(context).textScaleFactor * textScale),
|
||||||
|
child: child!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
navigatorObservers: [
|
navigatorObservers: [
|
||||||
VideoDetailPage.routeObserver,
|
VideoDetailPage.routeObserver,
|
||||||
SearchPage.routeObserver,
|
SearchPage.routeObserver,
|
||||||
|
|||||||
23
lib/models/common/color_type.dart
Normal file
23
lib/models/common/color_type.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> colorThemeTypes = [
|
||||||
|
{'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'},
|
||||||
|
{'color': Colors.pink, 'label': '粉红色'},
|
||||||
|
{'color': Colors.red, 'label': '红色'},
|
||||||
|
{'color': Colors.orange, 'label': '橙色'},
|
||||||
|
{'color': Colors.amber, 'label': '琥珀色'},
|
||||||
|
{'color': Colors.yellow, 'label': '黄色'},
|
||||||
|
{'color': Colors.lime, 'label': '酸橙色'},
|
||||||
|
{'color': Colors.lightGreen, 'label': '浅绿色'},
|
||||||
|
{'color': Colors.green, 'label': '绿色'},
|
||||||
|
{'color': Colors.teal, 'label': '青色'},
|
||||||
|
{'color': Colors.cyan, 'label': '蓝绿色'},
|
||||||
|
{'color': Colors.lightBlue, 'label': '浅蓝色'},
|
||||||
|
{'color': Colors.blue, 'label': '蓝色'},
|
||||||
|
{'color': Colors.indigo, 'label': '靛蓝色'},
|
||||||
|
{'color': Colors.purple, 'label': '紫色'},
|
||||||
|
{'color': Colors.deepPurple, 'label': '深紫色'},
|
||||||
|
{'color': Colors.blueGrey, 'label': '蓝灰色'},
|
||||||
|
{'color': Colors.brown, 'label': '棕色'},
|
||||||
|
{'color': Colors.grey, 'label': '灰色'},
|
||||||
|
];
|
||||||
@ -12,18 +12,35 @@ enum SearchType {
|
|||||||
live_room,
|
live_room,
|
||||||
// 主播:live_user
|
// 主播:live_user
|
||||||
// live_user,
|
// live_user,
|
||||||
// 专栏:article
|
|
||||||
// article,
|
|
||||||
// 话题:topic
|
// 话题:topic
|
||||||
// topic,
|
// topic,
|
||||||
// 用户:bili_user
|
// 用户:bili_user
|
||||||
bili_user,
|
bili_user,
|
||||||
|
// 专栏:article
|
||||||
|
article,
|
||||||
// 相簿:photo
|
// 相簿:photo
|
||||||
// photo
|
// photo
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SearchTypeExtension on SearchType {
|
extension SearchTypeExtension on SearchType {
|
||||||
String get type =>
|
String get type =>
|
||||||
['video', 'media_bangumi', 'live_room', 'bili_user'][index];
|
['video', 'media_bangumi', 'live_room', 'bili_user', 'article'][index];
|
||||||
String get label => ['视频', '番剧', '直播间', '用户'][index];
|
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索类型为视频、专栏及相簿时
|
||||||
|
enum ArchiveFilterType {
|
||||||
|
totalrank,
|
||||||
|
click,
|
||||||
|
pubdate,
|
||||||
|
dm,
|
||||||
|
stow,
|
||||||
|
scores,
|
||||||
|
// 专栏
|
||||||
|
// attention,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ArchiveFilterTypeExtension on ArchiveFilterType {
|
||||||
|
String get description =>
|
||||||
|
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
||||||
}
|
}
|
||||||
|
|||||||
10194
lib/models/danmaku/dm.pb.dart
Normal file
10194
lib/models/danmaku/dm.pb.dart
Normal file
File diff suppressed because it is too large
Load Diff
347
lib/models/danmaku/dm.pbenum.dart
Normal file
347
lib/models/danmaku/dm.pbenum.dart
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: dm.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name
|
||||||
|
|
||||||
|
// ignore_for_file: UNDEFINED_SHOWN_NAME
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
class AvatarType extends $pb.ProtobufEnum {
|
||||||
|
static const AvatarType AvatarTypeNone = AvatarType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'AvatarTypeNone');
|
||||||
|
static const AvatarType AvatarTypeNFT = AvatarType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'AvatarTypeNFT');
|
||||||
|
|
||||||
|
static const $core.List<AvatarType> values = <AvatarType>[
|
||||||
|
AvatarTypeNone,
|
||||||
|
AvatarTypeNFT,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, AvatarType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static AvatarType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const AvatarType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BubbleType extends $pb.ProtobufEnum {
|
||||||
|
static const BubbleType BubbleTypeNone = BubbleType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'BubbleTypeNone');
|
||||||
|
static const BubbleType BubbleTypeClickButton = BubbleType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'BubbleTypeClickButton');
|
||||||
|
static const BubbleType BubbleTypeDmSettingPanel = BubbleType._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'BubbleTypeDmSettingPanel');
|
||||||
|
|
||||||
|
static const $core.List<BubbleType> values = <BubbleType>[
|
||||||
|
BubbleTypeNone,
|
||||||
|
BubbleTypeClickButton,
|
||||||
|
BubbleTypeDmSettingPanel,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, BubbleType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static BubbleType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const BubbleType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CheckboxType extends $pb.ProtobufEnum {
|
||||||
|
static const CheckboxType CheckboxTypeNone = CheckboxType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'CheckboxTypeNone');
|
||||||
|
static const CheckboxType CheckboxTypeEncourage = CheckboxType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'CheckboxTypeEncourage');
|
||||||
|
static const CheckboxType CheckboxTypeColorDM = CheckboxType._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'CheckboxTypeColorDM');
|
||||||
|
|
||||||
|
static const $core.List<CheckboxType> values = <CheckboxType>[
|
||||||
|
CheckboxTypeNone,
|
||||||
|
CheckboxTypeEncourage,
|
||||||
|
CheckboxTypeColorDM,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, CheckboxType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static CheckboxType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const CheckboxType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DMAttrBit extends $pb.ProtobufEnum {
|
||||||
|
static const DMAttrBit DMAttrBitProtect = DMAttrBit._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'DMAttrBitProtect');
|
||||||
|
static const DMAttrBit DMAttrBitFromLive = DMAttrBit._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'DMAttrBitFromLive');
|
||||||
|
static const DMAttrBit DMAttrHighLike = DMAttrBit._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'DMAttrHighLike');
|
||||||
|
|
||||||
|
static const $core.List<DMAttrBit> values = <DMAttrBit>[
|
||||||
|
DMAttrBitProtect,
|
||||||
|
DMAttrBitFromLive,
|
||||||
|
DMAttrHighLike,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, DMAttrBit> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static DMAttrBit? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const DMAttrBit._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExposureType extends $pb.ProtobufEnum {
|
||||||
|
static const ExposureType ExposureTypeNone = ExposureType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'ExposureTypeNone');
|
||||||
|
static const ExposureType ExposureTypeDMSend = ExposureType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'ExposureTypeDMSend');
|
||||||
|
|
||||||
|
static const $core.List<ExposureType> values = <ExposureType>[
|
||||||
|
ExposureTypeNone,
|
||||||
|
ExposureTypeDMSend,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, ExposureType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static ExposureType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const ExposureType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostPanelBizType extends $pb.ProtobufEnum {
|
||||||
|
static const PostPanelBizType PostPanelBizTypeNone = PostPanelBizType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeNone');
|
||||||
|
static const PostPanelBizType PostPanelBizTypeEncourage = PostPanelBizType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeEncourage');
|
||||||
|
static const PostPanelBizType PostPanelBizTypeColorDM = PostPanelBizType._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeColorDM');
|
||||||
|
static const PostPanelBizType PostPanelBizTypeNFTDM = PostPanelBizType._(
|
||||||
|
3,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeNFTDM');
|
||||||
|
static const PostPanelBizType PostPanelBizTypeFragClose = PostPanelBizType._(
|
||||||
|
4,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeFragClose');
|
||||||
|
static const PostPanelBizType PostPanelBizTypeRecommend = PostPanelBizType._(
|
||||||
|
5,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostPanelBizTypeRecommend');
|
||||||
|
|
||||||
|
static const $core.List<PostPanelBizType> values = <PostPanelBizType>[
|
||||||
|
PostPanelBizTypeNone,
|
||||||
|
PostPanelBizTypeEncourage,
|
||||||
|
PostPanelBizTypeColorDM,
|
||||||
|
PostPanelBizTypeNFTDM,
|
||||||
|
PostPanelBizTypeFragClose,
|
||||||
|
PostPanelBizTypeRecommend,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, PostPanelBizType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static PostPanelBizType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const PostPanelBizType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostStatus extends $pb.ProtobufEnum {
|
||||||
|
static const PostStatus PostStatusNormal = PostStatus._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostStatusNormal');
|
||||||
|
static const PostStatus PostStatusClosed = PostStatus._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'PostStatusClosed');
|
||||||
|
|
||||||
|
static const $core.List<PostStatus> values = <PostStatus>[
|
||||||
|
PostStatusNormal,
|
||||||
|
PostStatusClosed,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, PostStatus> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static PostStatus? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const PostStatus._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RenderType extends $pb.ProtobufEnum {
|
||||||
|
static const RenderType RenderTypeNone = RenderType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'RenderTypeNone');
|
||||||
|
static const RenderType RenderTypeSingle = RenderType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'RenderTypeSingle');
|
||||||
|
static const RenderType RenderTypeRotation = RenderType._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'RenderTypeRotation');
|
||||||
|
|
||||||
|
static const $core.List<RenderType> values = <RenderType>[
|
||||||
|
RenderTypeNone,
|
||||||
|
RenderTypeSingle,
|
||||||
|
RenderTypeRotation,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, RenderType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static RenderType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const RenderType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubtitleAiStatus extends $pb.ProtobufEnum {
|
||||||
|
static const SubtitleAiStatus None = SubtitleAiStatus._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'None');
|
||||||
|
static const SubtitleAiStatus Exposure = SubtitleAiStatus._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'Exposure');
|
||||||
|
static const SubtitleAiStatus Assist = SubtitleAiStatus._(
|
||||||
|
2,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'Assist');
|
||||||
|
|
||||||
|
static const $core.List<SubtitleAiStatus> values = <SubtitleAiStatus>[
|
||||||
|
None,
|
||||||
|
Exposure,
|
||||||
|
Assist,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, SubtitleAiStatus> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static SubtitleAiStatus? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const SubtitleAiStatus._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubtitleAiType extends $pb.ProtobufEnum {
|
||||||
|
static const SubtitleAiType Normal = SubtitleAiType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'Normal');
|
||||||
|
static const SubtitleAiType Translate = SubtitleAiType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'Translate');
|
||||||
|
|
||||||
|
static const $core.List<SubtitleAiType> values = <SubtitleAiType>[
|
||||||
|
Normal,
|
||||||
|
Translate,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, SubtitleAiType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static SubtitleAiType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const SubtitleAiType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubtitleType extends $pb.ProtobufEnum {
|
||||||
|
static const SubtitleType CC = SubtitleType._(0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CC');
|
||||||
|
static const SubtitleType AI = SubtitleType._(1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'AI');
|
||||||
|
|
||||||
|
static const $core.List<SubtitleType> values = <SubtitleType>[
|
||||||
|
CC,
|
||||||
|
AI,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, SubtitleType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static SubtitleType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const SubtitleType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ToastFunctionType extends $pb.ProtobufEnum {
|
||||||
|
static const ToastFunctionType ToastFunctionTypeNone = ToastFunctionType._(
|
||||||
|
0,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'ToastFunctionTypeNone');
|
||||||
|
static const ToastFunctionType ToastFunctionTypePostPanel =
|
||||||
|
ToastFunctionType._(
|
||||||
|
1,
|
||||||
|
const $core.bool.fromEnvironment('protobuf.omit_enum_names')
|
||||||
|
? ''
|
||||||
|
: 'ToastFunctionTypePostPanel');
|
||||||
|
|
||||||
|
static const $core.List<ToastFunctionType> values = <ToastFunctionType>[
|
||||||
|
ToastFunctionTypeNone,
|
||||||
|
ToastFunctionTypePostPanel,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, ToastFunctionType> _byValue =
|
||||||
|
$pb.ProtobufEnum.initByValue(values);
|
||||||
|
static ToastFunctionType? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const ToastFunctionType._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
2318
lib/models/danmaku/dm.pbjson.dart
Normal file
2318
lib/models/danmaku/dm.pbjson.dart
Normal file
File diff suppressed because it is too large
Load Diff
74
lib/models/danmaku/dm.pbserver.dart
Normal file
74
lib/models/danmaku/dm.pbserver.dart
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: dm.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,constant_identifier_names,deprecated_member_use_from_same_package,directives_ordering,library_prefixes,non_constant_identifier_names,prefer_final_fields,return_of_invalid_type,unnecessary_const,unnecessary_import,unnecessary_this,unused_import,unused_shown_name, avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'dart:async' as $async;
|
||||||
|
|
||||||
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
import 'dm.pb.dart' as $0;
|
||||||
|
import 'dm.pbjson.dart';
|
||||||
|
|
||||||
|
export 'dm.pb.dart';
|
||||||
|
|
||||||
|
abstract class DMServiceBase extends $pb.GeneratedService {
|
||||||
|
$async.Future<$0.DmSegMobileReply> dmSegMobile(
|
||||||
|
$pb.ServerContext ctx, $0.DmSegMobileReq request);
|
||||||
|
$async.Future<$0.DmViewReply> dmView(
|
||||||
|
$pb.ServerContext ctx, $0.DmViewReq request);
|
||||||
|
$async.Future<$0.Response> dmPlayerConfig(
|
||||||
|
$pb.ServerContext ctx, $0.DmPlayerConfigReq request);
|
||||||
|
$async.Future<$0.DmSegOttReply> dmSegOtt(
|
||||||
|
$pb.ServerContext ctx, $0.DmSegOttReq request);
|
||||||
|
$async.Future<$0.DmSegSDKReply> dmSegSDK(
|
||||||
|
$pb.ServerContext ctx, $0.DmSegSDKReq request);
|
||||||
|
$async.Future<$0.DmExpoReportRes> dmExpoReport(
|
||||||
|
$pb.ServerContext ctx, $0.DmExpoReportReq request);
|
||||||
|
|
||||||
|
$pb.GeneratedMessage createRequest($core.String method) {
|
||||||
|
switch (method) {
|
||||||
|
case 'DmSegMobile':
|
||||||
|
return $0.DmSegMobileReq();
|
||||||
|
case 'DmView':
|
||||||
|
return $0.DmViewReq();
|
||||||
|
case 'DmPlayerConfig':
|
||||||
|
return $0.DmPlayerConfigReq();
|
||||||
|
case 'DmSegOtt':
|
||||||
|
return $0.DmSegOttReq();
|
||||||
|
case 'DmSegSDK':
|
||||||
|
return $0.DmSegSDKReq();
|
||||||
|
case 'DmExpoReport':
|
||||||
|
return $0.DmExpoReportReq();
|
||||||
|
default:
|
||||||
|
throw $core.ArgumentError('Unknown method: $method');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$async.Future<$pb.GeneratedMessage> handleCall($pb.ServerContext ctx,
|
||||||
|
$core.String method, $pb.GeneratedMessage request) {
|
||||||
|
switch (method) {
|
||||||
|
case 'DmSegMobile':
|
||||||
|
return this.dmSegMobile(ctx, request as $0.DmSegMobileReq);
|
||||||
|
case 'DmView':
|
||||||
|
return this.dmView(ctx, request as $0.DmViewReq);
|
||||||
|
case 'DmPlayerConfig':
|
||||||
|
return this.dmPlayerConfig(ctx, request as $0.DmPlayerConfigReq);
|
||||||
|
case 'DmSegOtt':
|
||||||
|
return this.dmSegOtt(ctx, request as $0.DmSegOttReq);
|
||||||
|
case 'DmSegSDK':
|
||||||
|
return this.dmSegSDK(ctx, request as $0.DmSegSDKReq);
|
||||||
|
case 'DmExpoReport':
|
||||||
|
return this.dmExpoReport(ctx, request as $0.DmExpoReportReq);
|
||||||
|
default:
|
||||||
|
throw $core.ArgumentError('Unknown method: $method');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$core.Map<$core.String, $core.dynamic> get $json => DMServiceBase$json;
|
||||||
|
$core.Map<$core.String, $core.Map<$core.String, $core.dynamic>>
|
||||||
|
get $messageJson => DMServiceBase$messageJson;
|
||||||
|
}
|
||||||
893
lib/models/danmaku/dm.proto
Normal file
893
lib/models/danmaku/dm.proto
Normal file
@ -0,0 +1,893 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package bilibili.community.service.dm.v1;
|
||||||
|
|
||||||
|
// 说明文档
|
||||||
|
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/danmaku_proto.md
|
||||||
|
|
||||||
|
//弹幕
|
||||||
|
service DM {
|
||||||
|
// 获取分段弹幕
|
||||||
|
rpc DmSegMobile (DmSegMobileReq) returns (DmSegMobileReply);
|
||||||
|
// 客户端弹幕元数据 字幕、分段、防挡蒙版等
|
||||||
|
rpc DmView(DmViewReq) returns (DmViewReply);
|
||||||
|
// 修改弹幕配置
|
||||||
|
rpc DmPlayerConfig (DmPlayerConfigReq) returns (Response);
|
||||||
|
// ott弹幕列表
|
||||||
|
rpc DmSegOtt(DmSegOttReq) returns(DmSegOttReply);
|
||||||
|
// SDK弹幕列表
|
||||||
|
rpc DmSegSDK(DmSegSDKReq) returns(DmSegSDKReply);
|
||||||
|
//
|
||||||
|
rpc DmExpoReport(DmExpoReportReq) returns (DmExpoReportRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Avatar {
|
||||||
|
//
|
||||||
|
string id = 1;
|
||||||
|
//
|
||||||
|
string url = 2;
|
||||||
|
//
|
||||||
|
AvatarType avatar_type = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum AvatarType {
|
||||||
|
AvatarTypeNone = 0; //
|
||||||
|
AvatarTypeNFT = 1; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Bubble {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
string url = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum BubbleType {
|
||||||
|
BubbleTypeNone = 0; //
|
||||||
|
BubbleTypeClickButton = 1; //
|
||||||
|
BubbleTypeDmSettingPanel = 2; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message BubbleV2 {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
string url = 2;
|
||||||
|
//
|
||||||
|
BubbleType bubble_type = 3;
|
||||||
|
//
|
||||||
|
bool exposure_once = 4;
|
||||||
|
//
|
||||||
|
ExposureType exposure_type = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Button {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
int32 action = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message BuzzwordConfig {
|
||||||
|
//
|
||||||
|
repeated BuzzwordShowConfig keywords = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message BuzzwordShowConfig {
|
||||||
|
//
|
||||||
|
string name = 1;
|
||||||
|
//
|
||||||
|
string schema = 2;
|
||||||
|
//
|
||||||
|
int32 source = 3;
|
||||||
|
//
|
||||||
|
int64 id = 4;
|
||||||
|
//
|
||||||
|
int64 buzzword_id = 5;
|
||||||
|
//
|
||||||
|
int32 schema_type = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message CheckBox {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
CheckboxType type = 2;
|
||||||
|
//
|
||||||
|
bool default_value = 3;
|
||||||
|
//
|
||||||
|
bool show = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum CheckboxType {
|
||||||
|
CheckboxTypeNone = 0; //
|
||||||
|
CheckboxTypeEncourage = 1; //
|
||||||
|
CheckboxTypeColorDM = 2; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message CheckBoxV2 {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
int32 type = 2;
|
||||||
|
//
|
||||||
|
bool default_value = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ClickButton {
|
||||||
|
//
|
||||||
|
repeated string portrait_text = 1;
|
||||||
|
//
|
||||||
|
repeated string landscape_text = 2;
|
||||||
|
//
|
||||||
|
repeated string portrait_text_focus = 3;
|
||||||
|
//
|
||||||
|
repeated string landscape_text_focus = 4;
|
||||||
|
//
|
||||||
|
RenderType render_type = 5;
|
||||||
|
//
|
||||||
|
bool show = 6;
|
||||||
|
//
|
||||||
|
Bubble bubble = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ClickButtonV2 {
|
||||||
|
//
|
||||||
|
repeated string portrait_text = 1;
|
||||||
|
//
|
||||||
|
repeated string landscape_text = 2;
|
||||||
|
//
|
||||||
|
repeated string portrait_text_focus = 3;
|
||||||
|
//
|
||||||
|
repeated string landscape_text_focus = 4;
|
||||||
|
//
|
||||||
|
int32 render_type = 5;
|
||||||
|
//
|
||||||
|
bool text_input_post = 6;
|
||||||
|
//
|
||||||
|
bool exposure_once = 7;
|
||||||
|
//
|
||||||
|
int32 exposure_type = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 互动弹幕条目信息
|
||||||
|
message CommandDm {
|
||||||
|
// 弹幕id
|
||||||
|
int64 id = 1;
|
||||||
|
// 对象视频cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 发送者mid
|
||||||
|
string mid = 3;
|
||||||
|
// 互动弹幕指令
|
||||||
|
string command = 4;
|
||||||
|
// 互动弹幕正文
|
||||||
|
string content = 5;
|
||||||
|
// 出现时间
|
||||||
|
int32 progress = 6;
|
||||||
|
// 创建时间
|
||||||
|
string ctime = 7;
|
||||||
|
// 发布时间
|
||||||
|
string mtime = 8;
|
||||||
|
// 扩展json数据
|
||||||
|
string extra = 9;
|
||||||
|
// 弹幕id str类型
|
||||||
|
string idStr = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕ai云屏蔽列表
|
||||||
|
message DanmakuAIFlag {
|
||||||
|
// 弹幕ai云屏蔽条目
|
||||||
|
repeated DanmakuFlag dm_flags = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕条目
|
||||||
|
message DanmakuElem {
|
||||||
|
// 弹幕dmid
|
||||||
|
int64 id = 1;
|
||||||
|
// 弹幕出现位置(单位ms)
|
||||||
|
int32 progress = 2;
|
||||||
|
// 弹幕类型 1 2 3:普通弹幕 4:底部弹幕 5:顶部弹幕 6:逆向弹幕 7:高级弹幕 8:代码弹幕 9:BAS弹幕(pool必须为2)
|
||||||
|
int32 mode = 3;
|
||||||
|
// 弹幕字号
|
||||||
|
int32 fontsize = 4;
|
||||||
|
// 弹幕颜色
|
||||||
|
uint32 color = 5;
|
||||||
|
// 发送者mid hash
|
||||||
|
string midHash = 6;
|
||||||
|
// 弹幕正文
|
||||||
|
string content = 7;
|
||||||
|
// 发送时间
|
||||||
|
int64 ctime = 8;
|
||||||
|
// 权重 用于屏蔽等级 区间:[1,10]
|
||||||
|
int32 weight = 9;
|
||||||
|
// 动作
|
||||||
|
string action = 10;
|
||||||
|
// 弹幕池 0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)
|
||||||
|
int32 pool = 11;
|
||||||
|
// 弹幕dmid str
|
||||||
|
string idStr = 12;
|
||||||
|
// 弹幕属性位(bin求AND)
|
||||||
|
// bit0:保护 bit1:直播 bit2:高赞
|
||||||
|
int32 attr = 13;
|
||||||
|
//
|
||||||
|
string animation = 22;
|
||||||
|
// 大会员专属颜色
|
||||||
|
DmColorfulType colorful = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕ai云屏蔽条目
|
||||||
|
message DanmakuFlag {
|
||||||
|
// 弹幕dmid
|
||||||
|
int64 dmid = 1;
|
||||||
|
// 评分
|
||||||
|
uint32 flag = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
message DanmakuFlagConfig {
|
||||||
|
// 云屏蔽等级
|
||||||
|
int32 rec_flag = 1;
|
||||||
|
// 云屏蔽文案
|
||||||
|
string rec_text = 2;
|
||||||
|
// 云屏蔽开关
|
||||||
|
int32 rec_switch = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕默认配置
|
||||||
|
message DanmuDefaultPlayerConfig {
|
||||||
|
bool player_danmaku_use_default_config = 1; // 是否使用推荐弹幕设置
|
||||||
|
bool player_danmaku_ai_recommended_switch = 4; // 是否开启智能云屏蔽
|
||||||
|
int32 player_danmaku_ai_recommended_level = 5; // 智能云屏蔽等级
|
||||||
|
bool player_danmaku_blocktop = 6; // 是否屏蔽顶端弹幕
|
||||||
|
bool player_danmaku_blockscroll = 7; // 是否屏蔽滚动弹幕
|
||||||
|
bool player_danmaku_blockbottom = 8; // 是否屏蔽底端弹幕
|
||||||
|
bool player_danmaku_blockcolorful = 9; // 是否屏蔽彩色弹幕
|
||||||
|
bool player_danmaku_blockrepeat = 10; // 是否屏蔽重复弹幕
|
||||||
|
bool player_danmaku_blockspecial = 11; // 是否屏蔽高级弹幕
|
||||||
|
float player_danmaku_opacity = 12; // 弹幕不透明度
|
||||||
|
float player_danmaku_scalingfactor = 13; // 弹幕缩放比例
|
||||||
|
float player_danmaku_domain = 14; // 弹幕显示区域
|
||||||
|
int32 player_danmaku_speed = 15; // 弹幕速度
|
||||||
|
bool inline_player_danmaku_switch = 16; // 是否开启弹幕
|
||||||
|
int32 player_danmaku_senior_mode_switch = 17; //
|
||||||
|
int32 player_danmaku_ai_recommended_level_v2 = 18; //
|
||||||
|
map<int32, int32> player_danmaku_ai_recommended_level_v2_map = 19; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕配置
|
||||||
|
message DanmuPlayerConfig {
|
||||||
|
bool player_danmaku_switch = 1; // 是否开启弹幕
|
||||||
|
bool player_danmaku_switch_save = 2; // 是否记录弹幕开关设置
|
||||||
|
bool player_danmaku_use_default_config = 3; // 是否使用推荐弹幕设置
|
||||||
|
bool player_danmaku_ai_recommended_switch = 4; // 是否开启智能云屏蔽
|
||||||
|
int32 player_danmaku_ai_recommended_level = 5; // 智能云屏蔽等级
|
||||||
|
bool player_danmaku_blocktop = 6; // 是否屏蔽顶端弹幕
|
||||||
|
bool player_danmaku_blockscroll = 7; // 是否屏蔽滚动弹幕
|
||||||
|
bool player_danmaku_blockbottom = 8; // 是否屏蔽底端弹幕
|
||||||
|
bool player_danmaku_blockcolorful = 9; // 是否屏蔽彩色弹幕
|
||||||
|
bool player_danmaku_blockrepeat = 10; // 是否屏蔽重复弹幕
|
||||||
|
bool player_danmaku_blockspecial = 11; // 是否屏蔽高级弹幕
|
||||||
|
float player_danmaku_opacity = 12; // 弹幕不透明度
|
||||||
|
float player_danmaku_scalingfactor = 13; // 弹幕缩放比例
|
||||||
|
float player_danmaku_domain = 14; // 弹幕显示区域
|
||||||
|
int32 player_danmaku_speed = 15; // 弹幕速度
|
||||||
|
bool player_danmaku_enableblocklist = 16; // 是否开启屏蔽列表
|
||||||
|
bool inline_player_danmaku_switch = 17; // 是否开启弹幕
|
||||||
|
int32 inline_player_danmaku_config = 18; //
|
||||||
|
int32 player_danmaku_ios_switch_save = 19; //
|
||||||
|
int32 player_danmaku_senior_mode_switch = 20; //
|
||||||
|
int32 player_danmaku_ai_recommended_level_v2 = 21; //
|
||||||
|
map<int32, int32> player_danmaku_ai_recommended_level_v2_map = 22; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DanmuPlayerConfigPanel {
|
||||||
|
//
|
||||||
|
string selection_text = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕显示区域自动配置
|
||||||
|
message DanmuPlayerDynamicConfig {
|
||||||
|
// 时间
|
||||||
|
int32 progress = 1;
|
||||||
|
// 弹幕显示区域
|
||||||
|
float player_danmaku_domain = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕配置信息
|
||||||
|
message DanmuPlayerViewConfig {
|
||||||
|
// 弹幕默认配置
|
||||||
|
DanmuDefaultPlayerConfig danmuku_default_player_config = 1;
|
||||||
|
// 弹幕用户配置
|
||||||
|
DanmuPlayerConfig danmuku_player_config = 2;
|
||||||
|
// 弹幕显示区域自动配置列表
|
||||||
|
repeated DanmuPlayerDynamicConfig danmuku_player_dynamic_config = 3;
|
||||||
|
//
|
||||||
|
DanmuPlayerConfigPanel danmuku_player_config_panel = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端用户弹幕配置
|
||||||
|
message DanmuWebPlayerConfig {
|
||||||
|
bool dm_switch = 1; // 是否开启弹幕
|
||||||
|
bool ai_switch = 2; // 是否开启智能云屏蔽
|
||||||
|
int32 ai_level = 3; // 智能云屏蔽等级
|
||||||
|
bool blocktop = 4; // 是否屏蔽顶端弹幕
|
||||||
|
bool blockscroll = 5; // 是否屏蔽滚动弹幕
|
||||||
|
bool blockbottom = 6; // 是否屏蔽底端弹幕
|
||||||
|
bool blockcolor = 7; // 是否屏蔽彩色弹幕
|
||||||
|
bool blockspecial = 8; // 是否屏蔽重复弹幕
|
||||||
|
bool preventshade = 9; //
|
||||||
|
bool dmask = 10; //
|
||||||
|
float opacity = 11; //
|
||||||
|
int32 dmarea = 12; //
|
||||||
|
float speedplus = 13; //
|
||||||
|
float fontsize = 14; // 弹幕字号
|
||||||
|
bool screensync = 15; //
|
||||||
|
bool speedsync = 16; //
|
||||||
|
string fontfamily = 17; //
|
||||||
|
bool bold = 18; // 是否使用加粗
|
||||||
|
int32 fontborder = 19; //
|
||||||
|
string draw_type = 20; // 弹幕渲染类型
|
||||||
|
int32 senior_mode_switch = 21; //
|
||||||
|
int32 ai_level_v2 = 22; //
|
||||||
|
map<int32, int32> ai_level_v2_map = 23; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕属性位值
|
||||||
|
enum DMAttrBit {
|
||||||
|
DMAttrBitProtect = 0; // 保护弹幕
|
||||||
|
DMAttrBitFromLive = 1; // 直播弹幕
|
||||||
|
DMAttrHighLike = 2; // 高赞弹幕
|
||||||
|
}
|
||||||
|
|
||||||
|
message DmColorful {
|
||||||
|
DmColorfulType type = 1; // 颜色类型
|
||||||
|
string src = 2; //
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DmColorfulType {
|
||||||
|
NoneType = 0; // 无
|
||||||
|
VipGradualColor = 60001; // 渐变色
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmExpoReportReq {
|
||||||
|
//
|
||||||
|
string session_id = 1;
|
||||||
|
//
|
||||||
|
int64 oid = 2;
|
||||||
|
//
|
||||||
|
string spmid = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmExpoReportRes {}
|
||||||
|
|
||||||
|
// 修改弹幕配置-请求
|
||||||
|
message DmPlayerConfigReq {
|
||||||
|
int64 ts = 1; //
|
||||||
|
PlayerDanmakuSwitch switch = 2; // 是否开启弹幕
|
||||||
|
PlayerDanmakuSwitchSave switch_save = 3; // 是否记录弹幕开关设置
|
||||||
|
PlayerDanmakuUseDefaultConfig use_default_config = 4; // 是否使用推荐弹幕设置
|
||||||
|
PlayerDanmakuAiRecommendedSwitch ai_recommended_switch = 5; // 是否开启智能云屏蔽
|
||||||
|
PlayerDanmakuAiRecommendedLevel ai_recommended_level = 6; // 智能云屏蔽等级
|
||||||
|
PlayerDanmakuBlocktop blocktop = 7; // 是否屏蔽顶端弹幕
|
||||||
|
PlayerDanmakuBlockscroll blockscroll = 8; // 是否屏蔽滚动弹幕
|
||||||
|
PlayerDanmakuBlockbottom blockbottom = 9; // 是否屏蔽底端弹幕
|
||||||
|
PlayerDanmakuBlockcolorful blockcolorful = 10; // 是否屏蔽彩色弹幕
|
||||||
|
PlayerDanmakuBlockrepeat blockrepeat = 11; // 是否屏蔽重复弹幕
|
||||||
|
PlayerDanmakuBlockspecial blockspecial = 12; // 是否屏蔽高级弹幕
|
||||||
|
PlayerDanmakuOpacity opacity = 13; // 弹幕不透明度
|
||||||
|
PlayerDanmakuScalingfactor scalingfactor = 14; // 弹幕缩放比例
|
||||||
|
PlayerDanmakuDomain domain = 15; // 弹幕显示区域
|
||||||
|
PlayerDanmakuSpeed speed = 16; // 弹幕速度
|
||||||
|
PlayerDanmakuEnableblocklist enableblocklist = 17; // 是否开启屏蔽列表
|
||||||
|
InlinePlayerDanmakuSwitch inlinePlayerDanmakuSwitch = 18; // 是否开启弹幕
|
||||||
|
PlayerDanmakuSeniorModeSwitch senior_mode_switch = 19; //
|
||||||
|
PlayerDanmakuAiRecommendedLevelV2 ai_recommended_level_v2 = 20; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message DmSegConfig {
|
||||||
|
//
|
||||||
|
int64 page_size = 1;
|
||||||
|
//
|
||||||
|
int64 total = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取弹幕-响应
|
||||||
|
message DmSegMobileReply {
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 1;
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
int32 state = 2;
|
||||||
|
// 弹幕云屏蔽ai评分值
|
||||||
|
DanmakuAIFlag ai_flag = 3;
|
||||||
|
repeated DmColorful colorfulSrc = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取弹幕-请求
|
||||||
|
message DmSegMobileReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
// 是否青少年模式
|
||||||
|
int32 teenagers_mode = 5;
|
||||||
|
//
|
||||||
|
int64 ps = 6;
|
||||||
|
//
|
||||||
|
int64 pe = 7;
|
||||||
|
//
|
||||||
|
int32 pull_mode = 8;
|
||||||
|
//
|
||||||
|
int32 from_scene = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ott弹幕列表-响应
|
||||||
|
message DmSegOttReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ott弹幕列表-请求
|
||||||
|
message DmSegOttReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕SDK-响应
|
||||||
|
message DmSegSDKReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 弹幕列表
|
||||||
|
repeated DanmakuElem elems = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹幕SDK-请求
|
||||||
|
message DmSegSDKReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 分段(6min)
|
||||||
|
int64 segment_index = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端弹幕元数据-响应
|
||||||
|
message DmViewReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
bool closed = 1;
|
||||||
|
// 智能防挡弹幕蒙版信息
|
||||||
|
VideoMask mask = 2;
|
||||||
|
// 视频字幕
|
||||||
|
VideoSubtitle subtitle = 3;
|
||||||
|
// 高级弹幕专包url(bfs)
|
||||||
|
repeated string special_dms = 4;
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
DanmakuFlagConfig ai_flag = 5;
|
||||||
|
// 弹幕配置信息
|
||||||
|
DanmuPlayerViewConfig player_config = 6;
|
||||||
|
// 弹幕发送框样式
|
||||||
|
int32 send_box_style = 7;
|
||||||
|
// 是否允许
|
||||||
|
bool allow = 8;
|
||||||
|
// check box 是否展示
|
||||||
|
string check_box = 9;
|
||||||
|
// check box 展示文本
|
||||||
|
string check_box_show_msg = 10;
|
||||||
|
// 展示文案
|
||||||
|
string text_placeholder = 11;
|
||||||
|
// 弹幕输入框文案
|
||||||
|
string input_placeholder = 12;
|
||||||
|
// 用户举报弹幕 cid维度屏蔽的正则规则
|
||||||
|
repeated string report_filter_content = 13;
|
||||||
|
//
|
||||||
|
ExpoReport expo_report = 14;
|
||||||
|
//
|
||||||
|
BuzzwordConfig buzzword_config = 15;
|
||||||
|
//
|
||||||
|
repeated Expressions expressions = 16;
|
||||||
|
//
|
||||||
|
repeated PostPanel post_panel = 17;
|
||||||
|
//
|
||||||
|
repeated string activity_meta = 18;
|
||||||
|
//
|
||||||
|
repeated PostPanelV2 post_panel2 = 19;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端弹幕元数据-请求
|
||||||
|
message DmViewReq {
|
||||||
|
// 稿件avid/漫画epid
|
||||||
|
int64 pid = 1;
|
||||||
|
// 视频cid/漫画cid
|
||||||
|
int64 oid = 2;
|
||||||
|
// 弹幕类型
|
||||||
|
// 1:视频 2:漫画
|
||||||
|
int32 type = 3;
|
||||||
|
// 页面spm
|
||||||
|
string spmid = 4;
|
||||||
|
// 是否冷启
|
||||||
|
int32 is_hard_boot = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端弹幕元数据-响应
|
||||||
|
// https://api.bilibili.com/x/v2/dm/web/view
|
||||||
|
message DmWebViewReply {
|
||||||
|
// 是否已关闭弹幕
|
||||||
|
// 0:未关闭 1:已关闭
|
||||||
|
int32 state = 1;
|
||||||
|
//
|
||||||
|
string text = 2;
|
||||||
|
//
|
||||||
|
string text_side = 3;
|
||||||
|
// 分段弹幕配置
|
||||||
|
DmSegConfig dm_sge = 4;
|
||||||
|
// 云屏蔽配置信息
|
||||||
|
DanmakuFlagConfig flag = 5;
|
||||||
|
// 高级弹幕专包url(bfs)
|
||||||
|
repeated string special_dms = 6;
|
||||||
|
// check box 是否展示
|
||||||
|
bool check_box = 7;
|
||||||
|
// 弹幕数
|
||||||
|
int64 count = 8;
|
||||||
|
// 互动弹幕
|
||||||
|
repeated CommandDm commandDms = 9;
|
||||||
|
// 用户弹幕配置
|
||||||
|
DanmuWebPlayerConfig player_config = 10;
|
||||||
|
// 用户举报弹幕 cid维度屏蔽
|
||||||
|
repeated string report_filter_content = 11;
|
||||||
|
//
|
||||||
|
repeated Expressions expressions = 12;
|
||||||
|
//
|
||||||
|
repeated PostPanel post_panel = 13;
|
||||||
|
//
|
||||||
|
repeated string activity_meta = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ExpoReport {
|
||||||
|
//
|
||||||
|
bool should_report_at_end = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum ExposureType {
|
||||||
|
ExposureTypeNone = 0; //
|
||||||
|
ExposureTypeDMSend = 1; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Expression {
|
||||||
|
//
|
||||||
|
repeated string keyword = 1;
|
||||||
|
//
|
||||||
|
string url = 2;
|
||||||
|
//
|
||||||
|
repeated Period period = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Expressions {
|
||||||
|
//
|
||||||
|
repeated Expression data = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否开启弹幕
|
||||||
|
message InlinePlayerDanmakuSwitch {
|
||||||
|
//
|
||||||
|
bool value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Label {
|
||||||
|
//
|
||||||
|
string title = 1;
|
||||||
|
//
|
||||||
|
repeated string content = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message LabelV2 {
|
||||||
|
//
|
||||||
|
string title = 1;
|
||||||
|
//
|
||||||
|
repeated string content = 2;
|
||||||
|
//
|
||||||
|
bool exposure_once = 3;
|
||||||
|
//
|
||||||
|
int32 exposure_type = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Period {
|
||||||
|
//
|
||||||
|
int64 start = 1;
|
||||||
|
//
|
||||||
|
int64 end = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlayerDanmakuAiRecommendedLevel {bool value = 1;} // 智能云屏蔽等级
|
||||||
|
message PlayerDanmakuAiRecommendedLevelV2 {int32 value = 1;} //
|
||||||
|
message PlayerDanmakuAiRecommendedSwitch {bool value = 1;} // 是否开启智能云屏蔽
|
||||||
|
message PlayerDanmakuBlockbottom {bool value = 1;} // 是否屏蔽底端弹幕
|
||||||
|
message PlayerDanmakuBlockcolorful {bool value = 1;} // 是否屏蔽彩色弹幕
|
||||||
|
message PlayerDanmakuBlockrepeat {bool value = 1;} // 是否屏蔽重复弹幕
|
||||||
|
message PlayerDanmakuBlockscroll {bool value = 1;} // 是否屏蔽滚动弹幕
|
||||||
|
message PlayerDanmakuBlockspecial {bool value = 1;} // 是否屏蔽高级弹幕
|
||||||
|
message PlayerDanmakuBlocktop {bool value = 1;} // 是否屏蔽顶端弹幕
|
||||||
|
message PlayerDanmakuDomain {float value = 1;} // 弹幕显示区域
|
||||||
|
message PlayerDanmakuEnableblocklist {bool value = 1;} // 是否开启屏蔽列表
|
||||||
|
message PlayerDanmakuOpacity {float value = 1;} // 弹幕不透明度
|
||||||
|
message PlayerDanmakuScalingfactor {float value = 1;} // 弹幕缩放比例
|
||||||
|
message PlayerDanmakuSeniorModeSwitch {int32 value = 1;} //
|
||||||
|
message PlayerDanmakuSpeed {int32 value = 1;} // 弹幕速度
|
||||||
|
message PlayerDanmakuSwitch {bool value = 1; bool can_ignore = 2;} // 是否开启弹幕
|
||||||
|
message PlayerDanmakuSwitchSave {bool value = 1;} // 是否记录弹幕开关设置
|
||||||
|
message PlayerDanmakuUseDefaultConfig {bool value = 1;} // 是否使用推荐弹幕设置
|
||||||
|
|
||||||
|
//
|
||||||
|
message PostPanel {
|
||||||
|
//
|
||||||
|
int64 start = 1;
|
||||||
|
//
|
||||||
|
int64 end = 2;
|
||||||
|
//
|
||||||
|
int64 priority = 3;
|
||||||
|
//
|
||||||
|
int64 biz_id = 4;
|
||||||
|
//
|
||||||
|
PostPanelBizType biz_type = 5;
|
||||||
|
//
|
||||||
|
ClickButton click_button = 6;
|
||||||
|
//
|
||||||
|
TextInput text_input = 7;
|
||||||
|
//
|
||||||
|
CheckBox check_box = 8;
|
||||||
|
//
|
||||||
|
Toast toast = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum PostPanelBizType {
|
||||||
|
PostPanelBizTypeNone = 0; //
|
||||||
|
PostPanelBizTypeEncourage = 1; //
|
||||||
|
PostPanelBizTypeColorDM = 2; //
|
||||||
|
PostPanelBizTypeNFTDM = 3; //
|
||||||
|
PostPanelBizTypeFragClose = 4; //
|
||||||
|
PostPanelBizTypeRecommend = 5; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message PostPanelV2 {
|
||||||
|
//
|
||||||
|
int64 start = 1;
|
||||||
|
//
|
||||||
|
int64 end = 2;
|
||||||
|
//
|
||||||
|
int32 biz_type = 3;
|
||||||
|
//
|
||||||
|
ClickButtonV2 click_button = 4;
|
||||||
|
//
|
||||||
|
TextInputV2 text_input = 5;
|
||||||
|
//
|
||||||
|
CheckBoxV2 check_box = 6;
|
||||||
|
//
|
||||||
|
ToastV2 toast = 7;
|
||||||
|
//
|
||||||
|
BubbleV2 bubble = 8;
|
||||||
|
//
|
||||||
|
LabelV2 label = 9;
|
||||||
|
//
|
||||||
|
int32 post_status = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum PostStatus {
|
||||||
|
PostStatusNormal = 0; //
|
||||||
|
PostStatusClosed = 1; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum RenderType {
|
||||||
|
RenderTypeNone = 0; //
|
||||||
|
RenderTypeSingle = 1; //
|
||||||
|
RenderTypeRotation = 2; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改弹幕配置-响应
|
||||||
|
message Response {
|
||||||
|
//
|
||||||
|
int32 code = 1;
|
||||||
|
//
|
||||||
|
string message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum SubtitleAiStatus {
|
||||||
|
None = 0; //
|
||||||
|
Exposure = 1; //
|
||||||
|
Assist = 2; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum SubtitleAiType {
|
||||||
|
Normal = 0; //
|
||||||
|
Translate = 1; //
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个字幕信息
|
||||||
|
message SubtitleItem {
|
||||||
|
// 字幕id
|
||||||
|
int64 id = 1;
|
||||||
|
// 字幕id str
|
||||||
|
string id_str = 2;
|
||||||
|
// 字幕语言代码
|
||||||
|
string lan = 3;
|
||||||
|
// 字幕语言
|
||||||
|
string lan_doc = 4;
|
||||||
|
// 字幕文件url
|
||||||
|
string subtitle_url = 5;
|
||||||
|
// 字幕作者信息
|
||||||
|
UserInfo author = 6;
|
||||||
|
// 字幕类型
|
||||||
|
SubtitleType type = 7;
|
||||||
|
//
|
||||||
|
string lan_doc_brief = 8;
|
||||||
|
//
|
||||||
|
SubtitleAiType ai_type = 9;
|
||||||
|
//
|
||||||
|
SubtitleAiStatus ai_status = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SubtitleType {
|
||||||
|
CC = 0; // CC字幕
|
||||||
|
AI = 1; // AI生成字幕
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message TextInput {
|
||||||
|
//
|
||||||
|
repeated string portrait_placeholder = 1;
|
||||||
|
//
|
||||||
|
repeated string landscape_placeholder = 2;
|
||||||
|
//
|
||||||
|
RenderType render_type = 3;
|
||||||
|
//
|
||||||
|
bool placeholder_post = 4;
|
||||||
|
//
|
||||||
|
bool show = 5;
|
||||||
|
//
|
||||||
|
repeated Avatar avatar = 6;
|
||||||
|
//
|
||||||
|
PostStatus post_status = 7;
|
||||||
|
//
|
||||||
|
Label label = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message TextInputV2 {
|
||||||
|
//
|
||||||
|
repeated string portrait_placeholder = 1;
|
||||||
|
//
|
||||||
|
repeated string landscape_placeholder = 2;
|
||||||
|
//
|
||||||
|
RenderType render_type = 3;
|
||||||
|
//
|
||||||
|
bool placeholder_post = 4;
|
||||||
|
//
|
||||||
|
repeated Avatar avatar = 5;
|
||||||
|
//
|
||||||
|
int32 text_input_limit = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message Toast {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
int32 duration = 2;
|
||||||
|
//
|
||||||
|
bool show = 3;
|
||||||
|
//
|
||||||
|
Button button = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ToastButtonV2 {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
int32 action = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
enum ToastFunctionType {
|
||||||
|
ToastFunctionTypeNone = 0; //
|
||||||
|
ToastFunctionTypePostPanel = 1; //
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
message ToastV2 {
|
||||||
|
//
|
||||||
|
string text = 1;
|
||||||
|
//
|
||||||
|
int32 duration = 2;
|
||||||
|
//
|
||||||
|
ToastButtonV2 toast_button_v2 = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字幕作者信息
|
||||||
|
message UserInfo {
|
||||||
|
// 用户mid
|
||||||
|
int64 mid = 1;
|
||||||
|
// 用户昵称
|
||||||
|
string name = 2;
|
||||||
|
// 用户性别
|
||||||
|
string sex = 3;
|
||||||
|
// 用户头像url
|
||||||
|
string face = 4;
|
||||||
|
// 用户签名
|
||||||
|
string sign = 5;
|
||||||
|
// 用户等级
|
||||||
|
int32 rank = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能防挡弹幕蒙版信息
|
||||||
|
message VideoMask {
|
||||||
|
// 视频cid
|
||||||
|
int64 cid = 1;
|
||||||
|
// 平台
|
||||||
|
// 0:web端 1:客户端
|
||||||
|
int32 plat = 2;
|
||||||
|
// 帧率
|
||||||
|
int32 fps = 3;
|
||||||
|
// 间隔时间
|
||||||
|
int64 time = 4;
|
||||||
|
// 蒙版url
|
||||||
|
string mask_url = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频字幕信息
|
||||||
|
message VideoSubtitle {
|
||||||
|
// 视频原语言代码
|
||||||
|
string lan = 1;
|
||||||
|
// 视频原语言
|
||||||
|
string lanDoc = 2;
|
||||||
|
// 视频字幕列表
|
||||||
|
repeated SubtitleItem subtitles = 3;
|
||||||
|
}
|
||||||
@ -360,7 +360,7 @@ class GoodItem {
|
|||||||
|
|
||||||
String? brief;
|
String? brief;
|
||||||
String? cover;
|
String? cover;
|
||||||
String? id;
|
dynamic id;
|
||||||
String? jumpDesc;
|
String? jumpDesc;
|
||||||
String? jumpUrl;
|
String? jumpUrl;
|
||||||
String? name;
|
String? name;
|
||||||
@ -408,6 +408,7 @@ class DynamicMajorModel {
|
|||||||
this.live,
|
this.live,
|
||||||
this.none,
|
this.none,
|
||||||
this.type,
|
this.type,
|
||||||
|
this.courses,
|
||||||
});
|
});
|
||||||
|
|
||||||
DynamicArchiveModel? archive;
|
DynamicArchiveModel? archive;
|
||||||
@ -422,6 +423,7 @@ class DynamicMajorModel {
|
|||||||
// MAJOR_TYPE_ARCHIVE 视频
|
// MAJOR_TYPE_ARCHIVE 视频
|
||||||
// MAJOR_TYPE_OPUS 图文/文章
|
// MAJOR_TYPE_OPUS 图文/文章
|
||||||
String? type;
|
String? type;
|
||||||
|
Map? courses;
|
||||||
|
|
||||||
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
|
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
|
||||||
archive = json['archive'] != null
|
archive = json['archive'] != null
|
||||||
@ -444,6 +446,7 @@ class DynamicMajorModel {
|
|||||||
none =
|
none =
|
||||||
json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null;
|
json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null;
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
|
courses = json['courses'] ?? {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -478,6 +481,8 @@ class DynamicArchiveModel {
|
|||||||
this.stat,
|
this.stat,
|
||||||
this.title,
|
this.title,
|
||||||
this.type,
|
this.type,
|
||||||
|
this.epid,
|
||||||
|
this.seasonId,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? aid;
|
int? aid;
|
||||||
@ -491,6 +496,8 @@ class DynamicArchiveModel {
|
|||||||
Stat? stat;
|
Stat? stat;
|
||||||
String? title;
|
String? title;
|
||||||
int? type;
|
int? type;
|
||||||
|
int? epid;
|
||||||
|
int? seasonId;
|
||||||
|
|
||||||
DynamicArchiveModel.fromJson(Map<String, dynamic> json) {
|
DynamicArchiveModel.fromJson(Map<String, dynamic> json) {
|
||||||
aid = json['aid'] is String ? int.parse(json['aid']) : json['aid'];
|
aid = json['aid'] is String ? int.parse(json['aid']) : json['aid'];
|
||||||
@ -503,6 +510,8 @@ class DynamicArchiveModel {
|
|||||||
stat = json['stat'] != null ? Stat.fromJson(json['stat']) : null;
|
stat = json['stat'] != null ? Stat.fromJson(json['stat']) : null;
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
|
epid = json['epid'];
|
||||||
|
seasonId = json['season_id'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,9 @@ class FollowUpModel {
|
|||||||
List<UpItem>? upList;
|
List<UpItem>? upList;
|
||||||
|
|
||||||
FollowUpModel.fromJson(Map<String, dynamic> json) {
|
FollowUpModel.fromJson(Map<String, dynamic> json) {
|
||||||
liveUsers = LiveUsers.fromJson(json['live_users']);
|
liveUsers = json['live_users'] != null
|
||||||
|
? LiveUsers.fromJson(json['live_users'])
|
||||||
|
: null;
|
||||||
upList = json['up_list'] != null
|
upList = json['up_list'] != null
|
||||||
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
||||||
: [];
|
: [];
|
||||||
|
|||||||
@ -4,12 +4,14 @@ class LatestDataModel {
|
|||||||
this.tagName,
|
this.tagName,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.assets,
|
this.assets,
|
||||||
|
this.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? url;
|
String? url;
|
||||||
String? tagName;
|
String? tagName;
|
||||||
String? createdAt;
|
String? createdAt;
|
||||||
List? assets;
|
List? assets;
|
||||||
|
String? body;
|
||||||
|
|
||||||
LatestDataModel.fromJson(Map<String, dynamic> json) {
|
LatestDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
url = json['url'];
|
url = json['url'];
|
||||||
@ -17,6 +19,7 @@ class LatestDataModel {
|
|||||||
createdAt = json['created_at'];
|
createdAt = json['created_at'];
|
||||||
assets =
|
assets =
|
||||||
json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList();
|
json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList();
|
||||||
|
body = json['body'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ class SearchVideoModel {
|
|||||||
List<SearchVideoItemModel>? list;
|
List<SearchVideoItemModel>? list;
|
||||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||||
list = json['result']
|
list = json['result']
|
||||||
|
.where((e) => e['available'] == true)
|
||||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -17,7 +18,7 @@ class SearchVideoItemModel {
|
|||||||
this.id,
|
this.id,
|
||||||
this.cid,
|
this.cid,
|
||||||
// this.author,
|
// this.author,
|
||||||
// this.mid,
|
this.mid,
|
||||||
// this.typeid,
|
// this.typeid,
|
||||||
// this.typename,
|
// this.typename,
|
||||||
this.arcurl,
|
this.arcurl,
|
||||||
@ -47,7 +48,7 @@ class SearchVideoItemModel {
|
|||||||
int? id;
|
int? id;
|
||||||
int? cid;
|
int? cid;
|
||||||
// String? author;
|
// String? author;
|
||||||
// String? mid;
|
int? mid;
|
||||||
// String? typeid;
|
// String? typeid;
|
||||||
// String? typename;
|
// String? typename;
|
||||||
String? arcurl;
|
String? arcurl;
|
||||||
@ -80,6 +81,7 @@ class SearchVideoItemModel {
|
|||||||
arcurl = json['arcurl'];
|
arcurl = json['arcurl'];
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
mid = json['mid'];
|
||||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
@ -376,3 +378,75 @@ class SearchMBangumiItemModel {
|
|||||||
indexShow = json['index_show'];
|
indexShow = json['index_show'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SearchArticleModel {
|
||||||
|
SearchArticleModel({this.list});
|
||||||
|
|
||||||
|
List<SearchArticleItemModel>? list;
|
||||||
|
|
||||||
|
SearchArticleModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
list = json['result'] != null
|
||||||
|
? json['result']
|
||||||
|
.map<SearchArticleItemModel>(
|
||||||
|
(e) => SearchArticleItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchArticleItemModel {
|
||||||
|
SearchArticleItemModel({
|
||||||
|
this.pubTime,
|
||||||
|
this.like,
|
||||||
|
this.title,
|
||||||
|
this.subTitle,
|
||||||
|
this.rankOffset,
|
||||||
|
this.mid,
|
||||||
|
this.imageUrls,
|
||||||
|
this.id,
|
||||||
|
this.categoryId,
|
||||||
|
this.view,
|
||||||
|
this.reply,
|
||||||
|
this.desc,
|
||||||
|
this.rankScore,
|
||||||
|
this.type,
|
||||||
|
this.templateId,
|
||||||
|
this.categoryName,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? pubTime;
|
||||||
|
int? like;
|
||||||
|
List? title;
|
||||||
|
String? subTitle;
|
||||||
|
int? rankOffset;
|
||||||
|
int? mid;
|
||||||
|
List? imageUrls;
|
||||||
|
int? id;
|
||||||
|
int? categoryId;
|
||||||
|
int? view;
|
||||||
|
int? reply;
|
||||||
|
String? desc;
|
||||||
|
int? rankScore;
|
||||||
|
String? type;
|
||||||
|
int? templateId;
|
||||||
|
String? categoryName;
|
||||||
|
|
||||||
|
SearchArticleItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
pubTime = json['pub_time'];
|
||||||
|
like = json['like'];
|
||||||
|
title = Em.regTitle(json['title']);
|
||||||
|
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
|
rankOffset = json['rank_offset'];
|
||||||
|
mid = json['mid'];
|
||||||
|
imageUrls = json['image_urls'];
|
||||||
|
id = json['id'];
|
||||||
|
categoryId = json['category_id'];
|
||||||
|
view = json['view'];
|
||||||
|
reply = json['reply'];
|
||||||
|
desc = json['desc'];
|
||||||
|
rankScore = json['rank_score'];
|
||||||
|
type = json['type'];
|
||||||
|
templateId = json['templateId'];
|
||||||
|
categoryName = json['category_name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class SearchSuggestModel {
|
class SearchSuggestModel {
|
||||||
SearchSuggestModel({
|
SearchSuggestModel({
|
||||||
this.tag,
|
this.tag,
|
||||||
@ -19,32 +22,74 @@ class SearchSuggestItem {
|
|||||||
SearchSuggestItem({
|
SearchSuggestItem({
|
||||||
this.value,
|
this.value,
|
||||||
this.term,
|
this.term,
|
||||||
this.name,
|
|
||||||
this.spid,
|
this.spid,
|
||||||
|
this.textRich,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? value;
|
String? value;
|
||||||
String? term;
|
String? term;
|
||||||
List? name;
|
|
||||||
int? spid;
|
int? spid;
|
||||||
|
Widget? textRich;
|
||||||
|
|
||||||
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
|
SearchSuggestItem.fromJson(Map<String, dynamic> json, String inputTerm) {
|
||||||
value = json['value'];
|
value = json['value'];
|
||||||
term = json['term'];
|
term = json['term'];
|
||||||
String reg = '<em class="suggest_high_light">$inputTerm</em>';
|
textRich = highlightText(json['name']);
|
||||||
try {
|
|
||||||
if (json['name'].indexOf(inputTerm) != -1) {
|
|
||||||
String str = json['name'].replaceAll(reg, '^');
|
|
||||||
List arr = str.split('^');
|
|
||||||
arr.insert(arr.length - 1, inputTerm);
|
|
||||||
name = arr;
|
|
||||||
} else {
|
|
||||||
name = ['', '', json['term']];
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
name = ['', '', json['term']];
|
|
||||||
}
|
|
||||||
|
|
||||||
spid = json['spid'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget highlightText(String str) {
|
||||||
|
// 创建正则表达式,匹配 <em class="suggest_high_light">...</em> 格式的文本
|
||||||
|
RegExp regex = RegExp(r'<em class="suggest_high_light">(.*?)<\/em>');
|
||||||
|
|
||||||
|
// 用于存储每个匹配项的列表
|
||||||
|
List<InlineSpan> children = [];
|
||||||
|
|
||||||
|
// 获取所有匹配项
|
||||||
|
Iterable<Match> matches = regex.allMatches(str);
|
||||||
|
|
||||||
|
// 当前索引位置
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
// 遍历每个匹配项
|
||||||
|
for (var match in matches) {
|
||||||
|
// 获取当前匹配项之前的普通文本部分
|
||||||
|
String normalText = str.substring(currentIndex, match.start);
|
||||||
|
|
||||||
|
// 获取需要高亮显示的文本部分
|
||||||
|
String highlightedText = match.group(1)!;
|
||||||
|
|
||||||
|
// 如果普通文本部分不为空,则将其添加到 children 列表中
|
||||||
|
if (normalText.isNotEmpty) {
|
||||||
|
children.add(TextSpan(
|
||||||
|
text: normalText,
|
||||||
|
style: DefaultTextStyle.of(Get.context!).style,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将需要高亮显示的文本部分添加到 children 列表中,并设置相应样式
|
||||||
|
children.add(TextSpan(
|
||||||
|
text: highlightedText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(Get.context!).colorScheme.primary),
|
||||||
|
));
|
||||||
|
|
||||||
|
// 更新当前索引位置
|
||||||
|
currentIndex = match.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果当前索引位置小于文本长度,表示还有剩余的普通文本部分
|
||||||
|
if (currentIndex < str.length) {
|
||||||
|
String remainingText = str.substring(currentIndex);
|
||||||
|
|
||||||
|
// 将剩余的普通文本部分添加到 children 列表中
|
||||||
|
children.add(TextSpan(
|
||||||
|
text: remainingText,
|
||||||
|
style: DefaultTextStyle.of(Get.context!).style,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Text.rich 创建包含高亮显示的富文本小部件,并返回
|
||||||
|
return Text.rich(TextSpan(children: children));
|
||||||
|
}
|
||||||
|
|||||||
@ -3,17 +3,23 @@ class HistoryData {
|
|||||||
this.cursor,
|
this.cursor,
|
||||||
this.tab,
|
this.tab,
|
||||||
this.list,
|
this.list,
|
||||||
|
this.page,
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor? cursor;
|
Cursor? cursor;
|
||||||
List<HisTabItem>? tab;
|
List<HisTabItem>? tab;
|
||||||
List<HisListItem>? list;
|
List<HisListItem>? list;
|
||||||
|
Map? page;
|
||||||
|
|
||||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||||
cursor = Cursor.fromJson(json['cursor']);
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
tab = json['tab'] != null
|
||||||
list =
|
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
: [];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
page = json['page'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ class HisListItem {
|
|||||||
this.kid,
|
this.kid,
|
||||||
this.tagName,
|
this.tagName,
|
||||||
this.liveStatus,
|
this.liveStatus,
|
||||||
|
this.checked,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? title;
|
String? title;
|
||||||
@ -105,6 +112,7 @@ class HisListItem {
|
|||||||
int? kid;
|
int? kid;
|
||||||
String? tagName;
|
String? tagName;
|
||||||
int? liveStatus;
|
int? liveStatus;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
@ -121,7 +129,7 @@ class HisListItem {
|
|||||||
viewAt = json['view_at'];
|
viewAt = json['view_at'];
|
||||||
progress = json['progress'];
|
progress = json['progress'];
|
||||||
badge = json['badge'];
|
badge = json['badge'];
|
||||||
showTitle = json['show_title'];
|
showTitle = json['show_title'] == '' ? null : json['show_title'];
|
||||||
duration = json['duration'];
|
duration = json['duration'];
|
||||||
current = json['current'];
|
current = json['current'];
|
||||||
total = json['total'];
|
total = json['total'];
|
||||||
@ -131,6 +139,7 @@ class HisListItem {
|
|||||||
kid = json['kid'];
|
kid = json['kid'];
|
||||||
tagName = json['tag_name'];
|
tagName = json['tag_name'];
|
||||||
liveStatus = json['live_status'];
|
liveStatus = json['live_status'];
|
||||||
|
checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class UserInfoData {
|
|||||||
@HiveField(5)
|
@HiveField(5)
|
||||||
int? mobileVerified;
|
int? mobileVerified;
|
||||||
@HiveField(6)
|
@HiveField(6)
|
||||||
int? money;
|
double? money;
|
||||||
@HiveField(7)
|
@HiveField(7)
|
||||||
int? moral;
|
int? moral;
|
||||||
@HiveField(8)
|
@HiveField(8)
|
||||||
@ -88,7 +88,7 @@ class UserInfoData {
|
|||||||
: LevelInfo();
|
: LevelInfo();
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
mobileVerified = json['mobile_verified'];
|
mobileVerified = json['mobile_verified'];
|
||||||
money = json['money'];
|
money = json['money'] is int ? json['money'].toDouble() : json['money'];
|
||||||
moral = json['moral'];
|
moral = json['moral'];
|
||||||
official = json['official'];
|
official = json['official'];
|
||||||
officialVerify = json['officialVerify'];
|
officialVerify = json['officialVerify'];
|
||||||
@ -130,6 +130,7 @@ class LevelInfo {
|
|||||||
currentLevel = json['current_level'];
|
currentLevel = json['current_level'];
|
||||||
currentMin = json['current_min'];
|
currentMin = json['current_min'];
|
||||||
currentExp = json['current_exp'];
|
currentExp = json['current_exp'];
|
||||||
nextExp = json['next_exp'];
|
nextExp =
|
||||||
|
json['current_level'] == 6 ? json['current_exp'] : json['next_exp'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class UserInfoDataAdapter extends TypeAdapter<UserInfoData> {
|
|||||||
levelInfo: fields[3] as LevelInfo?,
|
levelInfo: fields[3] as LevelInfo?,
|
||||||
mid: fields[4] as int?,
|
mid: fields[4] as int?,
|
||||||
mobileVerified: fields[5] as int?,
|
mobileVerified: fields[5] as int?,
|
||||||
money: fields[6] as int?,
|
money: fields[6] as double?,
|
||||||
moral: fields[7] as int?,
|
moral: fields[7] as int?,
|
||||||
official: (fields[8] as Map?)?.cast<dynamic, dynamic>(),
|
official: (fields[8] as Map?)?.cast<dynamic, dynamic>(),
|
||||||
officialVerify: (fields[9] as Map?)?.cast<dynamic, dynamic>(),
|
officialVerify: (fields[9] as Map?)?.cast<dynamic, dynamic>(),
|
||||||
|
|||||||
@ -93,26 +93,19 @@ extension AudioQualityDesc on AudioQuality {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VideoDecodeFormats {
|
enum VideoDecodeFormats {
|
||||||
|
DVH1,
|
||||||
AV1,
|
AV1,
|
||||||
HEVC,
|
HEVC,
|
||||||
AVC,
|
AVC,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
|
||||||
static final List<String> _descList = [
|
static final List<String> _descList = ['DVH1', 'AV1', 'HEVC', 'AVC'];
|
||||||
'AV1',
|
|
||||||
'HEVC',
|
|
||||||
'AVC',
|
|
||||||
];
|
|
||||||
get description => _descList[index];
|
get description => _descList[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
extension VideoDecodeFormatsCode on VideoDecodeFormats {
|
||||||
static final List<String> _codeList = [
|
static final List<String> _codeList = ['dvh1', 'av01', 'hev1', 'avc1'];
|
||||||
'av01',
|
|
||||||
'hev1',
|
|
||||||
'avc1',
|
|
||||||
];
|
|
||||||
get code => _codeList[index];
|
get code => _codeList[index];
|
||||||
|
|
||||||
static VideoDecodeFormats? fromCode(String code) {
|
static VideoDecodeFormats? fromCode(String code) {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
|
|
||||||
class PlayUrlModel {
|
class PlayUrlModel {
|
||||||
@ -77,8 +79,8 @@ class Dash {
|
|||||||
double? minBufferTime;
|
double? minBufferTime;
|
||||||
List<VideoItem>? video;
|
List<VideoItem>? video;
|
||||||
List<AudioItem>? audio;
|
List<AudioItem>? audio;
|
||||||
Map? dolby;
|
Dolby? dolby;
|
||||||
Map? flac;
|
Flac? flac;
|
||||||
|
|
||||||
Dash.fromJson(Map<String, dynamic> json) {
|
Dash.fromJson(Map<String, dynamic> json) {
|
||||||
duration = json['duration'];
|
duration = json['duration'];
|
||||||
@ -87,8 +89,8 @@ class Dash {
|
|||||||
audio = json['audio'] != null
|
audio = json['audio'] != null
|
||||||
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
|
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
|
||||||
: [];
|
: [];
|
||||||
dolby = json['dolby'];
|
dolby = json['dolby'] != null ? Dolby.fromJson(json['dolby']) : null;
|
||||||
flac = json['flac'] ?? {};
|
flac = json['flac'] != null ? Flac.fromJson(json['flac']) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +130,8 @@ class VideoItem {
|
|||||||
VideoItem.fromJson(Map<String, dynamic> json) {
|
VideoItem.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
baseUrl = json['baseUrl'];
|
baseUrl = json['baseUrl'];
|
||||||
backupUrl = json['backupUrl'].toList().first;
|
backupUrl =
|
||||||
|
json['backupUrl'] != null ? json['backupUrl'].toList().first : '';
|
||||||
bandWidth = json['bandWidth'];
|
bandWidth = json['bandWidth'];
|
||||||
mimeType = json['mime_type'];
|
mimeType = json['mime_type'];
|
||||||
codecs = json['codecs'];
|
codecs = json['codecs'];
|
||||||
@ -179,7 +182,8 @@ class AudioItem {
|
|||||||
AudioItem.fromJson(Map<String, dynamic> json) {
|
AudioItem.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
baseUrl = json['baseUrl'];
|
baseUrl = json['baseUrl'];
|
||||||
backupUrl = json['backupUrl'].toList().first;
|
backupUrl =
|
||||||
|
json['backupUrl'] != null ? json['backupUrl'].toList().first : '';
|
||||||
bandWidth = json['bandWidth'];
|
bandWidth = json['bandWidth'];
|
||||||
mimeType = json['mime_type'];
|
mimeType = json['mime_type'];
|
||||||
codecs = json['codecs'];
|
codecs = json['codecs'];
|
||||||
@ -218,3 +222,33 @@ class FormatItem {
|
|||||||
codecs = json['codecs'];
|
codecs = json['codecs'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Dolby {
|
||||||
|
Dolby({
|
||||||
|
this.type,
|
||||||
|
this.audio,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1:普通杜比音效 2:全景杜比音效
|
||||||
|
int? type;
|
||||||
|
List<AudioItem>? audio;
|
||||||
|
|
||||||
|
Dolby.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
audio = json['audio'] != null
|
||||||
|
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Flac {
|
||||||
|
Flac({this.display, this.audio});
|
||||||
|
|
||||||
|
bool? display;
|
||||||
|
AudioItem? audio;
|
||||||
|
|
||||||
|
Flac.fromJson(Map<String, dynamic> json) {
|
||||||
|
display = json['display'];
|
||||||
|
audio = json['audio'] != null ? AudioItem.fromJson(json['audio']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/github/latest.dart';
|
import 'package:pilipala/models/github/latest.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -48,6 +47,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
'PiliPala',
|
'PiliPala',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
'使用Flutter开发的哔哩哔哩第三方客户端',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Obx(
|
Obx(
|
||||||
() => ListTile(
|
() => ListTile(
|
||||||
@ -83,16 +87,6 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
height: 30,
|
height: 30,
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
title: const Text('作者'),
|
|
||||||
trailing: Text('guozhigq', style: subTitleStyle),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
title: const Text('酷安'),
|
|
||||||
trailing: Text('影若风', style: subTitleStyle),
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _aboutController.githubUrl(),
|
onTap: () => _aboutController.githubUrl(),
|
||||||
title: const Text('Github'),
|
title: const Text('Github'),
|
||||||
@ -101,6 +95,17 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
onTap: () => _aboutController.panDownload(),
|
||||||
|
title: const Text('网盘下载'),
|
||||||
|
trailing: Text(
|
||||||
|
'提取码:pili',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _aboutController.feedback(),
|
onTap: () => _aboutController.feedback(),
|
||||||
title: const Text('问题反馈'),
|
title: const Text('问题反馈'),
|
||||||
@ -112,7 +117,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _aboutController.qqChanel(),
|
onTap: () => _aboutController.qqChanel(),
|
||||||
title: const Text('QQ频道'),
|
title: const Text('QQ群'),
|
||||||
trailing: Icon(
|
trailing: Icon(
|
||||||
Icons.arrow_forward_ios,
|
Icons.arrow_forward_ios,
|
||||||
size: 16,
|
size: 16,
|
||||||
@ -124,6 +129,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
title: const Text('TG频道'),
|
title: const Text('TG频道'),
|
||||||
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
onTap: () => _aboutController.aPay(),
|
||||||
|
title: const Text('赞助'),
|
||||||
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
||||||
|
),
|
||||||
Divider(
|
Divider(
|
||||||
thickness: 8,
|
thickness: 8,
|
||||||
height: 30,
|
height: 30,
|
||||||
@ -142,11 +152,12 @@ class AboutController extends GetxController {
|
|||||||
late LatestDataModel remoteAppInfo;
|
late LatestDataModel remoteAppInfo;
|
||||||
RxBool isUpdate = true.obs;
|
RxBool isUpdate = true.obs;
|
||||||
RxBool isLoading = true.obs;
|
RxBool isLoading = true.obs;
|
||||||
|
late LatestDataModel data;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
init();
|
// init();
|
||||||
// 获取当前版本
|
// 获取当前版本
|
||||||
getCurrentApp();
|
getCurrentApp();
|
||||||
// 获取最新的版本
|
// 获取最新的版本
|
||||||
@ -154,18 +165,18 @@ class AboutController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取设备信息
|
// 获取设备信息
|
||||||
Future init() async {
|
// Future init() async {
|
||||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
||||||
if (Platform.isAndroid) {
|
// if (Platform.isAndroid) {
|
||||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
// AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
||||||
print(androidInfo.supportedAbis);
|
// print(androidInfo.supportedAbis);
|
||||||
} else if (Platform.isIOS) {
|
// } else if (Platform.isIOS) {
|
||||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
// IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
|
||||||
print(iosInfo);
|
// print(iosInfo);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 获取啊当前版本
|
// 获取当前版本
|
||||||
Future getCurrentApp() async {
|
Future getCurrentApp() async {
|
||||||
var result = await PackageInfo.fromPlatform();
|
var result = await PackageInfo.fromPlatform();
|
||||||
currentVersion.value = result.version;
|
currentVersion.value = result.version;
|
||||||
@ -174,7 +185,7 @@ class AboutController extends GetxController {
|
|||||||
// 获取远程版本
|
// 获取远程版本
|
||||||
Future getRemoteApp() async {
|
Future getRemoteApp() async {
|
||||||
var result = await Request().get(Api.latestApp);
|
var result = await Request().get(Api.latestApp);
|
||||||
LatestDataModel data = LatestDataModel.fromJson(result.data);
|
data = LatestDataModel.fromJson(result.data);
|
||||||
remoteAppInfo = data;
|
remoteAppInfo = data;
|
||||||
remoteVersion.value = data.tagName!;
|
remoteVersion.value = data.tagName!;
|
||||||
isUpdate.value =
|
isUpdate.value =
|
||||||
@ -184,15 +195,7 @@ class AboutController extends GetxController {
|
|||||||
|
|
||||||
// 跳转下载/本地更新
|
// 跳转下载/本地更新
|
||||||
Future onUpdate() async {
|
Future onUpdate() async {
|
||||||
// final dir = await getApplicationSupportDirectory();
|
Utils.matchVersion(data);
|
||||||
// final path = '${dir.path}/pilipala.apk';
|
|
||||||
// var result = await Request()
|
|
||||||
// .downloadFile(remoteAppInfo.assets!.first.downloadUrl, path);
|
|
||||||
// print(result);
|
|
||||||
launchUrl(
|
|
||||||
Uri.parse('https://github.com/guozhigq/pilipala/releases'),
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 跳转github
|
// 跳转github
|
||||||
@ -203,6 +206,14 @@ class AboutController extends GetxController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从网盘下载
|
||||||
|
panDownload() {
|
||||||
|
launchUrl(
|
||||||
|
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 问题反馈
|
// 问题反馈
|
||||||
feedback() {
|
feedback() {
|
||||||
launchUrl(
|
launchUrl(
|
||||||
@ -215,17 +226,9 @@ class AboutController extends GetxController {
|
|||||||
// qq频道
|
// qq频道
|
||||||
qqChanel() {
|
qqChanel() {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
const ClipboardData(text: 'https://pd.qq.com/s/css9rdwga'),
|
const ClipboardData(text: '489981949'),
|
||||||
);
|
|
||||||
SmartDialog.showToast(
|
|
||||||
'已复制,即将在浏览器打开',
|
|
||||||
displayTime: const Duration(milliseconds: 500),
|
|
||||||
).then(
|
|
||||||
(value) => launchUrl(
|
|
||||||
Uri.parse('https://pd.qq.com/s/css9rdwga'),
|
|
||||||
mode: LaunchMode.externalApplication,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
SmartDialog.showToast('已复制QQ群号');
|
||||||
}
|
}
|
||||||
|
|
||||||
// tg频道
|
// tg频道
|
||||||
@ -243,4 +246,16 @@ class AboutController extends GetxController {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aPay() {
|
||||||
|
try {
|
||||||
|
launchUrl(
|
||||||
|
Uri.parse(
|
||||||
|
'alipayqr://platformapi/startapp?saId=10000007&qrcode=https://qr.alipay.com/fkx14623ddwl1ping3ddd73'),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,17 +11,19 @@ class BangumiController extends GetxController {
|
|||||||
RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
|
RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
|
||||||
int _currentPage = 1;
|
int _currentPage = 1;
|
||||||
bool isLoadingMore = true;
|
bool isLoadingMore = true;
|
||||||
Box user = GStrorage.user;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
|
var userInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (user.get(UserBoxKey.userMid) != null) {
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
mid = int.parse(user.get(UserBoxKey.userMid).toString());
|
if (userInfo != null) {
|
||||||
|
mid = userInfo.mid;
|
||||||
}
|
}
|
||||||
userLogin.value = user.get(UserBoxKey.userLogin) != null;
|
userLogin.value = userInfo != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryBangumiListFeed({type = 'init'}) async {
|
Future queryBangumiListFeed({type = 'init'}) async {
|
||||||
@ -48,7 +50,11 @@ class BangumiController extends GetxController {
|
|||||||
|
|
||||||
// 我的订阅
|
// 我的订阅
|
||||||
Future queryBangumiFollow() async {
|
Future queryBangumiFollow() async {
|
||||||
var result = await BangumiHttp.bangumiFollow(mid: 17340771);
|
userInfo = userInfo ?? userInfoCache.get('userInfoCache');
|
||||||
|
if (userInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var result = await BangumiHttp.bangumiFollow(mid: userInfo.mid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
bangumiFollowList.value = result['data'].list;
|
bangumiFollowList.value = result['data'].list;
|
||||||
} else {}
|
} else {}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -49,7 +50,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
RxBool hasCoin = false.obs;
|
RxBool hasCoin = false.obs;
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
RxBool hasFav = false.obs;
|
RxBool hasFav = false.obs;
|
||||||
Box user = GStrorage.user;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
bool userLogin = false;
|
bool userLogin = false;
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
List addMediaIdsNew = [];
|
List addMediaIdsNew = [];
|
||||||
@ -57,6 +58,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
// 关注状态 默认未关注
|
// 关注状态 默认未关注
|
||||||
RxMap followStatus = {}.obs;
|
RxMap followStatus = {}.obs;
|
||||||
int _tempThemeValue = -1;
|
int _tempThemeValue = -1;
|
||||||
|
var userInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -82,7 +84,8 @@ class BangumiIntroController extends GetxController {
|
|||||||
// videoItem!['owner'] = args.owner;
|
// videoItem!['owner'] = args.owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userLogin = user.get(UserBoxKey.userLogin) != null;
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
userLogin = userInfo != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取番剧简介&选集
|
// 获取番剧简介&选集
|
||||||
@ -142,7 +145,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
|
|
||||||
// 投币
|
// 投币
|
||||||
Future actionCoinVideo() async {
|
Future actionCoinVideo() async {
|
||||||
if (user.get(UserBoxKey.userMid) == null) {
|
if (userInfo == null) {
|
||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -256,6 +259,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid = cid;
|
videoDetailCtr.cid = cid;
|
||||||
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
@ -283,10 +287,37 @@ class BangumiIntroController extends GetxController {
|
|||||||
|
|
||||||
Future queryVideoInFolder() async {
|
Future queryVideoInFolder() async {
|
||||||
var result = await VideoHttp.videoInFolder(
|
var result = await VideoHttp.videoInFolder(
|
||||||
mid: user.get(UserBoxKey.userMid), rid: IdUtils.bv2av(bvid));
|
mid: userInfo.mid, rid: IdUtils.bv2av(bvid));
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
favFolderData.value = result['data'];
|
favFolderData.value = result['data'];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
if (bangumiDetail.value.episodes != null) {
|
||||||
|
episodes = bangumiDetail.value.episodes!;
|
||||||
|
}
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
int currentIndex =
|
||||||
|
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
// 列表循环
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
if (nextIndex == episodes.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextIndex <= episodes.length - 1 &&
|
||||||
|
platRepeat == PlayRepeat.listOrder) {}
|
||||||
|
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
|
int aid = episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,11 @@ import 'controller.dart';
|
|||||||
import 'widgets/intro_detail.dart';
|
import 'widgets/intro_detail.dart';
|
||||||
|
|
||||||
class BangumiIntroPanel extends StatefulWidget {
|
class BangumiIntroPanel extends StatefulWidget {
|
||||||
const BangumiIntroPanel({super.key});
|
final int? cid;
|
||||||
|
const BangumiIntroPanel({
|
||||||
|
Key? key,
|
||||||
|
this.cid,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BangumiIntroPanel> createState() => _BangumiIntroPanelState();
|
State<BangumiIntroPanel> createState() => _BangumiIntroPanelState();
|
||||||
@ -69,7 +73,11 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return BangumiInfo(loadingStatus: true, bangumiDetail: bangumiDetail);
|
return BangumiInfo(
|
||||||
|
loadingStatus: true,
|
||||||
|
bangumiDetail: bangumiDetail,
|
||||||
|
cid: widget.cid,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -79,11 +87,13 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
class BangumiInfo extends StatefulWidget {
|
class BangumiInfo extends StatefulWidget {
|
||||||
final bool loadingStatus;
|
final bool loadingStatus;
|
||||||
final BangumiInfoModel? bangumiDetail;
|
final BangumiInfoModel? bangumiDetail;
|
||||||
|
final int? cid;
|
||||||
|
|
||||||
const BangumiInfo({
|
const BangumiInfo({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.loadingStatus = false,
|
this.loadingStatus = false,
|
||||||
this.bangumiDetail,
|
this.bangumiDetail,
|
||||||
|
this.cid,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -97,6 +107,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
late final BangumiInfoModel? bangumiItem;
|
late final BangumiInfoModel? bangumiItem;
|
||||||
late double sheetHeight;
|
late double sheetHeight;
|
||||||
|
int? cid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -105,11 +116,12 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
bangumiItem = bangumiIntroController.bangumiItem;
|
bangumiItem = bangumiIntroController.bangumiItem;
|
||||||
sheetHeight = localCache.get('sheetHeight');
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
|
cid = widget.cid!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
showFavBottomSheet() {
|
showFavBottomSheet() {
|
||||||
if (bangumiIntroController.user.get(UserBoxKey.userMid) == null) {
|
if (bangumiIntroController.userInfo.mid == null) {
|
||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -320,9 +332,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
pages: bangumiItem != null
|
pages: bangumiItem != null
|
||||||
? bangumiItem!.episodes!
|
? bangumiItem!.episodes!
|
||||||
: widget.bangumiDetail!.episodes!,
|
: widget.bangumiDetail!.episodes!,
|
||||||
cid: bangumiItem != null
|
cid: cid ??
|
||||||
? bangumiItem!.episodes!.first.cid
|
(bangumiItem != null
|
||||||
: widget.bangumiDetail!.episodes!.first.cid,
|
? bangumiItem!.episodes!.first.cid
|
||||||
|
: widget.bangumiDetail!.episodes!.first.cid),
|
||||||
sheetHeight: sheetHeight,
|
sheetHeight: sheetHeight,
|
||||||
changeFuc: (bvid, cid, aid) => bangumiIntroController
|
changeFuc: (bvid, cid, aid) => bangumiIntroController
|
||||||
.changeSeasonOrbangu(bvid, cid, aid),
|
.changeSeasonOrbangu(bvid, cid, aid),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -22,24 +23,28 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final BangumiController _bangumidController = Get.put(BangumiController());
|
final BangumiController _bangumidController = Get.put(BangumiController());
|
||||||
late Future? _futureBuilderFuture;
|
late Future? _futureBuilderFuture;
|
||||||
|
late Future? _futureBuilderFutureFollow;
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
ScrollController scrollController = _bangumidController.scrollController;
|
scrollController = _bangumidController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
||||||
|
_futureBuilderFutureFollow = _bangumidController.queryBangumiFollow();
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_bangumidController.isLoadingMore) {
|
EasyThrottle.throttle('my-throttler', const Duration(seconds: 1), () {
|
||||||
_bangumidController.isLoadingMore = true;
|
_bangumidController.isLoadingMore = true;
|
||||||
await _bangumidController.onLoad();
|
_bangumidController.onLoad();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final ScrollDirection direction =
|
final ScrollDirection direction =
|
||||||
@ -53,6 +58,12 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
@ -80,43 +91,64 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
'最近追番',
|
'最近追番',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFutureFollow =
|
||||||
|
_bangumidController.queryBangumiFollow();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.refresh,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 258,
|
height: 258,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _bangumidController.queryBangumiFollow(),
|
future: _futureBuilderFutureFollow,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
ConnectionState.done) {
|
ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
Map data = snapshot.data as Map;
|
Map data = snapshot.data as Map;
|
||||||
|
List list = _bangumidController.bangumiFollowList;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => ListView.builder(
|
() => list.isNotEmpty
|
||||||
scrollDirection: Axis.horizontal,
|
? ListView.builder(
|
||||||
itemCount: _bangumidController
|
scrollDirection: Axis.horizontal,
|
||||||
.bangumiFollowList.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
width: Get.size.width / 3,
|
width: Get.size.width / 3,
|
||||||
height: 254,
|
height: 254,
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
left: StyleString.safeSpace,
|
left: StyleString.safeSpace,
|
||||||
right: index ==
|
right: index ==
|
||||||
_bangumidController
|
_bangumidController
|
||||||
.bangumiFollowList
|
.bangumiFollowList
|
||||||
.length -
|
.length -
|
||||||
1
|
1
|
||||||
? StyleString.safeSpace
|
? StyleString.safeSpace
|
||||||
: 0),
|
: 0),
|
||||||
child: BangumiCardV(
|
child: BangumiCardV(
|
||||||
bangumiItem: _bangumidController
|
bangumiItem: _bangumidController
|
||||||
.bangumiFollowList[index],
|
.bangumiFollowList[index],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const SizedBox(
|
||||||
|
child: Center(
|
||||||
|
child: Text('还没有追番'),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
@ -184,7 +216,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
crossAxisSpacing: StyleString.cardSpace,
|
crossAxisSpacing: StyleString.cardSpace,
|
||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
mainAxisExtent: Get.size.width / 3 / 0.65 + 30,
|
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
||||||
|
32 * MediaQuery.of(context).textScaleFactor,
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class BangumiPanel extends StatefulWidget {
|
class BangumiPanel extends StatefulWidget {
|
||||||
final List<EpisodeItem> pages;
|
final List<EpisodeItem> pages;
|
||||||
@ -22,82 +24,119 @@ class BangumiPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _BangumiPanelState extends State<BangumiPanel> {
|
class _BangumiPanelState extends State<BangumiPanel> {
|
||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
|
final ScrollController listViewScrollCtr = ScrollController();
|
||||||
|
final ScrollController listViewScrollCtr_2 = ScrollController();
|
||||||
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
|
dynamic userInfo;
|
||||||
|
// 默认未开通
|
||||||
|
int vipStatus = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
||||||
|
scrollToIndex();
|
||||||
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
if (userInfo != null) {
|
||||||
|
vipStatus = userInfo.vipStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
listViewScrollCtr.dispose();
|
||||||
|
listViewScrollCtr_2.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void showBangumiPanel() {
|
void showBangumiPanel() {
|
||||||
showBottomSheet(
|
showBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => Container(
|
builder: (BuildContext context) {
|
||||||
height: widget.sheetHeight,
|
return StatefulBuilder(
|
||||||
color: Theme.of(context).colorScheme.background,
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
child: Column(
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
children: [
|
await Future.delayed(const Duration(milliseconds: 200));
|
||||||
AppBar(
|
listViewScrollCtr_2.animateTo(currentIndex * 56,
|
||||||
toolbarHeight: 45,
|
duration: const Duration(milliseconds: 500),
|
||||||
automaticallyImplyLeading: false,
|
curve: Curves.easeInOut);
|
||||||
title: Row(
|
});
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// 在这里使用 setState 更新状态
|
||||||
|
return Container(
|
||||||
|
height: widget.sheetHeight,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
AppBar(
|
||||||
'合集(${widget.pages.length})',
|
toolbarHeight: 45,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
automaticallyImplyLeading: false,
|
||||||
|
title: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'合集(${widget.pages.length})',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
titleSpacing: 10,
|
||||||
),
|
),
|
||||||
IconButton(
|
Expanded(
|
||||||
icon: const Icon(Icons.close),
|
child: Material(
|
||||||
onPressed: () => Navigator.pop(context),
|
child: ListView.builder(
|
||||||
|
controller: listViewScrollCtr_2,
|
||||||
|
itemCount: widget.pages.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return 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,
|
||||||
|
)
|
||||||
|
: 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(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
titleSpacing: 10,
|
);
|
||||||
),
|
},
|
||||||
Expanded(
|
);
|
||||||
child: Material(
|
},
|
||||||
child: ListView.builder(
|
|
||||||
itemCount: widget.pages.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return ListTile(
|
|
||||||
onTap: () => changeFucCall(widget.pages[index], index),
|
|
||||||
dense: false,
|
|
||||||
leading: index == currentIndex
|
|
||||||
? Image.asset(
|
|
||||||
'assets/images/live.gif',
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
height: 12,
|
|
||||||
)
|
|
||||||
: 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(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
if (item.badge != null) {
|
if (item.badge != null && vipStatus != 1) {
|
||||||
SmartDialog.showToast('需要大会员');
|
SmartDialog.showToast('需要大会员');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -108,6 +147,15 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
);
|
);
|
||||||
currentIndex = i;
|
currentIndex = i;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
scrollToIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrollToIndex() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// 在回调函数中获取更新后的状态
|
||||||
|
listViewScrollCtr.animateTo(currentIndex * 150,
|
||||||
|
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -150,6 +198,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 60,
|
height: 60,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
controller: listViewScrollCtr,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: widget.pages.length,
|
itemCount: widget.pages.length,
|
||||||
itemExtent: 150,
|
itemExtent: 150,
|
||||||
@ -222,87 +271,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
);
|
);
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
// SingleChildScrollView(
|
|
||||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
|
||||||
// scrollDirection: Axis.horizontal,
|
|
||||||
// child: ConstrainedBox(
|
|
||||||
// constraints: BoxConstraints(
|
|
||||||
// minWidth: MediaQuery.of(context).size.width,
|
|
||||||
// ),
|
|
||||||
// child: Row(
|
|
||||||
// children: [
|
|
||||||
// for (int i = 0; i < widget.pages.length; i++) ...[
|
|
||||||
// Container(
|
|
||||||
// width: 150,
|
|
||||||
// margin: const EdgeInsets.only(right: 10),
|
|
||||||
// child: Material(
|
|
||||||
// color: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
// borderRadius: BorderRadius.circular(6),
|
|
||||||
// clipBehavior: Clip.hardEdge,
|
|
||||||
// child: InkWell(
|
|
||||||
// onTap: () => changeFucCall(widget.pages[i], i),
|
|
||||||
// child: Padding(
|
|
||||||
// padding: const EdgeInsets.symmetric(
|
|
||||||
// vertical: 8, horizontal: 10),
|
|
||||||
// child: Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
// children: [
|
|
||||||
// Row(
|
|
||||||
// children: [
|
|
||||||
// if (i == currentIndex) ...[
|
|
||||||
// Image.asset(
|
|
||||||
// 'assets/images/live.gif',
|
|
||||||
// color:
|
|
||||||
// Theme.of(context).colorScheme.primary,
|
|
||||||
// height: 12,
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 6)
|
|
||||||
// ],
|
|
||||||
// Text(
|
|
||||||
// '第${i + 1}话',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 13,
|
|
||||||
// color: i == currentIndex
|
|
||||||
// ? Theme.of(context)
|
|
||||||
// .colorScheme
|
|
||||||
// .primary
|
|
||||||
// : Theme.of(context)
|
|
||||||
// .colorScheme
|
|
||||||
// .onSurface),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(width: 2),
|
|
||||||
// if (widget.pages[i].badge != null) ...[
|
|
||||||
// Image.asset(
|
|
||||||
// 'assets/images/big-vip.png',
|
|
||||||
// height: 16,
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 3),
|
|
||||||
// Text(
|
|
||||||
// widget.pages[i].longTitle!,
|
|
||||||
// maxLines: 1,
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: 13,
|
|
||||||
// color: i == currentIndex
|
|
||||||
// ? Theme.of(context).colorScheme.primary
|
|
||||||
// : Theme.of(context)
|
|
||||||
// .colorScheme
|
|
||||||
// .onSurface),
|
|
||||||
// overflow: TextOverflow.ellipsis,
|
|
||||||
// )
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ]
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// )
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,9 +29,6 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: StyleString.mdRadius,
|
|
||||||
),
|
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
// onLongPress: () {
|
// onLongPress: () {
|
||||||
@ -65,7 +62,7 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -149,7 +146,6 @@ class BangumiContent extends StatelessWidget {
|
|||||||
bangumiItem.title,
|
bangumiItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
@ -158,6 +154,7 @@ class BangumiContent extends StatelessWidget {
|
|||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
if (bangumiItem.indexShow != null)
|
if (bangumiItem.indexShow != null)
|
||||||
Text(
|
Text(
|
||||||
bangumiItem.indexShow,
|
bangumiItem.indexShow,
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -46,6 +47,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
List<int> blackMidsList =
|
List<int> blackMidsList =
|
||||||
_blackListController.blackList.map<int>((e) => e.mid!).toList();
|
_blackListController.blackList.map<int>((e) => e.mid!).toList();
|
||||||
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||||
|
scrollController.removeListener(() {});
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'黑名单管理 (${_blackListController.blackList.length} / 5000)',
|
'黑名单管理 - ${_blackListController.total.value}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -103,10 +105,11 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
dense: true,
|
dense: true,
|
||||||
// trailing: TextButton(
|
trailing: TextButton(
|
||||||
// onPressed: () {},
|
onPressed: () => _blackListController
|
||||||
// child: const Text('移除'),
|
.removeBlack(list[index].mid),
|
||||||
// ),
|
child: const Text('移除'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -135,6 +138,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
class BlackListController extends GetxController {
|
class BlackListController extends GetxController {
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int pageSize = 50;
|
int pageSize = 50;
|
||||||
|
RxInt total = 0.obs;
|
||||||
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||||
|
|
||||||
Future queryBlacklist({type = 'init'}) async {
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
@ -145,6 +149,7 @@ class BlackListController extends GetxController {
|
|||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
blackList.value = result['data'].list;
|
blackList.value = result['data'].list;
|
||||||
|
total.value = result['data'].total;
|
||||||
} else {
|
} else {
|
||||||
blackList.addAll(result['data'].list);
|
blackList.addAll(result['data'].list);
|
||||||
}
|
}
|
||||||
@ -153,4 +158,13 @@ class BlackListController extends GetxController {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future removeBlack(mid) async {
|
||||||
|
var result = await BlackHttp.removeBlack(fid: mid);
|
||||||
|
if (result['status']) {
|
||||||
|
blackList.removeWhere((e) => e.mid == mid);
|
||||||
|
total.value = total.value - 1;
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
lib/pages/danmaku/controller.dart
Normal file
64
lib/pages/danmaku/controller.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'package:pilipala/http/danmaku.dart';
|
||||||
|
import 'package:pilipala/models/danmaku/dm.pb.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
|
||||||
|
class PlDanmakuController {
|
||||||
|
PlDanmakuController(this.cid, this.playerController);
|
||||||
|
final int cid;
|
||||||
|
final PlPlayerController playerController;
|
||||||
|
late Duration videoDuration;
|
||||||
|
// 按 6min 分段
|
||||||
|
int segCount = 0;
|
||||||
|
List<DmSegMobileReply> dmSegList = [];
|
||||||
|
int currentSegIndex = 0;
|
||||||
|
int currentDmIndex = 0;
|
||||||
|
|
||||||
|
void calcSegment() {
|
||||||
|
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<DmSegMobileReply>> queryDanmaku() async {
|
||||||
|
dmSegList.clear();
|
||||||
|
for (int segIndex = 1; segIndex <= segCount; segIndex++) {
|
||||||
|
DmSegMobileReply result =
|
||||||
|
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segIndex);
|
||||||
|
if (result.elems.isNotEmpty) {
|
||||||
|
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
|
||||||
|
dmSegList.add(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dmSegList.isNotEmpty) {
|
||||||
|
findClosestPositionIndex(playerController.position.value.inMilliseconds);
|
||||||
|
}
|
||||||
|
return dmSegList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 查询当前最接近的弹幕
|
||||||
|
void findClosestPositionIndex(int position) {
|
||||||
|
int segIndex = (position / (6 * 60 * 1000)).ceil() - 1;
|
||||||
|
if (segIndex < 0) segIndex = 0;
|
||||||
|
List elems = dmSegList[segIndex].elems;
|
||||||
|
|
||||||
|
if (segIndex < dmSegList.length) {
|
||||||
|
int left = 0;
|
||||||
|
int right = elems.length;
|
||||||
|
|
||||||
|
while (left < right) {
|
||||||
|
int mid = (right + left) ~/ 2;
|
||||||
|
var midPosition = elems[mid].progress;
|
||||||
|
|
||||||
|
if (midPosition >= position) {
|
||||||
|
right = mid;
|
||||||
|
} else {
|
||||||
|
left = mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentSegIndex = segIndex;
|
||||||
|
currentDmIndex = right;
|
||||||
|
} else {
|
||||||
|
currentSegIndex = segIndex;
|
||||||
|
currentDmIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/danmaku/index.dart
Normal file
4
lib/pages/danmaku/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library pldanmaku;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export 'view.dart';
|
||||||
162
lib/pages/danmaku/view.dart
Normal file
162
lib/pages/danmaku/view.dart
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||||
|
import 'package:pilipala/pages/danmaku/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/utils/danmaku.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
/// 传入播放器控制器,监听播放进度,加载对应弹幕
|
||||||
|
class PlDanmaku extends StatefulWidget {
|
||||||
|
final int cid;
|
||||||
|
final PlPlayerController playerController;
|
||||||
|
|
||||||
|
const PlDanmaku({
|
||||||
|
super.key,
|
||||||
|
required this.cid,
|
||||||
|
required this.playerController,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlDanmaku> createState() => _PlDanmakuState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlDanmakuState extends State<PlDanmaku> {
|
||||||
|
late PlPlayerController playerController;
|
||||||
|
late PlDanmakuController _plDanmakuController;
|
||||||
|
DanmakuController? _controller;
|
||||||
|
bool danmuPlayStatus = true;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
late bool enableShowDanmaku;
|
||||||
|
late List blockTypes;
|
||||||
|
late double showArea;
|
||||||
|
late double opacityVal;
|
||||||
|
late double fontSizeVal;
|
||||||
|
late double danmakuSpeedVal;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
enableShowDanmaku =
|
||||||
|
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
|
||||||
|
_plDanmakuController =
|
||||||
|
PlDanmakuController(widget.cid, widget.playerController);
|
||||||
|
if (mounted) {
|
||||||
|
playerController = widget.playerController;
|
||||||
|
_plDanmakuController.videoDuration = playerController.duration.value;
|
||||||
|
if (enableShowDanmaku || playerController.isOpenDanmu.value) {
|
||||||
|
_plDanmakuController
|
||||||
|
..calcSegment()
|
||||||
|
..queryDanmaku();
|
||||||
|
}
|
||||||
|
playerController
|
||||||
|
..addStatusLister(playerListener)
|
||||||
|
..addPositionListener(videoPositionListen);
|
||||||
|
}
|
||||||
|
playerController.isOpenDanmu.listen((p0) {
|
||||||
|
if (p0) {
|
||||||
|
if (_plDanmakuController.dmSegList.isEmpty) {
|
||||||
|
_plDanmakuController
|
||||||
|
..calcSegment()
|
||||||
|
..queryDanmaku();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
showArea = playerController.showArea;
|
||||||
|
opacityVal = playerController.opacityVal;
|
||||||
|
fontSizeVal = playerController.fontSizeVal;
|
||||||
|
danmakuSpeedVal = playerController.danmakuSpeedVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 播放器状态监听
|
||||||
|
void playerListener(PlayerStatus? status) {
|
||||||
|
if (status == PlayerStatus.paused) {
|
||||||
|
_controller!.pause();
|
||||||
|
}
|
||||||
|
if (status == PlayerStatus.playing) {
|
||||||
|
_controller!.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void videoPositionListen(Duration position) {
|
||||||
|
if (!danmuPlayStatus) {
|
||||||
|
_controller!.onResume();
|
||||||
|
danmuPlayStatus = true;
|
||||||
|
}
|
||||||
|
PlDanmakuController ctr = _plDanmakuController;
|
||||||
|
int currentPosition = position.inMilliseconds;
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
|
||||||
|
if (!playerController.isOpenDanmu.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 超出分段数返回
|
||||||
|
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctr.dmSegList.isEmpty ||
|
||||||
|
ctr.dmSegList[ctr.currentSegIndex].elems.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 超出当前分段的弹幕总数返回
|
||||||
|
if (ctr.currentDmIndex >= ctr.dmSegList[ctr.currentSegIndex].elems.length) {
|
||||||
|
ctr.currentDmIndex = 0;
|
||||||
|
ctr.currentSegIndex++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var element = ctr.dmSegList[ctr.currentSegIndex].elems[ctr.currentDmIndex];
|
||||||
|
var delta = currentPosition - element.progress;
|
||||||
|
|
||||||
|
if (delta >= 0 && delta < 200) {
|
||||||
|
// 屏蔽彩色弹幕
|
||||||
|
if (blockTypes.contains(6) ? element.color == 16777215 : true) {
|
||||||
|
_controller!.addItems([
|
||||||
|
DanmakuItem(
|
||||||
|
element.content,
|
||||||
|
color: DmUtils.decimalToColor(element.color),
|
||||||
|
time: element.progress,
|
||||||
|
type: DmUtils.getPosition(element.mode),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
ctr.currentDmIndex++;
|
||||||
|
} else {
|
||||||
|
if (!playerController.isOpenDanmu.value) {
|
||||||
|
_controller!.pause();
|
||||||
|
danmuPlayStatus = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctr.findClosestPositionIndex(position.inMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
playerController.removePositionListener(videoPositionListen);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(
|
||||||
|
() => AnimatedOpacity(
|
||||||
|
opacity: playerController.isOpenDanmu.value ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 100),
|
||||||
|
child: DanmakuView(
|
||||||
|
createdController: (DanmakuController e) async {
|
||||||
|
widget.playerController.danmakuController = _controller = e;
|
||||||
|
},
|
||||||
|
option: DanmakuOption(
|
||||||
|
fontSize: 15 * fontSizeVal,
|
||||||
|
area: showArea,
|
||||||
|
opacity: opacityVal,
|
||||||
|
duration: danmakuSpeedVal * widget.playerController.playbackSpeed,
|
||||||
|
),
|
||||||
|
statusChanged: (isPlaying) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,13 +3,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/dynamics.dart';
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/models/dynamics/up.dart';
|
import 'package:pilipala/models/dynamics/up.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class DynamicsController extends GetxController {
|
class DynamicsController extends GetxController {
|
||||||
int page = 1;
|
int page = 1;
|
||||||
@ -45,19 +51,47 @@ class DynamicsController extends GetxController {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
RxInt initialValue = 1.obs;
|
RxInt initialValue = 0.obs;
|
||||||
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
|
RxBool userLogin = false.obs;
|
||||||
|
var userInfo;
|
||||||
|
RxBool isLoadingDynamic = false.obs;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
userLogin.value = userInfo != null;
|
||||||
|
super.onInit();
|
||||||
|
initialValue.value =
|
||||||
|
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0);
|
||||||
|
dynamicsType = DynamicsType.values[initialValue.value].obs;
|
||||||
|
}
|
||||||
|
|
||||||
Future queryFollowDynamic({type = 'init'}) async {
|
Future queryFollowDynamic({type = 'init'}) async {
|
||||||
|
if (!userLogin.value) {
|
||||||
|
return {'status': false, 'msg': '账号未登录'};
|
||||||
|
}
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
dynamicsList.clear();
|
dynamicsList.clear();
|
||||||
}
|
}
|
||||||
|
// 下拉刷新数据渲染时会触发onLoad
|
||||||
|
if (type == 'onLoad' && page == 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoadingDynamic.value = true;
|
||||||
var res = await DynamicsHttp.followDynamic(
|
var res = await DynamicsHttp.followDynamic(
|
||||||
page: type == 'init' ? 1 : page,
|
page: type == 'init' ? 1 : page,
|
||||||
type: dynamicsType.value.values,
|
type: dynamicsType.value.values,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
mid: mid.value,
|
mid: mid.value,
|
||||||
);
|
);
|
||||||
|
isLoadingDynamic.value = false;
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
|
if (type == 'onLoad' && res['data'].items.isEmpty) {
|
||||||
|
SmartDialog.showToast('没有更多了');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
dynamicsList.value = res['data'].items;
|
dynamicsList.value = res['data'].items;
|
||||||
} else {
|
} else {
|
||||||
@ -70,7 +104,7 @@ class DynamicsController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSelectType(value) async {
|
onSelectType(value) async {
|
||||||
dynamicsType.value = filterTypeList[value - 1]['value'];
|
dynamicsType.value = filterTypeList[value]['value'];
|
||||||
dynamicsList.value = [DynamicItemModel()];
|
dynamicsList.value = [DynamicItemModel()];
|
||||||
page = 1;
|
page = 1;
|
||||||
initialValue.value = value;
|
initialValue.value = value;
|
||||||
@ -80,16 +114,21 @@ class DynamicsController extends GetxController {
|
|||||||
|
|
||||||
pushDetail(item, floor, {action = 'all'}) async {
|
pushDetail(item, floor, {action = 'all'}) async {
|
||||||
feedBack();
|
feedBack();
|
||||||
|
|
||||||
|
/// 点击评论action 直接查看评论
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor, 'action': action});
|
arguments: {'item': item, 'floor': floor, 'action': action});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch (item!.type) {
|
switch (item!.type) {
|
||||||
|
/// 转发的动态
|
||||||
case 'DYNAMIC_TYPE_FORWARD':
|
case 'DYNAMIC_TYPE_FORWARD':
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 图文动态查看
|
||||||
case 'DYNAMIC_TYPE_DRAW':
|
case 'DYNAMIC_TYPE_DRAW':
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
arguments: {'item': item, 'floor': floor});
|
arguments: {'item': item, 'floor': floor});
|
||||||
@ -105,6 +144,8 @@ class DynamicsController extends GetxController {
|
|||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast(err.toString());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 专栏文章查看
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
String title = item.modules.moduleDynamic.major.opus.title;
|
String title = item.modules.moduleDynamic.major.opus.title;
|
||||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
@ -115,7 +156,10 @@ class DynamicsController extends GetxController {
|
|||||||
break;
|
break;
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
print('番剧');
|
print('番剧');
|
||||||
|
SmartDialog.showToast('暂未支持的类型,请联系开发者');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
/// 纯文字动态查看
|
||||||
case 'DYNAMIC_TYPE_WORD':
|
case 'DYNAMIC_TYPE_WORD':
|
||||||
print('纯文本');
|
print('纯文本');
|
||||||
Get.toNamed('/dynamicDetail',
|
Get.toNamed('/dynamicDetail',
|
||||||
@ -139,16 +183,61 @@ class DynamicsController extends GetxController {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/// TODO
|
/// 合集查看
|
||||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||||
print('合集');
|
DynamicArchiveModel ugcSeason =
|
||||||
|
item.modules.moduleDynamic.major.ugcSeason;
|
||||||
|
int aid = ugcSeason.aid!;
|
||||||
|
String bvid = IdUtils.av2bv(aid);
|
||||||
|
String cover = ugcSeason.cover!;
|
||||||
|
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: {'pic': cover, 'heroTag': bvid});
|
||||||
|
break;
|
||||||
|
|
||||||
|
/// 番剧查看
|
||||||
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
|
print('DYNAMIC_TYPE_PGC_UNION 番剧');
|
||||||
|
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
||||||
|
if (pgc.epid != null) {
|
||||||
|
SmartDialog.showLoading(msg: '获取中...');
|
||||||
|
var res = await SearchHttp.bangumiInfo(epId: pgc.epid);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res['status']) {
|
||||||
|
EpisodeItem episode = res['data'].episodes.first;
|
||||||
|
String bvid = episode.bvid!;
|
||||||
|
int cid = episode.cid!;
|
||||||
|
String pic = episode.cover!;
|
||||||
|
String heroTag = Utils.makeHeroTag(cid);
|
||||||
|
Get.toNamed(
|
||||||
|
'/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}',
|
||||||
|
arguments: {
|
||||||
|
'pic': pic,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
// 'videoType': SearchType.media_bangumi,
|
||||||
|
'bangumiItem': res['data'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryFollowUp() async {
|
Future queryFollowUp({type = 'init'}) async {
|
||||||
|
if (!userLogin.value) {
|
||||||
|
return {'status': false, 'msg': '账号未登录'};
|
||||||
|
}
|
||||||
|
if (type == 'init') {
|
||||||
|
upData.value.upList = [];
|
||||||
|
upData.value.liveUsers = LiveUsers();
|
||||||
|
}
|
||||||
var res = await DynamicsHttp.followUp();
|
var res = await DynamicsHttp.followUp();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
upData.value = res['data'];
|
upData.value = res['data'];
|
||||||
|
if (upData.value.upList!.isEmpty) {
|
||||||
|
mid.value = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -162,7 +251,8 @@ class DynamicsController extends GetxController {
|
|||||||
|
|
||||||
onRefresh() async {
|
onRefresh() async {
|
||||||
page = 1;
|
page = 1;
|
||||||
queryFollowUp();
|
print('onRefresh');
|
||||||
|
await queryFollowUp();
|
||||||
await queryFollowDynamic();
|
await queryFollowDynamic();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,8 +271,8 @@ class DynamicsController extends GetxController {
|
|||||||
void resetSearch() {
|
void resetSearch() {
|
||||||
mid.value = -1;
|
mid.value = -1;
|
||||||
dynamicsType.value = DynamicsType.values[0];
|
dynamicsType.value = DynamicsType.values[0];
|
||||||
initialValue.value = 1;
|
initialValue.value = 0;
|
||||||
SmartDialog.showToast('还原默认加载', alignment: Alignment.topCenter);
|
SmartDialog.showToast('还原默认加载');
|
||||||
dynamicsList.value = [DynamicItemModel()];
|
dynamicsList.value = [DynamicItemModel()];
|
||||||
queryFollowDynamic();
|
queryFollowDynamic();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
import 'package:pilipala/models/common/reply_sort_type.dart';
|
import 'package:pilipala/models/common/reply_sort_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class DynamicDetailController extends GetxController {
|
class DynamicDetailController extends GetxController {
|
||||||
DynamicDetailController(this.oid, this.type);
|
DynamicDetailController(this.oid, this.type);
|
||||||
@ -16,9 +18,10 @@ class DynamicDetailController extends GetxController {
|
|||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
RxInt acount = 0.obs;
|
RxInt acount = 0.obs;
|
||||||
|
|
||||||
ReplySortType sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -29,6 +32,11 @@ class DynamicDetailController extends GetxController {
|
|||||||
acount.value =
|
acount.value =
|
||||||
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
||||||
}
|
}
|
||||||
|
int deaultReplySortIndex =
|
||||||
|
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
|
||||||
|
_sortType = ReplySortType.values[deaultReplySortIndex];
|
||||||
|
sortTypeTitle.value = _sortType.titles;
|
||||||
|
sortTypeLabel.value = _sortType.labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryReplyList({reqType = 'init'}) async {
|
Future queryReplyList({reqType = 'init'}) async {
|
||||||
@ -39,7 +47,7 @@ class DynamicDetailController extends GetxController {
|
|||||||
oid: oid!,
|
oid: oid!,
|
||||||
pageNum: currentPage + 1,
|
pageNum: currentPage + 1,
|
||||||
type: type!,
|
type: type!,
|
||||||
sort: sortType.index,
|
sort: _sortType.index,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
List<ReplyItemModel> replies = res['data'].replies;
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
@ -76,20 +84,20 @@ class DynamicDetailController extends GetxController {
|
|||||||
// 排序搜索评论
|
// 排序搜索评论
|
||||||
queryBySort() {
|
queryBySort() {
|
||||||
feedBack();
|
feedBack();
|
||||||
switch (sortType) {
|
switch (_sortType) {
|
||||||
case ReplySortType.time:
|
case ReplySortType.time:
|
||||||
sortType = ReplySortType.like;
|
_sortType = ReplySortType.like;
|
||||||
break;
|
break;
|
||||||
case ReplySortType.like:
|
case ReplySortType.like:
|
||||||
sortType = ReplySortType.reply;
|
_sortType = ReplySortType.reply;
|
||||||
break;
|
break;
|
||||||
case ReplySortType.reply:
|
case ReplySortType.reply:
|
||||||
sortType = ReplySortType.time;
|
_sortType = ReplySortType.time;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
sortTypeTitle.value = sortType.titles;
|
sortTypeTitle.value = _sortType.titles;
|
||||||
sortTypeLabel.value = sortType.labels;
|
sortTypeLabel.value = _sortType.labels;
|
||||||
replyList.clear();
|
replyList.clear();
|
||||||
queryReplyList(reqType: 'init');
|
queryReplyList(reqType: 'init');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
@ -37,13 +38,25 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
// floor 1原创 2转发
|
// floor 1原创 2转发
|
||||||
if (Get.arguments['floor'] == 1) {
|
if (Get.arguments['floor'] == 1) {
|
||||||
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
||||||
|
print(oid);
|
||||||
} else {
|
} else {
|
||||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
try {
|
||||||
|
String type = Get.arguments['item'].modules.moduleDynamic.major.type;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
if (type == 'MAJOR_TYPE_OPUS') {
|
||||||
|
} else {
|
||||||
|
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
type = Get.arguments['item'].basic!['comment_type'];
|
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
||||||
|
type = (commentType == 0) ? 11 : commentType;
|
||||||
|
|
||||||
action =
|
action =
|
||||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
_dynamicDetailController =
|
||||||
|
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
||||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
||||||
titleStreamC = StreamController<bool>();
|
titleStreamC = StreamController<bool>();
|
||||||
scrollController.addListener(_listen);
|
scrollController.addListener(_listen);
|
||||||
@ -56,10 +69,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
void _listen() async {
|
void _listen() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 300) {
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
if (!_dynamicDetailController!.isLoadingMore) {
|
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
||||||
_dynamicDetailController!.isLoadingMore = true;
|
_dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
||||||
await _dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollController.offset > 55 && !_visibleTitle) {
|
if (scrollController.offset > 55 && !_visibleTitle) {
|
||||||
@ -95,6 +107,12 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -236,6 +254,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
replyReply: (replyItem) =>
|
replyReply: (replyItem) =>
|
||||||
replyReply(replyItem),
|
replyReply(replyItem),
|
||||||
replyType: ReplyType.values[type],
|
replyType: ReplyType.values[type],
|
||||||
|
addReply: (replyItem) {
|
||||||
|
_dynamicDetailController!
|
||||||
|
.replyList[index].replies!
|
||||||
|
.add(replyItem);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
@ -28,8 +30,8 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
late Future _futureBuilderFutureUp;
|
late Future _futureBuilderFutureUp;
|
||||||
bool _isLoadingMore = false;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
Box user = GStrorage.user;
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -39,18 +41,17 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||||
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||||
ScrollController scrollController = _dynamicsController.scrollController;
|
scrollController = _dynamicsController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_isLoadingMore) {
|
EasyThrottle.throttle(
|
||||||
_isLoadingMore = true;
|
'queryFollowDynamic', const Duration(seconds: 1), () {
|
||||||
await _dynamicsController.queryFollowDynamic(type: 'onLoad');
|
_dynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||||
_isLoadingMore = false;
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final ScrollDirection direction =
|
final ScrollDirection direction =
|
||||||
@ -62,6 +63,21 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_dynamicsController.userLogin.listen((status) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||||
|
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -107,73 +123,82 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Obx(() => Visibility(
|
Obx(
|
||||||
visible: _dynamicsController.mid.value == -1,
|
() => _dynamicsController.userLogin.value
|
||||||
child: CustomSlidingSegmentedControl<int>(
|
? Visibility(
|
||||||
initialValue: _dynamicsController.initialValue.value,
|
visible: _dynamicsController.mid.value == -1,
|
||||||
children: {
|
child: CustomSlidingSegmentedControl<int>(
|
||||||
1: Text(
|
initialValue:
|
||||||
'全部',
|
_dynamicsController.initialValue.value,
|
||||||
style: TextStyle(
|
children: {
|
||||||
fontSize: Theme.of(context)
|
0: Text(
|
||||||
.textTheme
|
'全部',
|
||||||
.labelMedium!
|
style: TextStyle(
|
||||||
.fontSize),
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize),
|
||||||
|
),
|
||||||
|
1: Text('投稿',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize)),
|
||||||
|
2: Text('番剧',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize)),
|
||||||
|
3: Text('专栏',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize)),
|
||||||
|
},
|
||||||
|
padding: 13.0,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.surfaceVariant
|
||||||
|
.withOpacity(0.7),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
thumbDecoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
onValueChanged: (v) {
|
||||||
|
feedBack();
|
||||||
|
_dynamicsController.onSelectType(v);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
2: Text('投稿',
|
)
|
||||||
style: TextStyle(
|
: Text('动态',
|
||||||
fontSize: Theme.of(context)
|
style: Theme.of(context).textTheme.titleMedium),
|
||||||
.textTheme
|
)
|
||||||
.labelMedium!
|
|
||||||
.fontSize)),
|
|
||||||
3: Text('番剧',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize)),
|
|
||||||
// 4: Text(
|
|
||||||
// '专栏',
|
|
||||||
// style: TextStyle(
|
|
||||||
// fontSize: Theme.of(context)
|
|
||||||
// .textTheme
|
|
||||||
// .labelMedium!
|
|
||||||
// .fontSize),
|
|
||||||
// ),
|
|
||||||
},
|
|
||||||
padding: 13.0,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.surfaceVariant
|
|
||||||
.withOpacity(0.7),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
thumbDecoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
onValueChanged: (v) {
|
|
||||||
feedBack();
|
|
||||||
_dynamicsController.onSelectType(v);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
// Obx(
|
||||||
right: 4,
|
// () => Visibility(
|
||||||
top: 0,
|
// visible: _dynamicsController.userLogin.value,
|
||||||
bottom: 0,
|
// child: Positioned(
|
||||||
child: IconButton(
|
// right: 4,
|
||||||
padding: EdgeInsets.zero,
|
// top: 0,
|
||||||
onPressed: () =>
|
// bottom: 0,
|
||||||
{feedBack(), _dynamicsController.resetSearch()},
|
// child: IconButton(
|
||||||
icon: const Icon(Icons.history, size: 21),
|
// padding: EdgeInsets.zero,
|
||||||
),
|
// onPressed: () =>
|
||||||
),
|
// {feedBack(), _dynamicsController.resetSearch()},
|
||||||
|
// icon: const Icon(Icons.history, size: 21),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -187,6 +212,9 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
future: _futureBuilderFutureUp,
|
future: _futureBuilderFutureUp,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||||
@ -207,24 +235,44 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<DynamicItemModel> list =
|
List<DynamicItemModel> list =
|
||||||
_dynamicsController.dynamicsList;
|
_dynamicsController.dynamicsList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => list.isEmpty
|
() {
|
||||||
? skeleton()
|
if (list.isEmpty) {
|
||||||
: SliverList(
|
if (_dynamicsController.isLoadingDynamic.value) {
|
||||||
delegate:
|
return skeleton();
|
||||||
SliverChildBuilderDelegate((context, index) {
|
} else {
|
||||||
|
return const NoData();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
return DynamicPanel(item: list[index]);
|
return DynamicPanel(item: list[index]);
|
||||||
}, childCount: list.length),
|
},
|
||||||
|
childCount: list.length,
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return HttpError(
|
return HttpError(
|
||||||
errMsg: data['msg'],
|
errMsg: data['msg'],
|
||||||
fn: () => _dynamicsController.onRefresh(),
|
fn: () {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture =
|
||||||
|
_dynamicsController.queryFollowDynamic();
|
||||||
|
_futureBuilderFutureUp =
|
||||||
|
_dynamicsController.queryFollowUp();
|
||||||
|
});
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -233,6 +281,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SliverToBoxAdapter(child: SizedBox(height: 40))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class _ActionPanelState extends State<ActionPanel> {
|
|||||||
String dynamicId = item.idStr!;
|
String dynamicId = item.idStr!;
|
||||||
// 1 已点赞 2 不喜欢 0 未操作
|
// 1 已点赞 2 不喜欢 0 未操作
|
||||||
Like like = item.modules.moduleStat.like;
|
Like like = item.modules.moduleStat.like;
|
||||||
int count = int.parse(like.count!);
|
int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0');
|
||||||
bool status = like.status!;
|
bool status = like.status!;
|
||||||
int up = status ? 2 : 1;
|
int up = status ? 2 : 1;
|
||||||
var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up);
|
var res = await DynamicsHttp.likeDynamic(dynamicId: dynamicId, up: up);
|
||||||
@ -47,7 +47,11 @@ class _ActionPanelState extends State<ActionPanel> {
|
|||||||
item.modules.moduleStat.like.count = (count + 1).toString();
|
item.modules.moduleStat.like.count = (count + 1).toString();
|
||||||
item.modules.moduleStat.like.status = true;
|
item.modules.moduleStat.like.status = true;
|
||||||
} else {
|
} else {
|
||||||
item.modules.moduleStat.like.count = (count - 1).toString();
|
if (count == 1) {
|
||||||
|
item.modules.moduleStat.like.count = '点赞';
|
||||||
|
} else {
|
||||||
|
item.modules.moduleStat.like.count = (count - 1).toString();
|
||||||
|
}
|
||||||
item.modules.moduleStat.like.status = false;
|
item.modules.moduleStat.like.status = false;
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
@ -63,54 +67,63 @@ class _ActionPanelState extends State<ActionPanel> {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
TextButton.icon(
|
Expanded(
|
||||||
onPressed: () {},
|
flex: 1,
|
||||||
icon: const Icon(
|
child: TextButton.icon(
|
||||||
FontAwesomeIcons.shareFromSquare,
|
onPressed: () {},
|
||||||
size: 16,
|
icon: const Icon(
|
||||||
|
FontAwesomeIcons.shareFromSquare,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
label: Text(stat.forward!.count ?? '转发'),
|
||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
label: Text(stat.forward!.count ?? '转发'),
|
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
Expanded(
|
||||||
onPressed: () =>
|
flex: 1,
|
||||||
_dynamicsController.pushDetail(widget.item, 1, action: 'comment'),
|
child: TextButton.icon(
|
||||||
icon: const Icon(
|
onPressed: () => _dynamicsController.pushDetail(widget.item, 1,
|
||||||
FontAwesomeIcons.comment,
|
action: 'comment'),
|
||||||
size: 16,
|
icon: const Icon(
|
||||||
|
FontAwesomeIcons.comment,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
label: Text(stat.comment!.count ?? '评论'),
|
||||||
),
|
),
|
||||||
style: TextButton.styleFrom(
|
|
||||||
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
label: Text(stat.comment!.count ?? '评论'),
|
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
Expanded(
|
||||||
onPressed: () => onLikeDynamic(),
|
flex: 1,
|
||||||
icon: Icon(
|
child: TextButton.icon(
|
||||||
stat.like!.status!
|
onPressed: () => onLikeDynamic(),
|
||||||
? FontAwesomeIcons.solidThumbsUp
|
icon: Icon(
|
||||||
: FontAwesomeIcons.thumbsUp,
|
stat.like!.status!
|
||||||
size: 16,
|
? FontAwesomeIcons.solidThumbsUp
|
||||||
color: stat.like!.status! ? primary : color,
|
: FontAwesomeIcons.thumbsUp,
|
||||||
),
|
size: 16,
|
||||||
style: TextButton.styleFrom(
|
color: stat.like!.status! ? primary : color,
|
||||||
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
),
|
||||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
style: TextButton.styleFrom(
|
||||||
),
|
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||||
label: AnimatedSwitcher(
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
duration: const Duration(milliseconds: 400),
|
),
|
||||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
label: AnimatedSwitcher(
|
||||||
return ScaleTransition(scale: animation, child: child);
|
duration: const Duration(milliseconds: 400),
|
||||||
},
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
child: Text(
|
return ScaleTransition(scale: animation, child: child);
|
||||||
stat.like!.count ?? '点赞',
|
},
|
||||||
key: ValueKey<String>(stat.like!.count ?? '点赞'),
|
child: Text(
|
||||||
style: TextStyle(
|
stat.like!.count ?? '点赞',
|
||||||
color: stat.like!.status! ? primary : color,
|
key: ValueKey<String>(stat.like!.count ?? '点赞'),
|
||||||
|
style: TextStyle(
|
||||||
|
color: stat.like!.status! ? primary : color,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -60,43 +60,47 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
case 'ADDITIONAL_TYPE_RESERVE':
|
case 'ADDITIONAL_TYPE_RESERVE':
|
||||||
return Padding(
|
return dynamicProperty[type].state != -1
|
||||||
padding: const EdgeInsets.only(top: 8),
|
? Padding(
|
||||||
child: InkWell(
|
padding: const EdgeInsets.only(top: 8),
|
||||||
onTap: () {},
|
child: InkWell(
|
||||||
child: Container(
|
onTap: () {},
|
||||||
width: double.infinity,
|
child: Container(
|
||||||
padding:
|
width: double.infinity,
|
||||||
const EdgeInsets.only(left: 12, top: 10, right: 12, bottom: 10),
|
padding: const EdgeInsets.only(
|
||||||
color: bgColor,
|
left: 12, top: 10, right: 12, bottom: 10),
|
||||||
child: Column(
|
color: bgColor,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
|
||||||
dynamicProperty[type].title,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 1),
|
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: dynamicProperty[type].desc1['text']),
|
Text(
|
||||||
const TextSpan(text: ' '),
|
dynamicProperty[type].title,
|
||||||
TextSpan(text: dynamicProperty[type].desc2['text']),
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize),
|
||||||
|
children: [
|
||||||
|
TextSpan(text: dynamicProperty[type].desc1['text']),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
TextSpan(text: dynamicProperty[type].desc2['text']),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
// TextButton(onPressed: () {}, child: Text('123'))
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
// TextButton(onPressed: () {}, child: Text('123'))
|
)
|
||||||
),
|
: const SizedBox();
|
||||||
),
|
|
||||||
);
|
|
||||||
case 'ADDITIONAL_TYPE_GOODS':
|
case 'ADDITIONAL_TYPE_GOODS':
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 6),
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
|||||||
@ -42,13 +42,17 @@ Widget articlePanel(item, context, {floor = 1}) {
|
|||||||
.copyWith(fontWeight: FontWeight.bold),
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
if (item.modules.moduleDynamic.major.opus.summary.text != 'undefined')
|
if (item.modules.moduleDynamic.major.opus.summary.text !=
|
||||||
|
'undefined') ...[
|
||||||
Text(
|
Text(
|
||||||
item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
|
item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
|
||||||
.text,
|
.text,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
|
style: const TextStyle(height: 1.55),
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
],
|
||||||
picWidget(item, context)
|
picWidget(item, context)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -36,7 +36,8 @@ Widget author(item, context) {
|
|||||||
Text(
|
Text(
|
||||||
item.modules.moduleAuthor.name,
|
item.modules.moduleAuthor.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: item.modules.moduleAuthor!.vip!['status'] > 0
|
color: item.modules.moduleAuthor!.vip != null &&
|
||||||
|
item.modules.moduleAuthor!.vip['status'] > 0
|
||||||
? const Color.fromARGB(255, 251, 100, 163)
|
? const Color.fromARGB(255, 251, 100, 163)
|
||||||
: Theme.of(context).colorScheme.onBackground,
|
: Theme.of(context).colorScheme.onBackground,
|
||||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||||
|
|||||||
@ -28,6 +28,8 @@ Widget content(item, context, source) {
|
|||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
selectionControls: MaterialTextSelectionControls(),
|
selectionControls: MaterialTextSelectionControls(),
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
|
/// fix 默认20px高度
|
||||||
|
style: const TextStyle(height: 0),
|
||||||
richNode(item, context),
|
richNode(item, context),
|
||||||
maxLines: source == 'detail' ? 999 : 3,
|
maxLines: source == 'detail' ? 999 : 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
|||||||
@ -41,7 +41,8 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
child: author(item, context),
|
child: author(item, context),
|
||||||
),
|
),
|
||||||
if (item!.modules!.moduleDynamic!.desc != null)
|
if (item!.modules!.moduleDynamic!.desc != null ||
|
||||||
|
item!.modules!.moduleDynamic!.major != null)
|
||||||
content(item, context, source),
|
content(item, context, source),
|
||||||
forWard(item, context, _dynamicsController, source),
|
forWard(item, context, _dynamicsController, source),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
|
|||||||
@ -44,19 +44,21 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
|
||||||
Padding(
|
/// fix #话题跟content重复
|
||||||
padding: floor == 2
|
// if (item.modules.moduleDynamic.topic != null) ...[
|
||||||
? EdgeInsets.zero
|
// Padding(
|
||||||
: const EdgeInsets.only(left: 12, right: 12),
|
// padding: floor == 2
|
||||||
child: GestureDetector(
|
// ? EdgeInsets.zero
|
||||||
child: Text(
|
// : const EdgeInsets.only(left: 12, right: 12),
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
// child: GestureDetector(
|
||||||
style: authorStyle,
|
// child: Text(
|
||||||
),
|
// '#${item.modules.moduleDynamic.topic.name}',
|
||||||
),
|
// style: authorStyle,
|
||||||
),
|
// ),
|
||||||
],
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
Text.rich(
|
Text.rich(
|
||||||
richNode(item, context),
|
richNode(item, context),
|
||||||
// 被转发状态(floor=2) 隐藏
|
// 被转发状态(floor=2) 隐藏
|
||||||
@ -71,6 +73,8 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
: const EdgeInsets.only(left: 12, right: 12),
|
: const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: picWidget(item, context),
|
child: picWidget(item, context),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/// 附加内容 商品信息、直播预约等等
|
||||||
if (item.modules.moduleDynamic.additional != null)
|
if (item.modules.moduleDynamic.additional != null)
|
||||||
addWidget(
|
addWidget(
|
||||||
item,
|
item,
|
||||||
@ -100,6 +104,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
// 直播
|
// 直播
|
||||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||||
return liveRcmdPanel(item, context, floor: floor);
|
return liveRcmdPanel(item, context, floor: floor);
|
||||||
|
// 直播
|
||||||
case 'DYNAMIC_TYPE_LIVE':
|
case 'DYNAMIC_TYPE_LIVE':
|
||||||
return livePanel(item, context, floor: floor);
|
return livePanel(item, context, floor: floor);
|
||||||
// 合集
|
// 合集
|
||||||
@ -132,7 +137,12 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(item.modules.moduleDynamic.desc.text)
|
Text.rich(
|
||||||
|
richNode(item, context),
|
||||||
|
// 被转发状态(floor=2) 隐藏
|
||||||
|
maxLines: source == 'detail' && floor != 2 ? 999 : 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: item.modules.moduleDynamic.additional != null
|
: item.modules.moduleDynamic.additional != null
|
||||||
@ -147,6 +157,7 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
return videoSeasonWidget(item, context, 'pgc', floor: floor);
|
return videoSeasonWidget(item, context, 'pgc', floor: floor);
|
||||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
return videoSeasonWidget(item, context, 'pgc', floor: floor);
|
return videoSeasonWidget(item, context, 'pgc', floor: floor);
|
||||||
|
// 直播结束
|
||||||
case 'DYNAMIC_TYPE_NONE':
|
case 'DYNAMIC_TYPE_NONE':
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@ -158,7 +169,23 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
Text(item.modules.moduleDynamic.major.none.tips)
|
Text(item.modules.moduleDynamic.major.none.tips)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
// 课堂
|
||||||
|
case 'DYNAMIC_TYPE_COURSES_SEASON':
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"课堂💪:${item.modules.moduleDynamic.major.courses['title']}",
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return const SizedBox(height: 0);
|
return const SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Text('🙏 暂未支持的类型,请联系开发者反馈 '),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user