Compare commits
152 Commits
v1.0.4.082
...
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 | |||
| cafde6cc04 | |||
| c8810b458f | |||
| 0003c057cd |
22
README.md
22
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端、手表端等
|
||||||
|
|
||||||
@ -98,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,6 +20,21 @@
|
|||||||
"android.support.customtabs.action.CustomTabsService" />
|
"android.support.customtabs.action.CustomTabsService" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
|
<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
|
<application
|
||||||
android:label="PiliPala"
|
android:label="PiliPala"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@ -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>
|
||||||
|
|||||||
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 |
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,4 +1,6 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- appscheme (1.0.4):
|
||||||
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
@ -33,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):
|
||||||
@ -45,6 +51,7 @@ 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`)
|
||||||
@ -59,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`)
|
||||||
@ -71,6 +80,8 @@ 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:
|
||||||
@ -99,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:
|
||||||
@ -111,6 +126,7 @@ 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
|
||||||
@ -127,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
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<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>
|
||||||
@ -58,5 +58,50 @@
|
|||||||
<string>https</string>
|
<string>https</string>
|
||||||
<string>http</string>
|
<string>http</string>
|
||||||
</array>
|
</array>
|
||||||
|
<!-- 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>
|
</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,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
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,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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'
|
||||||
|
|||||||
@ -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,9 +34,10 @@ 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) {
|
// onLongPressEnd: (details) {
|
||||||
// if (longPressEnd != null) {
|
// if (longPressEnd != null) {
|
||||||
@ -188,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,
|
||||||
@ -261,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))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -15,12 +15,14 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
// 视频卡片 - 垂直布局
|
// 视频卡片 - 垂直布局
|
||||||
class VideoCardV extends StatelessWidget {
|
class VideoCardV extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic 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);
|
||||||
@ -46,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,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -77,7 +79,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 1,
|
elevation: 0,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -100,17 +102,37 @@ class VideoCardV extends StatelessWidget {
|
|||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
return Hero(
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
tag: heroTag,
|
tag: heroTag,
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
src: videoItem.pic,
|
src: videoItem.pic,
|
||||||
width: maxWidth,
|
width: maxWidth,
|
||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
if (videoItem.duration != null)
|
||||||
|
if (crossAxisCount == 1) ...[
|
||||||
|
PBadge(
|
||||||
|
bottom: 10,
|
||||||
|
right: 10,
|
||||||
|
text: videoItem.duration,
|
||||||
|
)
|
||||||
|
] else ...[
|
||||||
|
PBadge(
|
||||||
|
bottom: 6,
|
||||||
|
right: 7,
|
||||||
|
size: 'small',
|
||||||
|
type: 'gray',
|
||||||
|
text: videoItem.duration,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
VideoContent(videoItem: videoItem, crossAxisCount: crossAxisCount)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -121,22 +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(9, 8, 9, 4),
|
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(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
videoItem.title,
|
videoItem.title,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
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') ...[
|
||||||
@ -167,6 +220,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
|
flex: crossAxisCount == 1 ? 0 : 1,
|
||||||
child: Text(
|
child: Text(
|
||||||
videoItem.owner.name,
|
videoItem.owner.name,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@ -177,30 +231,105 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (videoItem.goto == 'av')
|
if (crossAxisCount == 1) ...[
|
||||||
SizedBox(
|
Text(
|
||||||
width: 24,
|
' • ',
|
||||||
height: 24,
|
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)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class VideoStat extends StatelessWidget {
|
||||||
|
final dynamic videoItem;
|
||||||
|
|
||||||
|
const VideoStat({
|
||||||
|
Key? key,
|
||||||
|
required this.videoItem,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${videoItem.stat.view}观看',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' • ${videoItem.stat.danmu}弹幕',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchLater extends StatelessWidget {
|
||||||
|
final double? size;
|
||||||
|
final double? iconSize;
|
||||||
|
final Function? callFn;
|
||||||
|
|
||||||
|
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>(
|
child: PopupMenuButton<String>(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
tooltip: '稍后再看',
|
tooltip: '稍后再看',
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.more_vert_outlined,
|
Icons.more_vert_outlined,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
size: 14,
|
size: iconSize,
|
||||||
),
|
),
|
||||||
position: PopupMenuPosition.under,
|
position: PopupMenuPosition.under,
|
||||||
// constraints: const BoxConstraints(maxHeight: 35),
|
// constraints: const BoxConstraints(maxHeight: 35),
|
||||||
onSelected: (String type) {},
|
onSelected: (String type) {},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||||
<PopupMenuEntry<String>>[
|
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () async {
|
onTap: () => callFn!(),
|
||||||
int aid = videoItem.param;
|
|
||||||
var res = await UserHttp.toViewLater(
|
|
||||||
bvid: IdUtils.av2bv(aid));
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
},
|
|
||||||
value: 'pause',
|
value: 'pause',
|
||||||
height: 35,
|
height: 35,
|
||||||
child: const Row(
|
child: const Row(
|
||||||
@ -213,116 +342,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// 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))
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class VideoStat extends StatelessWidget {
|
|
||||||
final int? view;
|
|
||||||
final int? danmaku;
|
|
||||||
final int? duration;
|
|
||||||
|
|
||||||
const VideoStat(
|
|
||||||
{Key? key,
|
|
||||||
required this.view,
|
|
||||||
required this.danmaku,
|
|
||||||
required this.duration})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
height: 48,
|
|
||||||
padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: <Color>[
|
|
||||||
Colors.transparent,
|
|
||||||
Colors.black54,
|
|
||||||
],
|
|
||||||
tileMode: TileMode.mirror,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatView(
|
|
||||||
theme: 'white',
|
|
||||||
view: view,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
StatDanMu(
|
|
||||||
theme: 'white',
|
|
||||||
danmu: danmaku,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
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';
|
||||||
|
|
||||||
@ -285,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) {
|
||||||
|
try {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
'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,7 +9,6 @@ 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 {
|
||||||
@ -41,6 +40,7 @@ class Request {
|
|||||||
log("setCookie, ${e.toString()}");
|
log("setCookie, ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setOptionsHeaders(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cookie.isEmpty) {
|
if (cookie.isEmpty) {
|
||||||
@ -69,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
|
||||||
*/
|
*/
|
||||||
@ -83,29 +92,14 @@ 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 userInfoCache = GStrorage.userInfo;
|
dio = Dio(options);
|
||||||
var userInfo = userInfoCache.get('userInfoCache');
|
|
||||||
if (userInfo != null && userInfo.mid != null) {
|
|
||||||
options.headers['x-bili-mid'] = userInfo.mid.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 = Dio(options)
|
|
||||||
..httpClientAdapter = Http2Adapter(
|
|
||||||
ConnectionManager(
|
|
||||||
idleTimeout: const Duration(milliseconds: 10000),
|
|
||||||
// Ignore bad certificate
|
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
dio.interceptors.add(ApiInterceptor());
|
dio.interceptors.add(ApiInterceptor());
|
||||||
@ -217,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 localCache = GStrorage.localCache;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
try {
|
try {
|
||||||
@ -29,8 +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'];
|
||||||
localCache
|
try {
|
||||||
.put(LocalCacheKey.accessKey, {'mid': mid, 'value': accessKey});
|
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,18 +1,56 @@
|
|||||||
|
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),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取搜索建议
|
||||||
|
static Future searchSuggest({required term}) async {
|
||||||
|
var res = await Request().get(Api.serachSuggest,
|
||||||
|
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||||
|
if (res.data is String) {
|
||||||
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
|
if (resultMap['code'] == 0) {
|
||||||
|
if (resultMap['result'] is Map) {
|
||||||
|
resultMap['result']['term'] = term;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': resultMap['result'] is Map
|
||||||
|
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||||
|
: [],
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
@ -20,18 +58,6 @@ class SearchHttp {
|
|||||||
'msg': '请求错误 🙅',
|
'msg': '请求错误 🙅',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 获取搜索建议
|
|
||||||
static Future searchSuggest({required term}) async {
|
|
||||||
var res = await Request().get(Api.serachSuggest,
|
|
||||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
res.data['result']['term'] = term;
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': SearchSuggestModel.fromJson(res.data['result']),
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
@ -61,8 +87,15 @@ class SearchHttp {
|
|||||||
var res = await Request().get(Api.searchByType, data: reqData);
|
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;
|
||||||
|
try {
|
||||||
switch (searchType) {
|
switch (searchType) {
|
||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
|
for (var i in res.data['data']['result']) {
|
||||||
|
// 屏蔽推广和拉黑用户
|
||||||
|
i['available'] = !blackMidsList.contains(i['mid']);
|
||||||
|
}
|
||||||
data = SearchVideoModel.fromJson(res.data['data']);
|
data = SearchVideoModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
case SearchType.live_room:
|
case SearchType.live_room:
|
||||||
@ -74,16 +107,24 @@ class SearchHttp {
|
|||||||
case SearchType.media_bangumi:
|
case SearchType.media_bangumi:
|
||||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
|
case SearchType.article:
|
||||||
|
data = SearchArticleModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': data,
|
'data': data,
|
||||||
};
|
};
|
||||||
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
|
}
|
||||||
} 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'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,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;
|
||||||
|
if (res.data['data'] != null) {
|
||||||
|
data = FavFolderData.fromJson(res.data['data']);
|
||||||
return {'status': true, 'data': data};
|
return {'status': true, 'data': data};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': '账号未登录'};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'] ?? '账号未登录'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,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!成功移除'};
|
||||||
@ -222,4 +231,39 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
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']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,8 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class VideoHttp {
|
class VideoHttp {
|
||||||
static Box localCache = GStrorage.localCache;
|
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 {
|
||||||
@ -73,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));
|
||||||
@ -399,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']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ 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.
|
||||||
@ -25,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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -12,20 +12,20 @@ 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];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索类型为视频、专栏及相簿时
|
// 搜索类型为视频、专栏及相簿时
|
||||||
|
|||||||
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;
|
||||||
|
|||||||
@ -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'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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'];
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -47,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(
|
||||||
@ -82,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'),
|
||||||
@ -100,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('问题反馈'),
|
||||||
@ -111,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,
|
||||||
@ -123,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,
|
||||||
@ -141,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();
|
||||||
// 获取最新的版本
|
// 获取最新的版本
|
||||||
@ -153,16 +165,16 @@ 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 {
|
||||||
@ -173,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 =
|
||||||
@ -183,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
|
||||||
@ -202,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(
|
||||||
@ -214,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频道
|
||||||
@ -242,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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';
|
||||||
@ -258,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 {
|
||||||
@ -291,4 +293,31 @@ class BangumiIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,6 +113,9 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
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;
|
List list = _bangumidController.bangumiFollowList;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
|
|||||||
@ -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;
|
||||||
@ -24,12 +26,20 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
final ScrollController listViewScrollCtr = ScrollController();
|
final ScrollController listViewScrollCtr = ScrollController();
|
||||||
final ScrollController listViewScrollCtr_2 = 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();
|
scrollToIndex();
|
||||||
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
|
if (userInfo != null) {
|
||||||
|
vipStatus = userInfo.vipStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -126,7 +136,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,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'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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';
|
||||||
@ -60,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,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -104,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('移除'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -136,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 {
|
||||||
@ -146,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);
|
||||||
}
|
}
|
||||||
@ -154,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) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,6 +13,7 @@ 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/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -50,17 +51,21 @@ class DynamicsController extends GetxController {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
RxInt initialValue = 1.obs;
|
RxInt initialValue = 0.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
RxBool isLoadingDynamic = false.obs;
|
RxBool isLoadingDynamic = false.obs;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
userLogin.value = userInfo != null;
|
userLogin.value = userInfo != null;
|
||||||
super.onInit();
|
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 {
|
||||||
@ -99,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;
|
||||||
@ -109,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});
|
||||||
@ -134,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;
|
||||||
@ -144,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',
|
||||||
@ -168,10 +183,19 @@ 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;
|
break;
|
||||||
|
|
||||||
|
/// 番剧查看
|
||||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
print('DYNAMIC_TYPE_PGC_UNION 番剧');
|
print('DYNAMIC_TYPE_PGC_UNION 番剧');
|
||||||
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
|
||||||
@ -190,7 +214,7 @@ class DynamicsController extends GetxController {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -247,7 +271,7 @@ 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('还原默认加载');
|
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');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,15 +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 {
|
||||||
|
try {
|
||||||
|
String type = Get.arguments['item'].modules.moduleDynamic.major.type;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
if (type == 'MAJOR_TYPE_OPUS') {
|
||||||
} else {
|
} else {
|
||||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||||
}
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
||||||
type = (commentType == 0) ? 11 : commentType;
|
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);
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
|
|||||||
import 'package:pilipala/common/widgets/no_data.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/event_bus.dart';
|
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
late Future _futureBuilderFutureUp;
|
late Future _futureBuilderFutureUp;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
EventBus eventBus = EventBus();
|
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -66,12 +64,13 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
eventBus.on(EventName.loginEvent, (args) {
|
_dynamicsController.userLogin.listen((status) {
|
||||||
_dynamicsController.userLogin.value = args['status'];
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||||
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +131,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
initialValue:
|
initialValue:
|
||||||
_dynamicsController.initialValue.value,
|
_dynamicsController.initialValue.value,
|
||||||
children: {
|
children: {
|
||||||
1: Text(
|
0: Text(
|
||||||
'全部',
|
'全部',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
@ -140,19 +139,19 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize),
|
.fontSize),
|
||||||
),
|
),
|
||||||
2: Text('投稿',
|
1: Text('投稿',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize)),
|
.fontSize)),
|
||||||
3: Text('番剧',
|
2: Text('番剧',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize)),
|
.fontSize)),
|
||||||
4: Text('专栏',
|
3: Text('专栏',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@ -213,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));
|
||||||
@ -233,6 +235,9 @@ 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 =
|
||||||
|
|||||||
@ -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)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -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,
|
||||||
@ -133,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
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
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/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
Widget picWidget(item, context) {
|
Widget picWidget(item, context) {
|
||||||
String type = item.modules.moduleDynamic.major.type;
|
String type = item.modules.moduleDynamic.major.type;
|
||||||
List pictures = [];
|
List pictures = [];
|
||||||
if (type == 'MAJOR_TYPE_OPUS') {
|
if (type == 'MAJOR_TYPE_OPUS') {
|
||||||
pictures = item.modules.moduleDynamic.major.opus.pics;
|
/// fix 图片跟rich_node_panel重复
|
||||||
|
// pictures = item.modules.moduleDynamic.major.opus.pics;
|
||||||
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
if (type == 'MAJOR_TYPE_DRAW') {
|
if (type == 'MAJOR_TYPE_DRAW') {
|
||||||
pictures = item.modules.moduleDynamic.major.draw.items;
|
pictures = item.modules.moduleDynamic.major.draw.items;
|
||||||
}
|
}
|
||||||
int len = pictures.length;
|
int len = pictures.length;
|
||||||
List picList = [];
|
List<String> picList = [];
|
||||||
List<Widget> list = [];
|
List<Widget> list = [];
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
picList.add(pictures[i].src ?? pictures[i].url);
|
picList.add(pictures[i].src ?? pictures[i].url);
|
||||||
@ -23,11 +25,14 @@ Widget picWidget(item, context) {
|
|||||||
builder: (context, BoxConstraints box) {
|
builder: (context, BoxConstraints box) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed('/preview',
|
showDialog(
|
||||||
arguments: {'initialPage': i, 'imgList': picList});
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
// child: Hero(
|
|
||||||
// tag: pictures[i].src ?? pictures[i].url,
|
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
src: pictures[i].src ?? pictures[i].url,
|
src: pictures[i].src ?? pictures[i].url,
|
||||||
width: box.maxWidth,
|
width: box.maxWidth,
|
||||||
@ -44,27 +49,25 @@ Widget picWidget(item, context) {
|
|||||||
double maxWidth = box.maxWidth;
|
double maxWidth = box.maxWidth;
|
||||||
double aspectRatio = 1.0;
|
double aspectRatio = 1.0;
|
||||||
double origAspectRatio = 0.0;
|
double origAspectRatio = 0.0;
|
||||||
double crossCount = len == 1
|
double crossCount = 3;
|
||||||
? 1
|
|
||||||
: len < 3
|
|
||||||
? 2
|
|
||||||
: 3;
|
|
||||||
|
|
||||||
double height = 0.0;
|
double height = 0.0;
|
||||||
if (len == 1) {
|
if (len == 1) {
|
||||||
|
try {
|
||||||
origAspectRatio =
|
origAspectRatio =
|
||||||
aspectRatio = pictures.first.width / pictures.first.height;
|
aspectRatio = pictures.first.width / pictures.first.height;
|
||||||
|
} catch (_) {}
|
||||||
if (aspectRatio < 0.4) {
|
if (aspectRatio < 0.4) {
|
||||||
aspectRatio = 0.4;
|
aspectRatio = 0.4;
|
||||||
}
|
}
|
||||||
height = pictures.first.height * maxWidth / pictures.first.width;
|
|
||||||
if (origAspectRatio < 0.5 || pictures.first.width < 1920) {
|
if (origAspectRatio < 0.5 || pictures.first.width < 1920) {
|
||||||
crossCount = 2;
|
crossCount = 2;
|
||||||
height = maxWidth / 2 / aspectRatio;
|
height = maxWidth / 2 / aspectRatio;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
aspectRatio = 1;
|
aspectRatio = 1;
|
||||||
height = maxWidth / crossCount * ((len / crossCount).ceil()) + 6;
|
height =
|
||||||
|
maxWidth / crossCount * ((len + crossCount - 1) ~/ crossCount) + 6;
|
||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
|||||||
@ -1,14 +1,34 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
// 富文本
|
// 富文本
|
||||||
InlineSpan richNode(item, context) {
|
InlineSpan richNode(item, context) {
|
||||||
|
final spacer = _VerticalSpaceSpan(0.0);
|
||||||
|
try {
|
||||||
TextStyle authorStyle =
|
TextStyle authorStyle =
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||||
List<InlineSpan> spanChilds = [];
|
List<InlineSpan> spanChilds = [];
|
||||||
for (var i in item.modules.moduleDynamic.desc.richTextNodes) {
|
String contentType = 'desc';
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
|
||||||
|
dynamic richTextNodes;
|
||||||
|
if (item.modules.moduleDynamic.desc != null) {
|
||||||
|
richTextNodes = item.modules.moduleDynamic.desc.richTextNodes;
|
||||||
|
} else if (item.modules.moduleDynamic.major != null) {
|
||||||
|
contentType = 'major';
|
||||||
|
// 动态页面 richTextNodes 层级可能与主页动态层级不同
|
||||||
|
richTextNodes =
|
||||||
|
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
|
||||||
|
}
|
||||||
|
if (richTextNodes == null || richTextNodes.isEmpty) {
|
||||||
|
return spacer;
|
||||||
|
} else {
|
||||||
|
for (var i in richTextNodes) {
|
||||||
|
/// fix 渲染专栏时内容会重复
|
||||||
|
if (item.modules.moduleDynamic.major.opus.title == null &&
|
||||||
|
i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
||||||
}
|
}
|
||||||
@ -67,7 +87,11 @@ InlineSpan richNode(item, context) {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
parameters: {'url': i.origText, 'type': 'url', 'pageTitle': ''},
|
parameters: {
|
||||||
|
'url': i.origText,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': ''
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -169,7 +193,127 @@ InlineSpan richNode(item, context) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (contentType == 'major' &&
|
||||||
|
item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
|
||||||
|
// 图片可能跟其他widget重复渲染
|
||||||
|
List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
|
||||||
|
int len = pics.length;
|
||||||
|
List<String> picList = [];
|
||||||
|
|
||||||
|
if (len == 1) {
|
||||||
|
OpusPicsModel pictureItem = pics.first;
|
||||||
|
picList.add(pictureItem.url!);
|
||||||
|
spanChilds.add(const TextSpan(text: '\n'));
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pictureItem.url,
|
||||||
|
width: box.maxWidth / 2,
|
||||||
|
height: box.maxWidth *
|
||||||
|
0.5 *
|
||||||
|
pictureItem.height! /
|
||||||
|
pictureItem.width!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (len > 1) {
|
||||||
|
List<Widget> list = [];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
picList.add(pics[i].url!);
|
||||||
|
list.add(
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pics[i].url,
|
||||||
|
width: box.maxWidth,
|
||||||
|
height: box.maxWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
double maxWidth = box.maxWidth;
|
||||||
|
double crossCount = len < 3 ? 2 : 3;
|
||||||
|
double height = maxWidth /
|
||||||
|
crossCount *
|
||||||
|
(len % crossCount == 0
|
||||||
|
? len ~/ crossCount
|
||||||
|
: len ~/ crossCount + 1) +
|
||||||
|
6;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
height: height,
|
||||||
|
child: GridView.count(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
crossAxisCount: crossCount.toInt(),
|
||||||
|
mainAxisSpacing: 4.0,
|
||||||
|
crossAxisSpacing: 4.0,
|
||||||
|
childAspectRatio: 1,
|
||||||
|
children: list,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// spanChilds.add(
|
||||||
|
// WidgetSpan(
|
||||||
|
// child: NetworkImgLayer(
|
||||||
|
// src: pics.first.url,
|
||||||
|
// type: 'emote',
|
||||||
|
// width: 100,
|
||||||
|
// height: 200,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
}
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
children: spanChilds,
|
children: spanChilds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
print('❌rich_node_panel err: $err');
|
||||||
|
return spacer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VerticalSpaceSpan extends WidgetSpan {
|
||||||
|
_VerticalSpaceSpan(double height)
|
||||||
|
: super(child: SizedBox(height: height, width: double.infinity));
|
||||||
|
}
|
||||||
|
|||||||
@ -57,20 +57,21 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
],
|
],
|
||||||
// const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
/// fix #话题跟content重复
|
||||||
Padding(
|
// if (item.modules.moduleDynamic.topic != null) ...[
|
||||||
padding: floor == 2
|
// Padding(
|
||||||
? EdgeInsets.zero
|
// padding: floor == 2
|
||||||
: const EdgeInsets.only(left: 12, right: 12),
|
// ? EdgeInsets.zero
|
||||||
child: GestureDetector(
|
// : const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: Text(
|
// child: GestureDetector(
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
// child: Text(
|
||||||
style: authorStyle,
|
// '#${item.modules.moduleDynamic.topic.name}',
|
||||||
),
|
// style: authorStyle,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(height: 6),
|
// ),
|
||||||
],
|
// const SizedBox(height: 6),
|
||||||
|
// ],
|
||||||
if (floor == 2 && item.modules.moduleDynamic.desc != null) ...[
|
if (floor == 2 && item.modules.moduleDynamic.desc != null) ...[
|
||||||
Text.rich(richNode(item, context)),
|
Text.rich(richNode(item, context)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
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/http/fan.dart';
|
import 'package:pilipala/http/fan.dart';
|
||||||
@ -7,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class FansController extends GetxController {
|
class FansController extends GetxController {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
|
int ps = 20;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
RxList<FansItemModel> fansList = [FansItemModel()].obs;
|
RxList<FansItemModel> fansList = [FansItemModel()].obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String name;
|
late String name;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -26,23 +29,31 @@ class FansController extends GetxController {
|
|||||||
Future queryFans(type) async {
|
Future queryFans(type) async {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
pn = 1;
|
pn = 1;
|
||||||
|
loadingText.value == '加载中...';
|
||||||
|
}
|
||||||
|
if (loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var res = await FanHttp.fans(
|
var res = await FanHttp.fans(
|
||||||
vmid: mid,
|
vmid: mid,
|
||||||
pn: pn,
|
pn: pn,
|
||||||
ps: 20,
|
ps: ps,
|
||||||
orderType: 'attention',
|
orderType: 'attention',
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
fansList.value = res['data'].list;
|
fansList.value = res['data'].list;
|
||||||
total = res['data'].total;
|
total = res['data'].total;
|
||||||
} else if (type == 'onRefresh') {
|
|
||||||
fansList.insertAll(0, res['data'].list);
|
|
||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
fansList.addAll(res['data'].list);
|
fansList.addAll(res['data'].list);
|
||||||
}
|
}
|
||||||
|
print(total);
|
||||||
|
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
pn += 1;
|
pn += 1;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
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/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
import 'package:pilipala/models/fans/result.dart';
|
import 'package:pilipala/models/fans/result.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
@ -17,7 +19,6 @@ class _FansPageState extends State<FansPage> {
|
|||||||
final FansController _fansController = Get.put(FansController());
|
final FansController _fansController = Get.put(FansController());
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
bool _isLoadingMore = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -27,11 +28,9 @@ class _FansPageState extends State<FansPage> {
|
|||||||
() async {
|
() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_isLoadingMore) {
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
_isLoadingMore = true;
|
_fansController.queryFans('onLoad');
|
||||||
await _fansController.queryFans('onLoad');
|
});
|
||||||
_isLoadingMore = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -66,14 +65,38 @@ class _FansPageState extends State<FansPage> {
|
|||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<FansItemModel> list = _fansController.fansList;
|
List<FansItemModel> list = _fansController.fansList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => list.length == 1
|
() => list.isNotEmpty
|
||||||
? const SizedBox()
|
? ListView.builder(
|
||||||
: ListView.builder(
|
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
itemCount: list.length,
|
itemCount: list.length + 1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == list.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_fansController.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return fanItem(item: list[index]);
|
return fanItem(item: list[index]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: [NoData()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
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/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
@ -6,23 +8,45 @@ import 'package:pilipala/models/user/info.dart';
|
|||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class FavController extends GetxController {
|
class FavController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
UserInfoData? userInfo;
|
UserInfoData? userInfo;
|
||||||
|
int currentPage = 1;
|
||||||
|
int pageSize = 10;
|
||||||
|
RxBool hasMore = true.obs;
|
||||||
|
|
||||||
Future<dynamic> queryFavFolder() async {
|
Future<dynamic> queryFavFolder({type = 'init'}) async {
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo == null) {
|
if (userInfo == null) {
|
||||||
return {'status': false, 'msg': '账号未登录'};
|
return {'status': false, 'msg': '账号未登录'};
|
||||||
}
|
}
|
||||||
|
if (!hasMore.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var res = await await UserHttp.userfavFolder(
|
var res = await await UserHttp.userfavFolder(
|
||||||
pn: 1,
|
pn: currentPage,
|
||||||
ps: 10,
|
ps: pageSize,
|
||||||
mid: userInfo!.mid!,
|
mid: userInfo!.mid!,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
|
if (type == 'init') {
|
||||||
favFolderData.value = res['data'];
|
favFolderData.value = res['data'];
|
||||||
|
} else {
|
||||||
|
if (res['data'].list.isNotEmpty) {
|
||||||
|
favFolderData.value.list!.addAll(res['data'].list);
|
||||||
|
favFolderData.update((val) {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasMore.value = res['data'].hasMore;
|
||||||
|
currentPage++;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future onLoad() async {
|
||||||
|
queryFavFolder(type: 'onload');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
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/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -14,11 +15,23 @@ class FavPage extends StatefulWidget {
|
|||||||
class _FavPageState extends State<FavPage> {
|
class _FavPageState extends State<FavPage> {
|
||||||
final FavController _favController = Get.put(FavController());
|
final FavController _favController = Get.put(FavController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _favController.queryFavFolder();
|
_futureBuilderFuture = _favController.queryFavFolder();
|
||||||
|
scrollController = _favController.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_favController.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,6 +53,7 @@ class _FavPageState extends State<FavPage> {
|
|||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => ListView.builder(
|
() => ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
itemCount: _favController.favFolderData.value.list!.length,
|
itemCount: _favController.favFolderData.value.list!.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return FavItem(
|
return FavItem(
|
||||||
|
|||||||
@ -14,7 +14,9 @@ class FavDetailController extends GetxController {
|
|||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxMap favInfo = {}.obs;
|
RxMap favInfo = {}.obs;
|
||||||
RxList<FavDetailItemData> favList = [FavDetailItemData()].obs;
|
RxList favList = [].obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
int mediaCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -27,6 +29,11 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && favList.length >= mediaCount) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoadingMore = true;
|
||||||
var res = await await UserHttp.userFavFolderDetail(
|
var res = await await UserHttp.userFavFolderDetail(
|
||||||
pn: currentPage,
|
pn: currentPage,
|
||||||
ps: 20,
|
ps: 20,
|
||||||
@ -36,11 +43,16 @@ class FavDetailController extends GetxController {
|
|||||||
favInfo.value = res['data'].info;
|
favInfo.value = res['data'].info;
|
||||||
if (currentPage == 1 && type == 'init') {
|
if (currentPage == 1 && type == 'init') {
|
||||||
favList.value = res['data'].medias;
|
favList.value = res['data'].medias;
|
||||||
} else if (type == 'onload') {
|
mediaCount = res['data'].info['media_count'];
|
||||||
|
} else if (type == 'onLoad') {
|
||||||
favList.addAll(res['data'].medias);
|
favList.addAll(res['data'].medias);
|
||||||
}
|
}
|
||||||
|
if (favList.length >= mediaCount) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
currentPage += 1;
|
currentPage += 1;
|
||||||
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,21 +61,19 @@ class FavDetailController extends GetxController {
|
|||||||
aid: id, addIds: '', delIds: mediaId.toString());
|
aid: id, addIds: '', delIds: mediaId.toString());
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (result['data']['prompt']) {
|
if (result['data']['prompt']) {
|
||||||
List<FavDetailItemData> dataList = favDetailData.value.medias!;
|
List dataList = favList;
|
||||||
for (var i in dataList) {
|
for (var i in dataList) {
|
||||||
if (i.id == id) {
|
if (i.id == id) {
|
||||||
dataList.remove(i);
|
dataList.remove(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
favDetailData.value.medias = dataList;
|
|
||||||
favDetailData.refresh();
|
|
||||||
SmartDialog.showToast('取消收藏');
|
SmartDialog.showToast('取消收藏');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad() {
|
onLoad() {
|
||||||
queryUserFavFolderDetail(type: 'onload');
|
queryUserFavFolderDetail(type: 'onLoad');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
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_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
import 'package:pilipala/pages/favDetail/index.dart';
|
import 'package:pilipala/pages/favDetail/index.dart';
|
||||||
|
|
||||||
import 'widget/fav_video_card.dart';
|
import 'widget/fav_video_card.dart';
|
||||||
@ -37,10 +40,9 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
|
|
||||||
if (_controller.position.pixels >=
|
if (_controller.position.pixels >=
|
||||||
_controller.position.maxScrollExtent - 200) {
|
_controller.position.maxScrollExtent - 200) {
|
||||||
if (!_favDetailController.isLoadingMore) {
|
EasyThrottle.throttle('favDetail', const Duration(seconds: 1), () {
|
||||||
_favDetailController.isLoadingMore = true;
|
|
||||||
_favDetailController.onLoad();
|
_favDetailController.onLoad();
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -166,7 +168,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'共${_favDetailController.favInfo['media_count'] ?? '-'}条视频',
|
'共${_favDetailController.favList.length}条视频',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize:
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
@ -183,20 +185,21 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
if (_favDetailController.item!.mediaCount == 0) {
|
if (_favDetailController.item!.mediaCount == 0) {
|
||||||
return const SliverToBoxAdapter(
|
return const NoData();
|
||||||
child: SizedBox(
|
|
||||||
height: 300,
|
|
||||||
child: Center(child: Text('没有内容')),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
|
List favList = _favDetailController.favList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => SliverList(
|
() => favList.isEmpty
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
? const SliverToBoxAdapter(child: SizedBox())
|
||||||
|
: SliverList(
|
||||||
|
delegate:
|
||||||
|
SliverChildBuilderDelegate((context, index) {
|
||||||
return FavVideoCardH(
|
return FavVideoCardH(
|
||||||
videoItem: _favDetailController.favList[index],
|
videoItem: favList[index],
|
||||||
|
callFn: () => _favDetailController
|
||||||
|
.onCancelFav(favList[index].id),
|
||||||
);
|
);
|
||||||
}, childCount: _favDetailController.favList.length),
|
}, childCount: favList.length),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -207,18 +210,30 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return const SliverToBoxAdapter(
|
// 骨架屏
|
||||||
child: SizedBox(
|
return SliverList(
|
||||||
height: 300,
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
child: Center(child: Text('加载中')),
|
return const VideoCardHSkeleton();
|
||||||
),
|
}, childCount: 10),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: SizedBox(
|
child: Container(
|
||||||
height: MediaQuery.of(context).padding.bottom + 20,
|
height: MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_favDetailController.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|||||||
@ -10,42 +10,20 @@ import 'package:pilipala/utils/id_utils.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';
|
||||||
|
|
||||||
import '../controller.dart';
|
|
||||||
|
|
||||||
// 收藏视频卡片 - 水平布局
|
// 收藏视频卡片 - 水平布局
|
||||||
class FavVideoCardH extends StatelessWidget {
|
class FavVideoCardH extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final FavDetailController _favDetailController =
|
final Function? callFn;
|
||||||
Get.put(FavDetailController());
|
|
||||||
|
|
||||||
FavVideoCardH({Key? key, required this.videoItem}) : super(key: key);
|
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int id = videoItem.id;
|
int id = videoItem.id;
|
||||||
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
||||||
String heroTag = Utils.makeHeroTag(id);
|
String heroTag = Utils.makeHeroTag(id);
|
||||||
return Dismissible(
|
return InkWell(
|
||||||
movementDuration: const Duration(milliseconds: 300),
|
|
||||||
background: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
|
||||||
),
|
|
||||||
child: const Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(Icons.clear_all_rounded),
|
|
||||||
SizedBox(width: 6),
|
|
||||||
Text('取消收藏')
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
direction: DismissDirection.endToStart,
|
|
||||||
key: ValueKey<int>(videoItem.id),
|
|
||||||
onDismissed: (DismissDirection direction) {
|
|
||||||
_favDetailController.onCancelFav(videoItem.id);
|
|
||||||
// widget.onDeleteNotice();
|
|
||||||
},
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// int? seasonId;
|
// int? seasonId;
|
||||||
String? epId;
|
String? epId;
|
||||||
@ -71,8 +49,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
Get.toNamed('/video', parameters: parameters, arguments: {
|
Get.toNamed('/video', parameters: parameters, arguments: {
|
||||||
'videoItem': videoItem,
|
'videoItem': videoItem,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType':
|
// 'videoType':
|
||||||
epId != null ? SearchType.media_bangumi : SearchType.video,
|
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -113,10 +91,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 1, horizontal: 6),
|
vertical: 1, horizontal: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius:
|
borderRadius: BorderRadius.circular(4),
|
||||||
BorderRadius.circular(4),
|
color: Colors.black54.withOpacity(0.4)),
|
||||||
color:
|
|
||||||
Colors.black54.withOpacity(0.4)),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
Utils.timeFormat(videoItem.duration!),
|
Utils.timeFormat(videoItem.duration!),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@ -129,7 +105,7 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
VideoContent(videoItem: videoItem, callFn: callFn)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -138,14 +114,14 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const VideoContent({super.key, required this.videoItem});
|
final Function? callFn;
|
||||||
|
const VideoContent({super.key, required this.videoItem, this.callFn});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -166,6 +142,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
videoItem.owner.name,
|
videoItem.owner.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -173,15 +151,50 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const Spacer(),
|
||||||
Row(
|
SizedBox(
|
||||||
children: [
|
width: 26,
|
||||||
StatView(
|
height: 26,
|
||||||
theme: 'gray',
|
child: IconButton(
|
||||||
view: videoItem.cntInfo['play'],
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('要取消收藏吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline),
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await callFn!();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确定取消'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
|
||||||
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku'])
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
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/http/follow.dart';
|
import 'package:pilipala/http/follow.dart';
|
||||||
@ -7,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class FollowController extends GetxController {
|
class FollowController extends GetxController {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
|
int ps = 20;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
RxList<FollowItemModel> followList = [FollowItemModel()].obs;
|
RxList<FollowItemModel> followList = [FollowItemModel()].obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String name;
|
late String name;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -26,23 +29,30 @@ class FollowController extends GetxController {
|
|||||||
Future queryFollowings(type) async {
|
Future queryFollowings(type) async {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
pn = 1;
|
pn = 1;
|
||||||
|
loadingText.value == '加载中...';
|
||||||
|
}
|
||||||
|
if (loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var res = await FollowHttp.followings(
|
var res = await FollowHttp.followings(
|
||||||
vmid: mid,
|
vmid: mid,
|
||||||
pn: pn,
|
pn: pn,
|
||||||
ps: 20,
|
ps: ps,
|
||||||
orderType: 'attention',
|
orderType: 'attention',
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
followList.value = res['data'].list;
|
followList.value = res['data'].list;
|
||||||
total = res['data'].total;
|
total = res['data'].total;
|
||||||
} else if (type == 'onRefresh') {
|
|
||||||
followList.insertAll(0, res['data'].list);
|
|
||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
followList.addAll(res['data'].list);
|
followList.addAll(res['data'].list);
|
||||||
}
|
}
|
||||||
|
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
pn += 1;
|
pn += 1;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
|
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/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
@ -17,7 +19,6 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
final FollowController _followController = Get.put(FollowController());
|
final FollowController _followController = Get.put(FollowController());
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
bool _isLoadingMore = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -27,11 +28,9 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
() async {
|
() async {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
if (!_isLoadingMore) {
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
_isLoadingMore = true;
|
_followController.queryFollowings('onLoad');
|
||||||
await _followController.queryFollowings('onLoad');
|
});
|
||||||
_isLoadingMore = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -67,14 +66,40 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<FollowItemModel> list = _followController.followList;
|
List<FollowItemModel> list = _followController.followList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => list.length == 1
|
() => list.isNotEmpty
|
||||||
? const SizedBox()
|
? ListView.builder(
|
||||||
: ListView.builder(
|
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
itemCount: list.length,
|
itemCount: list.length + 1,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == list.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_followController.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return followItem(item: list[index]);
|
return followItem(item: list[index]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: [NoData()],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -27,12 +27,6 @@ Widget followItem({item}) {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
|
||||||
item.sign,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
dense: true,
|
|
||||||
trailing: const SizedBox(width: 6),
|
trailing: const SizedBox(width: 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
|
|
||||||
class HistoryController extends GetxController {
|
class HistoryController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
RxBool isLoadingMore = false.obs;
|
RxBool isLoadingMore = false.obs;
|
||||||
RxBool pauseStatus = false.obs;
|
RxBool pauseStatus = false.obs;
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
RxBool isLoading = false.obs;
|
RxBool isLoading = false.obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
RxInt checkedCount = 0.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -121,4 +123,80 @@ class HistoryController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除某条历史记录
|
||||||
|
Future delHistory(kid, business) async {
|
||||||
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
|
if (res['status']) {
|
||||||
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除已看历史记录
|
||||||
|
Future onDelHistory() async {
|
||||||
|
/// TODO 优化
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.progress == -1).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String resKid = 'archive_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('操作完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除选中的记录
|
||||||
|
Future onDelCheckedHistory() async {
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认删除所选历史记录吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
/// TODO 优化
|
||||||
|
await SmartDialog.dismiss();
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.checked!).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String str = 'archive';
|
||||||
|
try {
|
||||||
|
str = i.history!.business!;
|
||||||
|
} catch (_) {}
|
||||||
|
String resKid = '${str}_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
checkedCount.value = 0;
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
enableMultiple.value = false;
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,23 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
_historyController.enableMultiple.listen((p0) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
onChoose(index) {
|
||||||
|
_historyController.historyList[index].checked =
|
||||||
|
!_historyController.historyList[index].checked!;
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.where((item) => item.checked!).length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新多选状态
|
||||||
|
onUpdateMultiple() {
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,14 +65,31 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBarWidget(
|
||||||
|
visible: _historyController.enableMultiple.value,
|
||||||
|
child1: AppBar(
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(Icons.arrow_back_outlined),
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'观看记录',
|
'观看记录',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// _historyController.enableMultiple.value = true;
|
||||||
|
// setState(() {});
|
||||||
|
// },
|
||||||
|
// child: const Text('多选'),
|
||||||
|
// ),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed('/historySearch'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (String type) {
|
onSelected: (String type) {
|
||||||
// 处理菜单项选择的逻辑
|
// 处理菜单项选择的逻辑
|
||||||
@ -66,6 +100,13 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
case 'clear':
|
case 'clear':
|
||||||
_historyController.onClearHistory();
|
_historyController.onClearHistory();
|
||||||
break;
|
break;
|
||||||
|
case 'del':
|
||||||
|
_historyController.onDelHistory();
|
||||||
|
break;
|
||||||
|
case 'multiple':
|
||||||
|
_historyController.enableMultiple.value = true;
|
||||||
|
setState(() {});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -82,11 +123,62 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
value: 'clear',
|
value: 'clear',
|
||||||
child: Text('清空观看记录'),
|
child: Text('清空观看记录'),
|
||||||
),
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
value: 'del',
|
||||||
|
child: Text('删除已看记录'),
|
||||||
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
value: 'multiple',
|
||||||
|
child: Text('多选删除'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child2: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_historyController.enableMultiple.value = false;
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value = 0;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close_outlined),
|
||||||
|
),
|
||||||
|
title: Obx(
|
||||||
|
() => Text(
|
||||||
|
'已选择${_historyController.checkedCount.value}项',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = true;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
},
|
||||||
|
child: const Text('全选'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _historyController.onDelCheckedHistory(),
|
||||||
|
child: Text(
|
||||||
|
'删除',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _historyController.onRefresh();
|
await _historyController.onRefresh();
|
||||||
@ -99,6 +191,9 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
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']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
@ -109,6 +204,9 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
return HistoryItem(
|
return HistoryItem(
|
||||||
videoItem:
|
videoItem:
|
||||||
_historyController.historyList[index],
|
_historyController.historyList[index],
|
||||||
|
ctr: _historyController,
|
||||||
|
onChoose: () => onChoose(index),
|
||||||
|
onUpdateMultiple: () => onUpdateMultiple(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount:
|
childCount:
|
||||||
@ -144,6 +242,36 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// bottomNavigationBar: BottomAppBar(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const AppBarWidget({
|
||||||
|
required this.child1,
|
||||||
|
required this.child2,
|
||||||
|
required this.visible,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final PreferredSizeWidget child1;
|
||||||
|
final PreferredSizeWidget child2;
|
||||||
|
final bool visible;
|
||||||
|
@override
|
||||||
|
Size get preferredSize => child1.preferredSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: !visible ? child1 : child2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,24 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/common/business_type.dart';
|
import 'package:pilipala/models/common/business_type.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/pages/history/index.dart';
|
||||||
|
import 'package:pilipala/pages/history_search/index.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/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class HistoryItem extends StatelessWidget {
|
class HistoryItem extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const HistoryItem({super.key, required this.videoItem});
|
final dynamic ctr;
|
||||||
|
final Function? onChoose;
|
||||||
|
final Function? onUpdateMultiple;
|
||||||
|
const HistoryItem({
|
||||||
|
super.key,
|
||||||
|
required this.videoItem,
|
||||||
|
this.ctr,
|
||||||
|
this.onChoose,
|
||||||
|
this.onUpdateMultiple,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -25,6 +37,11 @@ class HistoryItem extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(aid);
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
if (ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (videoItem.history.business.contains('article')) {
|
if (videoItem.history.business.contains('article')) {
|
||||||
int cid = videoItem.history.cid ??
|
int cid = videoItem.history.cid ??
|
||||||
// videoItem.history.oid ??
|
// videoItem.history.oid ??
|
||||||
@ -72,7 +89,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -100,7 +117,7 @@ class HistoryItem 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'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -115,6 +132,17 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
if (ctr is HistorySearchController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
ctr!.enableMultiple.value = true;
|
||||||
|
onChoose!();
|
||||||
|
onUpdateMultiple!();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@ -129,6 +157,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
@ -161,7 +191,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// 右上角
|
// 右上角
|
||||||
if (BusinessType.showBadge.showBadge
|
if (BusinessType.showBadge.showBadge
|
||||||
.contains(videoItem.history.business) ||
|
.contains(
|
||||||
|
videoItem.history.business) ||
|
||||||
videoItem.history.business ==
|
videoItem.history.business ==
|
||||||
BusinessType.live.type)
|
BusinessType.live.type)
|
||||||
PBadge(
|
PBadge(
|
||||||
@ -176,7 +207,61 @@ class HistoryItem extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
Obx(
|
||||||
|
() => Positioned.fill(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: ctr!.enableMultiple.value ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
ctr!.enableMultiple.value &&
|
||||||
|
videoItem.checked
|
||||||
|
? 0.6
|
||||||
|
: 0),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: videoItem.checked ? 1 : 0,
|
||||||
|
duration:
|
||||||
|
const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
EdgeInsets.zero),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.resolveWith(
|
||||||
|
(states) {
|
||||||
|
return Colors.white
|
||||||
|
.withOpacity(0.8);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.done_all_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -191,7 +276,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const VideoContent({super.key, required this.videoItem});
|
final dynamic? ctr;
|
||||||
|
const VideoContent({super.key, required this.videoItem, this.ctr});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -244,16 +330,12 @@ class VideoContent extends StatelessWidget {
|
|||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline),
|
color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
if (videoItem.badge != '番剧' &&
|
|
||||||
!videoItem.tagName.contains('动画') &&
|
|
||||||
videoItem.history.business != 'live' &&
|
|
||||||
!videoItem.history.business.contains('article'))
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
child: PopupMenuButton<String>(
|
child: PopupMenuButton<String>(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
tooltip: '稍后再看',
|
tooltip: '功能菜单',
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.more_vert_outlined,
|
Icons.more_vert_outlined,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
@ -264,6 +346,10 @@ class VideoContent extends StatelessWidget {
|
|||||||
onSelected: (String type) {},
|
onSelected: (String type) {},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<PopupMenuEntry<String>>[
|
||||||
|
if (videoItem.badge != '番剧' &&
|
||||||
|
!videoItem.tagName.contains('动画') &&
|
||||||
|
videoItem.history.business != 'live' &&
|
||||||
|
!videoItem.history.business.contains('article'))
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var res = await UserHttp.toViewLater(
|
var res = await UserHttp.toViewLater(
|
||||||
@ -280,6 +366,19 @@ class VideoContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
onTap: () => ctr!.delHistory(
|
||||||
|
videoItem.kid, videoItem.history.business),
|
||||||
|
value: 'pause',
|
||||||
|
height: 35,
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.close_outlined, size: 16),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text('删除记录', style: TextStyle(fontSize: 13))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
91
lib/pages/history_search/controller.dart
Normal file
91
lib/pages/history_search/controller.dart
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/models/user/history.dart';
|
||||||
|
|
||||||
|
class HistorySearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int pn = 1;
|
||||||
|
int count = 0;
|
||||||
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
pn = 1;
|
||||||
|
searchHistories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchHistories({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await UserHttp.searchHistory(
|
||||||
|
pn: pn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' && pn == 1) {
|
||||||
|
historyList.value = res['data'].list;
|
||||||
|
} else {
|
||||||
|
historyList.addAll(res['data'].list);
|
||||||
|
}
|
||||||
|
count = res['data'].page['total'];
|
||||||
|
if (historyList.length == count) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
pn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
searchHistories(type: 'onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future delHistory(kid, business) async {
|
||||||
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
|
if (res['status']) {
|
||||||
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/history_search/index.dart
Normal file
4
lib/pages/history_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library history_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
174
lib/pages/history_search/view.dart
Normal file
174
lib/pages/history_search/view.dart
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/pages/history/widgets/item.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class HistorySearchPage extends StatefulWidget {
|
||||||
|
const HistorySearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistorySearchPage> createState() => _HistorySearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||||
|
final HistorySearchController _historySearchCtr =
|
||||||
|
Get.put(HistorySearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _historySearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_historySearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _historySearchCtr.submit(),
|
||||||
|
icon: const Icon(Icons.search_outlined, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _historySearchCtr.searchFocusNode,
|
||||||
|
controller: _historySearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _historySearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _historySearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _historySearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _historySearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _historySearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [const SizedBox()]
|
||||||
|
: [
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _historySearchCtr.searchHistories(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _historySearchCtr.historyList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_historySearchCtr.historyList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_historySearchCtr
|
||||||
|
.historyList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_historySearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return HistoryItem(
|
||||||
|
videoItem: _historySearchCtr
|
||||||
|
.historyList[index],
|
||||||
|
ctr: _historySearchCtr,
|
||||||
|
onChoose: null,
|
||||||
|
onUpdateMultiple: () => null,
|
||||||
|
);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _historySearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? const SizedBox(child: Text('加载中...'))
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/http/black.dart';
|
||||||
import 'package:pilipala/models/common/tab_type.dart';
|
import 'package:pilipala/models/common/tab_type.dart';
|
||||||
|
import 'package:pilipala/models/user/black.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||||
@ -15,6 +17,13 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
RxString userFace = ''.obs;
|
RxString userFace = ''.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
|
late List<int> blackMidsList;
|
||||||
|
|
||||||
|
int currentPage = 1;
|
||||||
|
int pageSize = 50;
|
||||||
|
int total = 0;
|
||||||
|
List<BlackListItem> blackList = [BlackListItem()];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -48,9 +57,36 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 更新登录状态
|
// 更新登录状态
|
||||||
void updateLoginStatus(val) {
|
void updateLoginStatus(val) async {
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = await userInfoCache.get('userInfoCache');
|
||||||
userLogin.value = val ?? false;
|
userLogin.value = val ?? false;
|
||||||
|
if (val) {
|
||||||
|
// 获取黑名单
|
||||||
|
await queryBlacklist();
|
||||||
|
blackMidsList = blackList.map<int>((e) => e.mid!).toList();
|
||||||
|
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||||
|
return;
|
||||||
|
}
|
||||||
userFace.value = userInfo != null ? userInfo.face : '';
|
userFace.value = userInfo != null ? userInfo.face : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
|
if (type == 'init') {
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
var result = await BlackHttp.blackList(pn: currentPage, ps: pageSize);
|
||||||
|
if (result['status']) {
|
||||||
|
if (type == 'init') {
|
||||||
|
blackList = result['data'].list;
|
||||||
|
total = result['data'].total;
|
||||||
|
} else {
|
||||||
|
blackList.addAll(result['data'].list);
|
||||||
|
}
|
||||||
|
currentPage += 1;
|
||||||
|
if (blackList.length < total) {
|
||||||
|
await queryBlacklist(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import 'package:pilipala/utils/feed_back.dart';
|
|||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
Function? callFn;
|
||||||
|
HomePage({Key? key, this.callFn}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HomePage> createState() => _HomePageState();
|
State<HomePage> createState() => _HomePageState();
|
||||||
@ -25,15 +26,16 @@ class _HomePageState extends State<HomePage>
|
|||||||
|
|
||||||
showUserBottonSheet() {
|
showUserBottonSheet() {
|
||||||
feedBack();
|
feedBack();
|
||||||
showModalBottomSheet(
|
widget.callFn!();
|
||||||
context: context,
|
// showModalBottomSheet(
|
||||||
builder: (_) => const SizedBox(
|
// context: context,
|
||||||
height: 450,
|
// builder: (_) => const SizedBox(
|
||||||
child: MinePage(),
|
// height: 450,
|
||||||
),
|
// child: MinePage(),
|
||||||
clipBehavior: Clip.hardEdge,
|
// ),
|
||||||
isScrollControlled: true,
|
// clipBehavior: Clip.hardEdge,
|
||||||
);
|
// isScrollControlled: true,
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -50,37 +52,6 @@ class _HomePageState extends State<HomePage>
|
|||||||
ctr: _homeController,
|
ctr: _homeController,
|
||||||
callback: showUserBottonSheet,
|
callback: showUserBottonSheet,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 42,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: TabBar(
|
|
||||||
controller: _homeController.tabController,
|
|
||||||
tabs: [
|
|
||||||
for (var i in _homeController.tabs) Tab(text: i['label'])
|
|
||||||
],
|
|
||||||
isScrollable: true,
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
enableFeedback: true,
|
|
||||||
splashBorderRadius: BorderRadius.circular(10),
|
|
||||||
onTap: (value) {
|
|
||||||
feedBack();
|
|
||||||
if (_homeController.initialIndex == value) {
|
|
||||||
_homeController.tabsCtrList[value]().animateToTop();
|
|
||||||
}
|
|
||||||
_homeController.initialIndex = value;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: _homeController.tabController,
|
|
||||||
children: _homeController.tabsPageList,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -128,18 +99,18 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(child: SearchPage()),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => ctr!.userLogin.value
|
() => ctr!.userLogin.value
|
||||||
? Stack(
|
? Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
Obx(
|
||||||
|
() => NetworkImgLayer(
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
width: 34,
|
width: 34,
|
||||||
height: 34,
|
height: 34,
|
||||||
src: ctr!.userFace.value,
|
src: ctr!.userFace.value,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: Material(
|
child: Material(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
@ -180,6 +151,8 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Expanded(child: SearchPage()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
25
lib/pages/home/widgets/left_drawer.dart
Normal file
25
lib/pages/home/widgets/left_drawer.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
|
|
||||||
|
class LeftDrawer extends StatefulWidget {
|
||||||
|
const LeftDrawer({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LeftDrawer> createState() => _LeftDrawerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeftDrawerState extends State<LeftDrawer> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Drawer(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.84,
|
||||||
|
child: const Column(
|
||||||
|
children: [
|
||||||
|
Expanded(child: MinePage()),
|
||||||
|
Expanded(child: MediaPage()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
lib/pages/html/controller.dart
Normal file
19
lib/pages/html/controller.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/html.dart';
|
||||||
|
|
||||||
|
class HtmlRenderController extends GetxController {
|
||||||
|
late String id;
|
||||||
|
late Map response;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
id = Get.parameters['id']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future reqHtml() async {
|
||||||
|
var res = await HtmlHttp.reqHtml(id);
|
||||||
|
response = res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/html/index.dart
Normal file
4
lib/pages/html/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library html_render;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
117
lib/pages/html/view.dart
Normal file
117
lib/pages/html/view.dart
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/html_render.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class HtmlRenderPage extends StatefulWidget {
|
||||||
|
const HtmlRenderPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HtmlRenderPage> createState() => _HtmlRenderPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HtmlRenderPageState extends State<HtmlRenderPage> {
|
||||||
|
HtmlRenderController htmlRenderCtr = Get.put(HtmlRenderController());
|
||||||
|
late String title;
|
||||||
|
late String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
title = Get.parameters['title']!;
|
||||||
|
id = Get.parameters['id']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: false,
|
||||||
|
title: Text(title),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: htmlRenderCtr.reqHtml(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
type: 'avatar',
|
||||||
|
src: htmlRenderCtr.response['avatar']!,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(htmlRenderCtr.response['uname'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall!
|
||||||
|
.fontSize,
|
||||||
|
)),
|
||||||
|
Text(
|
||||||
|
htmlRenderCtr.response['updateTime'],
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall!
|
||||||
|
.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
|
child: HtmlRender(
|
||||||
|
htmlContent: htmlRenderCtr.response['content'],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
width: 8,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.dividerColor
|
||||||
|
.withOpacity(0.05),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Text('error');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,29 +23,38 @@ class LaterController extends GetxController {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future toViewDel() async {
|
Future toViewDel({int? aid}) async {
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
useSystem: true,
|
useSystem: true,
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: const Text('即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
content: Text(
|
||||||
|
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: const Text('取消')),
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var res = await UserHttp.toViewDel();
|
var res = await UserHttp.toViewDel(aid: aid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
|
if (aid != null) {
|
||||||
|
laterList.removeWhere((e) => e.aid == aid);
|
||||||
|
} else {
|
||||||
laterList.clear();
|
laterList.clear();
|
||||||
queryLaterList();
|
queryLaterList();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
},
|
},
|
||||||
child: const Text('确认删除'),
|
child: Text(aid != null ? '确认移除' : '确认删除'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -65,7 +74,11 @@ class LaterController extends GetxController {
|
|||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: const Text('取消')),
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var res = await UserHttp.toViewClear();
|
var res = await UserHttp.toViewClear();
|
||||||
|
|||||||
@ -80,10 +80,12 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
? SliverList(
|
? SliverList(
|
||||||
delegate:
|
delegate:
|
||||||
SliverChildBuilderDelegate((context, index) {
|
SliverChildBuilderDelegate((context, index) {
|
||||||
|
var videoItem = _laterController.laterList[index];
|
||||||
return VideoCardH(
|
return VideoCardH(
|
||||||
videoItem: _laterController.laterList[index],
|
videoItem: videoItem,
|
||||||
source: 'later',
|
source: 'later',
|
||||||
);
|
longPress: () => _laterController.toViewDel(
|
||||||
|
aid: videoItem.aid));
|
||||||
}, childCount: _laterController.laterList.length),
|
}, childCount: _laterController.laterList.length),
|
||||||
)
|
)
|
||||||
: _laterController.isLoading.value
|
: _laterController.isLoading.value
|
||||||
|
|||||||
@ -1,17 +1,27 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/live.dart';
|
import 'package:pilipala/http/live.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class LiveController extends GetxController {
|
class LiveController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
int count = 12;
|
int count = 12;
|
||||||
int _currentPage = 1;
|
int _currentPage = 1;
|
||||||
int crossAxisCount = 2;
|
RxInt crossAxisCount = 2.obs;
|
||||||
RxList<LiveItemModel> liveList = [LiveItemModel()].obs;
|
RxList<LiveItemModel> liveList = [LiveItemModel()].obs;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
OverlayEntry? popupDialog;
|
OverlayEntry? popupDialog;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
crossAxisCount.value =
|
||||||
|
setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2;
|
||||||
|
}
|
||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
Future queryLiveList(type) async {
|
Future queryLiveList(type) async {
|
||||||
|
|||||||
@ -84,6 +84,9 @@ class _LivePageState extends State<LivePage> {
|
|||||||
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 as Map;
|
Map data = snapshot.data as Map;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return SliverLayoutBuilder(
|
return SliverLayoutBuilder(
|
||||||
@ -129,14 +132,15 @@ class _LivePageState extends State<LivePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget contentGrid(ctr, liveList) {
|
Widget contentGrid(ctr, liveList) {
|
||||||
double maxWidth = Get.size.width;
|
// double maxWidth = Get.size.width;
|
||||||
int baseWidth = 500;
|
// int baseWidth = 500;
|
||||||
int step = 300;
|
// int step = 300;
|
||||||
int crossAxisCount =
|
// int crossAxisCount =
|
||||||
maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2;
|
// maxWidth > baseWidth ? 2 + ((maxWidth - baseWidth) / step).ceil() : 2;
|
||||||
if (maxWidth < 300) {
|
// if (maxWidth < 300) {
|
||||||
crossAxisCount = 1;
|
// crossAxisCount = 1;
|
||||||
}
|
// }
|
||||||
|
int crossAxisCount = ctr.crossAxisCount.value;
|
||||||
return SliverGrid(
|
return SliverGrid(
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
// 行间距
|
// 行间距
|
||||||
@ -147,13 +151,15 @@ class _LivePageState extends State<LivePage> {
|
|||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
Get.size.width / crossAxisCount / StyleString.aspectRatio +
|
Get.size.width / crossAxisCount / StyleString.aspectRatio +
|
||||||
68 * MediaQuery.of(context).textScaleFactor,
|
(crossAxisCount == 1 ? 48 : 68) *
|
||||||
|
MediaQuery.of(context).textScaleFactor,
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return liveList!.isNotEmpty
|
return liveList!.isNotEmpty
|
||||||
? LiveCardV(
|
? LiveCardV(
|
||||||
liveItem: liveList[index],
|
liveItem: liveList[index],
|
||||||
|
crossAxisCount: crossAxisCount,
|
||||||
longPress: () {
|
longPress: () {
|
||||||
_liveController.popupDialog =
|
_liveController.popupDialog =
|
||||||
_createPopupDialog(liveList[index]);
|
_createPopupDialog(liveList[index]);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.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';
|
||||||
@ -9,12 +8,14 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
// 视频卡片 - 垂直布局
|
// 视频卡片 - 垂直布局
|
||||||
class LiveCardV extends StatelessWidget {
|
class LiveCardV extends StatelessWidget {
|
||||||
final LiveItemModel liveItem;
|
final LiveItemModel liveItem;
|
||||||
|
final int crossAxisCount;
|
||||||
final Function()? longPress;
|
final Function()? longPress;
|
||||||
final Function()? longPressEnd;
|
final Function()? longPressEnd;
|
||||||
|
|
||||||
const LiveCardV({
|
const LiveCardV({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.liveItem,
|
required this.liveItem,
|
||||||
|
required this.crossAxisCount,
|
||||||
this.longPress,
|
this.longPress,
|
||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -23,7 +24,7 @@ class LiveCardV extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 1,
|
elevation: crossAxisCount == 1 ? 0 : 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -45,12 +46,7 @@ class LiveCardV extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.all(StyleString.imgRadius),
|
||||||
topLeft: StyleString.imgRadius,
|
|
||||||
topRight: StyleString.imgRadius,
|
|
||||||
bottomLeft: StyleString.imgRadius,
|
|
||||||
bottomRight: StyleString.imgRadius,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
@ -66,6 +62,7 @@ class LiveCardV extends StatelessWidget {
|
|||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (crossAxisCount != 1)
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@ -83,7 +80,7 @@ class LiveCardV extends StatelessWidget {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
LiveContent(liveItem: liveItem)
|
LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -94,13 +91,18 @@ class LiveCardV extends StatelessWidget {
|
|||||||
|
|
||||||
class LiveContent extends StatelessWidget {
|
class LiveContent extends StatelessWidget {
|
||||||
final dynamic liveItem;
|
final dynamic liveItem;
|
||||||
const LiveContent({Key? key, required this.liveItem}) : super(key: key);
|
final int crossAxisCount;
|
||||||
|
const LiveContent(
|
||||||
|
{Key? key, required this.liveItem, 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: crossAxisCount == 1
|
||||||
padding: const EdgeInsets.fromLTRB(9, 9, 9, 8),
|
? const EdgeInsets.fromLTRB(9, 9, 9, 4)
|
||||||
|
: const EdgeInsets.fromLTRB(9, 8, 9, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -112,29 +114,40 @@ class LiveContent extends StatelessWidget {
|
|||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: crossAxisCount == 1 ? 1 : 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
if (crossAxisCount == 1) const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
const PBadge(
|
Text(
|
||||||
text: 'UP',
|
|
||||||
size: 'small',
|
|
||||||
stack: 'normal',
|
|
||||||
fs: 9,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
liveItem.uname,
|
liveItem.uname,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
)
|
if (crossAxisCount == 1) ...[
|
||||||
|
Text(
|
||||||
|
' • ${liveItem!.areaName!}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
' • ${liveItem!.watchedShow!['text_small']}人观看',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -29,10 +29,10 @@ class LiveRoomController extends GetxController {
|
|||||||
if (Get.arguments != null) {
|
if (Get.arguments != null) {
|
||||||
liveItem = Get.arguments['liveItem'];
|
liveItem = Get.arguments['liveItem'];
|
||||||
heroTag = Get.arguments['heroTag'] ?? '';
|
heroTag = Get.arguments['heroTag'] ?? '';
|
||||||
if (liveItem.pic != null && liveItem.pic != '') {
|
if (liveItem != null && liveItem.pic != null && liveItem.pic != '') {
|
||||||
cover = liveItem.pic;
|
cover = liveItem.pic;
|
||||||
}
|
}
|
||||||
if (liveItem.cover != null && liveItem.cover != '') {
|
if (liveItem != null && liveItem.cover != null && liveItem.cover != '') {
|
||||||
cover = liveItem.cover;
|
cover = liveItem.cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:floating/floating.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/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
import 'widgets/bottom_control.dart';
|
||||||
|
|
||||||
class LiveRoomPage extends StatefulWidget {
|
class LiveRoomPage extends StatefulWidget {
|
||||||
const LiveRoomPage({super.key});
|
const LiveRoomPage({super.key});
|
||||||
@ -18,6 +22,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
|||||||
|
|
||||||
bool isShowCover = true;
|
bool isShowCover = true;
|
||||||
bool isPlay = true;
|
bool isPlay = true;
|
||||||
|
Floating? floating;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -31,24 +36,30 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
floating = Floating();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
plPlayerController!.dispose();
|
plPlayerController!.dispose();
|
||||||
|
if (floating != null) {
|
||||||
|
floating!.dispose();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
|
final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
|
||||||
|
Widget childWhenDisabled = Scaffold(
|
||||||
return Scaffold(
|
|
||||||
primary: true,
|
primary: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
title: Row(
|
title: _liveRoomController.liveItem != null
|
||||||
|
? Row(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 34,
|
width: 34,
|
||||||
@ -67,13 +78,15 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
|||||||
const SizedBox(height: 1),
|
const SizedBox(height: 1),
|
||||||
if (_liveRoomController.liveItem.watchedShow != null)
|
if (_liveRoomController.liveItem.watchedShow != null)
|
||||||
Text(
|
Text(
|
||||||
_liveRoomController.liveItem.watchedShow['text_large'] ??
|
_liveRoomController
|
||||||
|
.liveItem.watchedShow['text_large'] ??
|
||||||
'',
|
'',
|
||||||
style: const TextStyle(fontSize: 12)),
|
style: const TextStyle(fontSize: 12)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
|
: const SizedBox(),
|
||||||
// actions: [
|
// actions: [
|
||||||
// SizedBox(
|
// SizedBox(
|
||||||
// height: 34,
|
// height: 34,
|
||||||
@ -84,97 +97,61 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Stack(
|
||||||
tag: _liveRoomController.heroTag,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: plPlayerController!.videoPlayerController != null
|
child: plPlayerController!.videoPlayerController != null
|
||||||
? PLVideoPlayer(controller: plPlayerController!)
|
? PLVideoPlayer(
|
||||||
|
controller: plPlayerController!,
|
||||||
|
bottomControl: BottomControl(
|
||||||
|
controller: plPlayerController,
|
||||||
|
liveRoomCtr: _liveRoomController,
|
||||||
|
floating: floating,
|
||||||
|
),
|
||||||
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
if (_liveRoomController.liveItem.cover != null)
|
// if (_liveRoomController.liveItem != null &&
|
||||||
Visibility(
|
// _liveRoomController.liveItem.cover != null)
|
||||||
visible: isShowCover,
|
// Visibility(
|
||||||
child: Positioned(
|
// visible: isShowCover,
|
||||||
top: 0,
|
// child: Positioned(
|
||||||
left: 0,
|
// top: 0,
|
||||||
right: 0,
|
// left: 0,
|
||||||
child: NetworkImgLayer(
|
// right: 0,
|
||||||
type: 'emote',
|
// child: NetworkImgLayer(
|
||||||
src: _liveRoomController.liveItem.cover,
|
// type: 'emote',
|
||||||
width: Get.size.width,
|
// src: _liveRoomController.liveItem.cover,
|
||||||
height: videoHeight,
|
// width: Get.size.width,
|
||||||
),
|
// height: videoHeight,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
// Container(
|
|
||||||
// height: 45,
|
|
||||||
// padding: const EdgeInsets.only(left: 12, right: 12),
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: Theme.of(context).colorScheme.background,
|
|
||||||
// border: Border(
|
|
||||||
// bottom: BorderSide(
|
|
||||||
// color: Theme.of(context).dividerColor.withOpacity(0.1)),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// child: Row(children: <Widget>[
|
|
||||||
// SizedBox(
|
|
||||||
// width: 38,
|
|
||||||
// height: 38,
|
|
||||||
// child: IconButton(
|
|
||||||
// onPressed: () {},
|
|
||||||
// icon: const Icon(
|
|
||||||
// Icons.subtitles_outlined,
|
|
||||||
// size: 21,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// const Spacer(),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 38,
|
|
||||||
// height: 38,
|
|
||||||
// child: IconButton(
|
|
||||||
// onPressed: () {},
|
|
||||||
// icon: const Icon(
|
|
||||||
// Icons.hd_outlined,
|
|
||||||
// size: 20,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 38,
|
|
||||||
// height: 38,
|
|
||||||
// child: IconButton(
|
|
||||||
// onPressed: () => _liveRoomController
|
|
||||||
// .setVolumn(plPlayerController!.volume.value),
|
|
||||||
// icon: Obx(() => Icon(
|
|
||||||
// _liveRoomController.volumeOff.value
|
|
||||||
// ? Icons.volume_off_outlined
|
|
||||||
// : Icons.volume_up_outlined,
|
|
||||||
// size: 21,
|
|
||||||
// )),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// SizedBox(
|
|
||||||
// width: 38,
|
|
||||||
// height: 38,
|
|
||||||
// child: IconButton(
|
|
||||||
// onPressed: () => {},
|
|
||||||
// // plPlayerController!.goToFullscreen(context),
|
|
||||||
// icon: const Icon(
|
|
||||||
// Icons.fullscreen,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ]),
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Widget childWhenEnabled = AspectRatio(
|
||||||
|
aspectRatio: 16 / 9,
|
||||||
|
child: plPlayerController!.videoPlayerController != null
|
||||||
|
? PLVideoPlayer(
|
||||||
|
controller: plPlayerController!,
|
||||||
|
bottomControl: BottomControl(
|
||||||
|
controller: plPlayerController,
|
||||||
|
liveRoomCtr: _liveRoomController,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
);
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return PiPSwitcher(
|
||||||
|
childWhenDisabled: childWhenDisabled,
|
||||||
|
childWhenEnabled: childWhenEnabled,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return childWhenDisabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
151
lib/pages/liveRoom/widgets/bottom_control.dart
Normal file
151
lib/pages/liveRoom/widgets/bottom_control.dart
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:floating/floating.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
|
import 'package:pilipala/pages/liveRoom/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
class BottomControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
|
final PlPlayerController? controller;
|
||||||
|
final LiveRoomController? liveRoomCtr;
|
||||||
|
final Floating? floating;
|
||||||
|
const BottomControl({
|
||||||
|
this.controller,
|
||||||
|
this.liveRoomCtr,
|
||||||
|
this.floating,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BottomControl> createState() => _BottomControlState();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BottomControlState extends State<BottomControl> {
|
||||||
|
late PlayUrlModel videoInfo;
|
||||||
|
List<PlaySpeed> playSpeed = PlaySpeed.values;
|
||||||
|
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||||
|
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||||
|
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const textStyle = TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
);
|
||||||
|
return AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
primary: false,
|
||||||
|
centerTitle: false,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
titleSpacing: 14,
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
// ComBtn(
|
||||||
|
// icon: const Icon(
|
||||||
|
// Icons.subtitles_outlined,
|
||||||
|
// size: 18,
|
||||||
|
// color: Colors.white,
|
||||||
|
// ),
|
||||||
|
// fuc: () => Get.back(),
|
||||||
|
// ),
|
||||||
|
const Spacer(),
|
||||||
|
// ComBtn(
|
||||||
|
// icon: const Icon(
|
||||||
|
// Icons.hd_outlined,
|
||||||
|
// size: 18,
|
||||||
|
// color: Colors.white,
|
||||||
|
// ),
|
||||||
|
// fuc: () => {},
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 4),
|
||||||
|
// Obx(
|
||||||
|
// () => ComBtn(
|
||||||
|
// icon: Icon(
|
||||||
|
// widget.liveRoomCtr!.volumeOff.value
|
||||||
|
// ? Icons.volume_off_outlined
|
||||||
|
// : Icons.volume_up_outlined,
|
||||||
|
// size: 18,
|
||||||
|
// color: Colors.white,
|
||||||
|
// ),
|
||||||
|
// fuc: () => {},
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 4),
|
||||||
|
if (Platform.isAndroid) ...[
|
||||||
|
SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
bool canUsePiP = false;
|
||||||
|
widget.controller!.hiddenControls(false);
|
||||||
|
try {
|
||||||
|
canUsePiP = await widget.floating!.isPipAvailable;
|
||||||
|
} on PlatformException catch (_) {
|
||||||
|
canUsePiP = false;
|
||||||
|
}
|
||||||
|
if (canUsePiP) {
|
||||||
|
await widget.floating!.enable();
|
||||||
|
} else {}
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.picture_in_picture_outlined,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
ComBtn(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.fullscreen,
|
||||||
|
size: 20,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
fuc: () => widget.controller!.triggerFullScreen(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MSliderTrackShape extends RoundedRectSliderTrackShape {
|
||||||
|
@override
|
||||||
|
Rect getPreferredRect({
|
||||||
|
required RenderBox parentBox,
|
||||||
|
Offset offset = Offset.zero,
|
||||||
|
SliderThemeData? sliderTheme,
|
||||||
|
bool isEnabled = false,
|
||||||
|
bool isDiscrete = false,
|
||||||
|
}) {
|
||||||
|
const double trackHeight = 3;
|
||||||
|
final double trackLeft = offset.dx;
|
||||||
|
final double trackTop =
|
||||||
|
offset.dy + (parentBox.size.height - trackHeight) / 2 + 4;
|
||||||
|
final double trackWidth = parentBox.size.width;
|
||||||
|
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,58 +1,18 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
// import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/home/view.dart';
|
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class MainController extends GetxController {
|
class MainController extends GetxController {
|
||||||
List<Widget> pages = <Widget>[
|
|
||||||
const HomePage(),
|
|
||||||
const DynamicsPage(),
|
|
||||||
const MediaPage(),
|
|
||||||
];
|
|
||||||
RxList navigationBars = [
|
|
||||||
{
|
|
||||||
'icon': const Icon(
|
|
||||||
Icons.favorite_outline,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
'selectIcon': const Icon(
|
|
||||||
Icons.favorite,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
'label': "首页",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'icon': const Icon(
|
|
||||||
Icons.motion_photos_on_outlined,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
'selectIcon': const Icon(
|
|
||||||
Icons.motion_photos_on,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
'label': "动态",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'icon': const Icon(
|
|
||||||
Icons.folder_outlined,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
'selectIcon': const Icon(
|
|
||||||
Icons.folder,
|
|
||||||
size: 21,
|
|
||||||
),
|
|
||||||
'label': "媒体库",
|
|
||||||
}
|
|
||||||
].obs;
|
|
||||||
final StreamController<bool> bottomBarStream =
|
final StreamController<bool> bottomBarStream =
|
||||||
StreamController<bool>.broadcast();
|
StreamController<bool>.broadcast();
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
|
DateTime? _lastPressedAt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -61,4 +21,16 @@ class MainController extends GetxController {
|
|||||||
Utils.checkUpdata();
|
Utils.checkUpdata();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> onBackPressed(BuildContext context) {
|
||||||
|
if (_lastPressedAt == null ||
|
||||||
|
DateTime.now().difference(_lastPressedAt!) >
|
||||||
|
const Duration(seconds: 2)) {
|
||||||
|
// 两次点击时间间隔超过2秒,重新记录时间戳
|
||||||
|
_lastPressedAt = DateTime.now();
|
||||||
|
SmartDialog.showToast("再按一次退出Pili");
|
||||||
|
return Future.value(false); // 不退出应用
|
||||||
|
}
|
||||||
|
return Future.value(true); // 退出应用
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
|
import 'package:pilipala/pages/home/widgets/left_drawer.dart';
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/event_bus.dart';
|
import 'package:pilipala/utils/event_bus.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
@ -18,80 +19,16 @@ class MainApp extends StatefulWidget {
|
|||||||
|
|
||||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||||
final MainController _mainController = Get.put(MainController());
|
final MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
|
||||||
final MediaController _mediaController = Get.put(MediaController());
|
|
||||||
|
|
||||||
PageController? _pageController;
|
|
||||||
|
|
||||||
late AnimationController? _animationController;
|
|
||||||
late Animation<double>? _fadeAnimation;
|
|
||||||
late Animation<double>? _slideAnimation;
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
int? _lastSelectTime; //上次点击时间
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_animationController = AnimationController(
|
|
||||||
duration: const Duration(milliseconds: 800),
|
|
||||||
reverseDuration: const Duration(milliseconds: 0),
|
|
||||||
value: 1,
|
|
||||||
vsync: this,
|
|
||||||
);
|
|
||||||
_fadeAnimation =
|
|
||||||
Tween<double>(begin: 0.8, end: 1.0).animate(_animationController!);
|
|
||||||
_slideAnimation =
|
|
||||||
Tween(begin: 0.8, end: 1.0).animate(_animationController!);
|
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
_pageController = PageController(initialPage: selectedIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setIndex(int value) async {
|
void openDrawer() {
|
||||||
feedBack();
|
_scaffoldKey.currentState?.openDrawer();
|
||||||
if (selectedIndex != value) {
|
|
||||||
selectedIndex = value;
|
|
||||||
_animationController!.reverse().then((_) {
|
|
||||||
selectedIndex = value;
|
|
||||||
_animationController!.forward();
|
|
||||||
});
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
_pageController!.jumpToPage(value);
|
|
||||||
var currentPage = _mainController.pages[value];
|
|
||||||
if (currentPage is HomePage) {
|
|
||||||
if (_homeController.flag) {
|
|
||||||
// 单击返回顶部 双击并刷新
|
|
||||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
|
||||||
_homeController.onRefresh();
|
|
||||||
} else {
|
|
||||||
_homeController.animateToTop();
|
|
||||||
}
|
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
}
|
|
||||||
_homeController.flag = true;
|
|
||||||
} else {
|
|
||||||
_homeController.flag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPage is DynamicsPage) {
|
|
||||||
if (_dynamicController.flag) {
|
|
||||||
// 单击返回顶部 双击并刷新
|
|
||||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
|
||||||
_dynamicController.onRefresh();
|
|
||||||
} else {
|
|
||||||
_dynamicController.animateToTop();
|
|
||||||
}
|
|
||||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
}
|
|
||||||
_dynamicController.flag = true;
|
|
||||||
} else {
|
|
||||||
_dynamicController.flag = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPage is MediaPage) {
|
|
||||||
_mediaController.queryFavFolder();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -110,55 +47,13 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
MediaQuery.of(context).size.width * 9 / 16;
|
MediaQuery.of(context).size.width * 9 / 16;
|
||||||
localCache.put('sheetHeight', sheetHeight);
|
localCache.put('sheetHeight', sheetHeight);
|
||||||
localCache.put('statusBarHeight', statusBarHeight);
|
localCache.put('statusBarHeight', statusBarHeight);
|
||||||
return Scaffold(
|
return WillPopScope(
|
||||||
|
onWillPop: () => _mainController.onBackPressed(context),
|
||||||
|
child: Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: FadeTransition(
|
drawer: const LeftDrawer(),
|
||||||
opacity: _fadeAnimation!,
|
body: HomePage(callFn: openDrawer),
|
||||||
child: SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: const Offset(0, 0.5),
|
|
||||||
end: Offset.zero,
|
|
||||||
).animate(
|
|
||||||
CurvedAnimation(
|
|
||||||
parent: _slideAnimation!,
|
|
||||||
curve: Curves.fastOutSlowIn,
|
|
||||||
reverseCurve: Curves.linear,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: PageView(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
controller: _pageController,
|
|
||||||
onPageChanged: (index) {
|
|
||||||
selectedIndex = index;
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
children: _mainController.pages,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: StreamBuilder(
|
|
||||||
stream: _mainController.bottomBarStream.stream,
|
|
||||||
initialData: true,
|
|
||||||
builder: (context, AsyncSnapshot snapshot) {
|
|
||||||
return AnimatedSlide(
|
|
||||||
curve: Curves.easeInOutCubicEmphasized,
|
|
||||||
duration: const Duration(milliseconds: 1000),
|
|
||||||
offset: Offset(0, snapshot.data ? 0 : 1),
|
|
||||||
child: NavigationBar(
|
|
||||||
onDestinationSelected: (value) => setIndex(value),
|
|
||||||
selectedIndex: selectedIndex,
|
|
||||||
destinations: <Widget>[
|
|
||||||
..._mainController.navigationBars.map((e) {
|
|
||||||
return NavigationDestination(
|
|
||||||
icon: e['icon'],
|
|
||||||
selectedIcon: e['selectIcon'],
|
|
||||||
label: e['label'],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -35,6 +35,7 @@ class MediaController extends GetxController {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
int? mid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -44,13 +45,13 @@ class MediaController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> queryFavFolder() async {
|
Future<dynamic> queryFavFolder() async {
|
||||||
if (!userLogin.value || GStrorage.userInfo.get('userInfoCache') == null) {
|
if (!userLogin.value) {
|
||||||
return {'status': false, 'data': [], 'msg': '未登录'};
|
return {'status': false, 'data': [], 'msg': '未登录'};
|
||||||
}
|
}
|
||||||
var res = await await UserHttp.userfavFolder(
|
var res = await await UserHttp.userfavFolder(
|
||||||
pn: 1,
|
pn: 1,
|
||||||
ps: 5,
|
ps: 5,
|
||||||
mid: GStrorage.userInfo.get('userInfoCache').mid,
|
mid: mid ?? GStrorage.userInfo.get('userInfoCache').mid,
|
||||||
);
|
);
|
||||||
favFolderData.value = res['data'];
|
favFolderData.value = res['data'];
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/event_bus.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
class MediaPage extends StatefulWidget {
|
class MediaPage extends StatefulWidget {
|
||||||
const MediaPage({super.key});
|
const MediaPage({super.key});
|
||||||
@ -16,8 +12,6 @@ class MediaPage extends StatefulWidget {
|
|||||||
class _MediaPageState extends State<MediaPage>
|
class _MediaPageState extends State<MediaPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
late MediaController mediaController;
|
late MediaController mediaController;
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
EventBus eventBus = EventBus();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -26,13 +20,6 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mediaController = Get.put(MediaController());
|
mediaController = Get.put(MediaController());
|
||||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
|
||||||
eventBus.on(EventName.loginEvent, (args) {
|
|
||||||
mediaController.userLogin.value = args['status'];
|
|
||||||
setState(() {
|
|
||||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,22 +27,8 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(toolbarHeight: 30),
|
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
|
||||||
leading: null,
|
|
||||||
title: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 20),
|
|
||||||
child: Text(
|
|
||||||
'媒体库',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
for (var i in mediaController.list) ...[
|
for (var i in mediaController.list) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => i['onTap'](),
|
onTap: () => i['onTap'](),
|
||||||
@ -76,203 +49,8 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Obx(() => mediaController.userLogin.value
|
|
||||||
? favFolder(mediaController, context)
|
|
||||||
: const SizedBox())
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget favFolder(mediaController, context) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
Divider(
|
|
||||||
height: 35,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
leading: null,
|
|
||||||
dense: true,
|
|
||||||
title: Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 10),
|
|
||||||
child: Obx(
|
|
||||||
() => Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '收藏夹 ',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleMedium!.fontSize,
|
|
||||||
fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
if (mediaController.favFolderData.value.count != null)
|
|
||||||
TextSpan(
|
|
||||||
text: mediaController.favFolderData.value.count
|
|
||||||
.toString(),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
trailing: IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
setState(() {
|
|
||||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.refresh,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// const SizedBox(height: 10),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 170 * MediaQuery.of(context).textScaleFactor,
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
if (data['status']) {
|
|
||||||
List favFolderList =
|
|
||||||
mediaController.favFolderData.value.list!;
|
|
||||||
int favFolderCount =
|
|
||||||
mediaController.favFolderData.value.count!;
|
|
||||||
bool flag = favFolderCount > favFolderList.length;
|
|
||||||
return Obx(() => ListView.builder(
|
|
||||||
itemCount:
|
|
||||||
mediaController.favFolderData.value.list!.length +
|
|
||||||
(flag ? 1 : 0),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
if (flag && index == favFolderList.length) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
right: 14, bottom: 35),
|
|
||||||
child: Center(
|
|
||||||
child: IconButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(
|
|
||||||
EdgeInsets.zero),
|
|
||||||
backgroundColor:
|
|
||||||
MaterialStateProperty.resolveWith(
|
|
||||||
(states) {
|
|
||||||
return Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer
|
|
||||||
.withOpacity(0.5);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
onPressed: () => Get.toNamed('/fav'),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.arrow_forward_ios,
|
|
||||||
size: 18,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return FavFolderItem(
|
|
||||||
item: mediaController
|
|
||||||
.favFolderData.value.list![index],
|
|
||||||
index: index);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
return SizedBox(
|
|
||||||
height: 160,
|
|
||||||
child: Center(child: Text(data['msg'])),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FavFolderItem extends StatelessWidget {
|
|
||||||
const FavFolderItem({super.key, this.item, this.index});
|
|
||||||
final FavFolderItemData? item;
|
|
||||||
final int? index;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String heroTag = Utils.makeHeroTag(item!.fid);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () => Get.toNamed('/favDetail',
|
|
||||||
arguments: item,
|
|
||||||
parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Container(
|
|
||||||
width: 180,
|
|
||||||
height: 110,
|
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
offset: const Offset(4, -12), // 阴影与容器的距离
|
|
||||||
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
|
|
||||||
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, BoxConstraints box) {
|
|
||||||
return Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: item!.cover,
|
|
||||||
width: box.maxWidth,
|
|
||||||
height: box.maxHeight,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' ${item!.title}',
|
|
||||||
overflow: TextOverflow.fade,
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' 共${item!.mediaCount}条视频',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall!
|
|
||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -142,7 +142,8 @@ class _ArchivePanelState extends State<ArchivePanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||||
final ArchiveController _archiveController = Get.put(ArchiveController());
|
final ArchiveController _archiveController =
|
||||||
|
Get.put(ArchiveController(), tag: Get.arguments['heroTag']);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class MemberController extends GetxController {
|
|||||||
// 投稿列表
|
// 投稿列表
|
||||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -70,11 +71,11 @@ class MemberController extends GetxController {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
content: Text(memberInfo.value.isFollowed! ? '取消关注该用户?' : '关注该用户?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: const Text('点错了')),
|
child: const Text('取消')),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await VideoHttp.relationMod(
|
await VideoHttp.relationMod(
|
||||||
@ -95,4 +96,48 @@ class MemberController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拉黑用户
|
||||||
|
Future blockUser(int mid) async {
|
||||||
|
if (userInfoCache.get('userInfoCache') == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认拉黑该用户?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var res = await VideoHttp.relationMod(
|
||||||
|
mid: mid,
|
||||||
|
act: 5,
|
||||||
|
reSrc: 11,
|
||||||
|
);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res['status']) {
|
||||||
|
List<int> blackMidsList = setting
|
||||||
|
.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
|
blackMidsList.add(mid);
|
||||||
|
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -118,7 +118,8 @@ class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||||
final _dynamicController = Get.put(MemberDynamicPanelController());
|
final _dynamicController =
|
||||||
|
Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
|||||||
@ -102,40 +102,79 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
|
// PopupMenuButton(
|
||||||
|
// icon: const Icon(Icons.more_vert),
|
||||||
|
// itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
// if (_memberController.ownerMid !=
|
||||||
|
// _memberController.mid) ...[
|
||||||
|
// PopupMenuItem(
|
||||||
|
// onTap: () => _memberController.blockUser(),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// const Icon(Icons.block, size: 19),
|
||||||
|
// const SizedBox(width: 10),
|
||||||
|
// Text(_memberController.attribute.value != 128
|
||||||
|
// ? '加入黑名单'
|
||||||
|
// : '移除黑名单'),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// PopupMenuItem(
|
||||||
|
// onTap: () => _memberController.shareUser(),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// const Icon(Icons.share_outlined, size: 19),
|
||||||
|
// const SizedBox(width: 10),
|
||||||
|
// Text(_memberController.ownerMid !=
|
||||||
|
// _memberController.mid
|
||||||
|
// ? '分享UP主'
|
||||||
|
// : '分享我的主页'),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Stack(
|
background: Stack(
|
||||||
children: [
|
children: [
|
||||||
if (_memberController.face != null)
|
// if (_memberController.face != null)
|
||||||
Positioned.fill(
|
// Positioned.fill(
|
||||||
bottom: 10,
|
// bottom: 10,
|
||||||
child: Container(
|
// child: Container(
|
||||||
decoration: BoxDecoration(
|
// decoration: BoxDecoration(
|
||||||
image: DecorationImage(
|
// image: DecorationImage(
|
||||||
fit: BoxFit.fitWidth,
|
// fit: BoxFit.fitWidth,
|
||||||
image: NetworkImage(_memberController.face!),
|
// image: NetworkImage(_memberController.face!),
|
||||||
alignment: Alignment.topCenter,
|
// alignment: Alignment.topCenter,
|
||||||
isAntiAlias: true,
|
// isAntiAlias: true,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
foregroundDecoration: BoxDecoration(
|
// foregroundDecoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
// gradient: LinearGradient(
|
||||||
colors: [
|
// colors: [
|
||||||
Theme.of(context)
|
// Theme.of(context)
|
||||||
.colorScheme
|
// .colorScheme
|
||||||
.background
|
// .background
|
||||||
.withOpacity(0.44),
|
// .withOpacity(0.44),
|
||||||
Theme.of(context).colorScheme.background,
|
// Theme.of(context).colorScheme.background,
|
||||||
],
|
// ],
|
||||||
begin: Alignment.topCenter,
|
// begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter,
|
// end: Alignment.bottomCenter,
|
||||||
stops: const [0.0, 0.46],
|
// stops: const [0.0, 0.46],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@ -179,109 +218,109 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
fontWeight:
|
fontWeight:
|
||||||
FontWeight.bold),
|
FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 2),
|
// const SizedBox(width: 2),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sex ==
|
// .memberInfo.value.sex ==
|
||||||
'女')
|
// '女')
|
||||||
const Icon(
|
// const Icon(
|
||||||
FontAwesomeIcons.venus,
|
// FontAwesomeIcons.venus,
|
||||||
size: 14,
|
// size: 14,
|
||||||
color: Colors.pink,
|
// color: Colors.pink,
|
||||||
),
|
// ),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sex ==
|
// .memberInfo.value.sex ==
|
||||||
'男')
|
// '男')
|
||||||
const Icon(
|
// const Icon(
|
||||||
FontAwesomeIcons.mars,
|
// FontAwesomeIcons.mars,
|
||||||
size: 14,
|
// size: 14,
|
||||||
color: Colors.blue,
|
// color: Colors.blue,
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
Image.asset(
|
// Image.asset(
|
||||||
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
// 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
||||||
height: 11,
|
// height: 11,
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 6),
|
// const SizedBox(width: 6),
|
||||||
if (_memberController.memberInfo
|
// if (_memberController.memberInfo
|
||||||
.value.vip!.status ==
|
// .value.vip!.status ==
|
||||||
1 &&
|
// 1 &&
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans'] !=
|
// 'img_label_uri_hans'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
Image.network(
|
// Image.network(
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans'],
|
// 'img_label_uri_hans'],
|
||||||
height: 20,
|
// height: 20,
|
||||||
),
|
// ),
|
||||||
] else if (_memberController
|
// ] else if (_memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.vip!
|
// .vip!
|
||||||
.status ==
|
// .status ==
|
||||||
1 &&
|
// 1 &&
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans_static'] !=
|
// 'img_label_uri_hans_static'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
Image.network(
|
// Image.network(
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans_static'],
|
// 'img_label_uri_hans_static'],
|
||||||
height: 20,
|
// height: 20,
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_memberController.memberInfo.value
|
// if (_memberController.memberInfo.value
|
||||||
.official!['title'] !=
|
// .official!['title'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
const SizedBox(height: 6),
|
// const SizedBox(height: 6),
|
||||||
Text.rich(
|
// Text.rich(
|
||||||
maxLines: 2,
|
// maxLines: 2,
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
text: _memberController
|
// text: _memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.official!['role'] ==
|
// .official!['role'] ==
|
||||||
1
|
// 1
|
||||||
? '个人认证:'
|
// ? '个人认证:'
|
||||||
: '企业认证:',
|
// : '企业认证:',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
color: Theme.of(context)
|
// color: Theme.of(context)
|
||||||
.primaryColor,
|
// .primaryColor,
|
||||||
),
|
// ),
|
||||||
children: [
|
// children: [
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
text: _memberController
|
// text: _memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.official!['title'],
|
// .official!['title'],
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
softWrap: true,
|
// softWrap: true,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sign !=
|
// .memberInfo.value.sign !=
|
||||||
'')
|
// '')
|
||||||
SelectableRegion(
|
// SelectableRegion(
|
||||||
magnifierConfiguration:
|
// magnifierConfiguration:
|
||||||
const TextMagnifierConfiguration(),
|
// const TextMagnifierConfiguration(),
|
||||||
focusNode: FocusNode(),
|
// focusNode: FocusNode(),
|
||||||
selectionControls:
|
// selectionControls:
|
||||||
MaterialTextSelectionControls(),
|
// MaterialTextSelectionControls(),
|
||||||
child: Text(
|
// child: Text(
|
||||||
_memberController
|
// _memberController
|
||||||
.memberInfo.value.sign!,
|
// .memberInfo.value.sign!,
|
||||||
textAlign: TextAlign.left,
|
// textAlign: TextAlign.left,
|
||||||
maxLines: 2,
|
// maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
// overflow: TextOverflow.ellipsis,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -308,28 +347,29 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||||
},
|
},
|
||||||
onlyOneScrollInBody: true,
|
onlyOneScrollInBody: true,
|
||||||
body: Column(
|
// body: Column(
|
||||||
children: [
|
// children: [
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
height: 50,
|
// height: 50,
|
||||||
child: TabBar(controller: _tabController, tabs: const [
|
// child: TabBar(controller: _tabController, tabs: const [
|
||||||
Tab(text: '主页'),
|
// Tab(text: '主页'),
|
||||||
Tab(text: '动态'),
|
// Tab(text: '动态'),
|
||||||
Tab(text: '投稿'),
|
// Tab(text: '投稿'),
|
||||||
]),
|
// ]),
|
||||||
),
|
// ),
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: TabBarView(
|
// child: TabBarView(
|
||||||
controller: _tabController,
|
// controller: _tabController,
|
||||||
children: const [
|
// children: const [
|
||||||
Text('主页'),
|
// Text('主页'),
|
||||||
MemberDynamicPanel(),
|
// MemberDynamicPanel(),
|
||||||
ArchivePanel(),
|
// ArchivePanel(),
|
||||||
],
|
// ],
|
||||||
))
|
// ))
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
|
body: ArchivePanel(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,79 +76,79 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
// padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
child: Row(
|
// child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
// mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
// children: [
|
||||||
InkWell(
|
// InkWell(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Get.toNamed(
|
// Get.toNamed(
|
||||||
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
// '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
||||||
},
|
// },
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
!loadingStatus
|
// !loadingStatus
|
||||||
? ctr.userStat!['following'].toString()
|
// ? ctr.userStat!['following'].toString()
|
||||||
: '-',
|
// : '-',
|
||||||
style: const TextStyle(
|
// style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
// fontWeight: FontWeight.bold),
|
||||||
),
|
// ),
|
||||||
Text(
|
// Text(
|
||||||
'关注',
|
// '关注',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
// fontSize: Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.labelMedium!
|
// .labelMedium!
|
||||||
.fontSize),
|
// .fontSize),
|
||||||
)
|
// )
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
InkWell(
|
// InkWell(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Get.toNamed(
|
// Get.toNamed(
|
||||||
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
// '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
||||||
},
|
// },
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
!loadingStatus
|
// !loadingStatus
|
||||||
? Utils.numFormat(
|
// ? Utils.numFormat(
|
||||||
ctr.userStat!['follower'],
|
// ctr.userStat!['follower'],
|
||||||
)
|
// )
|
||||||
: '-',
|
// : '-',
|
||||||
style: const TextStyle(
|
// style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold)),
|
// fontWeight: FontWeight.bold)),
|
||||||
Text('粉丝',
|
// Text('粉丝',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
// fontSize: Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.labelMedium!
|
// .labelMedium!
|
||||||
.fontSize))
|
// .fontSize))
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
Column(
|
// Column(
|
||||||
children: [
|
// children: [
|
||||||
const Text('-',
|
// const Text('-',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
// style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
Text(
|
// Text(
|
||||||
'获赞',
|
// '获赞',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
// fontSize: Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.labelMedium!
|
// .labelMedium!
|
||||||
.fontSize),
|
// .fontSize),
|
||||||
)
|
// )
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(height: 10),
|
// const SizedBox(height: 10),
|
||||||
if (ctr.ownerMid != ctr.mid) ...[
|
if (ctr.ownerMid != ctr.mid) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
94
lib/pages/member_search/controller.dart
Normal file
94
lib/pages/member_search/controller.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
|
|
||||||
|
class MemberSearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int archivePn = 1;
|
||||||
|
int archiveCount = 0;
|
||||||
|
RxList<VListItemModel> archiveList = <VListItemModel>[].obs;
|
||||||
|
int dynamic_pn = 1;
|
||||||
|
RxList<VListItemModel> dynamicList = <VListItemModel>[].obs;
|
||||||
|
|
||||||
|
int ps = 30;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
|
uname.value = Get.parameters['uname']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
archivePn = 1;
|
||||||
|
searchArchives();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchArchives({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.memberArchive(
|
||||||
|
mid: mid,
|
||||||
|
pn: archivePn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
order: 'pubdate',
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' || archivePn == 1) {
|
||||||
|
archiveList.value = res['data'].list.vlist;
|
||||||
|
} else {
|
||||||
|
archiveList.addAll(res['data'].list.vlist);
|
||||||
|
}
|
||||||
|
archiveCount = res['data'].page['count'];
|
||||||
|
if (archiveList.length == archiveCount) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
archivePn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
if (res['data'].list.vlist.isEmpty) {
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索动态
|
||||||
|
Future searchDynamic() async {}
|
||||||
|
|
||||||
|
//
|
||||||
|
onLoad() {
|
||||||
|
searchArchives(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/member_search/index.dart
Normal file
4
lib/pages/member_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library member_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
208
lib/pages/member_search/view.dart
Normal file
208
lib/pages/member_search/view.dart
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MemberSearchPage extends StatefulWidget {
|
||||||
|
const MemberSearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemberSearchPage> createState() => _MemberSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberSearchPageState extends State<MemberSearchPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final MemberSearchController _memberSearchCtr =
|
||||||
|
Get.put(MemberSearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _memberSearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_memberSearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// _tabController = TabController(length: 2, vsync: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// _tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _memberSearchCtr.submit(),
|
||||||
|
icon: const Icon(CupertinoIcons.search, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _memberSearchCtr.searchFocusNode,
|
||||||
|
controller: _memberSearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _memberSearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _memberSearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _memberSearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _memberSearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _memberSearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text('搜索「${_memberSearchCtr.uname.value}」的视频'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
// TabBar(
|
||||||
|
// controller: _tabController,
|
||||||
|
// tabs: const [
|
||||||
|
// Tab(text: "视频"),
|
||||||
|
// Tab(text: "动态"),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
// TabBarView(
|
||||||
|
// controller: _tabController,
|
||||||
|
// children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: _memberSearchCtr.searchArchives(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _memberSearchCtr.archiveList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_memberSearchCtr.archiveList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_memberSearchCtr
|
||||||
|
.archiveList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_memberSearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return VideoCardH(
|
||||||
|
videoItem: _memberSearchCtr
|
||||||
|
.archiveList[index]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _memberSearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 400,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'没有搜索到相关内容\n请尝试别的搜索词',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,6 @@ 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/models/common/theme_type.dart';
|
import 'package:pilipala/models/common/theme_type.dart';
|
||||||
import 'package:pilipala/models/user/info.dart';
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/event_bus.dart';
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class MinePage extends StatefulWidget {
|
class MinePage extends StatefulWidget {
|
||||||
@ -20,14 +19,13 @@ class MinePage extends StatefulWidget {
|
|||||||
class _MinePageState extends State<MinePage> {
|
class _MinePageState extends State<MinePage> {
|
||||||
final MineController mineController = Get.put(MineController());
|
final MineController mineController = Get.put(MineController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
EventBus eventBus = EventBus();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = mineController.queryUserInfo();
|
_futureBuilderFuture = mineController.queryUserInfo();
|
||||||
eventBus.on(EventName.loginEvent, (args) {
|
|
||||||
mineController.userLogin.value = args['status'];
|
mineController.userLogin.listen((status) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_futureBuilderFuture = mineController.queryUserInfo();
|
_futureBuilderFuture = mineController.queryUserInfo();
|
||||||
@ -74,12 +72,8 @@ class _MinePageState extends State<MinePage> {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: LayoutBuilder(
|
body: SingleChildScrollView(
|
||||||
builder: (context, constraint) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: SizedBox(
|
|
||||||
height: constraint.maxHeight,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
@ -87,9 +81,11 @@ class _MinePageState extends State<MinePage> {
|
|||||||
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 SizedBox();
|
||||||
|
}
|
||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
return Obx(
|
return Obx(() => userInfoBuild(mineController, context));
|
||||||
() => userInfoBuild(mineController, context));
|
|
||||||
} else {
|
} else {
|
||||||
return userInfoBuild(mineController, context);
|
return userInfoBuild(mineController, context);
|
||||||
}
|
}
|
||||||
@ -102,9 +98,6 @@ class _MinePageState extends State<MinePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget userInfoBuild(_mineController, context) {
|
Widget userInfoBuild(_mineController, context) {
|
||||||
@ -137,115 +130,9 @@ class _MinePageState extends State<MinePage> {
|
|||||||
_mineController.userInfo.value.uname ?? '点击头像登录',
|
_mineController.userInfo.value.uname ?? '点击头像登录',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png',
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 5),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text.rich(TextSpan(children: [
|
|
||||||
TextSpan(
|
|
||||||
text: '硬币: ',
|
|
||||||
style:
|
|
||||||
TextStyle(color: Theme.of(context).colorScheme.outline)),
|
|
||||||
TextSpan(
|
|
||||||
text: (_mineController.userInfo.value.money ?? 'pilipala')
|
|
||||||
.toString(),
|
|
||||||
style:
|
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary)),
|
|
||||||
]))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 25),
|
const SizedBox(height: 25),
|
||||||
if (_mineController.userInfo.value.levelInfo != null) ...[
|
|
||||||
LayoutBuilder(
|
|
||||||
builder: (context, BoxConstraints box) {
|
|
||||||
LevelInfo levelInfo = _mineController.userInfo.value.levelInfo;
|
|
||||||
return SizedBox(
|
|
||||||
width: box.maxWidth,
|
|
||||||
height: 24,
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
height: 24,
|
|
||||||
constraints:
|
|
||||||
const BoxConstraints(minWidth: 100), // 设置最小宽度为100
|
|
||||||
width: box.maxWidth *
|
|
||||||
(1 - (levelInfo.currentExp! / levelInfo.nextExp!)),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'${levelInfo.currentExp!}/${levelInfo.nextExp!}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 23,
|
|
||||||
left: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
width: box.maxWidth *
|
|
||||||
(_mineController
|
|
||||||
.userInfo.value.levelInfo!.currentExp! /
|
|
||||||
_mineController
|
|
||||||
.userInfo.value.levelInfo!.nextExp!),
|
|
||||||
height: 1,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// LayoutBuilder(
|
|
||||||
// builder: (context, BoxConstraints box) {
|
|
||||||
// return Container(
|
|
||||||
// width: box.maxWidth,
|
|
||||||
// height: 1,
|
|
||||||
// color: Theme.of(context).colorScheme.onInverseSurface,
|
|
||||||
// child: Stack(
|
|
||||||
// children: [
|
|
||||||
// Positioned(
|
|
||||||
// top: 0,
|
|
||||||
// left: 0,
|
|
||||||
// bottom: 0,
|
|
||||||
// child: Container(
|
|
||||||
// width: box.maxWidth *
|
|
||||||
// (_mineController
|
|
||||||
// .userInfo.value.levelInfo!.currentExp! /
|
|
||||||
// _mineController
|
|
||||||
// .userInfo.value.levelInfo!.nextExp!),
|
|
||||||
// height: 1,
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// borderRadius: BorderRadius.circular(4),
|
|
||||||
// color: Theme.of(context).colorScheme.primary,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 30),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user