Compare commits

..

2 Commits

Author SHA1 Message Date
57f7628795 Merge branch 'main' into feature-customBottomControl 2024-03-23 18:13:47 +08:00
ab10223eca feat: 接收自定义组件传入 2024-03-11 23:03:50 +08:00
205 changed files with 5004 additions and 9659 deletions

View File

@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- "x-main" - "main"
paths-ignore: paths-ignore:
- "**.md" - "**.md"
- "**.txt" - "**.txt"
@ -205,4 +205,4 @@ jobs:
method: sendFile method: sendFile
path: Pilipala-Beta/* path: Pilipala-Beta/*
parse_mode: Markdown parse_mode: Markdown
context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}]" context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}](${{ github.event.head_commit.url }})"

View File

@ -2,6 +2,8 @@
<img width="200" height="200" src="https://github.com/guozhigq/pilipala/blob/main/assets/images/logo/logo_android.png"> <img width="200" height="200" src="https://github.com/guozhigq/pilipala/blob/main/assets/images/logo/logo_android.png">
</div> </div>
<div align="center"> <div align="center">
<h1>PiliPala</h1> <h1>PiliPala</h1>
<div align="center"> <div align="center">
@ -9,9 +11,8 @@
![GitHub repo size](https://img.shields.io/github/repo-size/guozhigq/pilipala) ![GitHub repo size](https://img.shields.io/github/repo-size/guozhigq/pilipala)
![GitHub Repo stars](https://img.shields.io/github/stars/guozhigq/pilipala) ![GitHub Repo stars](https://img.shields.io/github/stars/guozhigq/pilipala)
![GitHub all releases](https://img.shields.io/github/downloads/guozhigq/pilipala/total) ![GitHub all releases](https://img.shields.io/github/downloads/guozhigq/pilipala/total)
</div> </div>
<p>使用 Flutter 开发的 BiliBili 第三方客户端</p> <p>使用Flutter开发的BiliBili第三方客户端</p>
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" /> <img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/510shots_so.png" width="32%" alt="home" />
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" /> <img src="https://github.com/guozhigq/pilipala/blob/main/assets/screenshots/174shots_so.png" width="32%" alt="home" />
@ -22,41 +23,46 @@
</div> </div>
## 开发环境 ## 开发环境
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
Xcode 13.4 不支持 ```auto_orientation```,请注释相关代码
```bash ```bash
[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale [] Flutter (Channel stable, 3.16.4, on macOS 14.1.2 23B92 darwin-arm64, locale
zh-Hans-CN) zh-Hans-CN)
[] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[] Xcode - develop for iOS and macOS (Xcode 15.1) [] Xcode - develop for iOS and macOS (Xcode 15.1)
[] Chrome - develop for the web [] Chrome - develop for the web
[] Android Studio (version 2022.3) [] Android Studio (version 2022.3)
[✓] VS Code (version 1.87.2) [] VS Code (version 1.85.1)
[] Connected device (3 available) [] Connected device (3 available)
[] Network resources [] Network resources
``` ```
<br/>
## 技术交流 ## 技术交流
Telegram: https://t.me/+lm_oOVmF0RJiODk1 Telegram: https://t.me/+lm_oOVmF0RJiODk1
QQ频道: https://pd.qq.com/s/365esodk3
Telegram Beta 版本:@PiliPala_Beta
QQ 频道: https://pd.qq.com/s/365esodk3 <br/>
## 功能 ## 功能
目前着重移动端 (Android、iOS)暂时没有适配桌面端、Pad 端、手表端等 目前着重移动端(Android、iOS)暂时没有适配桌面端、Pad端、手表端等
<br/>
现有功能及[开发计划](https://github.com/users/guozhigq/projects/5) 现有功能及[开发计划](https://github.com/users/guozhigq/projects/5)
- [x] 推荐视频列表 (app 端)
- [x] 推荐视频列表(app端)
- [x] 最热视频列表 - [x] 最热视频列表
- [x] 热门直播 - [x] 热门直播
- [x] 番剧列表 - [x] 番剧列表
- [x] 屏蔽黑名单内用户视频 - [x] 屏蔽黑名单内用户视频
- [x] 排行榜
- [x] 用户相关 - [x] 用户相关
- [x] 粉丝、关注用户、拉黑用户查看 - [x] 粉丝、关注用户、拉黑用户查看
@ -66,13 +72,11 @@ QQ 频道: https://pd.qq.com/s/365esodk3
- [x] 稍后再看 - [x] 稍后再看
- [x] 观看记录 - [x] 观看记录
- [x] 我的收藏 - [x] 我的收藏
- [x] 黑名单管理
- [x] 动态相关 - [x] 动态相关
- [x] 全部、投稿、番剧分类查看 - [x] 全部、投稿、番剧分类查看
- [x] 动态评论查看 - [x] 动态评论查看
- [x] 动态评论回复功能 - [x] 动态评论回复功能
- [x] 动态未读标记
- [x] 视频播放相关 - [x] 视频播放相关
- [x] 双击快进/快退 - [x] 双击快进/快退
@ -81,13 +85,13 @@ QQ 频道: https://pd.qq.com/s/365esodk3
- [x] 垂直方向上滑全屏、下滑退出全屏 - [x] 垂直方向上滑全屏、下滑退出全屏
- [x] 水平方向手势快进/快退 - [x] 水平方向手势快进/快退
- [x] 全屏方向设置 - [x] 全屏方向设置
- [x] 倍速选择/长按 2 倍速 - [x] 倍速选择/长按2倍速
- [x] 硬件加速 (视机型而定) - [x] 硬件加速视机型而定
- [x] 画质选择 (高清画质未解锁) - [x] 画质选择高清画质未解锁
- [x] 音质选择 (视视频而定) - [x] 音质选择视视频而定
- [x] 解码格式选择 (视视频而定) - [x] 解码格式选择视视频而定
- [x] 弹幕 - [x] 弹幕
- [x] 字幕 - [ ] 字幕
- [x] 记忆播放 - [x] 记忆播放
- [x] 视频比例:高度/宽度适应、填充、包含等 - [x] 视频比例:高度/宽度适应、填充、包含等
@ -99,12 +103,12 @@ QQ 频道: https://pd.qq.com/s/365esodk3
- [x] 视频搜索排序、按时长筛选 - [x] 视频搜索排序、按时长筛选
- [x] 视频详情页相关 - [x] 视频详情页相关
- [x] 视频选集 (分 p) 切换 - [x] 视频选集(分p)切换
- [x] 点赞、投币、收藏/取消收藏 - [x] 点赞、投币、收藏/取消收藏
- [x] 相关视频查看 - [x] 相关视频查看
- [x] 评论用户身份标识 - [x] 评论用户身份标识
- [x] 评论 (排序) 查看、二楼评论查看 - [x] 评论(排序)查看、二楼评论查看
- [x] 主楼、二楼评论/表情回复功能 - [x] 主楼、二楼评论回复功能
- [x] 评论点赞 - [x] 评论点赞
- [x] 评论笔记图片查看、保存 - [x] 评论笔记图片查看、保存
@ -112,30 +116,29 @@ QQ 频道: https://pd.qq.com/s/365esodk3
- [x] 画质、音质、解码方式预设 - [x] 画质、音质、解码方式预设
- [x] 图片质量设定 - [x] 图片质量设定
- [x] 主题模式:亮色/暗色/跟随系统 - [x] 主题模式:亮色/暗色/跟随系统
- [x] 震动反馈 (可选) - [x] 震动反馈(可选)
- [x] 高帧率 - [x] 高帧率
- [x] 自动全屏 - [x] 自动全屏
- [ ] 等等 - [ ] 等等
<br/>
## 下载 ## 下载
可以通过右侧 Releases 进行下载或拉取代码到本地进行编译 可以通过右侧release进行下载或拉取代码到本地进行编译
### 从 F-Droid 安装 <br/>
<a href="https://f-droid.org/packages/com.guozhigq.pilipala">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on-zh-cn.png"
alt="Get it on F-Droid"
height="80">
</a>
## 声明 ## 声明
此项目 (PiliPala) 是个人为了兴趣而开发, 仅用于学习和测试。 此项目PiliPala是个人为了兴趣而开发, 仅用于学习和测试。
所用 API 皆从官方网站收集, 不提供任何破解内容。 所用API皆从官方网站收集, 不提供任何破解内容。
感谢使用 感谢使用
<br/>
## 致谢 ## 致谢
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect) - [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
@ -143,3 +146,7 @@ QQ 频道: https://pd.qq.com/s/365esodk3
- [media-kit](https://github.com/media-kit/media-kit) - [media-kit](https://github.com/media-kit/media-kit)
- [dio](https://pub.dev/packages/dio) - [dio](https://pub.dev/packages/dio)
- 等等 - 等等
<br/>
<br/>
<br/>

View File

@ -45,7 +45,7 @@
android:fullBackupContent="false" android:fullBackupContent="false"
tools:replace="android:allowBackup"> tools:replace="android:allowBackup">
<activity <activity
android:name="com.guozhigq.pilipala.MainActivity" android:name="com.ryanheise.audioservice.AudioServiceActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"

View File

@ -1,8 +1,6 @@
package com.guozhigq.pilipala package com.guozhigq.pilipala
// import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import com.ryanheise.audioservice.AudioServiceActivity;
class MainActivity: AudioServiceActivity() {
class MainActivity: FlutterActivity() {
} }

View File

@ -1,5 +1,5 @@
buildscript { buildscript {
ext.kotlin_version = '1.9.0' ext.kotlin_version = '1.7.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,27 +0,0 @@
## 1.0.22
### 功能
+ 字幕
+ 全屏时选集
+ 动态转发
+ 评论视频并转发
+ 收藏夹删除
+ 合集显示封面
+ 底部导航栏编辑、排序功能
+ 历史记录进度条展示
+ 直播画质切换
+ 排行榜功能
+ 视频详情页推荐视频开关
+ 显示联合投稿up
### 修复
+ 收藏夹个数错误
+ 封面保存权限问题
+ 合集最后1p未展示
+ up主页关注按钮触发灰屏
### 优化
+ 视频简介查看逻辑
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -1,14 +0,0 @@
## 1.0.23
### 功能
+ 封面下载
### 修复
+ 全屏问题
+ 视频播放器灰屏问题
+ 评论区点击区域问题
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -1,15 +0,0 @@
## 1.0.23
### 功能
+ 封面下载
### 修复
+ 全屏问题
+ 视频播放器灰屏问题
+ 评论区点击区域问题
+ 动态详情跳转异常问题
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -1,23 +0,0 @@
## 1.0.24
### 功能
+ 私信功能
+ 回复我的、收到的赞查看
+ 新的登录方式
+ 全屏选集
+ 一键三连
+ 按分区搜索
### 优化
+ 页面跳转动画
+ 评论区跳转
### 修复
+ 音画不同步问题
+ 分集字幕未同步
+ 多语言字幕
+ 弹幕设置未生效
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>12.0</string> <string>11.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -9,7 +9,7 @@ PODS:
- Flutter - Flutter
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- FlutterMacOS - ReachabilitySwift
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
@ -23,7 +23,7 @@ PODS:
- FMDB (2.7.5): - FMDB (2.7.5):
- FMDB/standard (= 2.7.5) - FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5) - FMDB/standard (2.7.5)
- gt3_flutter_plugin (0.0.9): - gt3_flutter_plugin (0.0.8):
- Flutter - Flutter
- GT3Captcha-iOS - GT3Captcha-iOS
- GT3Captcha-iOS (0.15.8.3) - GT3Captcha-iOS (0.15.8.3)
@ -38,8 +38,9 @@ PODS:
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.1.1):
- Flutter - Flutter
- ReachabilitySwift (5.0.0)
- saver_gallery (0.0.1): - saver_gallery (0.0.1):
- Flutter - Flutter
- screen_brightness_ios (0.1.0): - screen_brightness_ios (0.1.0):
@ -70,7 +71,7 @@ DEPENDENCIES:
- audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`) - auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
@ -99,6 +100,7 @@ SPEC REPOS:
trunk: trunk:
- FMDB - FMDB
- GT3Captcha-iOS - GT3Captcha-iOS
- ReachabilitySwift
- Toast - Toast
EXTERNAL SOURCES: EXTERNAL SOURCES:
@ -111,7 +113,7 @@ EXTERNAL SOURCES:
auto_orientation: auto_orientation:
:path: ".symlinks/plugins/auto_orientation/ios" :path: ".symlinks/plugins/auto_orientation/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin" :path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus: device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
@ -164,21 +166,22 @@ SPEC CHECKSUMS:
audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529 flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78 saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
@ -186,11 +189,11 @@ SPEC CHECKSUMS:
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446 status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44 system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
Toast: ec33c32b8688982cecc6348adeae667c1b9938da Toast: ec33c32b8688982cecc6348adeae667c1b9938da
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7 webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36 webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be

View File

@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1510; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1510" LastUpgradeVersion = "1430"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -49,8 +49,6 @@
<true/> <true/>
<key>NSPhotoLibraryAddUsageDescription</key> <key>NSPhotoLibraryAddUsageDescription</key>
<string>请允许APP保存图片到相册</string> <string>请允许APP保存图片到相册</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>请允许APP保存图片到相册</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相册</string> <string>App需要您的同意,才能访问相册</string>
<key>NSAppleMusicUsageDescription</key> <key>NSAppleMusicUsageDescription</key>

View File

@ -1,172 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../models/common/video_episode_type.dart';
class EpisodeBottomSheet {
final List<dynamic> episodes;
final int currentCid;
final dynamic dataType;
final BuildContext context;
final Function changeFucCall;
final int? cid;
final double? sheetHeight;
bool isFullScreen = false;
EpisodeBottomSheet({
required this.episodes,
required this.currentCid,
required this.dataType,
required this.context,
required this.changeFucCall,
this.cid,
this.sheetHeight,
this.isFullScreen = false,
});
Widget buildEpisodeListItem(
dynamic episode,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
Color onSurface = Theme.of(context).colorScheme.onSurface;
String title = '';
switch (dataType) {
case VideoEpidoesType.videoEpisode:
title = episode.title;
break;
case VideoEpidoesType.videoPart:
title = episode.pagePart;
break;
case VideoEpidoesType.bangumiEpisode:
title = '${episode.title}${episode.longTitle!}';
break;
}
return isFullScreen || episode?.cover == null || episode?.cover == ''
? ListTile(
onTap: () {
SmartDialog.showToast('切换至「$title');
changeFucCall.call(episode, index);
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(title,
style: TextStyle(
fontSize: 14,
color: isCurrentIndex ? primary : onSurface,
)))
: InkWell(
onTap: () {
SmartDialog.showToast('切换至「$title');
changeFucCall.call(episode, index);
},
child: Padding(
padding:
const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
child: Row(
children: [
NetworkImgLayer(
width: 130, height: 75, src: episode?.cover ?? ''),
const SizedBox(width: 10),
Expanded(
child: Text(
title,
maxLines: 2,
style: TextStyle(
fontSize: 14,
color: isCurrentIndex ? primary : onSurface,
),
),
),
],
),
),
);
}
Widget buildTitle() {
return AppBar(
toolbarHeight: 45,
automaticallyImplyLeading: false,
centerTitle: false,
title: Text(
'合集(${episodes.length}',
style: Theme.of(context).textTheme.titleMedium,
),
actions: !isFullScreen
? [
IconButton(
icon: const Icon(Icons.close, size: 20),
onPressed: () => Navigator.pop(context),
),
const SizedBox(width: 14),
]
: null,
);
}
Widget buildShowContent(BuildContext context) {
final ItemScrollController itemScrollController = ItemScrollController();
int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid);
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
itemScrollController.jumpTo(index: currentIndex);
});
return Container(
height: sheetHeight,
color: Theme.of(context).colorScheme.surface,
child: Column(
children: [
buildTitle(),
Expanded(
child: Material(
child: PageStorage(
bucket: PageStorageBucket(),
child: ScrollablePositionedList.builder(
itemScrollController: itemScrollController,
itemCount: episodes.length + 1,
itemBuilder: (BuildContext context, int index) {
bool isLastItem = index == episodes.length;
bool isCurrentIndex = currentIndex == index;
return isLastItem
? SizedBox(
height:
MediaQuery.of(context).padding.bottom + 20,
)
: buildEpisodeListItem(
episodes[index],
index,
isCurrentIndex,
);
},
),
),
),
),
],
),
);
});
}
/// The [BuildContext] of the widget that calls the bottom sheet.
PersistentBottomSheetController show(BuildContext context) {
final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
context: context,
builder: (BuildContext context) {
return buildShowContent(context);
},
);
return btmSheetCtr;
}
}

View File

@ -13,8 +13,8 @@ class Skeleton extends StatelessWidget {
var shimmerGradient = LinearGradient( var shimmerGradient = LinearGradient(
colors: [ colors: [
Colors.transparent, Colors.transparent,
Theme.of(context).colorScheme.surface.withAlpha(10), Theme.of(context).colorScheme.background.withAlpha(10),
Theme.of(context).colorScheme.surface.withAlpha(10), Theme.of(context).colorScheme.background.withAlpha(10),
Colors.transparent, Colors.transparent,
], ],
stops: const [ stops: const [

View File

@ -2,18 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
class HttpError extends StatelessWidget { class HttpError extends StatelessWidget {
const HttpError({ const HttpError(
required this.errMsg, {required this.errMsg, required this.fn, this.btnText, super.key});
required this.fn,
this.btnText,
this.isShowBtn = true,
super.key,
});
final String? errMsg; final String? errMsg;
final Function()? fn; final Function()? fn;
final String? btnText; final String? btnText;
final bool isShowBtn;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -35,7 +29,6 @@ class HttpError extends StatelessWidget {
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
if (isShowBtn)
FilledButton.tonal( FilledButton.tonal(
onPressed: () { onPressed: () {
fn!(); fn!();
@ -47,8 +40,7 @@ class HttpError extends StatelessWidget {
), ),
child: Text( child: Text(
btnText ?? '点击重试', btnText ?? '点击重试',
style: style: TextStyle(color: Theme.of(context).colorScheme.primary),
TextStyle(color: Theme.of(context).colorScheme.primary),
), ),
), ),
], ],

View File

@ -34,11 +34,9 @@ class NetworkImgLayer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int defaultImgQuality = GlobalData().imgQuality; final int defaultImgQuality = GlobalData().imgQuality;
if (src == '' || src == null) {
return placeholder(context);
}
final String imageUrl = final String imageUrl =
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp'; '${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp';
print(imageUrl);
int? memCacheWidth, memCacheHeight; int? memCacheWidth, memCacheHeight;
double aspectRatio = (width / height).toDouble(); double aspectRatio = (width / height).toDouble();

View File

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import '../../utils/download.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class OverlayPop extends StatelessWidget {
const OverlayPop({super.key, this.videoItem, this.closeFn});
final dynamic videoItem;
final Function? closeFn;
@override
Widget build(BuildContext context) {
final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: videoItem.pic! as String,
quality: 100,
),
Positioned(
right: 8,
top: 8,
child: Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
borderRadius:
const BorderRadius.all(Radius.circular(20))),
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () => closeFn!(),
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: Text(
videoItem.title! as String,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: '保存封面图',
onPressed: () async {
await DownloadUtils.downloadImg(
videoItem.pic != null
? videoItem.pic as String
: videoItem.cover as String,
);
// closeFn!();
},
icon: const Icon(Icons.download, size: 20),
)
],
)),
],
),
);
}
}

View File

@ -14,7 +14,7 @@ class StatDanMu extends StatelessWidget {
Map<String, Color> colorObject = { Map<String, Color> colorObject = {
'white': Colors.white, 'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline, 'gray': Theme.of(context).colorScheme.outline,
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8), 'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
}; };
Color color = colorObject[theme]!; Color color = colorObject[theme]!;
return Row( return Row(

View File

@ -14,7 +14,7 @@ class StatView extends StatelessWidget {
Map<String, Color> colorObject = { Map<String, Color> colorObject = {
'white': Colors.white, 'white': Colors.white,
'gray': Theme.of(context).colorScheme.outline, 'gray': Theme.of(context).colorScheme.outline,
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8), 'black': Theme.of(context).colorScheme.onBackground.withOpacity(0.8),
}; };
Color color = colorObject[theme]!; Color color = colorObject[theme]!;
return Row( return Row(

View File

@ -1,11 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/constants.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/route_push.dart';
import 'package:pilipala/utils/url_utils.dart';
import '../../http/search.dart'; import '../../http/search.dart';
import '../../http/user.dart'; import '../../http/user.dart';
import '../../http/video.dart'; import '../../http/video.dart';
@ -21,24 +16,23 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({ const VideoCardH({
super.key, super.key,
required this.videoItem, required this.videoItem,
this.onPressedFn, this.longPress,
this.longPressEnd,
this.source = 'normal', this.source = 'normal',
this.showOwner = true, this.showOwner = true,
this.showView = true, this.showView = true,
this.showDanmaku = true, this.showDanmaku = true,
this.showPubdate = false, this.showPubdate = false,
this.showCharge = false,
}); });
// ignore: prefer_typing_uninitialized_variables // ignore: prefer_typing_uninitialized_variables
final videoItem; final videoItem;
final Function()? onPressedFn; final Function()? longPress;
// normal 推荐, later 稍后再看, search 搜索 final Function()? longPressEnd;
final String source; final String source;
final bool showOwner; final bool showOwner;
final bool showView; final bool showView;
final bool showDanmaku; final bool showDanmaku;
final bool showPubdate; final bool showPubdate;
final bool showCharge;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,27 +43,24 @@ class VideoCardH extends StatelessWidget {
type = videoItem.type; type = videoItem.type;
} catch (_) {} } catch (_) {}
final String heroTag = Utils.makeHeroTag(aid); final String heroTag = Utils.makeHeroTag(aid);
return InkWell( return GestureDetector(
onLongPress: () {
if (longPress != null) {
longPress!();
}
},
// onLongPressEnd: (details) {
// if (longPressEnd != null) {
// longPressEnd!();
// }
// },
child: InkWell(
onTap: () async { onTap: () async {
try { try {
if (type == 'ketang') { if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放'); SmartDialog.showToast('课堂视频暂不支持播放');
return; return;
} }
if (showCharge && videoItem?.typeid == 33) {
final String redirectUrl = await UrlUtils.parseRedirectUrl(
'${HttpString.baseUrl}/video/$bvid/');
final String lastPathSegment = redirectUrl.split('/').last;
if (lastPathSegment.contains('ss')) {
RoutePush.bangumiPush(
Utils.matchNum(lastPathSegment).first, null);
}
if (lastPathSegment.contains('ep')) {
RoutePush.bangumiPush(
null, Utils.matchNum(lastPathSegment).first);
}
return;
}
final int cid = final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid', Get.toNamed('/video?bvid=$bvid&cid=$cid',
@ -78,11 +69,6 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast(err.toString()); SmartDialog.showToast(err.toString());
} }
}, },
onLongPress: () => imageSaveDialog(
context,
videoItem,
SmartDialog.dismiss,
),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5), StyleString.safeSpace, 5, StyleString.safeSpace, 5),
@ -135,13 +121,6 @@ class VideoCardH extends StatelessWidget {
// videoItem.rcmdReason.content != '') // videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context, // pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null), // 6.0, 6.0, null, null),
if (showCharge && videoItem?.isChargingSrc)
const PBadge(
text: '充电专属',
right: 6.0,
top: 6.0,
type: 'primary',
),
], ],
); );
}, },
@ -154,7 +133,6 @@ class VideoCardH extends StatelessWidget {
showView: showView, showView: showView,
showDanmaku: showDanmaku, showDanmaku: showDanmaku,
showPubdate: showPubdate, showPubdate: showPubdate,
onPressedFn: onPressedFn,
) )
], ],
), ),
@ -162,6 +140,7 @@ class VideoCardH extends StatelessWidget {
}, },
), ),
), ),
),
); );
} }
} }
@ -174,7 +153,6 @@ class VideoContent extends StatelessWidget {
final bool showView; final bool showView;
final bool showDanmaku; final bool showDanmaku;
final bool showPubdate; final bool showPubdate;
final Function()? onPressedFn;
const VideoContent({ const VideoContent({
super.key, super.key,
@ -184,7 +162,6 @@ class VideoContent extends StatelessWidget {
this.showView = true, this.showView = true,
this.showDanmaku = true, this.showDanmaku = true,
this.showPubdate = false, this.showPubdate = false,
this.onPressedFn,
}); });
@override @override
@ -195,7 +172,7 @@ class VideoContent extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (source == 'normal' || source == 'later') ...[ if (videoItem.title is String) ...[
Text( Text(
videoItem.title as String, videoItem.title as String,
textAlign: TextAlign.start, textAlign: TextAlign.start,
@ -210,7 +187,7 @@ class VideoContent extends StatelessWidget {
maxLines: 2, maxLines: 2,
text: TextSpan( text: TextSpan(
children: [ children: [
for (final i in videoItem.titleList) ...[ for (final i in videoItem.title) ...[
TextSpan( TextSpan(
text: i['text'] as String, text: i['text'] as String,
style: TextStyle( style: TextStyle(
@ -277,86 +254,82 @@ class VideoContent extends StatelessWidget {
theme: 'gray', theme: 'gray',
danmu: videoItem.stat.danmaku as int, danmu: videoItem.stat.danmaku as int,
), ),
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,
height: 24, height: 24,
child: IconButton( child: PopupMenuButton<String>(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: () {
feedBack();
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(videoItem: videoItem);
},
);
},
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: 14,
), ),
), position: PopupMenuPosition.under,
), // constraints: const BoxConstraints(maxHeight: 35),
if (source == 'later') ...[ onSelected: (String type) {},
IconButton( itemBuilder: (BuildContext context) =>
style: ButtonStyle( <PopupMenuEntry<String>>[
padding: MaterialStateProperty.all(EdgeInsets.zero), PopupMenuItem<String>(
), onTap: () async {
onPressed: () => onPressedFn?.call(), var res = await UserHttp.toViewLater(
icon: Icon( bvid: videoItem.bvid as String);
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
)
],
],
),
],
),
),
);
}
}
class MorePanel extends StatelessWidget {
final dynamic videoItem;
const MorePanel({super.key, required this.videoItem});
Future<dynamic> menuActionHandler(String type) async {
switch (type) {
case 'block':
blockUser();
break;
case 'watchLater':
var res = await UserHttp.toViewLater(bvid: videoItem.bvid as String);
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
Get.back(); },
break; value: 'pause',
default: height: 40,
} child: const Row(
} children: [
Icon(Icons.watch_later_outlined, size: 16),
void blockUser() async { SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
const PopupMenuDivider(),
PopupMenuItem<String>(
onTap: () 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: Text('确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' content: Text(
'确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'), '\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => SmartDialog.dismiss(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
), ),
), ),
TextButton( TextButton(
@ -367,7 +340,9 @@ class MorePanel extends StatelessWidget {
reSrc: 11, reSrc: 11,
); );
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast(res['msg'] ?? '成功'); SmartDialog.showToast(res['code'] == 0
? '成功'
: res['msg']);
}, },
child: const Text('确认'), child: const Text('确认'),
) )
@ -375,58 +350,26 @@ class MorePanel extends StatelessWidget {
); );
}, },
); );
} },
value: 'pause',
@override height: 40,
Widget build(BuildContext context) { child: Row(
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
InkWell( const Icon(Icons.block, size: 16),
onTap: () => Get.back(), const SizedBox(width: 6),
child: Container( Text('拉黑:${videoItem.owner.name}',
height: 35, style: const TextStyle(fontSize: 13))
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
),
ListTile(
onTap: () async => await menuActionHandler('block'),
minLeadingWidth: 0,
leading: const Icon(Icons.block, size: 19),
title: Text(
'拉黑up主 「${videoItem.owner.name}',
style: Theme.of(context).textTheme.titleSmall,
),
),
ListTile(
onTap: () async => await menuActionHandler('watchLater'),
minLeadingWidth: 0,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title:
Text('添加至稍后再看', style: Theme.of(context).textTheme.titleSmall),
),
ListTile(
onTap: () =>
imageSaveDialog(context, videoItem, SmartDialog.dismiss),
minLeadingWidth: 0,
leading: const Icon(Icons.photo_outlined, size: 19),
title:
Text('查看视频封面', style: Theme.of(context).textTheme.titleSmall),
),
const SizedBox(height: 20),
], ],
), ),
),
],
),
),
],
),
],
),
),
); );
} }
} }

View File

@ -1,15 +1,14 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/route_push.dart';
import '../../models/model_rec_video_item.dart'; import '../../models/model_rec_video_item.dart';
import 'stat/danmu.dart'; import 'stat/danmu.dart';
import 'stat/view.dart'; import 'stat/view.dart';
import '../../http/dynamics.dart'; import '../../http/dynamics.dart';
import '../../http/search.dart';
import '../../http/user.dart'; import '../../http/user.dart';
import '../../http/video.dart'; import '../../http/video.dart';
import '../../models/common/search_type.dart';
import '../../utils/id_utils.dart'; import '../../utils/id_utils.dart';
import '../../utils/utils.dart'; import '../../utils/utils.dart';
import '../constants.dart'; import '../constants.dart';
@ -20,13 +19,15 @@ import 'network_img_layer.dart';
class VideoCardV extends StatelessWidget { class VideoCardV extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final int crossAxisCount; final int crossAxisCount;
final Function? blockUserCb; final Function()? longPress;
final Function()? longPressEnd;
const VideoCardV({ const VideoCardV({
Key? key, Key? key,
required this.videoItem, required this.videoItem,
required this.crossAxisCount, required this.crossAxisCount,
this.blockUserCb, this.longPress,
this.longPressEnd,
}) : super(key: key); }) : super(key: key);
bool isStringNumeric(String str) { bool isStringNumeric(String str) {
@ -43,11 +44,23 @@ class VideoCardV extends StatelessWidget {
return; return;
} }
int epId = videoItem.param; int epId = videoItem.param;
RoutePush.bangumiPush( SmartDialog.showLoading(msg: '资源获取中');
null, var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
epId, if (result['status']) {
heroTag: heroTag, var bangumiDetail = result['data'];
int cid = bangumiDetail.episodes!.first.cid;
String bvid = IdUtils.av2bv(bangumiDetail.episodes!.first.aid);
SmartDialog.dismiss().then(
(value) => Get.toNamed(
'/video?bvid=$bvid&cid=$cid&epId=$epId',
arguments: {
'pic': videoItem.pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
},
),
); );
}
break; break;
case 'av': case 'av':
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid); String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid);
@ -114,14 +127,23 @@ class VideoCardV extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(videoItem.id); String heroTag = Utils.makeHeroTag(videoItem.id);
return InkWell( return Card(
elevation: 0,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: GestureDetector(
onLongPress: () {
if (longPress != null) {
longPress!();
}
},
// onLongPressEnd: (details) {
// if (longPressEnd != null) {
// longPressEnd!();
// }
// },
child: InkWell(
onTap: () async => onPushDetail(heroTag), onTap: () async => onPushDetail(heroTag),
onLongPress: () => imageSaveDialog(
context,
videoItem,
SmartDialog.dismiss,
),
borderRadius: BorderRadius.circular(16),
child: Column( child: Column(
children: [ children: [
AspectRatio( AspectRatio(
@ -159,13 +181,11 @@ class VideoCardV extends StatelessWidget {
); );
}), }),
), ),
VideoContent( VideoContent(videoItem: videoItem, crossAxisCount: crossAxisCount)
videoItem: videoItem,
crossAxisCount: crossAxisCount,
blockUserCb: blockUserCb,
)
], ],
), ),
),
),
); );
} }
} }
@ -173,103 +193,126 @@ class VideoCardV extends StatelessWidget {
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final int crossAxisCount; final int crossAxisCount;
final Function? blockUserCb; const VideoContent(
{Key? key, required this.videoItem, required this.crossAxisCount})
const VideoContent({ : super(key: key);
Key? key,
required this.videoItem,
required this.crossAxisCount,
this.blockUserCb,
}) : super(key: key);
Widget _buildBadge(String text, String type, [double fs = 12]) {
return PBadge(
text: text,
stack: 'normal',
size: 'small',
type: type,
fs: fs,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Expanded(
flex: crossAxisCount == 1 ? 0 : 1,
child: Padding(
padding: crossAxisCount == 1 padding: crossAxisCount == 1
? const EdgeInsets.fromLTRB(9, 9, 9, 4) ? const EdgeInsets.fromLTRB(9, 9, 9, 4)
: const EdgeInsets.fromLTRB(5, 8, 5, 4), : const EdgeInsets.fromLTRB(5, 8, 5, 4),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
// 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),
VideoPopupMenu(
size: 32,
iconSize: 18,
videoItem: videoItem,
),
],
],
),
if (crossAxisCount > 1) ...[ if (crossAxisCount > 1) ...[
const SizedBox(height: 2), const SizedBox(height: 2),
VideoStat(videoItem: videoItem, crossAxisCount: crossAxisCount), VideoStat(
videoItem: videoItem,
crossAxisCount: crossAxisCount,
),
], ],
if (crossAxisCount == 1) const SizedBox(height: 4), if (crossAxisCount == 1) const SizedBox(height: 4),
Row( Row(
children: [ children: [
if (videoItem.goto == 'bangumi') if (videoItem.goto == 'bangumi') ...[
_buildBadge(videoItem.bangumiBadge, 'line', 9), PBadge(
if (videoItem.rcmdReason?.content != null && text: videoItem.bangumiBadge,
videoItem.rcmdReason.content != '') stack: 'normal',
_buildBadge(videoItem.rcmdReason.content, 'color'), size: 'small',
if (videoItem.goto == 'picture') _buildBadge('动态', 'line', 9), type: 'line',
if (videoItem.isFollowed == 1) _buildBadge('已关注', 'color'), fs: 9,
)
],
if (videoItem.rcmdReason != null &&
videoItem.rcmdReason.content != '') ...[
PBadge(
text: videoItem.rcmdReason.content,
stack: 'normal',
size: 'small',
type: 'color',
)
],
if (videoItem.goto == 'picture') ...[
const PBadge(
text: '动态',
stack: 'normal',
size: 'small',
type: 'line',
fs: 9,
)
],
if (videoItem.isFollowed == 1) ...[
const PBadge(
text: '已关注',
stack: 'normal',
size: 'small',
type: 'color',
)
],
Expanded( Expanded(
flex: crossAxisCount == 1 ? 0 : 1, flex: crossAxisCount == 1 ? 0 : 1,
child: Text( child: Text(
videoItem.owner.name, videoItem.owner.name,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
), ),
if (crossAxisCount == 1) ...[ if (crossAxisCount == 1) ...[
const SizedBox(width: 10), Text(
'',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
), ),
const Spacer(), const Spacer(),
], ],
if (videoItem.goto == 'av') if (videoItem.goto == 'av' && crossAxisCount != 1) ...[
SizedBox( VideoPopupMenu(
width: 24, size: 24,
height: 24, iconSize: 14,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: () {
feedBack();
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(
videoItem: videoItem, videoItem: videoItem,
blockUserCb: blockUserCb,
);
},
);
},
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 14,
), ),
), ] else ...[
) const SizedBox(height: 24)
]
], ],
), ),
], ],
), ),
),
); );
} }
} }
@ -288,9 +331,15 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: [ children: [
StatView(theme: 'gray', view: videoItem.stat.view), StatView(
theme: 'gray',
view: videoItem.stat.view,
),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu(theme: 'gray', danmu: videoItem.stat.danmu), StatDanMu(
theme: 'gray',
danmu: videoItem.stat.danmu,
),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...<Widget>[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8), crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText( RichText(
@ -309,45 +358,69 @@ class VideoStat extends StatelessWidget {
} }
} }
class MorePanel extends StatelessWidget { class VideoPopupMenu extends StatelessWidget {
final double? size;
final double? iconSize;
final dynamic videoItem; final dynamic videoItem;
final Function? blockUserCb;
const MorePanel({ const VideoPopupMenu({
super.key, Key? key,
required this.size,
required this.iconSize,
required this.videoItem, required this.videoItem,
this.blockUserCb, }) : super(key: key);
});
Future<dynamic> menuActionHandler(String type) async { @override
switch (type) { Widget build(BuildContext context) {
case 'block': return SizedBox(
Get.back(); width: size,
blockUser(); height: size,
break; child: PopupMenuButton<String>(
case 'watchLater': padding: EdgeInsets.zero,
var res = await UserHttp.toViewLater(bvid: videoItem.bvid as String); icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: iconSize,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
onTap: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid as String);
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
Get.back(); },
break; value: 'pause',
default: height: 40,
} child: const Row(
} children: [
Icon(Icons.watch_later_outlined, size: 16),
void blockUser() async { SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
const PopupMenuDivider(),
PopupMenuItem<String>(
onTap: () 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: Text('确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?' content: Text(
'确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'), '\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => SmartDialog.dismiss(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(
color: Theme.of(context).colorScheme.outline),
), ),
), ),
TextButton( TextButton(
@ -358,10 +431,7 @@ class MorePanel extends StatelessWidget {
reSrc: 11, reSrc: 11,
); );
SmartDialog.dismiss(); SmartDialog.dismiss();
if (res['status']) { SmartDialog.showToast(res['msg'] ?? '成功');
blockUserCb?.call(videoItem.owner.mid);
}
SmartDialog.showToast(res['msg']);
}, },
child: const Text('确认'), child: const Text('确认'),
) )
@ -369,56 +439,18 @@ class MorePanel extends StatelessWidget {
); );
}, },
); );
} },
value: 'pause',
@override height: 40,
Widget build(BuildContext context) { child: Row(
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [ children: [
InkWell( const Icon(Icons.block, size: 16),
onTap: () => Get.back(), const SizedBox(width: 6),
child: Container( Text('拉黑:${videoItem.owner.name}',
height: 35, style: const TextStyle(fontSize: 13))
padding: const EdgeInsets.only(bottom: 2), ],
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
), ),
), ),
),
),
ListTile(
onTap: () async => await menuActionHandler('block'),
minLeadingWidth: 0,
leading: const Icon(Icons.block, size: 19),
title: Text(
'拉黑up主 「${videoItem.owner.name}',
style: Theme.of(context).textTheme.titleSmall,
),
),
ListTile(
onTap: () async => await menuActionHandler('watchLater'),
minLeadingWidth: 0,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title:
Text('添加至稍后再看', style: Theme.of(context).textTheme.titleSmall),
),
ListTile(
onTap: () =>
imageSaveDialog(context, videoItem, SmartDialog.dismiss),
minLeadingWidth: 0,
leading: const Icon(Icons.photo_outlined, size: 19),
title:
Text('查看视频封面', style: Theme.of(context).textTheme.titleSmall),
),
const SizedBox(height: 20),
], ],
), ),
); );

View File

@ -189,7 +189,7 @@ class Api {
'https://s.search.bilibili.com/main/suggest'; 'https://s.search.bilibili.com/main/suggest';
// 分类搜索 // 分类搜索
static const String searchByType = '/x/web-interface/wbi/search/type'; static const String searchByType = '/x/web-interface/search/type';
// 记录视频播放进度 // 记录视频播放进度
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md // https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md
@ -400,24 +400,12 @@ class Api {
'${HttpString.passBaseUrl}/x/passport-login/captcha?source=main_web'; '${HttpString.passBaseUrl}/x/passport-login/captcha?source=main_web';
// web端短信验证码 // web端短信验证码
static const String webSmsCode = static const String smsCode =
'${HttpString.passBaseUrl}/x/passport-login/web/sms/send'; '${HttpString.passBaseUrl}/x/passport-login/web/sms/send';
// web端验证码登录 // web端验证码登录
static const String webSmsLogin =
'${HttpString.passBaseUrl}/x/passport-login/web/login/sms';
// web端密码登录 // web端密码登录
static const String loginInByWebPwd =
'${HttpString.passBaseUrl}/x/passport-login/web/login';
// web端二维码
static const String qrCodeApi =
'${HttpString.passBaseUrl}/x/passport-login/web/qrcode/generate';
// 扫码登录
static const String loginInByQrcode =
'${HttpString.passBaseUrl}/x/passport-login/web/qrcode/poll';
// app端短信验证码 // app端短信验证码
static const String appSmsCode = static const String appSmsCode =
@ -496,17 +484,11 @@ class Api {
/// 激活buvid3 /// 激活buvid3
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi'; static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
/// 获取字幕配置
static const getSubtitleConfig = '/x/player/v2';
/// 我的订阅 /// 我的订阅
static const userSubFolder = '/x/v3/fav/folder/collected/list'; static const userSubFolder = '/x/v3/fav/folder/collected/list';
/// 我的订阅详情 type 21 /// 我的订阅详情
static const userSeasonList = '/x/space/fav/season/list'; static const userSubFolderDetail = '/x/space/fav/season/list';
/// 我的订阅详情 type 11
static const userResourceList = '/x/v3/fav/resource/list';
/// 表情 /// 表情
static const emojiList = '/x/emote/user/panel/web'; static const emojiList = '/x/emote/user/panel/web';
@ -521,31 +503,4 @@ class Api {
/// 排行榜 /// 排行榜
static const String getRankApi = "/x/web-interface/ranking/v2"; static const String getRankApi = "/x/web-interface/ranking/v2";
/// 取消订阅
static const String cancelSub = '/x/v3/fav/season/unfav';
/// 动态转发
static const String dynamicForwardUrl = '/x/dynamic/feed/create/submit_check';
/// 创建动态
static const String dynamicCreate = '/x/dynamic/feed/create/dyn';
/// 删除收藏夹
static const String delFavFolder = '/x/v3/fav/folder/del';
/// 搜索结果计数
static const String searchCount = '/x/web-interface/wbi/search/all/v2';
/// 关闭会话
static const String removeSession =
'${HttpString.tUrl}/session_svr/v1/session_svr/remove_session';
/// 消息未读数
static const String unread = '${HttpString.tUrl}/x/im/web/msgfeed/unread';
/// 回复我的
static const String messageReplyAPi = '/x/msgfeed/reply';
/// 收到的赞
static const String messageLikeAPi = '/x/msgfeed/like';
} }

View File

@ -1,4 +1,3 @@
import 'dart:math';
import '../models/dynamics/result.dart'; import '../models/dynamics/result.dart';
import '../models/dynamics/up.dart'; import '../models/dynamics/up.dart';
import 'index.dart'; import 'index.dart';
@ -41,7 +40,6 @@ class DynamicsHttp {
'status': false, 'status': false,
'data': [], 'data': [],
'msg': res.data['message'], 'msg': res.data['message'],
'code': res.data['code'],
}; };
} }
} }
@ -119,94 +117,4 @@ class DynamicsHttp {
}; };
} }
} }
static Future dynamicForward() async {
var res = await Request().post(
Api.dynamicForwardUrl,
queryParameters: {
'csrf': await Request.getCsrf(),
'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
'x-bili-web-req-json': {'spm_id': '333.999'},
},
data: {
'attach_card': null,
'scene': 4,
'content': {
'conetents': [
{'raw_text': "2", 'type': 1, 'biz_id': ""}
]
}
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
static Future dynamicCreate({
required int mid,
required int scene,
int? oid,
String? dynIdStr,
String? rawText,
}) async {
DateTime now = DateTime.now();
int timestamp = now.millisecondsSinceEpoch ~/ 1000;
Random random = Random();
int randomNumber = random.nextInt(9000) + 1000;
String uploadId = '${mid}_${timestamp}_$randomNumber';
Map<String, dynamic> webRepostSrc = {
'dyn_id_str': dynIdStr ?? '',
};
/// 投稿转发
if (scene == 5) {
webRepostSrc = {
'revs_id': {'dyn_type': 8, 'rid': oid}
};
}
var res = await Request().post(Api.dynamicCreate, queryParameters: {
'platform': 'web',
'csrf': await Request.getCsrf(),
'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
'x-bili-web-req-json': {'spm_id': '333.999'},
}, data: {
'dyn_req': {
'content': {
'contents': [
{'raw_text': rawText ?? '', 'type': 1, 'biz_id': ''}
]
},
'scene': scene,
'attach_card': null,
'upload_id': uploadId,
'meta': {
'app_meta': {'from': 'create.dynamic.web', 'mobi_app': 'web'}
}
},
'web_repost_src': webRepostSrc
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
} }

View File

@ -46,7 +46,8 @@ class ApiInterceptor extends Interceptor {
// 处理网络请求错误 // 处理网络请求错误
// handler.next(err); // handler.next(err);
String url = err.requestOptions.uri.toString(); String url = err.requestOptions.uri.toString();
if (!url.contains('heartbeat')) { print('🌹🌹ApiInterceptor: $url');
if (!url.contains('heartBeat')) {
SmartDialog.showToast( SmartDialog.showToast(
await dioError(err), await dioError(err),
displayType: SmartToastType.onlyRefresh, displayType: SmartToastType.onlyRefresh,
@ -78,23 +79,22 @@ class ApiInterceptor extends Interceptor {
} }
static Future<String> checkConnect() async { static Future<String> checkConnect() async {
final List<ConnectivityResult> connectivityResult = final ConnectivityResult connectivityResult =
await Connectivity().checkConnectivity(); await Connectivity().checkConnectivity();
if (connectivityResult.contains(ConnectivityResult.mobile)) { switch (connectivityResult) {
case ConnectivityResult.mobile:
return '正在使用移动流量'; return '正在使用移动流量';
} else if (connectivityResult.contains(ConnectivityResult.wifi)) { case ConnectivityResult.wifi:
return '正在使用wifi'; return '正在使用wifi';
} else if (connectivityResult.contains(ConnectivityResult.ethernet)) { case ConnectivityResult.ethernet:
return '正在使用局域网'; return '正在使用局域网';
} else if (connectivityResult.contains(ConnectivityResult.vpn)) { case ConnectivityResult.vpn:
return '正在使用代理网络'; return '正在使用代理网络';
} else if (connectivityResult.contains(ConnectivityResult.bluetooth)) { case ConnectivityResult.other:
return '正在使用蓝牙网络';
} else if (connectivityResult.contains(ConnectivityResult.other)) {
return '正在使用其他网络'; return '正在使用其他网络';
} else if (connectivityResult.contains(ConnectivityResult.none)) { case ConnectivityResult.none:
return '未连接到任何网络'; return '未连接到任何网络';
} else { default:
return ''; return '';
} }
} }

View File

@ -3,7 +3,6 @@ import 'dart:math';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart'; import 'package:encrypt/encrypt.dart';
import 'package:pilipala/http/constants.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import '../models/login/index.dart'; import '../models/login/index.dart';
import '../utils/login.dart'; import '../utils/login.dart';
@ -22,32 +21,32 @@ class LoginHttp {
} }
} }
// static Future sendSmsCode({ static Future sendSmsCode({
// int? cid, int? cid,
// required int tel, required int tel,
// required String token, required String token,
// required String challenge, required String challenge,
// required String validate, required String validate,
// required String seccode, required String seccode,
// }) async { }) async {
// var res = await Request().post( var res = await Request().post(
// Api.appSmsCode, Api.appSmsCode,
// data: { data: {
// 'cid': cid, 'cid': cid,
// 'tel': tel, 'tel': tel,
// "source": "main_web", "source": "main_web",
// 'token': token, 'token': token,
// 'challenge': challenge, 'challenge': challenge,
// 'validate': validate, 'validate': validate,
// 'seccode': seccode, 'seccode': seccode,
// }, },
// options: Options( options: Options(
// contentType: Headers.formUrlEncodedContentType, contentType: Headers.formUrlEncodedContentType,
// // headers: {'user-agent': ApiConstants.userAgent} // headers: {'user-agent': ApiConstants.userAgent}
// ), ),
// ); );
// print(res); print(res);
// } }
// web端验证码 // web端验证码
static Future sendWebSmsCode({ static Future sendWebSmsCode({
@ -61,7 +60,6 @@ class LoginHttp {
Map data = { Map data = {
'cid': cid, 'cid': cid,
'tel': tel, 'tel': tel,
"source": "main_web",
'token': token, 'token': token,
'challenge': challenge, 'challenge': challenge,
'validate': validate, 'validate': validate,
@ -69,56 +67,17 @@ class LoginHttp {
}; };
FormData formData = FormData.fromMap({...data}); FormData formData = FormData.fromMap({...data});
var res = await Request().post( var res = await Request().post(
Api.webSmsCode, Api.smsCode,
data: formData, data: formData,
options: Options( options: Options(
contentType: Headers.formUrlEncodedContentType, contentType: Headers.formUrlEncodedContentType,
), ),
); );
if (res.data['code'] == 0) { print(res);
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
} }
// web端验证码登录 // web端验证码登录
static Future loginInByWebSmsCode({ static Future loginInByWebSmsCode() async {}
int? cid,
required int tel,
required int code,
required String captchaKey,
}) async {
// webSmsLogin
Map data = {
"cid": cid,
"tel": tel,
"code": code,
"source": "main_mini",
"keep": 0,
"captcha_key": captchaKey,
"go_url": HttpString.baseUrl
};
FormData formData = FormData.fromMap({...data});
var res = await Request().post(
Api.webSmsLogin,
data: formData,
options: Options(
contentType: Headers.formUrlEncodedContentType,
),
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
// web端密码登录 // web端密码登录
static Future liginInByWebPwd() async {} static Future liginInByWebPwd() async {}
@ -214,69 +173,4 @@ class LoginHttp {
); );
print(res); print(res);
} }
// web端密码登录
static Future loginInByWebPwd({
required int username,
required String password,
required String token,
required String challenge,
required String validate,
required String seccode,
}) async {
Map data = {
'username': username,
'password': password,
'keep': 0,
'token': token,
'challenge': challenge,
'validate': validate,
'seccode': seccode,
'source': 'main-fe-header',
"go_url": HttpString.baseUrl
};
FormData formData = FormData.fromMap({...data});
var res = await Request().post(
Api.loginInByWebPwd,
data: formData,
options: Options(
contentType: Headers.formUrlEncodedContentType,
),
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
// web端登录二维码
static Future getWebQrcode() async {
var res = await Request().get(Api.qrCodeApi);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
// web端二维码轮询登录状态
static Future queryWebQrcodeStatus(String qrcodeKey) async {
var res = await Request()
.get(Api.loginInByQrcode, data: {'qrcode_key': qrcodeKey});
if (res.data['data']['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
} }

View File

@ -1,8 +1,4 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:dio/dio.dart';
import 'package:pilipala/models/msg/like.dart';
import 'package:pilipala/models/msg/reply.dart';
import '../models/msg/account.dart'; import '../models/msg/account.dart';
import '../models/msg/session.dart'; import '../models/msg/session.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
@ -126,48 +122,68 @@ class MsgHttp {
'data': res.data['data'], 'data': res.data['data'],
}; };
} else { } else {
return {'status': false, 'date': [], 'msg': res.data['message']}; return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
};
} }
} }
// 发送私信 // 发送私信
static Future sendMsg({ static Future sendMsg({
required int senderUid, int? senderUid,
required int receiverId, int? receiverId,
int? receiverType, int? receiverType,
int? msgType, int? msgType,
dynamic content, dynamic content,
}) async { }) async {
String csrf = await Request.getCsrf(); String csrf = await Request.getCsrf();
var res = await Request().post( Map<String, dynamic> params = await WbiSign().makSign({
Api.sendMsg,
data: {
'msg[sender_uid]': senderUid, 'msg[sender_uid]': senderUid,
'msg[receiver_id]': receiverId, 'msg[receiver_id]': receiverId,
'msg[receiver_type]': 1, 'msg[receiver_type]': receiverType ?? 1,
'msg[msg_type]': 1, 'msg[msg_type]': msgType ?? 1,
'msg[msg_status]': 0, 'msg[msg_status]': 0,
'msg[content]': jsonEncode(content), 'msg[dev_id]': getDevId(),
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'msg[new_face_version]': 0, 'msg[new_face_version]': 0,
'msg[dev_id]': getDevId(), 'msg[content]': content,
'from_firework': 0, 'from_firework': 0,
'build': 0, 'build': 0,
'mobi_app': 'web', 'mobi_app': 'web',
'csrf_token': csrf, 'csrf_token': csrf,
'csrf': csrf, 'csrf': csrf,
}, });
options: Options( var res =
contentType: Headers.formUrlEncodedContentType, await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{
), ...params,
); 'csrf_token': csrf,
'csrf': csrf,
}, data: {
'w_sender_uid': params['msg[sender_uid]'],
'w_receiver_id': params['msg[receiver_id]'],
'w_dev_id': params['msg[dev_id]'],
'w_rid': params['w_rid'],
'wts': params['wts'],
'csrf_token': csrf,
'csrf': csrf,
});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
'status': true, 'status': true,
'data': res.data['data'], 'data': res.data['data'],
}; };
} else { } else {
return {'status': false, 'date': [], 'msg': res.data['message']}; return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
};
} }
} }
@ -204,87 +220,4 @@ class MsgHttp {
} }
return s.join(); return s.join();
} }
static Future removeSession({
int? talkerId,
}) async {
String csrf = await Request.getCsrf();
Map params = await WbiSign().makSign({
'talker_id': talkerId,
'session_type': 1,
'build': 0,
'mobi_app': 'web',
'csrf_token': csrf,
'csrf': csrf
});
var res = await Request().get(Api.removeSession, data: params);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
static Future unread() async {
var res = await Request().get(Api.unread);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
// 回复我的
static Future messageReply({
int? id,
int? replyTime,
}) async {
var params = {
if (id != null) 'id': id,
if (replyTime != null) 'reply_time': replyTime,
};
var res = await Request().get(Api.messageReplyAPi, data: params);
if (res.data['code'] == 0) {
try {
return {
'status': true,
'data': MessageReplyModel.fromJson(res.data['data']),
};
} catch (err) {
return {'status': false, 'date': [], 'msg': err.toString()};
}
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
// 收到的赞
static Future messageLike({
int? id,
int? likeTime,
}) async {
var params = {
if (id != null) 'id': id,
if (likeTime != null) 'like_time': likeTime,
};
var res = await Request().get(Api.messageLikeAPi, data: params);
if (res.data['code'] == 0) {
try {
return {
'status': true,
'data': MessageLikeModel.fromJson(res.data['data']),
};
} catch (err) {
return {'status': false, 'date': [], 'msg': err.toString()};
}
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
} }

View File

@ -22,14 +22,19 @@ class ReplyHttp {
return { return {
'status': true, 'status': true,
'data': ReplyData.fromJson(res.data['data']), 'data': ReplyData.fromJson(res.data['data']),
'code': 200,
}; };
} else { } else {
Map errMap = {
-400: '请求错误',
-404: '无此项',
12002: '当前页面评论功能已关闭',
12009: '评论主体的type不合法',
12061: 'UP主已关闭评论区',
};
return { return {
'status': false, 'status': false,
'date': [], 'date': [],
'code': res.data['code'], 'msg': errMap[res.data['code']] ?? res.data['message'],
'msg': res.data['message'],
}; };
} }
} }

View File

@ -1,7 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/search/all.dart';
import 'package:pilipala/utils/wbi_sign.dart';
import '../models/bangumi/info.dart'; import '../models/bangumi/info.dart';
import '../models/common/search_type.dart'; import '../models/common/search_type.dart';
import '../models/search/hot.dart'; import '../models/search/hot.dart';
@ -75,7 +73,6 @@ class SearchHttp {
required page, required page,
String? order, String? order,
int? duration, int? duration,
int? tids,
}) async { }) async {
var reqData = { var reqData = {
'search_type': searchType.type, 'search_type': searchType.type,
@ -85,14 +82,9 @@ class SearchHttp {
'page': page, 'page': page,
if (order != null) 'order': order, if (order != null) 'order': order,
if (duration != null) 'duration': duration, if (duration != null) 'duration': duration,
if (tids != null && tids != -1) 'tids': tids,
}; };
var res = await Request().get(Api.searchByType, data: reqData); var res = await Request().get(Api.searchByType, data: reqData);
if (res.data['code'] == 0) { if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
if (res.data['data']['numPages'] == 0) {
// 我想返回数据使得可以通过data.list 取值,结果为[]
return {'status': true, 'data': Data()};
}
Object data; Object data;
try { try {
switch (searchType) { switch (searchType) {
@ -129,7 +121,9 @@ class SearchHttp {
return { return {
'status': false, 'status': false,
'data': [], 'data': [],
'msg': res.data['message'], 'msg': res.data['data'] != null && res.data['data']['numPages'] == 0
? '没有相关数据'
: res.data['message'],
}; };
} }
} }
@ -169,48 +163,4 @@ class SearchHttp {
}; };
} }
} }
static Future<Map<String, dynamic>> ab2cWithPic(
{int? aid, String? bvid}) async {
Map<String, dynamic> data = {};
if (aid != null) {
data['aid'] = aid;
} else if (bvid != null) {
data['bvid'] = bvid;
}
final dynamic res =
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
return {
'cid': res.data['data'].first['cid'],
'pic': res.data['data'].first['first_frame'],
};
}
static Future<Map<String, dynamic>> searchCount(
{required String keyword}) async {
Map<String, dynamic> data = {
'keyword': keyword,
'web_location': 333.999,
};
Map params = await WbiSign().makSign(data);
final dynamic res = await Request().get(Api.searchCount, data: params);
if (res.data['code'] == 0) {
return {
'status': true,
'data': SearchAllModel.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'data': [],
'msg': '请求错误 🙅',
};
}
}
}
class Data {
List<dynamic> list;
Data({this.list = const []});
} }

View File

@ -62,8 +62,7 @@ class UserHttp {
return { return {
'status': false, 'status': false,
'data': [], 'data': [],
'msg': res.data['message'], 'msg': res.data['message'] ?? '账号未登录'
'code': res.data['code'],
}; };
} }
} }
@ -112,12 +111,7 @@ class UserHttp {
'data': {'list': list, 'count': res.data['data']['count']} 'data': {'list': list, 'count': res.data['data']['count']}
}; };
} else { } else {
return { return {'status': false, 'data': [], 'msg': res.data['message']};
'status': false,
'data': [],
'msg': res.data['message'],
'code': res.data['code'],
};
} }
} }
@ -132,12 +126,7 @@ class UserHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])}; return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
} else { } else {
return { return {'status': false, 'data': [], 'msg': res.data['message']};
'status': false,
'data': [],
'msg': res.data['message'],
'code': res.data['code'],
};
} }
} }
@ -337,21 +326,16 @@ class UserHttp {
'data': SubFolderModelData.fromJson(res.data['data']) 'data': SubFolderModelData.fromJson(res.data['data'])
}; };
} else { } else {
return { return {'status': false, 'msg': res.data['message']};
'status': false,
'data': [],
'msg': res.data['message'],
'code': res.data['code'],
};
} }
} }
static Future userSeasonList({ static Future userSubFolderDetail({
required int seasonId, required int seasonId,
required int pn, required int pn,
required int ps, required int ps,
}) async { }) async {
var res = await Request().get(Api.userSeasonList, data: { var res = await Request().get(Api.userSubFolderDetail, data: {
'season_id': seasonId, 'season_id': seasonId,
'ps': ps, 'ps': ps,
'pn': pn, 'pn': pn,
@ -365,67 +349,4 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']}; return {'status': false, 'msg': res.data['message']};
} }
} }
static Future userResourceList({
required int seasonId,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userResourceList, data: {
'media_id': seasonId,
'ps': ps,
'pn': pn,
'keyword': '',
'order': 'mtime',
'type': 0,
'tid': 0,
'platform': 'web',
});
if (res.data['code'] == 0) {
try {
return {
'status': true,
'data': SubDetailModelData.fromJson(res.data['data'])
};
} catch (err) {
return {'status': false, 'msg': err};
}
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 取消订阅
static Future cancelSub({required int seasonId}) async {
var res = await Request().post(
Api.cancelSub,
queryParameters: {
'platform': 'web',
'season_id': seasonId,
'csrf': await Request.getCsrf(),
},
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 删除文件夹
static Future delFavFolder({required int mediaIds}) async {
var res = await Request().post(
Api.delFavFolder,
queryParameters: {
'media_ids': mediaIds,
'platform': 'web',
'csrf': await Request.getCsrf(),
},
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
} }

View File

@ -8,11 +8,9 @@ import '../models/model_rec_video_item.dart';
import '../models/user/fav_folder.dart'; import '../models/user/fav_folder.dart';
import '../models/video/ai.dart'; import '../models/video/ai.dart';
import '../models/video/play/url.dart'; import '../models/video/play/url.dart';
import '../models/video/subTitile/result.dart';
import '../models/video_detail_res.dart'; import '../models/video_detail_res.dart';
import '../utils/recommend_filter.dart'; import '../utils/recommend_filter.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/subtitle.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
@ -35,7 +33,7 @@ class VideoHttp {
Api.recommendListWeb, Api.recommendListWeb,
data: { data: {
'version': 1, 'version': 1,
'feed_version': 'V3', 'feed_version': 'V8',
'homepage_ver': 1, 'homepage_ver': 1,
'ps': ps, 'ps': ps,
'fresh_idx': freshIdx, 'fresh_idx': freshIdx,
@ -192,15 +190,22 @@ class VideoHttp {
// 视频信息 标题、简介 // 视频信息 标题、简介
static Future videoIntro({required String bvid}) async { static Future videoIntro({required String bvid}) async {
var res = await Request().get(Api.videoIntro, data: {'bvid': bvid}); var res = await Request().get(Api.videoIntro, data: {'bvid': bvid});
if (res.data['code'] == 0) {
VideoDetailResponse result = VideoDetailResponse.fromJson(res.data); VideoDetailResponse result = VideoDetailResponse.fromJson(res.data);
if (result.code == 0) {
return {'status': true, 'data': result.data!}; return {'status': true, 'data': result.data!};
} else { } else {
Map errMap = {
-400: '请求错误',
-403: '权限不足',
-404: '视频资源失效',
62002: '稿件不可见',
62004: '稿件审核中',
};
return { return {
'status': false, 'status': false,
'data': null, 'data': null,
'code': res.data['code'], 'code': result.code,
'msg': res.data['message'], 'msg': errMap[result.code] ?? '请求异常',
}; };
} }
} }
@ -387,15 +392,9 @@ class VideoHttp {
'csrf': await Request.getCsrf(), 'csrf': await Request.getCsrf(),
}); });
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
if (act == 5) { return {'status': true, 'data': res.data['data']};
List<int> blackMidsList =
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
blackMidsList.add(mid);
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
}
return {'status': true, 'data': res.data['data'], 'msg': '成功'};
} else { } else {
return {'status': false, 'data': [], 'msg': res.data['message']}; return {'status': false, 'data': []};
} }
} }
@ -477,25 +476,6 @@ class VideoHttp {
} }
} }
static Future getSubtitle({int? cid, String? bvid}) async {
var res = await Request().get(Api.getSubtitleConfig, data: {
'cid': cid,
'bvid': bvid,
});
try {
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubTitlteModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'data': [], 'msg': res.data['msg']};
}
} catch (err) {
print(err);
}
}
// 视频排行 // 视频排行
static Future getRankVideoList(int rid) async { static Future getRankVideoList(int rid) async {
try { try {
@ -518,12 +498,4 @@ class VideoHttp {
return {'status': false, 'data': [], 'msg': err}; return {'status': false, 'data': [], 'msg': err};
} }
} }
// 获取字幕内容
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
var res = await Request().get('https:$url');
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
final List body = res.data['body'];
return {'content': content, 'body': body};
}
} }

View File

@ -1,6 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
@ -17,12 +16,12 @@ 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/services/disable_battery_opt.dart';
import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart'; import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
import 'package:pilipala/utils/recommend_filter.dart'; import 'package:pilipala/utils/recommend_filter.dart';
import 'package:catcher_2/catcher_2.dart'; import 'package:catcher_2/catcher_2.dart';
import './services/loggeer.dart'; import './services/loggeer.dart';
@ -30,21 +29,34 @@ import './services/loggeer.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized(); MediaKit.ensureInitialized();
await SystemChrome.setPreferredOrientations( SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]); [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
.then((_) async {
await GStrorage.init(); await GStrorage.init();
await setupServiceLocator(); await setupServiceLocator();
clearLogs();
Request(); Request();
await Request.setCookie(); await Request.setCookie();
RecommendFilter();
// 异常捕获 logo记录 // 异常捕获 logo记录
final Catcher2Options debugConfig = Catcher2Options(
SilentReportMode(),
[
FileHandler(await getLogsPath()),
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
)
],
);
final Catcher2Options releaseConfig = Catcher2Options( final Catcher2Options releaseConfig = Catcher2Options(
SilentReportMode(), SilentReportMode(),
[FileHandler(await getLogsPath())], [FileHandler(await getLogsPath())],
); );
Catcher2( Catcher2(
debugConfig: debugConfig,
releaseConfig: releaseConfig, releaseConfig: releaseConfig,
runAppFunction: () { runAppFunction: () {
runApp(const MyApp()); runApp(const MyApp());
@ -52,19 +64,16 @@ void main() async {
); );
// 小白条、导航栏沉浸 // 小白条、导航栏沉浸
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt >= 29) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
}
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
)); ));
} Data.init();
PiliSchame.init(); PiliSchame.init();
DisableBatteryOpt();
});
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
@ -105,39 +114,6 @@ class MyApp extends StatelessWidget {
} catch (_) {} } catch (_) {}
} }
if (Platform.isAndroid) {
return AndroidApp(
brandColor: brandColor,
isDynamicColor: isDynamicColor,
currentThemeValue: currentThemeValue,
textScale: textScale,
);
} else {
return OtherApp(
brandColor: brandColor,
currentThemeValue: currentThemeValue,
textScale: textScale,
);
}
}
}
class AndroidApp extends StatelessWidget {
const AndroidApp({
super.key,
required this.brandColor,
required this.isDynamicColor,
required this.currentThemeValue,
required this.textScale,
});
final Color brandColor;
final bool isDynamicColor;
final ThemeType currentThemeValue;
final double textScale;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder( return DynamicColorBuilder(
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) { builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
ColorScheme? lightColorScheme; ColorScheme? lightColorScheme;
@ -157,77 +133,23 @@ class AndroidApp extends StatelessWidget {
brightness: Brightness.dark, brightness: Brightness.dark,
); );
} }
return BuildMainApp( // 图片缓存
lightColorScheme: lightColorScheme, // PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
darkColorScheme: darkColorScheme, return GetMaterialApp(
currentThemeValue: currentThemeValue, title: 'PiLiPaLa',
textScale: textScale, theme: ThemeData(
); // fontFamily: 'HarmonyOS',
}), colorScheme: currentThemeValue == ThemeType.dark
); ? darkColorScheme
} : lightColorScheme,
} useMaterial3: true,
snackBarTheme: SnackBarThemeData(
class OtherApp extends StatelessWidget {
const OtherApp({
super.key,
required this.brandColor,
required this.currentThemeValue,
required this.textScale,
});
final Color brandColor;
final ThemeType currentThemeValue;
final double textScale;
@override
Widget build(BuildContext context) {
return BuildMainApp(
lightColorScheme: ColorScheme.fromSeed(
seedColor: brandColor,
brightness: Brightness.light,
),
darkColorScheme: ColorScheme.fromSeed(
seedColor: brandColor,
brightness: Brightness.dark,
),
currentThemeValue: currentThemeValue,
textScale: textScale,
);
}
}
class BuildMainApp extends StatelessWidget {
const BuildMainApp({
super.key,
required this.lightColorScheme,
required this.darkColorScheme,
required this.currentThemeValue,
required this.textScale,
});
final ColorScheme lightColorScheme;
final ColorScheme darkColorScheme;
final ThemeType currentThemeValue;
final double textScale;
@override
Widget build(BuildContext context) {
final SnackBarThemeData snackBarTheme = SnackBarThemeData(
actionTextColor: lightColorScheme.primary, actionTextColor: lightColorScheme.primary,
backgroundColor: lightColorScheme.secondaryContainer, backgroundColor: lightColorScheme.secondaryContainer,
closeIconColor: lightColorScheme.secondary, closeIconColor: lightColorScheme.secondary,
contentTextStyle: TextStyle(color: lightColorScheme.secondary), contentTextStyle: TextStyle(color: lightColorScheme.secondary),
elevation: 20, elevation: 20,
); ),
return GetMaterialApp(
title: 'PiliPala',
theme: ThemeData(
colorScheme: currentThemeValue == ThemeType.dark
? darkColorScheme
: lightColorScheme,
snackBarTheme: snackBarTheme,
pageTransitionsTheme: const PageTransitionsTheme( pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{ builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder( TargetPlatform.android: ZoomPageTransitionsBuilder(
@ -237,10 +159,18 @@ class BuildMainApp extends StatelessWidget {
), ),
), ),
darkTheme: ThemeData( darkTheme: ThemeData(
// fontFamily: 'HarmonyOS',
colorScheme: currentThemeValue == ThemeType.light colorScheme: currentThemeValue == ThemeType.light
? lightColorScheme ? lightColorScheme
: darkColorScheme, : darkColorScheme,
snackBarTheme: snackBarTheme, useMaterial3: true,
snackBarTheme: SnackBarThemeData(
actionTextColor: darkColorScheme.primary,
backgroundColor: darkColorScheme.secondaryContainer,
closeIconColor: darkColorScheme.secondary,
contentTextStyle: TextStyle(color: darkColorScheme.secondary),
elevation: 20,
),
), ),
localizationsDelegates: const [ localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
@ -266,11 +196,8 @@ class BuildMainApp extends StatelessWidget {
VideoDetailPage.routeObserver, VideoDetailPage.routeObserver,
SearchPage.routeObserver, SearchPage.routeObserver,
], ],
onInit: () { );
RecommendFilter(); }),
Data.init();
GlobalData();
},
); );
} }
} }

View File

@ -30,7 +30,6 @@ class BangumiListItemModel {
BangumiListItemModel({ BangumiListItemModel({
this.badge, this.badge,
this.badgeType, this.badgeType,
this.pic,
this.cover, this.cover,
// this.firstEp, // this.firstEp,
this.indexShow, this.indexShow,
@ -51,7 +50,6 @@ class BangumiListItemModel {
String? badge; String? badge;
int? badgeType; int? badgeType;
String? pic;
String? cover; String? cover;
String? indexShow; String? indexShow;
int? isFinish; int? isFinish;
@ -72,7 +70,6 @@ class BangumiListItemModel {
BangumiListItemModel.fromJson(Map<String, dynamic> json) { BangumiListItemModel.fromJson(Map<String, dynamic> json) {
badge = json['badge'] == '' ? null : json['badge']; badge = json['badge'] == '' ? null : json['badge'];
badgeType = json['badge_type']; badgeType = json['badge_type'];
pic = json['cover'];
cover = json['cover']; cover = json['cover'];
indexShow = json['index_show']; indexShow = json['index_show'];
isFinish = json['is_finish']; isFinish = json['is_finish'];

View File

@ -1,93 +0,0 @@
// 操作类型的枚举值:点赞 不喜欢 收藏 投币 稍后再看 下载封面 后台播放 听视频 分享 下载视频
import 'package:flutter/material.dart';
import 'package:get/get.dart';
enum ActionType {
like,
coin,
collect,
watchLater,
share,
dislike,
downloadCover,
copyLink,
// backgroundPlay,
// listenVideo,
// downloadVideo,
}
extension ActionTypeExtension on ActionType {
String get value => [
'like',
'coin',
'collect',
'watchLater',
'share',
'dislike',
'downloadCover',
'copyLink',
// 'backgroundPlay',
// 'listenVideo',
// 'downloadVideo',
][index];
String get label => [
'点赞视频',
'投币',
'收藏视频',
'稍后再看',
'视频分享',
'不喜欢',
'下载封面',
'复制链接',
// '后台播放',
// '听视频',
// '下载视频',
][index];
}
List<Map> actionMenuConfig = [
{
'icon': const Icon(Icons.thumb_up_alt_outlined),
'label': '点赞视频',
'value': ActionType.like,
},
{
'icon': Image.asset(
'assets/images/coin.png',
width: 26,
color: IconTheme.of(Get.context!).color!.withOpacity(0.65),
),
'label': '投币',
'value': ActionType.coin,
},
{
'icon': const Icon(Icons.star_border),
'label': '收藏视频',
'value': ActionType.collect,
},
{
'icon': const Icon(Icons.watch_later_outlined),
'label': '稍后再看',
'value': ActionType.watchLater,
},
{
'icon': const Icon(Icons.share),
'label': '视频分享',
'value': ActionType.share,
},
{
'icon': const Icon(Icons.thumb_down_alt_outlined),
'label': '不喜欢',
'value': ActionType.dislike,
},
{
'icon': const Icon(Icons.image_outlined),
'label': '下载封面',
'value': ActionType.downloadCover,
},
{
'icon': const Icon(Icons.link_outlined),
'label': '复制链接',
'value': ActionType.copyLink,
},
];

View File

@ -1,10 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../pages/dynamics/index.dart';
import '../../pages/home/index.dart';
import '../../pages/media/index.dart';
import '../../pages/rank/index.dart';
List defaultNavigationBars = [ List defaultNavigationBars = [
{ {
'id': 0, 'id': 0,
@ -18,7 +13,6 @@ List defaultNavigationBars = [
), ),
'label': "首页", 'label': "首页",
'count': 0, 'count': 0,
'page': const HomePage(),
}, },
{ {
'id': 1, 'id': 1,
@ -32,7 +26,6 @@ List defaultNavigationBars = [
), ),
'label': "排行榜", 'label': "排行榜",
'count': 0, 'count': 0,
'page': const RankPage(),
}, },
{ {
'id': 2, 'id': 2,
@ -46,7 +39,6 @@ List defaultNavigationBars = [
), ),
'label': "动态", 'label': "动态",
'count': 0, 'count': 0,
'page': const DynamicsPage(),
}, },
{ {
'id': 3, 'id': 3,
@ -60,6 +52,5 @@ List defaultNavigationBars = [
), ),
'label': "媒体库", 'label': "媒体库",
'count': 0, 'count': 0,
'page': const MediaPage(),
} }
]; ];

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/pages/rank/zone/index.dart'; import 'package:pilipala/pages/rank/zone/index.dart';
enum RandType { enum RandType {
@ -73,6 +74,7 @@ List tabsConfig = [
), ),
'label': '全站', 'label': '全站',
'type': RandType.all, 'type': RandType.all,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 0), 'page': const ZonePage(rid: 0),
}, },
{ {
@ -82,6 +84,7 @@ List tabsConfig = [
), ),
'label': '国创相关', 'label': '国创相关',
'type': RandType.creation, 'type': RandType.creation,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 168), 'page': const ZonePage(rid: 168),
}, },
{ {
@ -91,6 +94,7 @@ List tabsConfig = [
), ),
'label': '动画', 'label': '动画',
'type': RandType.animation, 'type': RandType.animation,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 1), 'page': const ZonePage(rid: 1),
}, },
{ {
@ -100,6 +104,7 @@ List tabsConfig = [
), ),
'label': '音乐', 'label': '音乐',
'type': RandType.music, 'type': RandType.music,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 3), 'page': const ZonePage(rid: 3),
}, },
{ {
@ -109,6 +114,7 @@ List tabsConfig = [
), ),
'label': '舞蹈', 'label': '舞蹈',
'type': RandType.dance, 'type': RandType.dance,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 129), 'page': const ZonePage(rid: 129),
}, },
{ {
@ -118,6 +124,7 @@ List tabsConfig = [
), ),
'label': '游戏', 'label': '游戏',
'type': RandType.game, 'type': RandType.game,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 4), 'page': const ZonePage(rid: 4),
}, },
{ {
@ -127,6 +134,7 @@ List tabsConfig = [
), ),
'label': '知识', 'label': '知识',
'type': RandType.knowledge, 'type': RandType.knowledge,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 36), 'page': const ZonePage(rid: 36),
}, },
{ {
@ -136,6 +144,7 @@ List tabsConfig = [
), ),
'label': '科技', 'label': '科技',
'type': RandType.technology, 'type': RandType.technology,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 188), 'page': const ZonePage(rid: 188),
}, },
{ {
@ -145,6 +154,7 @@ List tabsConfig = [
), ),
'label': '运动', 'label': '运动',
'type': RandType.sport, 'type': RandType.sport,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 234), 'page': const ZonePage(rid: 234),
}, },
{ {
@ -154,6 +164,7 @@ List tabsConfig = [
), ),
'label': '汽车', 'label': '汽车',
'type': RandType.car, 'type': RandType.car,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 223), 'page': const ZonePage(rid: 223),
}, },
{ {
@ -163,6 +174,7 @@ List tabsConfig = [
), ),
'label': '生活', 'label': '生活',
'type': RandType.life, 'type': RandType.life,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 160), 'page': const ZonePage(rid: 160),
}, },
{ {
@ -172,6 +184,7 @@ List tabsConfig = [
), ),
'label': '美食', 'label': '美食',
'type': RandType.food, 'type': RandType.food,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 211), 'page': const ZonePage(rid: 211),
}, },
{ {
@ -181,6 +194,7 @@ List tabsConfig = [
), ),
'label': '动物圈', 'label': '动物圈',
'type': RandType.animal, 'type': RandType.animal,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 217), 'page': const ZonePage(rid: 217),
}, },
{ {
@ -190,6 +204,7 @@ List tabsConfig = [
), ),
'label': '鬼畜', 'label': '鬼畜',
'type': RandType.madness, 'type': RandType.madness,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 119), 'page': const ZonePage(rid: 119),
}, },
{ {
@ -199,6 +214,7 @@ List tabsConfig = [
), ),
'label': '时尚', 'label': '时尚',
'type': RandType.fashion, 'type': RandType.fashion,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 155), 'page': const ZonePage(rid: 155),
}, },
{ {
@ -208,6 +224,7 @@ List tabsConfig = [
), ),
'label': '娱乐', 'label': '娱乐',
'type': RandType.entertainment, 'type': RandType.entertainment,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 5), 'page': const ZonePage(rid: 5),
}, },
{ {
@ -217,6 +234,7 @@ List tabsConfig = [
), ),
'label': '影视', 'label': '影视',
'type': RandType.film, 'type': RandType.film,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 181), 'page': const ZonePage(rid: 181),
} }
]; ];

View File

@ -1,95 +0,0 @@
enum SubtitleType {
// 中文(中国)
zhCN,
// 中文(自动翻译)
aizh,
// 英语(自动生成)
aien,
// 中文(简体)
zhHans,
// 英文(美国)
enUS,
// 中文繁体
zhTW,
//
en,
//
pt,
//
es,
}
extension SubtitleTypeExtension on SubtitleType {
String get description {
switch (this) {
case SubtitleType.zhCN:
return '中文(中国)';
case SubtitleType.aizh:
return '中文(自动翻译)';
case SubtitleType.aien:
return '英语(自动生成)';
case SubtitleType.zhHans:
return '中文(简体)';
case SubtitleType.enUS:
return '英文(美国)';
case SubtitleType.zhTW:
return '中文(繁体)';
case SubtitleType.en:
return '英文';
case SubtitleType.pt:
return '葡萄牙语';
case SubtitleType.es:
return '西班牙语';
}
}
}
extension SubtitleIdExtension on SubtitleType {
String get id {
switch (this) {
case SubtitleType.zhCN:
return 'zh-CN';
case SubtitleType.aizh:
return 'ai-zh';
case SubtitleType.aien:
return 'ai-en';
case SubtitleType.zhHans:
return 'zh-Hans';
case SubtitleType.enUS:
return 'en-US';
case SubtitleType.zhTW:
return 'zh-TW';
case SubtitleType.en:
return 'en';
case SubtitleType.pt:
return 'pt';
case SubtitleType.es:
return 'es';
}
}
}
extension SubtitleCodeExtension on SubtitleType {
int get code {
switch (this) {
case SubtitleType.zhCN:
return 1;
case SubtitleType.aizh:
return 2;
case SubtitleType.aien:
return 3;
case SubtitleType.zhHans:
return 4;
case SubtitleType.enUS:
return 5;
case SubtitleType.zhTW:
return 6;
case SubtitleType.en:
return 7;
case SubtitleType.pt:
return 8;
case SubtitleType.es:
return 9;
}
}
}

View File

@ -1,5 +0,0 @@
enum VideoEpidoesType {
videoEpisode,
videoPart,
bangumiEpisode,
}

View File

@ -414,8 +414,6 @@ class DynamicMajorModel {
this.none, this.none,
this.type, this.type,
this.courses, this.courses,
this.common,
this.music,
}); });
DynamicArchiveModel? archive; DynamicArchiveModel? archive;
@ -431,8 +429,6 @@ class DynamicMajorModel {
// MAJOR_TYPE_OPUS 图文/文章 // MAJOR_TYPE_OPUS 图文/文章
String? type; String? type;
Map? courses; Map? courses;
Map? common;
Map? music;
DynamicMajorModel.fromJson(Map<String, dynamic> json) { DynamicMajorModel.fromJson(Map<String, dynamic> json) {
archive = json['archive'] != null archive = json['archive'] != null
@ -456,8 +452,6 @@ class DynamicMajorModel {
json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null; json['none'] != null ? DynamicNoneModel.fromJson(json['none']) : null;
type = json['type']; type = json['type'];
courses = json['courses'] ?? {}; courses = json['courses'] ?? {};
common = json['common'] ?? {};
music = json['music'] ?? {};
} }
} }

View File

@ -69,10 +69,9 @@ class RecVideoItemAppModel {
: null; : null;
// 由于app端api并不会直接返回与owner的关注状态 // 由于app端api并不会直接返回与owner的关注状态
// 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态从而与web端接口等效 // 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态从而与web端接口等效
RegExp regex = RegExp(r'已关注|新关注');
isFollowed = rcmdReason != null && isFollowed = rcmdReason != null &&
rcmdReason!.content != null && rcmdReason!.content != null &&
regex.hasMatch(rcmdReason!.content!) rcmdReason!.content!.contains('关注')
? 1 ? 1
: 0; : 0;
// 如果是就无需再显示推荐原因交由view统一处理即可 // 如果是就无需再显示推荐原因交由view统一处理即可

View File

@ -47,23 +47,18 @@ class Vip {
this.status, this.status,
this.dueDate, this.dueDate,
this.label, this.label,
this.nicknameColor,
}); });
int? type; int? type;
int? status; int? status;
int? dueDate; int? dueDate;
Map? label; Map? label;
int? nicknameColor;
Vip.fromJson(Map<String, dynamic> json) { Vip.fromJson(Map<String, dynamic> json) {
type = json['type']; type = json['type'];
status = json['status']; status = json['status'];
dueDate = json['due_date']; dueDate = json['due_date'];
label = json['label']; label = json['label'];
nicknameColor = json['nickname_color'] == ''
? null
: int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
} }
} }

View File

@ -23,7 +23,6 @@ class HotVideoItemModel {
this.dimension, this.dimension,
this.shortLinkV2, this.shortLinkV2,
this.firstFrame, this.firstFrame,
this.cover,
this.pubLocation, this.pubLocation,
this.seasontype, this.seasontype,
this.isOgv, this.isOgv,
@ -51,7 +50,6 @@ class HotVideoItemModel {
Dimension? dimension; Dimension? dimension;
String? shortLinkV2; String? shortLinkV2;
String? firstFrame; String? firstFrame;
String? cover;
String? pubLocation; String? pubLocation;
int? seasontype; int? seasontype;
bool? isOgv; bool? isOgv;
@ -79,7 +77,6 @@ class HotVideoItemModel {
dimension = Dimension.fromMap(json['dimension']); dimension = Dimension.fromMap(json['dimension']);
shortLinkV2 = json["short_link_v2"]; shortLinkV2 = json["short_link_v2"];
firstFrame = json["first_frame"]; firstFrame = json["first_frame"];
cover = json["first_frame"];
pubLocation = json["pub_location"]; pubLocation = json["pub_location"];
seasontype = json["seasontype"]; seasontype = json["seasontype"];
isOgv = json["isOgv"]; isOgv = json["isOgv"];

View File

@ -1,183 +0,0 @@
class MessageLikeModel {
MessageLikeModel({
this.latest,
this.total,
});
Latest? latest;
Total? total;
factory MessageLikeModel.fromJson(Map<String, dynamic> json) =>
MessageLikeModel(
latest: json["latest"] == null ? null : Latest.fromJson(json["latest"]),
total: json["total"] == null ? null : Total.fromJson(json["total"]),
);
}
class Latest {
Latest({
this.items,
this.lastViewAt,
});
List? items;
int? lastViewAt;
factory Latest.fromJson(Map<String, dynamic> json) => Latest(
items: json["items"],
lastViewAt: json["last_view_at"],
);
}
class Total {
Total({
this.cursor,
this.items,
});
Cursor? cursor;
List<MessageLikeItem>? items;
factory Total.fromJson(Map<String, dynamic> json) => Total(
cursor: Cursor.fromJson(json['cursor']),
items: json["items"] == null
? []
: json["items"].map<MessageLikeItem>((e) {
return MessageLikeItem.fromJson(e);
}).toList(),
);
}
class Cursor {
Cursor({
this.id,
this.isEnd,
this.time,
});
int? id;
bool? isEnd;
int? time;
factory Cursor.fromJson(Map<String, dynamic> json) => Cursor(
id: json['id'],
isEnd: json['is_end'],
time: json['time'],
);
}
class MessageLikeItem {
MessageLikeItem({
this.id,
this.users,
this.item,
this.counts,
this.likeTime,
this.noticeState,
this.isExpand = false,
});
int? id;
List<MessageLikeUser>? users;
MessageLikeItemItem? item;
int? counts;
int? likeTime;
int? noticeState;
bool isExpand;
factory MessageLikeItem.fromJson(Map<String, dynamic> json) =>
MessageLikeItem(
id: json["id"],
users: json["users"] == null
? []
: json["users"].map<MessageLikeUser>((e) {
return MessageLikeUser.fromJson(e);
}).toList(),
item: json["item"] == null
? null
: MessageLikeItemItem.fromJson(json["item"]),
counts: json["counts"],
likeTime: json["like_time"],
noticeState: json["notice_state"],
);
}
class MessageLikeUser {
MessageLikeUser({
this.mid,
this.fans,
this.nickname,
this.avatar,
this.midLink,
this.follow,
});
int? mid;
int? fans;
String? nickname;
String? avatar;
String? midLink;
bool? follow;
factory MessageLikeUser.fromJson(Map<String, dynamic> json) =>
MessageLikeUser(
mid: json["mid"],
fans: json["fans"],
nickname: json["nickname"],
avatar: json["avatar"],
midLink: json["mid_link"],
follow: json["follow"],
);
}
class MessageLikeItemItem {
MessageLikeItemItem({
this.itemId,
this.pid,
this.type,
this.business,
this.businessId,
this.replyBusinessId,
this.likeBusinessId,
this.title,
this.desc,
this.image,
this.uri,
this.detailName,
this.nativeUri,
this.ctime,
});
int? itemId;
int? pid;
String? type;
String? business;
int? businessId;
int? replyBusinessId;
int? likeBusinessId;
String? title;
String? desc;
String? image;
String? uri;
String? detailName;
String? nativeUri;
int? ctime;
factory MessageLikeItemItem.fromJson(Map<String, dynamic> json) =>
MessageLikeItemItem(
itemId: json["item_id"],
pid: json["pid"],
type: json["type"],
business: json["business"],
businessId: json["business_id"],
replyBusinessId: json["reply_business_id"],
likeBusinessId: json["like_business_id"],
title: json["title"],
desc: json["desc"],
image: json["image"],
uri: json["uri"],
detailName: json["detail_name"],
nativeUri: json["native_uri"],
ctime: json["ctime"],
);
}

View File

@ -1,168 +0,0 @@
class MessageReplyModel {
MessageReplyModel({
this.cursor,
this.items,
});
Cursor? cursor;
List<MessageReplyItem>? items;
MessageReplyModel.fromJson(Map<String, dynamic> json) {
cursor = Cursor.fromJson(json['cursor']);
items = json["items"] != null
? json["items"].map<MessageReplyItem>((e) {
return MessageReplyItem.fromJson(e);
}).toList()
: [];
}
}
class Cursor {
Cursor({
this.id,
this.isEnd,
this.time,
});
int? id;
bool? isEnd;
int? time;
Cursor.fromJson(Map<String, dynamic> json) {
id = json['id'];
isEnd = json['is_end'];
time = json['time'];
}
}
class MessageReplyItem {
MessageReplyItem({
this.count,
this.id,
this.isMulti,
this.item,
this.replyTime,
this.user,
});
int? count;
int? id;
int? isMulti;
ReplyContentItem? item;
int? replyTime;
ReplyUser? user;
MessageReplyItem.fromJson(Map<String, dynamic> json) {
count = json['count'];
id = json['id'];
isMulti = json['is_multi'];
item = ReplyContentItem.fromJson(json["item"]);
replyTime = json['reply_time'];
user = ReplyUser.fromJson(json['user']);
}
}
class ReplyContentItem {
ReplyContentItem({
this.subjectId,
this.rootId,
this.sourceId,
this.targetId,
this.type,
this.businessId,
this.business,
this.title,
this.desc,
this.image,
this.uri,
this.nativeUri,
this.detailTitle,
this.rootReplyContent,
this.sourceContent,
this.targetReplyContent,
this.atDetails,
this.topicDetails,
this.hideReplyButton,
this.hideLikeButton,
this.likeState,
this.danmu,
this.message,
});
int? subjectId;
int? rootId;
int? sourceId;
int? targetId;
String? type;
int? businessId;
String? business;
String? title;
String? desc;
String? image;
String? uri;
String? nativeUri;
String? detailTitle;
String? rootReplyContent;
String? sourceContent;
String? targetReplyContent;
List? atDetails;
List? topicDetails;
bool? hideReplyButton;
bool? hideLikeButton;
int? likeState;
String? danmu;
String? message;
ReplyContentItem.fromJson(Map<String, dynamic> json) {
subjectId = json['subject_id'];
rootId = json['root_id'];
sourceId = json['source_id'];
targetId = json['target_id'];
type = json['type'];
businessId = json['business_id'];
business = json['business'];
title = json['title'];
desc = json['desc'];
image = json['image'];
uri = json['uri'];
nativeUri = json['native_uri'];
detailTitle = json['detail_title'];
rootReplyContent = json['root_reply_content'];
sourceContent = json['source_content'];
targetReplyContent = json['target_reply_content'];
atDetails = json['at_details'];
topicDetails = json['topic_details'];
hideReplyButton = json['hide_reply_button'];
hideLikeButton = json['hide_like_button'];
likeState = json['like_state'];
danmu = json['danmu'];
message = json['message'];
}
}
class ReplyUser {
ReplyUser({
this.mid,
this.fans,
this.nickname,
this.avatar,
this.midLink,
this.follow,
});
int? mid;
int? fans;
String? nickname;
String? avatar;
String? midLink;
bool? follow;
ReplyUser.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
fans = json['fans'];
nickname = json['nickname'];
avatar = json['avatar'];
midLink = json['mid_link'];
follow = json['follow'];
}
}

View File

@ -1,9 +0,0 @@
class SearchAllModel {
SearchAllModel({this.topTList});
Map? topTList;
SearchAllModel.fromJson(Map<String, dynamic> json) {
topTList = json['top_tlist'];
}
}

View File

@ -5,9 +5,7 @@ class SearchVideoModel {
SearchVideoModel({this.list}); SearchVideoModel({this.list});
List<SearchVideoItemModel>? list; List<SearchVideoItemModel>? list;
SearchVideoModel.fromJson(Map<String, dynamic> json) { SearchVideoModel.fromJson(Map<String, dynamic> json) {
list = json['result'] == null list = json['result']
? []
: json['result']
.where((e) => e['available'] == true) .where((e) => e['available'] == true)
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e)) .map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
.toList(); .toList();
@ -27,7 +25,6 @@ class SearchVideoItemModel {
this.aid, this.aid,
this.bvid, this.bvid,
this.title, this.title,
this.titleList,
this.description, this.description,
this.pic, this.pic,
// this.play, // this.play,
@ -57,8 +54,8 @@ class SearchVideoItemModel {
String? arcurl; String? arcurl;
int? aid; int? aid;
String? bvid; String? bvid;
String? title; List? title;
List? titleList; // List? titleList;
String? description; String? description;
String? pic; String? pic;
// String? play; // String? play;
@ -85,9 +82,8 @@ class SearchVideoItemModel {
aid = json['aid']; aid = json['aid'];
bvid = json['bvid']; bvid = json['bvid'];
mid = json['mid']; 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']);
titleList = Em.regTitle(json['title']);
description = json['description']; description = json['description'];
pic = json['pic'] != null && json['pic'].startsWith('//') pic = json['pic'] != null && json['pic'].startsWith('//')
? 'https:${json['pic']}' ? 'https:${json['pic']}'
@ -236,7 +232,6 @@ class SearchLiveItemModel {
this.userCover, this.userCover,
this.type, this.type,
this.title, this.title,
this.titleList,
this.cover, this.cover,
this.pic, this.pic,
this.online, this.online,
@ -256,8 +251,7 @@ class SearchLiveItemModel {
String? face; String? face;
String? userCover; String? userCover;
String? type; String? type;
String? title; List? title;
List? titleList;
String? cover; String? cover;
String? pic; String? pic;
int? online; int? online;
@ -278,8 +272,7 @@ class SearchLiveItemModel {
face = json['uface']; face = json['uface'];
userCover = json['user_cover']; userCover = json['user_cover'];
type = json['type']; type = json['type'];
title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); title = Em.regTitle(json['title']);
titleList = Em.regTitle(json['title']);
cover = json['cover']; cover = json['cover'];
pic = json['cover']; pic = json['cover'];
online = json['online']; online = json['online'];
@ -309,7 +302,6 @@ class SearchMBangumiItemModel {
this.type, this.type,
this.mediaId, this.mediaId,
this.title, this.title,
this.titleList,
this.orgTitle, this.orgTitle,
this.mediaType, this.mediaType,
this.cv, this.cv,
@ -336,8 +328,7 @@ class SearchMBangumiItemModel {
String? type; String? type;
int? mediaId; int? mediaId;
String? title; List? title;
List? titleList;
String? orgTitle; String? orgTitle;
int? mediaType; int? mediaType;
String? cv; String? cv;
@ -364,8 +355,7 @@ class SearchMBangumiItemModel {
SearchMBangumiItemModel.fromJson(Map<String, dynamic> json) { SearchMBangumiItemModel.fromJson(Map<String, dynamic> json) {
type = json['type']; type = json['type'];
mediaId = json['media_id']; mediaId = json['media_id'];
title = json['title'].replaceAll(RegExp(r'<.*?>'), ''); title = Em.regTitle(json['title']);
titleList = Em.regTitle(json['title']);
orgTitle = json['org_title']; orgTitle = json['org_title'];
mediaType = json['media_type']; mediaType = json['media_type'];
cv = json['cv']; cv = json['cv'];
@ -447,8 +437,7 @@ class SearchArticleItemModel {
pubTime = json['pub_time']; pubTime = json['pub_time'];
like = json['like']; like = json['like'];
title = Em.regTitle(json['title']); title = Em.regTitle(json['title']);
subTitle = subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
Em.decodeHtmlEntities(json['title'].replaceAll(RegExp(r'<[^>]*>'), ''));
rankOffset = json['rank_offset']; rankOffset = json['rank_offset'];
mid = json['mid']; mid = json['mid'];
imageUrls = json['image_urls']; imageUrls = json['image_urls'];

View File

@ -1,6 +0,0 @@
final List aoOutputList = [
{'title': 'audiotrack,opensles', 'value': '0'},
{'title': 'opensles,audiotrack', 'value': '1'},
{'title': 'audiotrack', 'value': '2'},
{'title': 'opensles', 'value': '3'},
];

View File

@ -39,14 +39,6 @@ extension VideoQualityCode on VideoQuality {
} }
return null; return null;
} }
static int? toCode(VideoQuality quality) {
final index = VideoQuality.values.indexOf(quality);
if (index != -1 && index < _codeList.length) {
return _codeList[index];
}
return null;
}
} }
extension VideoQualityDesc on VideoQuality { extension VideoQualityDesc on VideoQuality {

View File

@ -1,20 +0,0 @@
class SubTitileContentModel {
double? from;
double? to;
int? location;
String? content;
SubTitileContentModel({
this.from,
this.to,
this.location,
this.content,
});
SubTitileContentModel.fromJson(Map<String, dynamic> json) {
from = json['from'];
to = json['to'];
location = json['location'];
content = json['content'];
}
}

View File

@ -1,89 +0,0 @@
import 'package:get/get.dart';
import '../../common/subtitle_type.dart';
class SubTitlteModel {
SubTitlteModel({
this.aid,
this.bvid,
this.cid,
this.loginMid,
this.loginMidHash,
this.isOwner,
this.name,
this.subtitles,
});
int? aid;
String? bvid;
int? cid;
int? loginMid;
String? loginMidHash;
bool? isOwner;
String? name;
List<SubTitlteItemModel>? subtitles;
factory SubTitlteModel.fromJson(Map<String, dynamic> json) => SubTitlteModel(
aid: json["aid"],
bvid: json["bvid"],
cid: json["cid"],
loginMid: json["login_mid"],
loginMidHash: json["login_mid_hash"],
isOwner: json["is_owner"],
name: json["name"],
subtitles: json["subtitle"] != null
? json["subtitle"]["subtitles"]
.map<SubTitlteItemModel>((x) => SubTitlteItemModel.fromJson(x))
.toList()
: [],
);
}
class SubTitlteItemModel {
SubTitlteItemModel({
this.id,
this.lan,
this.lanDoc,
this.isLock,
this.subtitleUrl,
this.type,
this.aiType,
this.aiStatus,
this.title,
this.code,
this.content,
this.body,
});
int? id;
String? lan;
String? lanDoc;
bool? isLock;
String? subtitleUrl;
int? type;
int? aiType;
int? aiStatus;
String? title;
int? code;
String? content;
List? body;
factory SubTitlteItemModel.fromJson(Map<String, dynamic> json) =>
SubTitlteItemModel(
id: json["id"],
lan: json["lan"].replaceAll('-', ''),
lanDoc: json["lan_doc"],
isLock: json["is_lock"],
subtitleUrl: json["subtitle_url"],
type: json["type"],
aiType: json["ai_type"],
aiStatus: json["ai_status"],
title: json["lan_doc"],
code: SubtitleType.values
.firstWhereOrNull(
(element) => element.id.toString() == json["lan"])
?.index ??
-1,
content: '',
body: [],
);
}

View File

@ -67,7 +67,6 @@ class VideoDetailData {
String? likeIcon; String? likeIcon;
bool? needJumpBv; bool? needJumpBv;
String? epId; String? epId;
List<Staff>? staff;
VideoDetailData({ VideoDetailData({
this.bvid, this.bvid,
@ -104,7 +103,6 @@ class VideoDetailData {
this.likeIcon, this.likeIcon,
this.needJumpBv, this.needJumpBv,
this.epId, this.epId,
this.staff,
}); });
VideoDetailData.fromJson(Map<String, dynamic> json) { VideoDetailData.fromJson(Map<String, dynamic> json) {
@ -157,9 +155,6 @@ class VideoDetailData {
if (json['redirect_url'] != null) { if (json['redirect_url'] != null) {
epId = resolveEpId(json['redirect_url']); epId = resolveEpId(json['redirect_url']);
} }
staff = json["staff"] != null
? List<Staff>.from(json["staff"]!.map((e) => Staff.fromJson(e)))
: null;
} }
String resolveEpId(url) { String resolveEpId(url) {
@ -382,7 +377,6 @@ class Part {
String? weblink; String? weblink;
Dimension? dimension; Dimension? dimension;
String? firstFrame; String? firstFrame;
String? cover;
Part({ Part({
this.cid, this.cid,
@ -394,7 +388,6 @@ class Part {
this.weblink, this.weblink,
this.dimension, this.dimension,
this.firstFrame, this.firstFrame,
this.cover,
}); });
fromRawJson(String str) => Part.fromJson(json.decode(str)); fromRawJson(String str) => Part.fromJson(json.decode(str));
@ -412,8 +405,7 @@ class Part {
dimension = json["dimension"] == null dimension = json["dimension"] == null
? null ? null
: Dimension.fromJson(json["dimension"]); : Dimension.fromJson(json["dimension"]);
firstFrame = json["first_frame"] ?? ''; firstFrame = json["first_frame"];
cover = json["first_frame"] ?? '';
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -637,7 +629,6 @@ class EpisodeItem {
this.attribute, this.attribute,
this.page, this.page,
this.bvid, this.bvid,
this.cover,
}); });
int? seasonId; int? seasonId;
int? sectionId; int? sectionId;
@ -648,7 +639,6 @@ class EpisodeItem {
int? attribute; int? attribute;
Part? page; Part? page;
String? bvid; String? bvid;
String? cover;
EpisodeItem.fromJson(Map<String, dynamic> json) { EpisodeItem.fromJson(Map<String, dynamic> json) {
seasonId = json['season_id']; seasonId = json['season_id'];
@ -660,46 +650,5 @@ class EpisodeItem {
attribute = json['attribute']; attribute = json['attribute'];
page = Part.fromJson(json['page']); page = Part.fromJson(json['page']);
bvid = json['bvid']; bvid = json['bvid'];
cover = json['arc']['pic'];
}
}
class Staff {
Staff({
this.mid,
this.title,
this.name,
this.face,
this.vip,
});
int? mid;
String? title;
String? name;
String? face;
int? status;
Vip? vip;
Staff.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
title = json['title'];
name = json['name'];
face = json['face'];
vip = Vip.fromJson(json['vip']);
}
}
class Vip {
Vip({
this.type,
this.status,
});
int? type;
int? status;
Vip.fromJson(Map<String, dynamic> json) {
type = json['type'];
status = json['status'];
} }
} }

View File

@ -218,7 +218,7 @@ class AboutController extends GetxController {
RxString currentVersion = ''.obs; RxString currentVersion = ''.obs;
RxString remoteVersion = ''.obs; RxString remoteVersion = ''.obs;
late LatestDataModel remoteAppInfo; late LatestDataModel remoteAppInfo;
RxBool isUpdate = false.obs; RxBool isUpdate = true.obs;
RxBool isLoading = true.obs; RxBool isLoading = true.obs;
late LatestDataModel data; late LatestDataModel data;

View File

@ -15,10 +15,6 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../../../common/pages_bottom_sheet.dart';
import '../../../models/common/video_episode_type.dart';
import '../../../utils/drawer.dart';
class BangumiIntroController extends GetxController { class BangumiIntroController extends GetxController {
// 视频bvid // 视频bvid
String bvid = Get.parameters['bvid']!; String bvid = Get.parameters['bvid']!;
@ -56,7 +52,6 @@ class BangumiIntroController extends GetxController {
RxMap followStatus = {}.obs; RxMap followStatus = {}.obs;
int _tempThemeValue = -1; int _tempThemeValue = -1;
var userInfo; var userInfo;
PersistentBottomSheetController? bottomSheetController;
@override @override
void onInit() { void onInit() {
@ -131,37 +126,51 @@ class BangumiIntroController extends GetxController {
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('选择投币个数'), title: const Text('选择投币个数'),
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 24), contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
content: StatefulBuilder(builder: (context, StateSetter setState) { content: StatefulBuilder(builder: (context, StateSetter setState) {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [1, 2] children: [
.map( RadioListTile(
(e) => RadioListTile( value: 1,
value: e, title: const Text('1枚'),
title: Text('$e枚'),
groupValue: _tempThemeValue, groupValue: _tempThemeValue,
onChanged: (value) async { onChanged: (value) {
_tempThemeValue = value!; _tempThemeValue = value!;
setState(() {}); Get.appUpdate();
},
),
RadioListTile(
value: 2,
title: const Text('2枚'),
groupValue: _tempThemeValue,
onChanged: (value) {
_tempThemeValue = value!;
Get.appUpdate();
},
),
],
);
}),
actions: [
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
TextButton(
onPressed: () async {
var res = await VideoHttp.coinVideo( var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue); bvid: bvid, multiply: _tempThemeValue);
if (res['status']) { if (res['status']) {
SmartDialog.showToast('投币成功 👏'); SmartDialog.showToast('投币成功 👏');
hasCoin.value = true; hasCoin.value = true;
bangumiDetail.value.stat!['coins'] = bangumiDetail.value.stat!['coins'] =
bangumiDetail.value.stat!['coins'] + bangumiDetail.value.stat!['coins'] + _tempThemeValue;
_tempThemeValue;
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }
Get.back(); Get.back();
}, },
), child: const Text('确定'),
) )
.toList(), ],
);
}),
); );
}); });
} }
@ -215,18 +224,14 @@ class BangumiIntroController extends GetxController {
} }
// 修改分P或番剧分集 // 修改分P或番剧分集
Future changeSeasonOrbangu(bvid, cid, aid, cover) async { Future changeSeasonOrbangu(bvid, cid, aid) async {
// 重新获取视频资源 // 重新获取视频资源
VideoDetailController videoDetailCtr = VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']); Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
videoDetailCtr.bvid = bvid; videoDetailCtr.bvid = bvid;
videoDetailCtr.cid.value = cid; videoDetailCtr.cid.value = cid;
videoDetailCtr.danmakuCid.value = cid; videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.oid.value = aid;
videoDetailCtr.cover.value = cover;
videoDetailCtr.queryVideoUrl(); videoDetailCtr.queryVideoUrl();
videoDetailCtr.getSubtitle();
videoDetailCtr.setSubtitleContent();
// 重新请求评论 // 重新请求评论
try { try {
/// 未渲染回复组件时可能异常 /// 未渲染回复组件时可能异常
@ -284,36 +289,6 @@ class BangumiIntroController extends GetxController {
int cid = episodes[nextIndex].cid!; int cid = episodes[nextIndex].cid!;
String bvid = episodes[nextIndex].bvid!; String bvid = episodes[nextIndex].bvid!;
int aid = episodes[nextIndex].aid!; int aid = episodes[nextIndex].aid!;
String cover = episodes[nextIndex].cover!; changeSeasonOrbangu(bvid, cid, aid);
changeSeasonOrbangu(bvid, cid, aid, cover);
}
// 播放器底栏 选集 回调
void showEposideHandler() {
late List episodes = bangumiDetail.value.episodes!;
VideoEpidoesType dataType = VideoEpidoesType.bangumiEpisode;
if (episodes.isEmpty) {
return;
}
VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
DrawerUtils.showRightDialog(
child: EpisodeBottomSheet(
episodes: episodes,
currentCid: videoDetailCtr.cid.value,
dataType: dataType,
context: Get.context!,
sheetHeight: Get.size.height,
isFullScreen: true,
changeFucCall: (item, index) {
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
SmartDialog.dismiss();
},
).buildShowContent(Get.context!),
);
}
hiddenEpisodeBottomSheet() {
bottomSheetController?.close();
} }
} }

View File

@ -138,9 +138,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
cid = widget.cid!; cid = widget.cid!;
videoDetailCtr.cid.listen((p0) { videoDetailCtr.cid.listen((p0) {
cid = p0; cid = p0;
if (!mounted) {
return;
}
setState(() {}); setState(() {});
}); });
} }
@ -194,8 +191,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
src: widget.bangumiDetail!.cover!, src: widget.bangumiDetail!.cover!,
), ),
PBadge( PBadge(
text: text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
'评分 ${widget.bangumiDetail?.rating?['score']! ?? '暂无'}',
top: null, top: null,
right: 6, right: 6,
bottom: 6, bottom: 6,
@ -321,12 +317,11 @@ class _BangumiInfoState extends State<BangumiInfo> {
if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[ if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[
BangumiPanel( BangumiPanel(
pages: widget.bangumiDetail!.episodes!, pages: widget.bangumiDetail!.episodes!,
cid: cid! ?? widget.bangumiDetail!.episodes!.first.cid!, cid: cid ?? widget.bangumiDetail!.episodes!.first.cid,
sheetHeight: sheetHeight, sheetHeight: sheetHeight,
changeFuc: (bvid, cid, aid, cover) => bangumiIntroController changeFuc: (bvid, cid, aid) =>
.changeSeasonOrbangu(bvid, cid, aid, cover), bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
bangumiDetail: bangumiIntroController.bangumiDetail.value, bangumiDetail: bangumiIntroController.bangumiDetail.value,
bangumiIntroController: bangumiIntroController,
) )
], ],
], ],

View File

@ -20,10 +20,10 @@ class IntroDetail extends StatelessWidget {
sheetHeight = localCache.get('sheetHeight'); sheetHeight = localCache.get('sheetHeight');
TextStyle smallTitle = TextStyle( TextStyle smallTitle = TextStyle(
fontSize: 12, fontSize: 12,
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onBackground,
); );
return Container( return Container(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(left: 14, right: 14), padding: const EdgeInsets.only(left: 14, right: 14),
height: sheetHeight, height: sheetHeight,
child: Column( child: Column(

View File

@ -2,10 +2,13 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:nil/nil.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/utils/main_stream.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/bangumu_card_v.dart'; import 'widgets/bangumu_card_v.dart';
@ -31,6 +34,10 @@ class _BangumiPageState extends State<BangumiPage>
void initState() { void initState() {
super.initState(); super.initState();
scrollController = _bangumidController.scrollController; scrollController = _bangumidController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
_futureBuilderFuture = _bangumidController.queryBangumiListFeed(); _futureBuilderFuture = _bangumidController.queryBangumiListFeed();
_futureBuilderFutureFollow = _bangumidController.queryBangumiFollow(); _futureBuilderFutureFollow = _bangumidController.queryBangumiFollow();
scrollController.addListener( scrollController.addListener(
@ -42,7 +49,16 @@ class _BangumiPageState extends State<BangumiPage>
_bangumidController.onLoad(); _bangumidController.onLoad();
}); });
} }
handleScrollEvent(scrollController);
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
}, },
); );
} }
@ -141,10 +157,10 @@ class _BangumiPageState extends State<BangumiPage>
), ),
); );
} else { } else {
return const SizedBox(); return nil;
} }
} else { } else {
return const SizedBox(); return nil;
} }
}, },
), ),
@ -215,7 +231,7 @@ class _BangumiPageState extends State<BangumiPage>
(BuildContext context, int index) { (BuildContext context, int index) {
return bangumiList!.isNotEmpty return bangumiList!.isNotEmpty
? BangumiCardV(bangumiItem: bangumiList[index]) ? BangumiCardV(bangumiItem: bangumiList[index])
: const SizedBox(); : nil;
}, },
childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10, childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10,
), ),

View File

@ -1,5 +1,3 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -8,35 +6,31 @@ import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../../../common/pages_bottom_sheet.dart';
import '../../../models/common/video_episode_type.dart';
import '../introduction/controller.dart';
class BangumiPanel extends StatefulWidget { class BangumiPanel extends StatefulWidget {
const BangumiPanel({ const BangumiPanel({
super.key, super.key,
required this.pages, required this.pages,
required this.cid, this.cid,
this.sheetHeight, this.sheetHeight,
this.changeFuc, this.changeFuc,
this.bangumiDetail, this.bangumiDetail,
this.bangumiIntroController,
}); });
final List<EpisodeItem> pages; final List<EpisodeItem> pages;
final int cid; final int? cid;
final double? sheetHeight; final double? sheetHeight;
final Function? changeFuc; final Function? changeFuc;
final BangumiInfoModel? bangumiDetail; final BangumiInfoModel? bangumiDetail;
final BangumiIntroController? bangumiIntroController;
@override @override
State<BangumiPanel> createState() => _BangumiPanelState(); State<BangumiPanel> createState() => _BangumiPanelState();
} }
class _BangumiPanelState extends State<BangumiPanel> { class _BangumiPanelState extends State<BangumiPanel> {
late RxInt currentIndex = (-1).obs; late int currentIndex;
final ScrollController listViewScrollCtr = ScrollController(); final ScrollController listViewScrollCtr = ScrollController();
final ScrollController listViewScrollCtr_2 = ScrollController();
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
dynamic userInfo; dynamic userInfo;
// 默认未开通 // 默认未开通
@ -45,75 +39,169 @@ class _BangumiPanelState extends State<BangumiPanel> {
String heroTag = Get.arguments['heroTag']; String heroTag = Get.arguments['heroTag'];
late final VideoDetailController videoDetailCtr; late final VideoDetailController videoDetailCtr;
final ItemScrollController itemScrollController = ItemScrollController(); final ItemScrollController itemScrollController = ItemScrollController();
late PersistentBottomSheetController? _bottomSheetController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
cid = widget.cid; cid = widget.cid!;
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag); currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
currentIndex.value =
widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
scrollToIndex(); scrollToIndex();
videoDetailCtr.cid.listen((int p0) {
cid = p0;
currentIndex.value =
widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
scrollToIndex();
});
/// 获取大会员状态
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null) { if (userInfo != null) {
vipStatus = userInfo.vipStatus; vipStatus = userInfo.vipStatus;
} }
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
scrollToIndex();
});
} }
@override @override
void dispose() { void dispose() {
listViewScrollCtr.dispose(); listViewScrollCtr.dispose();
listViewScrollCtr_2.dispose();
super.dispose(); super.dispose();
} }
Widget buildPageListItem(
EpisodeItem page,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
return ListTile(
onTap: () {
Get.back();
setState(() {
changeFucCall(page, index);
});
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(
'${page.title}${page.longTitle!}',
style: TextStyle(
fontSize: 14,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: page.badge != null
? Text(
page.badge!,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
)
: const SizedBox(),
);
}
void showBangumiPanel() {
showBottomSheet(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
// await Future.delayed(const Duration(milliseconds: 200));
// listViewScrollCtr_2.animateTo(currentIndex * 56,
// duration: const Duration(milliseconds: 500),
// curve: Curves.easeInOut);
itemScrollController.jumpTo(index: currentIndex);
});
// 在这里使用 setState 更新状态
return Container(
height: widget.sheetHeight,
color: Theme.of(context).colorScheme.background,
child: Column(
children: [
AppBar(
toolbarHeight: 45,
automaticallyImplyLeading: false,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'合集(${widget.pages.length}',
style: Theme.of(context).textTheme.titleMedium,
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
],
),
titleSpacing: 10,
),
Expanded(
child: Material(
child: ScrollablePositionedList.builder(
itemCount: widget.pages.length + 1,
itemBuilder: (BuildContext context, int index) {
bool isLastItem = index == widget.pages.length;
bool isCurrentIndex = currentIndex == index;
return isLastItem
? SizedBox(
height:
MediaQuery.of(context).padding.bottom +
20,
)
: buildPageListItem(
widget.pages[index],
index,
isCurrentIndex,
);
},
itemScrollController: itemScrollController,
),
),
),
],
),
);
},
);
},
);
}
void changeFucCall(item, i) async { void changeFucCall(item, i) async {
if (item.badge != null && item.badge == '会员' && vipStatus != 1) { if (item.badge != null && item.badge == '会员' && vipStatus != 1) {
SmartDialog.showToast('需要大会员'); SmartDialog.showToast('需要大会员');
return; return;
} }
widget.changeFuc?.call( await widget.changeFuc!(
item.bvid, item.bvid,
item.cid, item.cid,
item.aid, item.aid,
item.cover,
); );
if (_bottomSheetController != null) { currentIndex = i;
_bottomSheetController?.close(); setState(() {});
}
currentIndex.value = i;
scrollToIndex(); scrollToIndex();
} }
void scrollToIndex() { void scrollToIndex() {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
// 在回调函数中获取更新后的状态 // 在回调函数中获取更新后的状态
final double offset = min((currentIndex * 150) - 75, listViewScrollCtr.animateTo(currentIndex * 150,
listViewScrollCtr.position.maxScrollExtent); duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
if (currentIndex.value == 0) {
listViewScrollCtr.jumpTo(0);
} else {
listViewScrollCtr.animateTo(
offset,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
}
}); });
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color primary = Theme.of(context).colorScheme.primary;
Color onSurface = Theme.of(context).colorScheme.onSurface;
return Column( return Column(
children: [ children: [
Padding( Padding(
@ -123,9 +211,8 @@ class _BangumiPanelState extends State<BangumiPanel> {
children: [ children: [
const Text('选集 '), const Text('选集 '),
Expanded( Expanded(
child: Obx( child: Text(
() => Text( ' 正在播放:${widget.pages[currentIndex].longTitle}',
' 正在播放:${widget.pages[currentIndex.value].longTitle}',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
@ -133,7 +220,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
), ),
), ),
), ),
),
const SizedBox(width: 10), const SizedBox(width: 10),
SizedBox( SizedBox(
height: 34, height: 34,
@ -141,17 +227,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
style: ButtonStyle( style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: MaterialStateProperty.all(EdgeInsets.zero),
), ),
onPressed: () { onPressed: () => showBangumiPanel(),
widget.bangumiIntroController?.bottomSheetController =
_bottomSheetController = EpisodeBottomSheet(
currentCid: cid,
episodes: widget.pages,
changeFucCall: changeFucCall,
sheetHeight: widget.sheetHeight,
dataType: VideoEpidoesType.bangumiEpisode,
context: context,
).show(context);
},
child: Text( child: Text(
'${widget.bangumiDetail!.newEp!['desc']}', '${widget.bangumiDetail!.newEp!['desc']}',
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
@ -169,8 +245,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
itemCount: widget.pages.length, itemCount: widget.pages.length,
itemExtent: 150, itemExtent: 150,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
var page = widget.pages[i];
bool isSelected = i == currentIndex.value;
return Container( return Container(
width: 150, width: 150,
margin: const EdgeInsets.only(right: 10), margin: const EdgeInsets.only(right: 10),
@ -179,37 +253,42 @@ class _BangumiPanelState extends State<BangumiPanel> {
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: InkWell( child: InkWell(
onTap: () => changeFucCall(page, i), onTap: () => changeFucCall(widget.pages[i], i),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8, vertical: 8, horizontal: 10),
horizontal: 10,
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Row( Row(
children: [ children: [
if (isSelected) ...<Widget>[ if (i == currentIndex) ...<Widget>[
Image.asset('assets/images/live.png', Image.asset(
color: primary, height: 12), 'assets/images/live.png',
color: Theme.of(context).colorScheme.primary,
height: 12,
),
const SizedBox(width: 6) const SizedBox(width: 6)
], ],
Text( Text(
'${i + 1}', '${i + 1}',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: isSelected ? primary : onSurface, color: i == currentIndex
), ? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface),
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
if (page.badge != null) ...[ if (widget.pages[i].badge != null) ...[
const Spacer(), const Spacer(),
Text( Text(
page.badge!, widget.pages[i].badge!,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: primary, color:
Theme.of(context).colorScheme.primary,
), ),
), ),
] ]
@ -217,12 +296,13 @@ class _BangumiPanelState extends State<BangumiPanel> {
), ),
const SizedBox(height: 3), const SizedBox(height: 3),
Text( Text(
page.longTitle!, widget.pages[i].longTitle!,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: isSelected ? primary : onSurface, color: i == currentIndex
), ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
) )
], ],

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package: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/models/bangumi/list.dart'; import 'package:pilipala/http/search.dart';
import 'package:pilipala/utils/image_save.dart'; import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/models/common/search_type.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';
@ -13,28 +14,68 @@ class BangumiCardV extends StatelessWidget {
const BangumiCardV({ const BangumiCardV({
super.key, super.key,
required this.bangumiItem, required this.bangumiItem,
this.longPress,
this.longPressEnd,
}); });
final BangumiListItemModel bangumiItem; final bangumiItem;
final Function()? longPress;
final Function()? longPressEnd;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId); String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
return InkWell( return Card(
onTap: () { elevation: 0,
RoutePush.bangumiPush( clipBehavior: Clip.hardEdge,
bangumiItem.seasonId, margin: EdgeInsets.zero,
null, child: GestureDetector(
heroTag: heroTag, // onLongPress: () {
); // if (longPress != null) {
// longPress!();
// }
// },
// onLongPressEnd: (details) {
// if (longPressEnd != null) {
// longPressEnd!();
// }
// },
child: InkWell(
onTap: () async {
final int seasonId = bangumiItem.seasonId;
SmartDialog.showLoading(msg: '获取中...');
final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
SmartDialog.dismiss().then((value) {
if (res['status']) {
if (res['data'].episodes.isEmpty) {
SmartDialog.showToast('资源加载失败');
return;
}
EpisodeItem episode = res['data'].episodes.first;
String bvid = episode.bvid!;
int cid = episode.cid!;
String pic = episode.cover!;
String heroTag = Utils.makeHeroTag(cid);
Get.toNamed(
'/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);
}
});
}, },
onLongPress: () =>
imageSaveDialog(context, bangumiItem, SmartDialog.dismiss),
child: Column( child: Column(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.only(
StyleString.imgRadius, topLeft: StyleString.imgRadius,
topRight: StyleString.imgRadius,
bottomLeft: StyleString.imgRadius,
bottomRight: StyleString.imgRadius,
), ),
child: AspectRatio( child: AspectRatio(
aspectRatio: 0.65, aspectRatio: 0.65,
@ -75,6 +116,8 @@ class BangumiCardV extends StatelessWidget {
BangumiContent(bangumiItem: bangumiItem) BangumiContent(bangumiItem: bangumiItem)
], ],
), ),
),
),
); );
} }
} }

View File

@ -1,111 +0,0 @@
import 'dart:async';
import 'package:dlna_dart/dlna.dart';
import 'package:flutter/material.dart';
class LiveDlnaPage extends StatefulWidget {
final String datasource;
const LiveDlnaPage({Key? key, required this.datasource}) : super(key: key);
@override
State<LiveDlnaPage> createState() => _LiveDlnaPageState();
}
class _LiveDlnaPageState extends State<LiveDlnaPage> {
final Map<String, DLNADevice> _deviceList = {};
final DLNAManager searcher = DLNAManager();
late final Timer stopSearchTimer;
String selectDeviceKey = '';
bool isSearching = true;
DLNADevice? get device => _deviceList[selectDeviceKey];
@override
void initState() {
stopSearchTimer = Timer(const Duration(seconds: 20), () {
setState(() => isSearching = false);
searcher.stop();
});
searcher.stop();
startSearch();
super.initState();
}
@override
void dispose() {
super.dispose();
searcher.stop();
stopSearchTimer.cancel();
}
void startSearch() async {
// clear old devices
isSearching = true;
selectDeviceKey = '';
_deviceList.clear();
setState(() {});
// start search server
final m = await searcher.start();
m.devices.stream.listen((deviceList) {
deviceList.forEach((key, value) {
_deviceList[key] = value;
});
setState(() {});
});
// close the server, the closed server can be start by call searcher.start()
}
void selectDevice(String key) {
if (selectDeviceKey.isNotEmpty) device?.pause();
selectDeviceKey = key;
device?.setUrl(widget.datasource);
device?.play();
setState(() {});
}
@override
Widget build(BuildContext context) {
Widget cur;
if (isSearching && _deviceList.isEmpty) {
cur = const Center(child: CircularProgressIndicator());
} else if (_deviceList.isEmpty) {
cur = Center(
child: Text(
'没有找到设备',
style: Theme.of(context).textTheme.bodyLarge,
),
);
} else {
cur = ListView(
children: _deviceList.keys
.map<Widget>((key) => ListTile(
contentPadding: const EdgeInsets.all(2),
title: Text(_deviceList[key]!.info.friendlyName),
subtitle: Text(key),
onTap: () => selectDevice(key),
))
.toList(),
);
}
return AlertDialog(
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('查找设备'),
IconButton(
onPressed: startSearch,
icon: const Icon(Icons.refresh_rounded),
),
],
),
content: SizedBox(
height: 200,
width: 200,
child: cur,
),
);
}
}

View File

@ -14,7 +14,6 @@ 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/id_utils.dart';
import 'package:pilipala/utils/route_push.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';
@ -71,7 +70,7 @@ class DynamicsController extends GetxController {
Future queryFollowDynamic({type = 'init'}) async { Future queryFollowDynamic({type = 'init'}) async {
if (!userLogin.value) { if (!userLogin.value) {
return {'status': false, 'msg': '账号未登录', 'code': -101}; return {'status': false, 'msg': '账号未登录'};
} }
if (type == 'init') { if (type == 'init') {
dynamicsList.clear(); dynamicsList.clear();
@ -221,7 +220,25 @@ class DynamicsController extends GetxController {
print('DYNAMIC_TYPE_PGC_UNION 番剧'); print('DYNAMIC_TYPE_PGC_UNION 番剧');
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc; DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
if (pgc.epid != null) { if (pgc.epid != null) {
RoutePush.bangumiPush(null, pgc.epid); SmartDialog.showLoading(msg: '获取中...');
var res = await SearchHttp.bangumiInfo(epId: pgc.epid);
SmartDialog.dismiss();
if (res['status']) {
EpisodeItem episode = res['data'].episodes.first;
String bvid = episode.bvid!;
int cid = episode.cid!;
String pic = episode.cover!;
String heroTag = Utils.makeHeroTag(cid);
Get.toNamed(
'/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}',
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);
}
} }
break; break;
} }
@ -229,7 +246,7 @@ class DynamicsController extends GetxController {
Future queryFollowUp({type = 'init'}) async { Future queryFollowUp({type = 'init'}) async {
if (!userLogin.value) { if (!userLogin.value) {
return {'status': false, 'msg': '账号未登录', 'code': -101}; return {'status': false, 'msg': '账号未登录'};
} }
if (type == 'init') { if (type == 'init') {
upData.value.upList = <UpItem>[]; upData.value.upList = <UpItem>[];

View File

@ -25,7 +25,6 @@ class DynamicDetailController extends GetxController {
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; Box setting = GStrorage.setting;
RxInt replyReqCode = 200.obs;
@override @override
void onInit() { void onInit() {
@ -85,7 +84,6 @@ class DynamicDetailController extends GetxController {
replyList.addAll(replies); replyList.addAll(replies);
} }
} }
replyReqCode.value = res['code'];
isLoadingMore = false; isLoadingMore = false;
return res; return res;
} }

View File

@ -16,7 +16,6 @@ import 'package:pilipala/pages/video/detail/reply_reply/index.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 '../../../models/video/reply/item.dart';
import '../widgets/dynamic_panel.dart'; import '../widgets/dynamic_panel.dart';
class DynamicDetailPage extends StatefulWidget { class DynamicDetailPage extends StatefulWidget {
@ -183,7 +182,6 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
scrollController.removeListener(() {}); scrollController.removeListener(() {});
fabAnimationCtr.dispose(); fabAnimationCtr.dispose();
scrollController.dispose(); scrollController.dispose();
titleStreamC.close();
super.dispose(); super.dispose();
} }
@ -212,7 +210,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
onRefresh: () async { onRefresh: () async {
await _dynamicDetailController.queryReplyList(); await _dynamicDetailController.queryReplyList();
}, },
child: CustomScrollView( child: Stack(
children: [
CustomScrollView(
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
if (action != 'comment') if (action != 'comment')
@ -230,7 +230,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
border: Border( border: Border(
top: BorderSide( top: BorderSide(
width: 0.6, width: 0.6,
color: Theme.of(context).dividerColor.withOpacity(0.05), color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
), ),
), ),
), ),
@ -262,7 +264,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
_dynamicDetailController.queryBySort(), _dynamicDetailController.queryBySort(),
icon: const Icon(Icons.sort, size: 16), icon: const Icon(Icons.sort, size: 16),
label: Obx(() => Text( label: Obx(() => Text(
_dynamicDetailController.sortTypeLabel.value, _dynamicDetailController
.sortTypeLabel.value,
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
)), )),
), ),
@ -279,22 +282,22 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map; Map data = snapshot.data as Map;
if (snapshot.data['status']) { if (snapshot.data['status']) {
RxList<ReplyItemModel> replyList =
_dynamicDetailController.replyList;
// 请求成功 // 请求成功
return Obx( return Obx(
() => replyList.isEmpty && () => _dynamicDetailController.replyList.isEmpty &&
_dynamicDetailController.isLoadingMore _dynamicDetailController.isLoadingMore
? SliverList( ? SliverList(
delegate: delegate: SliverChildBuilderDelegate(
SliverChildBuilderDelegate((context, index) { (context, index) {
return const VideoReplySkeleton(); return const VideoReplySkeleton();
}, childCount: 8), }, childCount: 8),
) )
: SliverList( : SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == replyList.length) { if (index ==
_dynamicDetailController
.replyList.length) {
return Container( return Container(
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context) bottom: MediaQuery.of(context)
@ -321,21 +324,25 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
); );
} else { } else {
return ReplyItem( return ReplyItem(
replyItem: replyList[index], replyItem: _dynamicDetailController
.replyList[index],
showReplyRow: true, showReplyRow: true,
replyLevel: '1', replyLevel: '1',
replyReply: (replyItem) => replyReply: (replyItem) =>
replyReply(replyItem), replyReply(replyItem),
replyType: ReplyType.values[replyType], replyType:
ReplyType.values[replyType],
addReply: (replyItem) { addReply: (replyItem) {
replyList[index] _dynamicDetailController
.replies! .replyList[index].replies!
.add(replyItem); .add(replyItem);
}, },
); );
} }
}, },
childCount: replyList.length + 1, childCount: _dynamicDetailController
.replyList.length +
1,
), ),
), ),
); );
@ -358,21 +365,18 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
) )
], ],
), ),
), Positioned(
floatingActionButton: SlideTransition( bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
child: SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
begin: const Offset(0, 2), begin: const Offset(0, 2),
end: const Offset(0, 0), end: const Offset(0, 0),
).animate( ).animate(CurvedAnimation(
CurvedAnimation(
parent: fabAnimationCtr, parent: fabAnimationCtr,
curve: Curves.easeInOut, curve: Curves.easeInOut,
), )),
), child: FloatingActionButton(
child: Obx(
() => _dynamicDetailController.replyReqCode.value == 12061
? const SizedBox()
: FloatingActionButton(
heroTag: null, heroTag: null,
onPressed: () { onPressed: () {
feedBack(); feedBack();
@ -405,6 +409,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
), ),
), ),
), ),
],
),
),
); );
} }
} }

View File

@ -3,15 +3,15 @@ import 'dart:async';
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/skeleton/dynamic_card.dart'; import 'package:pilipala/common/skeleton/dynamic_card.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/no_data.dart'; import 'package:pilipala/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/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'package:pilipala/utils/route_push.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../mine/controller.dart'; import '../mine/controller.dart';
@ -44,6 +44,8 @@ class _DynamicsPageState extends State<DynamicsPage>
_futureBuilderFuture = _dynamicsController.queryFollowDynamic(); _futureBuilderFuture = _dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp = _dynamicsController.queryFollowUp(); _futureBuilderFutureUp = _dynamicsController.queryFollowUp();
scrollController = _dynamicsController.scrollController; scrollController = _dynamicsController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
scrollController.addListener( scrollController.addListener(
() async { () async {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
@ -53,7 +55,14 @@ class _DynamicsPageState extends State<DynamicsPage>
_dynamicsController.queryFollowDynamic(type: 'onLoad'); _dynamicsController.queryFollowDynamic(type: 'onLoad');
}); });
} }
handleScrollEvent(scrollController);
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
}, },
); );
@ -167,7 +176,8 @@ class _DynamicsPageState extends State<DynamicsPage>
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
thumbDecoration: BoxDecoration( thumbDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color:
Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
@ -224,8 +234,8 @@ class _DynamicsPageState extends State<DynamicsPage>
if (snapshot.data == null) { if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox()); return const SliverToBoxAdapter(child: SizedBox());
} }
Map? data = snapshot.data; Map data = snapshot.data;
if (data != null && data['status']) { if (data['status']) {
List<DynamicItemModel> list = List<DynamicItemModel> list =
_dynamicsController.dynamicsList; _dynamicsController.dynamicsList;
return Obx( return Obx(
@ -248,21 +258,24 @@ class _DynamicsPageState extends State<DynamicsPage>
} }
}, },
); );
} else if (data['msg'] == "账号未登录") {
return HttpError(
errMsg: data['msg'],
btnText: "去登录",
fn: () {
mineController.onLogin();
},
);
} else { } else {
return HttpError( return HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data['msg'],
btnText: data?['code'] == -101 ? '去登录' : null,
fn: () { fn: () {
if (data?['code'] == -101) {
RoutePush.loginRedirectPush();
} else {
setState(() { setState(() {
_futureBuilderFuture = _futureBuilderFuture =
_dynamicsController.queryFollowDynamic(); _dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp = _futureBuilderFutureUp =
_dynamicsController.queryFollowUp(); _dynamicsController.queryFollowUp();
}); });
}
}, },
); );
} }

View File

@ -3,58 +3,38 @@ 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:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/http/dynamics.dart';
import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:status_bar_control/status_bar_control.dart';
import 'rich_node_panel.dart';
class ActionPanel extends StatefulWidget { class ActionPanel extends StatefulWidget {
const ActionPanel({ const ActionPanel({
super.key, super.key,
required this.item, this.item,
}); });
// ignore: prefer_typing_uninitialized_variables // ignore: prefer_typing_uninitialized_variables
final DynamicItemModel item; final item;
@override @override
State<ActionPanel> createState() => _ActionPanelState(); State<ActionPanel> createState() => _ActionPanelState();
} }
class _ActionPanelState extends State<ActionPanel> class _ActionPanelState extends State<ActionPanel> {
with TickerProviderStateMixin {
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());
late ModuleStatModel stat; late ModuleStatModel stat;
bool isProcessing = false; bool isProcessing = false;
double defaultHeight = 260;
RxDouble height = 0.0.obs;
RxBool isExpand = false.obs;
late double statusHeight;
TextEditingController _inputController = TextEditingController();
FocusNode myFocusNode = FocusNode();
String _inputText = '';
void Function()? handleState(Future Function() action) { void Function()? handleState(Future Function() action) {
return isProcessing return isProcessing ? null : () async {
? null setState(() => isProcessing = true);
: () async {
isProcessing = true;
await action(); await action();
isProcessing = false; setState(() => isProcessing = false);
}; };
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
stat = widget.item.modules!.moduleStat!; stat = widget.item!.modules.moduleStat;
onInit();
}
onInit() async {
statusHeight = await StatusBarControl.getHeight;
} }
// 动态点赞 // 动态点赞
@ -63,7 +43,7 @@ class _ActionPanelState extends State<ActionPanel>
var item = widget.item!; var item = widget.item!;
String dynamicId = item.idStr!; String dynamicId = item.idStr!;
// 1 已点赞 2 不喜欢 0 未操作 // 1 已点赞 2 不喜欢 0 未操作
Like like = item.modules!.moduleStat!.like!; Like like = item.modules.moduleStat.like;
int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0'); int count = like.count == '点赞' ? 0 : int.parse(like.count ?? '0');
bool status = like.status!; bool status = like.status!;
int up = status ? 2 : 1; int up = status ? 2 : 1;
@ -71,15 +51,15 @@ class _ActionPanelState extends State<ActionPanel>
if (res['status']) { if (res['status']) {
SmartDialog.showToast(!status ? '点赞成功' : '取消赞'); SmartDialog.showToast(!status ? '点赞成功' : '取消赞');
if (up == 1) { if (up == 1) {
item.modules!.moduleStat!.like!.count = (count + 1).toString(); item.modules.moduleStat.like.count = (count + 1).toString();
item.modules!.moduleStat!.like!.status = true; item.modules.moduleStat.like.status = true;
} else { } else {
if (count == 1) { if (count == 1) {
item.modules!.moduleStat!.like!.count = '点赞'; item.modules.moduleStat.like.count = '点赞';
} else { } else {
item.modules!.moduleStat!.like!.count = (count - 1).toString(); item.modules.moduleStat.like.count = (count - 1).toString();
} }
item.modules!.moduleStat!.like!.status = false; item.modules.moduleStat.like.status = false;
} }
setState(() {}); setState(() {});
} else { } else {
@ -87,307 +67,17 @@ class _ActionPanelState extends State<ActionPanel>
} }
} }
// 转发动态预览
Widget dynamicPreview() {
ItemModulesModel? modules = widget.item.modules;
final String type = widget.item.type!;
String? cover = modules?.moduleAuthor?.face;
switch (type) {
/// 图文动态
case 'DYNAMIC_TYPE_DRAW':
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
/// 投稿
case 'DYNAMIC_TYPE_AV':
cover = modules?.moduleDynamic?.major?.archive?.cover;
/// 转发的动态
case 'DYNAMIC_TYPE_FORWARD':
String forwardType = widget.item.orig!.type!;
switch (forwardType) {
/// 图文动态
case 'DYNAMIC_TYPE_DRAW':
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
/// 投稿
case 'DYNAMIC_TYPE_AV':
cover = modules?.moduleDynamic?.major?.archive?.cover;
/// 专栏文章
case 'DYNAMIC_TYPE_ARTICLE':
cover = '';
/// 番剧
case 'DYNAMIC_TYPE_PGC':
cover = '';
/// 纯文字动态
case 'DYNAMIC_TYPE_WORD':
cover = '';
/// 直播
case 'DYNAMIC_TYPE_LIVE_RCMD':
cover = '';
/// 合集查看
case 'DYNAMIC_TYPE_UGC_SEASON':
cover = '';
/// 番剧
case 'DYNAMIC_TYPE_PGC_UNION':
cover = modules?.moduleDynamic?.major?.pgc?.cover;
default:
cover = '';
}
/// 专栏文章
case 'DYNAMIC_TYPE_ARTICLE':
cover = '';
/// 番剧
case 'DYNAMIC_TYPE_PGC':
cover = '';
/// 纯文字动态
case 'DYNAMIC_TYPE_WORD':
cover = '';
/// 直播
case 'DYNAMIC_TYPE_LIVE_RCMD':
cover = '';
/// 合集查看
case 'DYNAMIC_TYPE_UGC_SEASON':
cover = '';
/// 番剧查看
case 'DYNAMIC_TYPE_PGC_UNION':
cover = '';
default:
cover = '';
}
return Container(
width: double.infinity,
height: 95,
margin: const EdgeInsets.fromLTRB(12, 0, 12, 14),
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
borderRadius: BorderRadius.circular(6),
border: Border(
left: BorderSide(
width: 4,
color: Theme.of(context).colorScheme.primary.withOpacity(0.8)),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'@${widget.item.modules!.moduleAuthor!.name}',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 8),
Row(
children: [
NetworkImgLayer(
src: cover ?? '',
width: 34,
height: 34,
type: 'emote',
),
const SizedBox(width: 10),
Expanded(
child: Text.rich(
style: const TextStyle(height: 0),
richNode(widget.item, context),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// Text(data)
],
)
],
),
),
);
}
// 动态转发
void forwardHandler() async {
showModalBottomSheet(
context: context,
enableDrag: false,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return Obx(
() => AnimatedContainer(
duration: Durations.medium1,
onEnd: () async {
if (isExpand.value) {
await Future.delayed(const Duration(milliseconds: 80));
myFocusNode.requestFocus();
}
},
height: height.value + MediaQuery.of(context).padding.bottom,
child: Column(
children: [
AnimatedContainer(
duration: Durations.medium1,
height: isExpand.value ? statusHeight : 0,
),
Padding(
padding: EdgeInsets.fromLTRB(
isExpand.value ? 10 : 16,
10,
isExpand.value ? 14 : 12,
0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (isExpand.value) ...[
IconButton(
onPressed: () => togglePanelState(false),
icon: const Icon(Icons.close),
),
Text(
'转发动态',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
)
] else ...[
const Text(
'转发动态',
style: TextStyle(fontWeight: FontWeight.bold),
)
],
isExpand.value
? FilledButton(
onPressed: () => dynamicForward('forward'),
child: const Text('转发'),
)
: TextButton(
onPressed: () {},
child: const Text('立即转发'),
)
],
),
),
if (!isExpand.value) ...[
GestureDetector(
onTap: () => togglePanelState(true),
behavior: HitTestBehavior.translucent,
child: Container(
width: double.infinity,
alignment: Alignment.centerLeft,
padding: const EdgeInsets.fromLTRB(16, 0, 10, 14),
child: Text(
'说点什么吧',
textAlign: TextAlign.start,
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
),
] else ...[
Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 0),
child: TextField(
maxLines: 5,
focusNode: myFocusNode,
controller: _inputController,
onChanged: (value) {
setState(() {
_inputText = value;
});
},
decoration: const InputDecoration(
border: InputBorder.none,
hintText: '说点什么吧',
),
),
),
],
dynamicPreview(),
if (!isExpand.value) ...[
const Divider(thickness: 0.1, height: 1),
ListTile(
onTap: () => Get.back(),
minLeadingWidth: 0,
dense: true,
title: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
textAlign: TextAlign.center,
),
),
]
],
),
),
);
},
);
}
togglePanelState(status) {
if (!status) {
Get.back();
height.value = defaultHeight;
_inputText = '';
_inputController.clear();
} else {
height.value = Get.size.height;
}
isExpand.value = !(isExpand.value);
}
dynamicForward(String type) async {
String dynamicId = widget.item.idStr!;
var res = await DynamicsHttp.dynamicCreate(
dynIdStr: dynamicId,
mid: _dynamicsController.userInfo.mid,
rawText: _inputText,
scene: 4,
);
if (res['status']) {
SmartDialog.showToast(type == 'forward' ? '转发成功' : '发布成功');
togglePanelState(false);
}
}
@override
void dispose() {
myFocusNode.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var color = Theme.of(context).colorScheme.outline; var color = Theme.of(context).colorScheme.outline;
var primary = Theme.of(context).colorScheme.primary; var primary = Theme.of(context).colorScheme.primary;
height.value = defaultHeight;
print('height.value: ${height.value}');
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [ children: [
Expanded( Expanded(
flex: 1, flex: 1,
child: TextButton.icon( child: TextButton.icon(
onPressed: forwardHandler, onPressed: () {},
icon: const Icon( icon: const Icon(
FontAwesomeIcons.shareFromSquare, FontAwesomeIcons.shareFromSquare,
size: 16, size: 16,

View File

@ -19,7 +19,7 @@ Widget addWidget(item, context, type, {floor = 1}) {
}; };
Color bgColor = floor == 1 Color bgColor = floor == 1
? Theme.of(context).dividerColor.withOpacity(0.08) ? Theme.of(context).dividerColor.withOpacity(0.08)
: Theme.of(context).colorScheme.surface; : Theme.of(context).colorScheme.background;
switch (type) { switch (type) {
case 'ADDITIONAL_TYPE_UGC': case 'ADDITIONAL_TYPE_UGC':
// 转发的投稿 // 转发的投稿

View File

@ -52,7 +52,7 @@ class AuthorPanel extends StatelessWidget {
color: item.modules.moduleAuthor!.vip != null && color: item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip['status'] > 0 item.modules.moduleAuthor!.vip['status'] > 0
? const Color.fromARGB(255, 251, 100, 163) ? const Color.fromARGB(255, 251, 100, 163)
: Theme.of(context).colorScheme.onSurface, : Theme.of(context).colorScheme.onBackground,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize, fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
), ),
), ),

View File

@ -9,7 +9,7 @@ import 'forward_panel.dart';
class DynamicPanel extends StatelessWidget { class DynamicPanel extends StatelessWidget {
final dynamic item; final dynamic item;
final String? source; final String? source;
DynamicPanel({required this.item, this.source, Key? key}) : super(key: key); DynamicPanel({this.item, this.source, Key? key}) : super(key: key);
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());
@override @override
@ -41,8 +41,8 @@ class DynamicPanel extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: AuthorPanel(item: item), child: AuthorPanel(item: item),
), ),
if (item.modules!.moduleDynamic!.desc != null || if (item!.modules!.moduleDynamic!.desc != null ||
item.modules!.moduleDynamic!.major != null) item!.modules!.moduleDynamic!.major != null)
Content(item: item, source: source), Content(item: item, source: source),
forWard(item, context, _dynamicsController, source), forWard(item, context, _dynamicsController, source),
const SizedBox(height: 2), const SizedBox(height: 2),

View File

@ -2,7 +2,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'additional_panel.dart'; import 'additional_panel.dart';
@ -183,116 +182,6 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
) )
], ],
); );
// 活动
case 'DYNAMIC_TYPE_COMMON_SQUARE':
return Padding(
padding: const EdgeInsets.only(top: 8),
child: InkWell(
onTap: () {
Get.toNamed('/webview', parameters: {
'url': item.modules.moduleDynamic.major.common['jump_url'],
'type': 'url',
'pageTitle': item.modules.moduleDynamic.major.common['title']
});
},
child: Container(
width: double.infinity,
padding:
const EdgeInsets.only(left: 12, top: 10, right: 12, bottom: 10),
color: Theme.of(context).dividerColor.withOpacity(0.08),
child: Row(
children: [
NetworkImgLayer(
width: 45,
height: 45,
src: item.modules.moduleDynamic.major.common['cover'],
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.modules.moduleDynamic.major.common['title'],
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
item.modules.moduleDynamic.major.common['desc'],
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
)
],
),
// TextButton(onPressed: () {}, child: Text('123'))
),
),
);
case 'DYNAMIC_TYPE_MUSIC':
final Map music = item.modules.moduleDynamic.major.music;
return Padding(
padding: const EdgeInsets.only(top: 8),
child: InkWell(
onTap: () {
Get.toNamed('/webview', parameters: {
'url': "https:${music['jump_url']}",
'type': 'url',
'pageTitle': music['title']
});
},
child: Container(
width: double.infinity,
padding:
const EdgeInsets.only(left: 12, top: 10, right: 12, bottom: 10),
color: Theme.of(context).dividerColor.withOpacity(0.08),
child: Row(
children: [
NetworkImgLayer(
width: 45,
height: 45,
src: music['cover'],
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
music['title'],
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
music['label'],
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
)
],
),
// TextButton(onPressed: () {}, child: Text('123'))
),
),
);
default: default:
return const SizedBox( return const SizedBox(
width: double.infinity, width: double.infinity,

View File

@ -1,4 +1,3 @@
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/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -31,31 +30,6 @@ class _UpPanelState extends State<UpPanel> {
liveList = widget.upData.liveList!; liveList = widget.upData.liveList!;
} }
void onClickUp(data, i) {
currentMid = data.mid;
Get.find<DynamicsController>().mid.value = data.mid;
Get.find<DynamicsController>().upInfo.value = data;
Get.find<DynamicsController>().onSelectUp(data.mid);
int liveLen = liveList.length;
int upLen = upList.length;
double itemWidth = contentWidth + itemPadding.horizontal;
double screenWidth = MediaQuery.sizeOf(context).width;
double moveDistance = 0.0;
if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
} else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
} else {
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
}
data.hasUpdate = false;
scrollController.animateTo(
moveDistance,
duration: const Duration(milliseconds: 200),
curve: Curves.linear,
);
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
listFormat(); listFormat();
@ -69,7 +43,7 @@ class _UpPanelState extends State<UpPanel> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.background,
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -95,7 +69,7 @@ class _UpPanelState extends State<UpPanel> {
), ),
Container( Container(
height: 90, height: 90,
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.background,
child: Row( child: Row(
children: [ children: [
Flexible( Flexible(
@ -146,10 +120,30 @@ class _UpPanelState extends State<UpPanel> {
onTap: () { onTap: () {
feedBack(); feedBack();
if (data.type == 'up') { if (data.type == 'up') {
EasyThrottle.throttle('follow', const Duration(milliseconds: 300), currentMid = data.mid;
() { Get.find<DynamicsController>().mid.value = data.mid;
onClickUp(data, i); Get.find<DynamicsController>().upInfo.value = data;
}); Get.find<DynamicsController>().onSelectUp(data.mid);
int liveLen = liveList.length;
int upLen = upList.length;
double itemWidth = contentWidth + itemPadding.horizontal;
double screenWidth = MediaQuery.sizeOf(context).width;
double moveDistance = 0.0;
if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
} else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
moveDistance =
(i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
} else {
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
}
data.hasUpdate = false;
scrollController.animateTo(
moveDistance,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
setState(() {});
} else if (data.type == 'live') { } else if (data.type == 'live') {
LiveItemModel liveItem = LiveItemModel.fromJson({ LiveItemModel liveItem = LiveItemModel.fromJson({
'title': data.title, 'title': data.title,

View File

@ -80,12 +80,15 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
double width = box.maxWidth; double width = box.maxWidth;
return Stack( return Stack(
children: [ children: [
NetworkImgLayer( Hero(
tag: content.bvid,
child: NetworkImgLayer(
type: floor == 1 ? 'emote' : null, type: floor == 1 ? 'emote' : null,
width: width, width: width,
height: width / StyleString.aspectRatio, height: width / StyleString.aspectRatio,
src: content.cover, src: content.cover,
), ),
),
if (content.badge != null && type == 'pgc') if (content.badge != null && type == 'pgc')
PBadge( PBadge(
text: content.badge['text'], text: content.badge['text'],

View File

@ -10,22 +10,16 @@ import 'package:pilipala/utils/storage.dart';
class FavController extends GetxController { class FavController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
Rx<FavFolderData> favFolderData = FavFolderData().obs; Rx<FavFolderData> favFolderData = FavFolderData().obs;
RxList<FavFolderItemData> favFolderList = <FavFolderItemData>[].obs;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
UserInfoData? userInfo; UserInfoData? userInfo;
int currentPage = 1; int currentPage = 1;
int pageSize = 60; int pageSize = 10;
RxBool hasMore = true.obs; RxBool hasMore = true.obs;
@override
void onInit() {
userInfo = userInfoCache.get('userInfoCache');
super.onInit();
}
Future<dynamic> queryFavFolder({type = 'init'}) async { Future<dynamic> queryFavFolder({type = 'init'}) async {
userInfo = userInfoCache.get('userInfoCache');
if (userInfo == null) { if (userInfo == null) {
return {'status': false, 'msg': '账号未登录', 'code': -101}; return {'status': false, 'msg': '账号未登录'};
} }
if (!hasMore.value) { if (!hasMore.value) {
return; return;
@ -38,10 +32,9 @@ class FavController extends GetxController {
if (res['status']) { if (res['status']) {
if (type == 'init') { if (type == 'init') {
favFolderData.value = res['data']; favFolderData.value = res['data'];
favFolderList.value = res['data'].list;
} else { } else {
if (res['data'].list.isNotEmpty) { if (res['data'].list.isNotEmpty) {
favFolderList.addAll(res['data'].list); favFolderData.value.list!.addAll(res['data'].list);
favFolderData.update((val) {}); favFolderData.update((val) {});
} }
} }
@ -56,13 +49,4 @@ class FavController extends GetxController {
Future onLoad() async { Future onLoad() async {
queryFavFolder(type: 'onload'); queryFavFolder(type: 'onload');
} }
removeFavFolder({required int mediaIds}) async {
for (var i in favFolderList) {
if (i.id == mediaIds) {
favFolderList.remove(i);
break;
}
}
}
} }

View File

@ -1,11 +1,9 @@
import 'package:easy_debounce/easy_throttle.dart'; 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/pages/fav/index.dart'; import 'package:pilipala/pages/fav/index.dart';
import 'package:pilipala/pages/fav/widgets/item.dart'; import 'package:pilipala/pages/fav/widgets/item.dart';
import 'package:pilipala/utils/route_push.dart';
class FavPage extends StatefulWidget { class FavPage extends StatefulWidget {
const FavPage({super.key}); const FavPage({super.key});
@ -59,15 +57,16 @@ class _FavPageState extends State<FavPage> {
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
Map? data = snapshot.data; Map data = snapshot.data as Map;
if (data != null && data['status']) { if (data['status']) {
return Obx( return Obx(
() => ListView.builder( () => ListView.builder(
controller: scrollController, controller: scrollController,
itemCount: _favController.favFolderList.length, itemCount: _favController.favFolderData.value.list!.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return FavItem( return FavItem(
favFolderItem: _favController.favFolderList[index]); favFolderItem:
_favController.favFolderData.value.list![index]);
}, },
), ),
); );
@ -76,30 +75,15 @@ class _FavPageState extends State<FavPage> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
slivers: [ slivers: [
HttpError( HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data['msg'],
btnText: data?['code'] == -101 ? '去登录' : null, fn: () => setState(() {}),
fn: () {
if (data?['code'] == -101) {
RoutePush.loginRedirectPush();
} else {
setState(() {
_futureBuilderFuture =
_favController.queryFavFolder();
});
}
},
), ),
], ],
); );
} }
} else { } else {
// 骨架屏 // 骨架屏
return ListView.builder( return const Text('请求中');
itemBuilder: (context, index) {
return const VideoCardHSkeleton();
},
itemCount: 10,
);
} }
}, },
), ),

View File

@ -13,16 +13,14 @@ class FavItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(favFolderItem.fid); String heroTag = Utils.makeHeroTag(favFolderItem.fid);
return InkWell( return InkWell(
onTap: () async { onTap: () => Get.toNamed(
Get.toNamed(
'/favDetail', '/favDetail',
arguments: favFolderItem, arguments: favFolderItem,
parameters: { parameters: {
'heroTag': heroTag, 'heroTag': heroTag,
'mediaId': favFolderItem.id.toString(), 'mediaId': favFolderItem.id.toString(),
}, },
); ),
},
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(12, 7, 12, 7), padding: const EdgeInsets.fromLTRB(12, 7, 12, 7),
child: LayoutBuilder( child: LayoutBuilder(

View File

@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/user/fav_detail.dart'; import 'package:pilipala/models/user/fav_detail.dart';
import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/fav/index.dart';
class FavDetailController extends GetxController { class FavDetailController extends GetxController {
FavFolderItemData? item; FavFolderItemData? item;
@ -76,41 +74,4 @@ class FavDetailController extends GetxController {
onLoad() { onLoad() {
queryUserFavFolderDetail(type: 'onLoad'); queryUserFavFolderDetail(type: 'onLoad');
} }
onDelFavFolder() async {
SmartDialog.show(
useSystem: true,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('确定删除这个收藏夹吗?'),
actions: [
TextButton(
onPressed: () async {
SmartDialog.dismiss();
},
child: Text(
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await UserHttp.delFavFolder(mediaIds: mediaId!);
SmartDialog.dismiss();
SmartDialog.showToast(res['status'] ? '操作成功' : res['msg']);
if (res['status']) {
FavController favController = Get.find<FavController>();
await favController.removeFavFolder(mediaIds: mediaId!);
Get.back();
}
},
child: const Text('确认'),
)
],
);
},
);
}
} }

View File

@ -53,7 +53,6 @@ class _FavDetailPageState extends State<FavDetailPage> {
@override @override
void dispose() { void dispose() {
_controller.dispose(); _controller.dispose();
titleStreamC.close();
super.dispose(); super.dispose();
} }
@ -68,7 +67,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
pinned: true, pinned: true,
titleSpacing: 0, titleSpacing: 0,
title: StreamBuilder( title: StreamBuilder(
stream: titleStreamC.stream.distinct(), stream: titleStreamC.stream,
initialData: false, initialData: false,
builder: (context, AsyncSnapshot snapshot) { builder: (context, AsyncSnapshot snapshot) {
return AnimatedOpacity( return AnimatedOpacity(
@ -101,19 +100,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'), Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
PopupMenuButton<String>( // IconButton(
icon: const Icon(Icons.more_vert_outlined), // onPressed: () {},
position: PopupMenuPosition.under, // icon: const Icon(Icons.more_vert),
onSelected: (String type) {}, // ),
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[ const SizedBox(width: 6),
PopupMenuItem<String>(
onTap: () => _favDetailController.onDelFavFolder(),
value: 'pause',
child: const Text('删除收藏夹'),
),
],
),
const SizedBox(width: 14),
], ],
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
background: Container( background: Container(

View File

@ -1,4 +1,3 @@
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:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
@ -8,7 +7,6 @@ import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/image_save.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 '../../../common/widgets/badge.dart'; import '../../../common/widgets/badge.dart';
@ -63,11 +61,6 @@ class FavVideoCardH extends StatelessWidget {
epId != null ? SearchType.media_bangumi : SearchType.video, epId != null ? SearchType.media_bangumi : SearchType.video,
}); });
}, },
onLongPress: () => imageSaveDialog(
context,
videoItem,
SmartDialog.dismiss,
),
child: Column( child: Column(
children: [ children: [
Padding( Padding(

View File

@ -4,7 +4,6 @@ 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';
import 'package:pilipala/models/user/history.dart'; import 'package:pilipala/models/user/history.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
class HistoryController extends GetxController { class HistoryController extends GetxController {
@ -16,20 +15,14 @@ class HistoryController extends GetxController {
RxBool isLoading = false.obs; RxBool isLoading = false.obs;
RxBool enableMultiple = false.obs; RxBool enableMultiple = false.obs;
RxInt checkedCount = 0.obs; RxInt checkedCount = 0.obs;
Box userInfoCache = GStrorage.userInfo;
UserInfoData? userInfo;
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
historyStatus(); historyStatus();
userInfo = userInfoCache.get('userInfoCache');
} }
Future queryHistoryList({type = 'init'}) async { Future queryHistoryList({type = 'init'}) async {
if (userInfo == null) {
return {'status': false, 'msg': '账号未登录', 'code': -101};
}
int max = 0; int max = 0;
int viewAt = 0; int viewAt = 0;
if (type == 'onload') { if (type == 'onload') {

View File

@ -5,7 +5,6 @@ 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/no_data.dart'; import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/pages/history/index.dart'; import 'package:pilipala/pages/history/index.dart';
import 'package:pilipala/utils/route_push.dart';
import 'widgets/item.dart'; import 'widgets/item.dart';
@ -184,8 +183,8 @@ class _HistoryPageState extends State<HistoryPage> {
if (snapshot.data == null) { if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox()); return const SliverToBoxAdapter(child: SizedBox());
} }
Map? data = snapshot.data; Map data = snapshot.data;
if (data != null && data['status']) { if (data['status']) {
return Obx( return Obx(
() => _historyController.historyList.isNotEmpty () => _historyController.historyList.isNotEmpty
? SliverList( ? SliverList(
@ -210,18 +209,8 @@ class _HistoryPageState extends State<HistoryPage> {
); );
} else { } else {
return HttpError( return HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data['msg'],
btnText: data?['code'] == -101 ? '去登录' : null, fn: () => setState(() {}),
fn: () {
if (data?['code'] == -101) {
RoutePush.loginRedirectPush();
} else {
setState(() {
_futureBuilderFuture =
_historyController.queryHistoryList();
});
}
},
); );
} }
} else { } else {

View File

@ -7,13 +7,13 @@ import 'package:pilipala/common/widgets/network_img_layer.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/http/video.dart'; import 'package:pilipala/http/video.dart';
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_search/index.dart'; import 'package:pilipala/pages/history_search/index.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/route_push.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class HistoryItem extends StatelessWidget { class HistoryItem extends StatelessWidget {
@ -101,13 +101,28 @@ class HistoryItem extends StatelessWidget {
} }
} else { } else {
if (videoItem.history.epid != '') { if (videoItem.history.epid != '') {
RoutePush.bangumiPush( SmartDialog.showLoading(msg: '获取中...');
null, var res =
videoItem.history.epid, await SearchHttp.bangumiInfo(epId: videoItem.history.epid);
heroTag: heroTag, SmartDialog.dismiss();
if (res['status']) {
EpisodeItem episode = res['data'].episodes.first;
String bvid = episode.bvid!;
int cid = episode.cid!;
String pic = episode.cover!;
String heroTag = Utils.makeHeroTag(cid);
Get.toNamed(
'/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}',
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
); );
} }
} }
}
} else { } else {
int cid = videoItem.history.cid ?? int cid = videoItem.history.cid ??
// videoItem.history.oid ?? // videoItem.history.oid ??
@ -170,7 +185,7 @@ class HistoryItem extends StatelessWidget {
? '已看完' ? '已看完'
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}', : '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
right: 6.0, right: 6.0,
bottom: 8.0, bottom: 6.0,
type: 'gray', type: 'gray',
), ),
// 右上角 // 右上角
@ -198,8 +213,7 @@ class HistoryItem extends StatelessWidget {
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular( borderRadius: BorderRadius.circular(12),
StyleString.imgRadius.x),
color: Colors.black.withOpacity( color: Colors.black.withOpacity(
ctr!.enableMultiple.value && ctr!.enableMultiple.value &&
videoItem.checked videoItem.checked
@ -244,27 +258,6 @@ class HistoryItem extends StatelessWidget {
), ),
), ),
), ),
videoItem.progress != 0 && videoItem.duration != 0
? Positioned(
left: 3,
right: 3,
bottom: 0,
child: ClipRRect(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(
StyleString.imgRadius.x),
bottomRight: Radius.circular(
StyleString.imgRadius.x),
),
child: LinearProgressIndicator(
value: videoItem.progress == -1
? 100
: videoItem.progress /
videoItem.duration,
),
),
)
: const SizedBox()
], ],
), ),
VideoContent(videoItem: videoItem, ctr: ctr) VideoContent(videoItem: videoItem, ctr: ctr)

View File

@ -10,8 +10,9 @@ class HistorySearchController extends GetxController {
final FocusNode searchFocusNode = FocusNode(); final FocusNode searchFocusNode = FocusNode();
RxString searchKeyWord = ''.obs; RxString searchKeyWord = ''.obs;
String hintText = '搜索'; String hintText = '搜索';
RxBool loadingStatus = false.obs; RxString loadingStatus = 'init'.obs;
RxString loadingText = '加载中...'.obs; RxString loadingText = '加载中...'.obs;
bool hasRequest = false;
late int mid; late int mid;
RxString uname = ''.obs; RxString uname = ''.obs;
int pn = 1; int pn = 1;
@ -35,7 +36,8 @@ class HistorySearchController extends GetxController {
// 提交搜索内容 // 提交搜索内容
void submit() { void submit() {
if (!loadingStatus.value) { loadingStatus.value = 'loading';
if (hasRequest) {
pn = 1; pn = 1;
searchHistories(); searchHistories();
} }
@ -46,7 +48,6 @@ class HistorySearchController extends GetxController {
if (type == 'onLoad' && loadingText.value == '没有更多了') { if (type == 'onLoad' && loadingText.value == '没有更多了') {
return; return;
} }
loadingStatus.value = true;
var res = await UserHttp.searchHistory( var res = await UserHttp.searchHistory(
pn: pn, pn: pn,
keyword: controller.value.text, keyword: controller.value.text,
@ -62,8 +63,9 @@ class HistorySearchController extends GetxController {
loadingText.value = '没有更多了'; loadingText.value = '没有更多了';
} }
pn += 1; pn += 1;
hasRequest = true;
} }
loadingStatus.value = false; loadingStatus.value = 'finish';
return res; return res;
} }
@ -84,6 +86,6 @@ class HistorySearchController extends GetxController {
historyList.removeWhere((e) => e.kid == kid); historyList.removeWhere((e) => e.kid == kid);
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }
// loadingStatus.value = fasle; loadingStatus.value = 'finish';
} }
} }

View File

@ -2,6 +2,7 @@ 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/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/no_data.dart';
import 'package:pilipala/pages/history/widgets/item.dart'; import 'package:pilipala/pages/history/widgets/item.dart';
@ -15,19 +16,20 @@ class HistorySearchPage extends StatefulWidget {
} }
class _HistorySearchPageState extends State<HistorySearchPage> { class _HistorySearchPageState extends State<HistorySearchPage> {
final HistorySearchController _hisCtr = Get.put(HistorySearchController()); final HistorySearchController _historySearchCtr =
Get.put(HistorySearchController());
late ScrollController scrollController; late ScrollController scrollController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
scrollController = _hisCtr.scrollController; scrollController = _historySearchCtr.scrollController;
scrollController.addListener( scrollController.addListener(
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) { scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('history', const Duration(seconds: 1), () { EasyThrottle.throttle('history', const Duration(seconds: 1), () {
_hisCtr.onLoad(); _historySearchCtr.onLoad();
}); });
} }
}, },
@ -48,19 +50,19 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
titleSpacing: 0, titleSpacing: 0,
actions: [ actions: [
IconButton( IconButton(
onPressed: () => _hisCtr.submit(), onPressed: () => _historySearchCtr.submit(),
icon: const Icon(Icons.search_outlined, size: 22)), icon: const Icon(Icons.search_outlined, size: 22)),
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
title: Obx( title: Obx(
() => TextField( () => TextField(
autofocus: true, autofocus: true,
focusNode: _hisCtr.searchFocusNode, focusNode: _historySearchCtr.searchFocusNode,
controller: _hisCtr.controller.value, controller: _historySearchCtr.controller.value,
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onChanged: (value) => _hisCtr.onChange(value), onChanged: (value) => _historySearchCtr.onChange(value),
decoration: InputDecoration( decoration: InputDecoration(
hintText: _hisCtr.hintText, hintText: _historySearchCtr.hintText,
border: InputBorder.none, border: InputBorder.none,
suffixIcon: IconButton( suffixIcon: IconButton(
icon: Icon( icon: Icon(
@ -68,61 +70,103 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
size: 22, size: 22,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
onPressed: () => _hisCtr.onClear(), onPressed: () => _historySearchCtr.onClear(),
), ),
), ),
onSubmitted: (String value) => _hisCtr.submit(), onSubmitted: (String value) => _historySearchCtr.submit(),
), ),
), ),
), ),
body: Obx( body: Obx(
() { () => Column(
return _hisCtr.loadingStatus.value && _hisCtr.historyList.isEmpty children: _historySearchCtr.loadingStatus.value == 'init'
? ListView.builder( ? [const SizedBox()]
itemCount: 10, : [
itemBuilder: (context, index) { Expanded(
return const VideoCardHSkeleton(); child: FutureBuilder(
}, future: _historySearchCtr.searchHistories(),
) builder: (context, snapshot) {
: _hisCtr.historyList.isNotEmpty if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => _historySearchCtr.historyList.isNotEmpty
? ListView.builder( ? ListView.builder(
controller: scrollController, controller: scrollController,
itemCount: _hisCtr.historyList.length + 1, itemCount:
_historySearchCtr.historyList.length +
1,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index == _hisCtr.historyList.length) { if (index ==
_historySearchCtr
.historyList.length) {
return Container( return Container(
height: MediaQuery.of(context).padding.bottom + 60, height: MediaQuery.of(context)
.padding
.bottom +
60,
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: MediaQuery.of(context).padding.bottom), bottom: MediaQuery.of(context)
.padding
.bottom),
child: Center( child: Center(
child: Obx( child: Obx(
() => Text( () => Text(
_hisCtr.loadingText.value, _historySearchCtr
.loadingText.value,
style: TextStyle( style: TextStyle(
color: color: Theme.of(context)
Theme.of(context).colorScheme.outline, .colorScheme
fontSize: 13, .outline,
), fontSize: 13),
), ),
), ),
), ),
); );
} else { } else {
return HistoryItem( return HistoryItem(
videoItem: _hisCtr.historyList[index], videoItem: _historySearchCtr
ctr: _hisCtr, .historyList[index],
ctr: _historySearchCtr,
onChoose: null, onChoose: null,
onUpdateMultiple: () => null, onUpdateMultiple: () => null,
); );
} }
}, },
) )
: _historySearchCtr.loadingStatus.value ==
'loading'
? const SizedBox(child: Text('加载中...'))
: const CustomScrollView( : const CustomScrollView(
slivers: <Widget>[ slivers: <Widget>[
NoData(), NoData(),
], ],
),
); );
} else {
return CustomScrollView(
slivers: <Widget>[
HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
)
],
);
}
} else {
// 骨架屏
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return const VideoCardHSkeleton();
}, },
);
}
},
),
),
],
),
), ),
); );
} }

View File

@ -35,7 +35,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : ''; userFace.value = userInfo != null ? userInfo.face : '';
hideSearchBar = hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: false); setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) { if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault(); searchDefault();
} }
@ -114,10 +114,4 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
defaultSearch.value = res.data['data']['name']; defaultSearch.value = res.data['data']['name'];
} }
} }
@override
void onClose() {
searchBarStream.close();
super.onClose();
}
} }

View File

@ -171,7 +171,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder( return StreamBuilder(
stream: stream!.distinct(), stream: stream,
initialData: true, initialData: true,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
final RxBool isUserLoggedIn = ctr!.userLogin; final RxBool isUserLoggedIn = ctr!.userLogin;
@ -214,8 +214,24 @@ class UserInfoWidget extends StatelessWidget {
final VoidCallback? callback; final VoidCallback? callback;
final HomeController? ctr; final HomeController? ctr;
Widget buildLoggedInWidget(context) { @override
return Stack( Widget build(BuildContext context) {
return Row(
children: [
SearchBar(ctr: ctr),
if (userLogin.value) ...[
const SizedBox(width: 4),
ClipRect(
child: IconButton(
onPressed: () => Get.toNamed('/whisper'),
icon: const Icon(Icons.notifications_none),
),
)
],
const SizedBox(width: 8),
Obx(
() => userLogin.value
? Stack(
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
type: 'avatar', type: 'avatar',
@ -239,27 +255,7 @@ class UserInfoWidget extends StatelessWidget {
), ),
) )
], ],
);
}
@override
Widget build(BuildContext context) {
return Row(
children: [
SearchBar(ctr: ctr),
if (userLogin.value) ...[
const SizedBox(width: 4),
ClipRect(
child: IconButton(
onPressed: () => Get.toNamed('/whisper'),
icon: const Icon(Icons.notifications_none),
),
) )
],
const SizedBox(width: 8),
Obx(
() => userLogin.value
? buildLoggedInWidget(context)
: DefaultUser(callback: () => callback!()), : DefaultUser(callback: () => callback!()),
), ),
], ],
@ -357,29 +353,25 @@ class CustomChip extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ColorScheme colorTheme = Theme.of(context).colorScheme; final ColorScheme colorTheme = Theme.of(context).colorScheme;
final Color secondaryContainer = colorTheme.secondaryContainer; final Color secondaryContainer = colorTheme.secondaryContainer;
final Color onPrimary = colorTheme.onPrimary;
final Color primary = colorTheme.primary;
final TextStyle chipTextStyle = selected final TextStyle chipTextStyle = selected
? TextStyle(fontSize: 13, color: onPrimary) ? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
: TextStyle(fontSize: 13, color: colorTheme.onSecondaryContainer); : const TextStyle(fontSize: 13);
final ColorScheme colorScheme = Theme.of(context).colorScheme;
const VisualDensity visualDensity = const VisualDensity visualDensity =
VisualDensity(horizontal: -4.0, vertical: -2.0); VisualDensity(horizontal: -4.0, vertical: -2.0);
return InputChip( return InputChip(
side: BorderSide.none, side: BorderSide(
color: selected
? colorScheme.onSecondaryContainer.withOpacity(0.2)
: Colors.transparent,
),
backgroundColor: secondaryContainer, backgroundColor: secondaryContainer,
color: MaterialStateProperty.resolveWith((states) { selectedColor: secondaryContainer,
if (states.contains(MaterialState.selected) || color: MaterialStateProperty.resolveWith<Color>(
states.contains(MaterialState.hovered)) { (Set<MaterialState> states) => secondaryContainer.withAlpha(200)),
return primary; padding: const EdgeInsets.fromLTRB(7, 1, 7, 1),
}
return colorTheme.secondaryContainer;
}),
padding: const EdgeInsets.fromLTRB(6, 1, 6, 1),
label: Text(label, style: chipTextStyle), label: Text(label, style: chipTextStyle),
onPressed: () => onTap(), onPressed: () => onTap(),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
selected: selected, selected: selected,
showCheckmark: false, showCheckmark: false,
visualDensity: visualDensity, visualDensity: visualDensity,
@ -410,27 +402,30 @@ class SearchBar extends StatelessWidget {
color: colorScheme.onSecondaryContainer.withOpacity(0.05), color: colorScheme.onSecondaryContainer.withOpacity(0.05),
child: InkWell( child: InkWell(
splashColor: colorScheme.primaryContainer.withOpacity(0.3), splashColor: colorScheme.primaryContainer.withOpacity(0.3),
onTap: () => Get.toNamed('/search', onTap: () => Get.toNamed(
parameters: {'hintText': ctr!.defaultSearch.value}), '/search',
child: Padding( parameters: {'hintText': ctr!.defaultSearch.value},
padding: const EdgeInsets.symmetric(horizontal: 14), ),
child: Row( child: Row(
children: [ children: [
const SizedBox(width: 14),
Icon( Icon(
Icons.search_outlined, Icons.search_outlined,
color: colorScheme.onSecondaryContainer, color: colorScheme.onSecondaryContainer,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Obx( Obx(
() => Text( () => Expanded(
child: Text(
ctr!.defaultSearch.value, ctr!.defaultSearch.value,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.outline), style: TextStyle(color: colorScheme.outline),
), ),
), ),
],
), ),
const SizedBox(width: 15),
],
), ),
), ),
), ),

View File

@ -1,13 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.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/animated_dialog.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/skeleton/video_card_h.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/video_card_h.dart'; import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/hot/controller.dart'; import 'package:pilipala/pages/hot/controller.dart';
import 'package:pilipala/utils/main_stream.dart'; import 'package:pilipala/pages/main/index.dart';
class HotPage extends StatefulWidget { class HotPage extends StatefulWidget {
const HotPage({Key? key}) : super(key: key); const HotPage({Key? key}) : super(key: key);
@ -30,6 +34,10 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
super.initState(); super.initState();
_futureBuilderFuture = _hotController.queryHotFeed('init'); _futureBuilderFuture = _hotController.queryHotFeed('init');
scrollController = _hotController.scrollController; scrollController = _hotController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener( scrollController.addListener(
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
@ -39,7 +47,16 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
_hotController.onLoad(); _hotController.onLoad();
} }
} }
handleScrollEvent(scrollController);
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
}, },
); );
} }
@ -76,6 +93,15 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
return VideoCardH( return VideoCardH(
videoItem: _hotController.videoList[index], videoItem: _hotController.videoList[index],
showPubdate: true, showPubdate: true,
longPress: () {
_hotController.popupDialog = _createPopupDialog(
_hotController.videoList[index]);
Overlay.of(context)
.insert(_hotController.popupDialog!);
},
longPressEnd: () {
_hotController.popupDialog?.remove();
},
); );
}, childCount: _hotController.videoList.length), }, childCount: _hotController.videoList.length),
), ),
@ -111,4 +137,14 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
), ),
); );
} }
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
closeFn: _hotController.popupDialog?.remove,
child: OverlayPop(
videoItem: videoItem, closeFn: _hotController.popupDialog?.remove),
),
);
}
} }

View File

@ -1,30 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/model_hot_video_item.dart'; import 'package:pilipala/models/model_hot_video_item.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/utils/storage.dart';
class LaterController extends GetxController { class LaterController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
RxList<HotVideoItemModel> laterList = <HotVideoItemModel>[].obs; RxList<HotVideoItemModel> laterList = <HotVideoItemModel>[].obs;
int count = 0; int count = 0;
RxBool isLoading = false.obs; RxBool isLoading = false.obs;
Box userInfoCache = GStrorage.userInfo;
UserInfoData? userInfo;
@override
void onInit() {
super.onInit();
userInfo = userInfoCache.get('userInfoCache');
}
Future queryLaterList() async { Future queryLaterList() async {
if (userInfo == null) {
return {'status': false, 'msg': '账号未登录', 'code': -101};
}
isLoading.value = true; isLoading.value = true;
var res = await UserHttp.seeYouLater(); var res = await UserHttp.seeYouLater();
if (res['status']) { if (res['status']) {

View File

@ -5,7 +5,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/common/widgets/video_card_h.dart'; import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/pages/later/index.dart'; import 'package:pilipala/pages/later/index.dart';
import 'package:pilipala/utils/route_push.dart';
class LaterPage extends StatefulWidget { class LaterPage extends StatefulWidget {
const LaterPage({super.key}); const LaterPage({super.key});
@ -73,8 +72,8 @@ class _LaterPageState extends State<LaterPage> {
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
Map? data = snapshot.data; Map data = snapshot.data as Map;
if (data != null && data['status']) { if (data['status']) {
return Obx( return Obx(
() => _laterController.laterList.isNotEmpty && () => _laterController.laterList.isNotEmpty &&
!_laterController.isLoading.value !_laterController.isLoading.value
@ -85,7 +84,7 @@ class _LaterPageState extends State<LaterPage> {
return VideoCardH( return VideoCardH(
videoItem: videoItem, videoItem: videoItem,
source: 'later', source: 'later',
onPressedFn: () => _laterController.toViewDel( longPress: () => _laterController.toViewDel(
aid: videoItem.aid)); aid: videoItem.aid));
}, childCount: _laterController.laterList.length), }, childCount: _laterController.laterList.length),
) )
@ -97,18 +96,10 @@ class _LaterPageState extends State<LaterPage> {
); );
} else { } else {
return HttpError( return HttpError(
errMsg: data?['msg'] ?? '请求异常', errMsg: data['msg'],
btnText: data?['code'] == -101 ? '去登录' : null, fn: () => setState(() {
fn: () { _futureBuilderFuture = _laterController.queryLaterList();
if (data?['code'] == -101) { }),
RoutePush.loginRedirectPush();
} else {
setState(() {
_futureBuilderFuture =
_laterController.queryLaterList();
});
}
},
); );
} }
} else { } else {

View File

@ -2,11 +2,15 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/video_card_v.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart';
import 'package:pilipala/common/widgets/animated_dialog.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/utils/main_stream.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/live_item.dart'; import 'widgets/live_item.dart';
@ -32,6 +36,10 @@ class _LivePageState extends State<LivePage>
super.initState(); super.initState();
_futureBuilderFuture = _liveController.queryLiveList('init'); _futureBuilderFuture = _liveController.queryLiveList('init');
scrollController = _liveController.scrollController; scrollController = _liveController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
StreamController<bool> searchBarStream =
Get.find<HomeController>().searchBarStream;
scrollController.addListener( scrollController.addListener(
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
@ -41,7 +49,16 @@ class _LivePageState extends State<LivePage>
_liveController.onLoad(); _liveController.onLoad();
}); });
} }
handleScrollEvent(scrollController);
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
searchBarStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
searchBarStream.add(false);
}
}, },
); );
} }
@ -110,6 +127,16 @@ class _LivePageState extends State<LivePage>
); );
} }
OverlayEntry _createPopupDialog(liveItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
closeFn: _liveController.popupDialog?.remove,
child: OverlayPop(
videoItem: liveItem, closeFn: _liveController.popupDialog?.remove),
),
);
}
Widget contentGrid(ctr, liveList) { Widget contentGrid(ctr, liveList) {
// double maxWidth = Get.size.width; // double maxWidth = Get.size.width;
// int baseWidth = 500; // int baseWidth = 500;
@ -140,6 +167,14 @@ class _LivePageState extends State<LivePage>
? LiveCardV( ? LiveCardV(
liveItem: liveList[index], liveItem: liveList[index],
crossAxisCount: crossAxisCount, crossAxisCount: crossAxisCount,
longPress: () {
_liveController.popupDialog =
_createPopupDialog(liveList[index]);
Overlay.of(context).insert(_liveController.popupDialog!);
},
longPressEnd: () {
_liveController.popupDialog?.remove();
},
) )
: const VideoCardVSkeleton(); : const VideoCardVSkeleton();
}, },

View File

@ -1,9 +1,7 @@
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:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/utils/image_save.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';
@ -11,23 +9,36 @@ 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 int crossAxisCount;
final Function()? longPress;
final Function()? longPressEnd;
const LiveCardV({ const LiveCardV({
Key? key, Key? key,
required this.liveItem, required this.liveItem,
required this.crossAxisCount, required this.crossAxisCount,
this.longPress,
this.longPressEnd,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(liveItem.roomId); String heroTag = Utils.makeHeroTag(liveItem.roomId);
return InkWell( return Card(
onLongPress: () => imageSaveDialog( elevation: 0,
context, clipBehavior: Clip.hardEdge,
liveItem, margin: EdgeInsets.zero,
SmartDialog.dismiss, child: GestureDetector(
), onLongPress: () {
borderRadius: BorderRadius.circular(16), if (longPress != null) {
longPress!();
}
},
// onLongPressEnd: (details) {
// if (longPressEnd != null) {
// longPressEnd!();
// }
// },
child: InkWell(
onTap: () async { onTap: () async {
Get.toNamed('/liveRoom?roomid=${liveItem.roomId}', Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
arguments: {'liveItem': liveItem, 'heroTag': heroTag}); arguments: {'liveItem': liveItem, 'heroTag': heroTag});
@ -72,6 +83,8 @@ class LiveCardV extends StatelessWidget {
LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount) LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount)
], ],
), ),
),
),
); );
} }
} }

View File

@ -17,7 +17,8 @@ class LiveRoomController extends GetxController {
double volume = 0.0; double volume = 0.0;
// 静音状态 // 静音状态
RxBool volumeOff = false.obs; RxBool volumeOff = false.obs;
PlPlayerController plPlayerController = PlPlayerController(videoType: 'live'); PlPlayerController plPlayerController =
PlPlayerController.getInstance(videoType: 'live');
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs; Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
late bool enableCDN; late bool enableCDN;
late int currentQn; late int currentQn;

View File

@ -62,11 +62,6 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
controller: plPlayerController, controller: plPlayerController,
liveRoomCtr: _liveRoomController, liveRoomCtr: _liveRoomController,
floating: floating, floating: floating,
onRefresh: () {
setState(() {
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
});
},
), ),
); );
} else { } else {

View File

@ -14,12 +14,10 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget {
final PlPlayerController? controller; final PlPlayerController? controller;
final LiveRoomController? liveRoomCtr; final LiveRoomController? liveRoomCtr;
final Floating? floating; final Floating? floating;
final Function? onRefresh;
const BottomControl({ const BottomControl({
this.controller, this.controller,
this.liveRoomCtr, this.liveRoomCtr,
this.floating, this.floating,
this.onRefresh,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -63,14 +61,6 @@ class _BottomControlState extends State<BottomControl> {
// ), // ),
// fuc: () => Get.back(), // fuc: () => Get.back(),
// ), // ),
ComBtn(
icon: const Icon(
Icons.refresh_outlined,
size: 18,
color: Colors.white,
),
fuc: widget.onRefresh,
),
const Spacer(), const Spacer(),
// ComBtn( // ComBtn(
// icon: const Icon( // icon: const Icon(
@ -153,11 +143,28 @@ class _BottomControlState extends State<BottomControl> {
size: 20, size: 20,
color: Colors.white, color: Colors.white,
), ),
fuc: () => widget.controller!.triggerFullScreen( fuc: () => widget.controller!.triggerFullScreen(),
status: !(widget.controller!.isFullScreen.value)),
), ),
], ],
), ),
); );
} }
} }
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);
}
}

View File

@ -1,14 +1,11 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:encrypt/encrypt.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/login.dart'; import 'package:pilipala/http/login.dart';
import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart'; import 'package:gt3_flutter_plugin/gt3_flutter_plugin.dart';
import 'package:pilipala/models/login/index.dart'; import 'package:pilipala/models/login/index.dart';
import 'package:pilipala/utils/login.dart';
class LoginPageController extends GetxController { class LoginPageController extends GetxController {
final GlobalKey mobFormKey = GlobalKey<FormState>(); final GlobalKey mobFormKey = GlobalKey<FormState>();
@ -29,23 +26,9 @@ class LoginPageController extends GetxController {
final Gt3FlutterPlugin captcha = Gt3FlutterPlugin(); final Gt3FlutterPlugin captcha = Gt3FlutterPlugin();
// 倒计时60s
RxInt seconds = 60.obs;
Timer? timer;
RxBool smsCodeSendStatus = false.obs;
// 默认密码登录 // 默认密码登录
RxInt loginType = 0.obs; RxInt loginType = 0.obs;
late String captchaKey;
late int tel;
late int webSmsCode;
RxInt validSeconds = 180.obs;
Timer? validTimer;
late String qrcodeKey;
// 监听pageView切换 // 监听pageView切换
void onPageChange(int index) { void onPageChange(int index) {
currentIndex.value = index; currentIndex.value = index;
@ -60,7 +43,6 @@ class LoginPageController extends GetxController {
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
passwordTextFieldNode.requestFocus(); passwordTextFieldNode.requestFocus();
(mobFormKey.currentState as FormState).save();
} }
} }
@ -104,64 +86,18 @@ class LoginPageController extends GetxController {
} }
} }
// web端密码登录 // 验证码登录
void loginInByWebPassword() async { void loginInByCode() {
if ((passwordFormKey.currentState as FormState).validate()) { if ((msgCodeFormKey.currentState as FormState).validate()) {}
getCaptcha((data) async {
CaptchaDataModel captchaData = data;
var webKeyRes = await LoginHttp.getWebKey();
if (webKeyRes['status']) {
String rhash = webKeyRes['data']['hash'];
String key = webKeyRes['data']['key'];
dynamic publicKey = RSAKeyParser().parse(key);
String passwordEncryptyed = Encrypter(RSA(publicKey: publicKey))
.encrypt(rhash + passwordTextController.text)
.base64;
var res = await LoginHttp.loginInByWebPwd(
username: tel,
password: passwordEncryptyed,
token: captchaData.token!,
challenge: captchaData.geetest!.challenge!,
validate: captchaData.validate!,
seccode: captchaData.seccode!,
);
if (res['status']) {
await LoginUtils.confirmLogin('', null);
} else {
SmartDialog.showToast(res['msg']);
}
} else {
SmartDialog.showToast(webKeyRes['msg']);
}
});
}
} }
// web端验证码登录 // app端验证码
void loginInByCode() async { void getMsgCode() async {
if ((msgCodeFormKey.currentState as FormState).validate()) {
(msgCodeFormKey.currentState as FormState).save();
var res = await LoginHttp.loginInByWebSmsCode(
cid: 86,
tel: tel,
code: webSmsCode,
captchaKey: captchaKey,
);
if (res['status']) {
await LoginUtils.confirmLogin('', null);
} else {
SmartDialog.showToast(res['msg']);
}
}
}
// 获取app端验证码
void getAppMsgCode() async {
getCaptcha((data) async { getCaptcha((data) async {
CaptchaDataModel captchaData = data; CaptchaDataModel captchaData = data;
var res = await LoginHttp.sendAppSmsCode( var res = await LoginHttp.sendAppSmsCode(
cid: 86, cid: 86,
tel: tel, tel: 13734077064,
token: captchaData.token!, token: captchaData.token!,
challenge: captchaData.geetest!.challenge!, challenge: captchaData.geetest!.challenge!,
validate: captchaData.validate!, validate: captchaData.validate!,
@ -185,7 +121,7 @@ class LoginPageController extends GetxController {
captcha.addEventHandler(onShow: (Map<String, dynamic> message) async { captcha.addEventHandler(onShow: (Map<String, dynamic> message) async {
SmartDialog.dismiss(); SmartDialog.dismiss();
}, onClose: (Map<String, dynamic> message) async { }, onClose: (Map<String, dynamic> message) async {
SmartDialog.showToast('取消验证'); SmartDialog.showToast('关闭验证');
}, onResult: (Map<String, dynamic> message) async { }, onResult: (Map<String, dynamic> message) async {
debugPrint("Captcha result: $message"); debugPrint("Captcha result: $message");
String code = message["code"]; String code = message["code"];
@ -265,72 +201,4 @@ class LoginPageController extends GetxController {
captcha.startCaptcha(registerData); captcha.startCaptcha(registerData);
} else {} } else {}
} }
// 获取web端验证码
void getWebMsgCode() async {
getCaptcha((data) async {
CaptchaDataModel captchaData = data;
var res = await LoginHttp.sendWebSmsCode(
cid: 86,
tel: tel,
token: captchaData.token!,
challenge: captchaData.geetest!.challenge!,
validate: captchaData.validate!,
seccode: captchaData.seccode!,
);
if (res['status']) {
captchaKey = res['data']['captcha_key'];
SmartDialog.showToast('验证码已发送');
// 倒计时60s
smsCodeSendStatus.value = true;
startTimer();
} else {
SmartDialog.showToast(res['msg']);
}
});
}
// 验证码倒计时
void startTimer() {
timer = Timer.periodic(const Duration(seconds: 1), (timer) {
if (seconds.value > 0) {
seconds.value--;
} else {
seconds.value = 60;
smsCodeSendStatus.value = false;
timer.cancel();
}
});
}
// 获取登录二维码
Future getWebQrcode() async {
var res = await LoginHttp.getWebQrcode();
validSeconds.value = 180;
if (res['status']) {
qrcodeKey = res['data']['qrcode_key'];
validTimer = Timer.periodic(const Duration(seconds: 1), (validTimer) {
if (validSeconds.value > 0) {
validSeconds.value--;
queryWebQrcodeStatus();
} else {
getWebQrcode();
validTimer.cancel();
}
});
return res;
} else {
SmartDialog.showToast(res['msg']);
}
}
// 轮询二维码登录状态
Future queryWebQrcodeStatus() async {
var res = await LoginHttp.queryWebQrcodeStatus(qrcodeKey);
if (res['status']) {
await LoginUtils.confirmLogin('', null);
validTimer?.cancel();
Get.back();
}
}
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'controller.dart'; import 'controller.dart';
@ -15,10 +14,8 @@ class _LoginPageState extends State<LoginPage> {
final LoginPageController _loginPageCtr = Get.put(LoginPageController()); final LoginPageController _loginPageCtr = Get.put(LoginPageController());
@override @override
void dispose() { void initState() {
_loginPageCtr.validTimer?.cancel(); super.initState();
_loginPageCtr.timer?.cancel();
super.dispose();
} }
@override @override
@ -40,107 +37,6 @@ class _LoginPageState extends State<LoginPage> {
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
), ),
actions: [
IconButton(
tooltip: '浏览器打开',
onPressed: () {
Get.offNamed(
'/webview',
parameters: {
'url': 'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
},
icon: const Icon(Icons.language, size: 20),
),
IconButton(
tooltip: '二维码登录',
onPressed: () {
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return AlertDialog(
title: Row(
children: [
const Text('扫码登录'),
IconButton(
onPressed: () {
setState(() {});
},
icon: const Icon(Icons.refresh),
),
],
),
contentPadding: const EdgeInsets.fromLTRB(0, 0, 0, 4),
content: AspectRatio(
aspectRatio: 1,
child: Container(
width: 200,
padding: const EdgeInsets.all(12),
child: FutureBuilder(
future: _loginPageCtr.getWebQrcode(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
return QrImageView(
data: data['data']['url'],
backgroundColor: Colors.white,
);
} else {
return const Center(
child: SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(),
),
);
}
},
),
),
),
actions: [
TextButton(
onPressed: () {},
child: Obx(() {
return Text(
'有效期: ${_loginPageCtr.validSeconds.value}s',
style: Theme.of(context).textTheme.titleMedium,
);
}),
),
TextButton(
onPressed: () {},
child: Text(
'检查登录状态',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleMedium!
.fontSize,
),
),
)
],
);
});
},
).then((value) {
_loginPageCtr.validTimer!.cancel();
});
},
icon: const Icon(Icons.qr_code, size: 20),
),
const SizedBox(width: 22),
],
), ),
body: PageView( body: PageView(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
@ -168,10 +64,18 @@ class _LoginPageState extends State<LoginPage> {
fontSize: 34, fontSize: 34,
fontWeight: FontWeight.w500), fontWeight: FontWeight.w500),
), ),
Row(
children: [
Text( Text(
'请使用您的 BiliBili 账号登录。', '请使用您的 BiliBili 账号登录。',
style: Theme.of(context).textTheme.titleSmall!, style: Theme.of(context).textTheme.titleSmall!,
), ),
GestureDetector(
onTap: () {},
child: const Icon(Icons.info_outline, size: 16),
)
],
),
Container( Container(
margin: const EdgeInsets.only(top: 38, bottom: 15), margin: const EdgeInsets.only(top: 38, bottom: 15),
child: TextFormField( child: TextFormField(
@ -189,12 +93,35 @@ class _LoginPageState extends State<LoginPage> {
validator: (v) { validator: (v) {
return v!.trim().isNotEmpty ? null : "手机号码不能为空"; return v!.trim().isNotEmpty ? null : "手机号码不能为空";
}, },
onSaved: (val) => _loginPageCtr.tel = int.parse(val!), onSaved: (val) {
print(val);
},
onEditingComplete: () { onEditingComplete: () {
_loginPageCtr.nextStep(); _loginPageCtr.nextStep();
}, },
), ),
), ),
GestureDetector(
onTap: () {
Get.offNamed(
'/webview',
parameters: {
'url':
'https://passport.bilibili.com/h5-app/passport/login',
'type': 'login',
'pageTitle': '登录bilibili',
},
);
},
child: Padding(
padding: const EdgeInsets.only(left: 2),
child: Text(
'使用网页端登录',
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
),
),
const Spacer(), const Spacer(),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -309,7 +236,7 @@ class _LoginPageState extends State<LoginPage> {
.primary, // 设置按钮背景色 .primary, // 设置按钮背景色
), ),
onPressed: () => onPressed: () =>
_loginPageCtr.loginInByWebPassword(), _loginPageCtr.loginInByAppPassword(),
child: const Text('确认登录'), child: const Text('确认登录'),
) )
], ],
@ -381,28 +308,21 @@ class _LoginPageState extends State<LoginPage> {
? null ? null
: "验证码不能为空"; : "验证码不能为空";
}, },
onSaved: (val) => _loginPageCtr.webSmsCode = onSaved: (val) {
int.parse(val!), print(val);
},
), ),
Obx(() { Positioned(
return Positioned(
right: 8, right: 8,
top: 0, top: 4,
child: Center( child: Center(
child: TextButton( child: TextButton(
onPressed: _loginPageCtr onPressed: () =>
.smsCodeSendStatus.value _loginPageCtr.getMsgCode(),
? null child: const Text('获取验证码'),
: () => ),
_loginPageCtr.getWebMsgCode(), ),
child: _loginPageCtr
.smsCodeSendStatus.value
? Text(
'重新获取(${_loginPageCtr.seconds.value}s)')
: const Text('获取验证码')),
), ),
);
})
], ],
), ),
), ),

View File

@ -6,16 +6,23 @@ 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/http/common.dart'; import 'package:pilipala/http/common.dart';
import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart'; import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController { class MainController extends GetxController {
List<Widget> pages = <Widget>[]; List<Widget> pages = <Widget>[
RxList navigationBars = [].obs; const HomePage(),
late List defaultNavTabs; const RankPage(),
late List<int> navBarSort; const DynamicsPage(),
const MediaPage(),
];
RxList navigationBars = defaultNavigationBars.obs;
final StreamController<bool> bottomBarStream = final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast(); StreamController<bool>.broadcast();
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -33,14 +40,16 @@ class MainController extends GetxController {
if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) { if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
Utils.checkUpdata(); Utils.checkUpdata();
} }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: false); hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
selectedIndex = defaultNavigationBars
.indexWhere((item) => item['id'] == defaultHomePage);
var userInfo = userInfoCache.get('userInfoCache'); var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
SettingBoxKey.dynamicBadgeMode, SettingBoxKey.dynamicBadgeMode,
defaultValue: DynamicBadgeMode.number.code)]; defaultValue: DynamicBadgeMode.number.code)];
setNavBarConfig();
if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { if (dynamicBadgeType.value != DynamicBadgeMode.hidden) {
getUnreadDynamic(); getUnreadDynamic();
} }
@ -84,27 +93,4 @@ class MainController extends GetxController {
} }
navigationBars.refresh(); navigationBars.refresh();
} }
void setNavBarConfig() async {
defaultNavTabs = [...defaultNavigationBars];
navBarSort =
setting.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]);
defaultNavTabs.retainWhere((item) => navBarSort.contains(item['id']));
defaultNavTabs.sort((a, b) =>
navBarSort.indexOf(a['id']).compareTo(navBarSort.indexOf(b['id'])));
navigationBars.value = defaultNavTabs;
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
int defaultIndex =
navigationBars.indexWhere((item) => item['id'] == defaultHomePage);
// 如果找不到匹配项默认索引设置为0或其他合适的值
selectedIndex = defaultIndex != -1 ? defaultIndex : 0;
pages = navigationBars.map<Widget>((e) => e['page']).toList();
}
@override
void onClose() {
bottomBarStream.close();
super.onClose();
}
} }

View File

@ -10,7 +10,6 @@ import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/pages/rank/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';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import './controller.dart'; import './controller.dart';
@ -30,6 +29,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
int? _lastSelectTime; //上次点击时间 int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
late bool enableMYBar;
@override @override
void initState() { void initState() {
@ -37,6 +37,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch; _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_mainController.pageController = _mainController.pageController =
PageController(initialPage: _mainController.selectedIndex); PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
} }
void setIndex(int value) async { void setIndex(int value) async {
@ -126,10 +127,9 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
}, },
children: _mainController.pages, children: _mainController.pages,
), ),
bottomNavigationBar: _mainController.navigationBars.length > 1 bottomNavigationBar: StreamBuilder(
? StreamBuilder(
stream: _mainController.hideTabBar stream: _mainController.hideTabBar
? _mainController.bottomBarStream.stream.distinct() ? _mainController.bottomBarStream.stream
: StreamController<bool>.broadcast().stream, : StreamController<bool>.broadcast().stream,
initialData: true, initialData: true,
builder: (context, AsyncSnapshot snapshot) { builder: (context, AsyncSnapshot snapshot) {
@ -137,39 +137,38 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
curve: Curves.easeInOutCubicEmphasized, curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1), offset: Offset(0, snapshot.data ? 0 : 1),
child: GlobalData().enableMYBar child: Obx(
? Obx( () => enableMYBar
() => NavigationBar( ? NavigationBar(
onDestinationSelected: (value) => setIndex(value), onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex, selectedIndex: _mainController.selectedIndex,
destinations: <Widget>[ destinations: <Widget>[
..._mainController.navigationBars.map((e) { ..._mainController.navigationBars.map((e) {
return NavigationDestination( return NavigationDestination(
icon: Badge( icon: Obx(
label: _mainController () => Badge(
.dynamicBadgeType.value == label:
_mainController.dynamicBadgeType.value ==
DynamicBadgeMode.number DynamicBadgeMode.number
? Text(e['count'].toString()) ? Text(e['count'].toString())
: null, : null,
padding: padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0), const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: _mainController isLabelVisible:
.dynamicBadgeType.value != _mainController.dynamicBadgeType.value !=
DynamicBadgeMode.hidden && DynamicBadgeMode.hidden &&
e['count'] > 0, e['count'] > 0,
child: e['icon'], child: e['icon'],
), ),
),
selectedIcon: e['selectIcon'], selectedIcon: e['selectIcon'],
label: e['label'], label: e['label'],
); );
}).toList(), }).toList(),
], ],
),
) )
: Obx( : BottomNavigationBar(
() => BottomNavigationBar(
currentIndex: _mainController.selectedIndex, currentIndex: _mainController.selectedIndex,
type: BottomNavigationBarType.fixed,
onTap: (value) => setIndex(value), onTap: (value) => setIndex(value),
iconSize: 16, iconSize: 16,
selectedFontSize: 12, selectedFontSize: 12,
@ -177,20 +176,22 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
items: [ items: [
..._mainController.navigationBars.map((e) { ..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem( return BottomNavigationBarItem(
icon: Badge( icon: Obx(
label: _mainController () => Badge(
.dynamicBadgeType.value == label:
_mainController.dynamicBadgeType.value ==
DynamicBadgeMode.number DynamicBadgeMode.number
? Text(e['count'].toString()) ? Text(e['count'].toString())
: null, : null,
padding: padding:
const EdgeInsets.fromLTRB(6, 0, 6, 0), const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: _mainController isLabelVisible:
.dynamicBadgeType.value != _mainController.dynamicBadgeType.value !=
DynamicBadgeMode.hidden && DynamicBadgeMode.hidden &&
e['count'] > 0, e['count'] > 0,
child: e['icon'], child: e['icon'],
), ),
),
activeIcon: e['selectIcon'], activeIcon: e['selectIcon'],
label: e['label'], label: e['label'],
); );
@ -200,8 +201,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
), ),
); );
}, },
) ),
: null,
), ),
); );
} }

View File

@ -1,11 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class MediaPage extends StatefulWidget { class MediaPage extends StatefulWidget {
@ -28,11 +30,26 @@ class _MediaPageState extends State<MediaPage>
super.initState(); super.initState();
mediaController = Get.put(MediaController()); mediaController = Get.put(MediaController());
_futureBuilderFuture = mediaController.queryFavFolder(); _futureBuilderFuture = mediaController.queryFavFolder();
ScrollController scrollController = mediaController.scrollController;
StreamController<bool> mainStream =
Get.find<MainController>().bottomBarStream;
mediaController.userLogin.listen((status) { mediaController.userLogin.listen((status) {
setState(() { setState(() {
_futureBuilderFuture = mediaController.queryFavFolder(); _futureBuilderFuture = mediaController.queryFavFolder();
}); });
}); });
scrollController.addListener(
() {
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
} }
@override @override
@ -105,7 +122,7 @@ class _MediaPageState extends State<MediaPage>
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
), ),
ListTile( ListTile(
onTap: () => Get.toNamed('/fav'), onTap: () {},
leading: null, leading: null,
dense: true, dense: true,
title: Padding( title: Padding(

Some files were not shown because too many files have changed in this diff Show More