.
diff --git a/README.md b/README.md
index 46f6d986..237bd07f 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,11 @@
使用Flutter开发的BiliBili第三方客户端
-
-
-
+
+
+
-
+
@@ -26,13 +26,15 @@
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
```bash
-[✓] Flutter (Channel stable, 3.10.6, on macOS 12.1 21C52 darwin-arm64, locale
+[✓] Flutter (Channel stable, 3.16.4, on macOS 14.1.2 23B92 darwin-arm64, locale
zh-Hans-CN)
-[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
-[✓] Xcode - develop for iOS and macOS (Xcode 13.4)
+[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
+[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
[✓] Chrome - develop for the web
-[✓] Android Studio (version 2022.2)
-[✓] VS Code (version 1.77.3)
+[✓] Android Studio (version 2022.3)
+[✓] VS Code (version 1.85.1)
+[✓] Connected device (3 available)
+[✓] Network resources
```
@@ -42,6 +44,7 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
## 技术交流
Telegram: https://t.me/+lm_oOVmF0RJiODk1
+QQ频道: https://pd.qq.com/s/365esodk3
@@ -87,7 +90,7 @@ Telegram: https://t.me/+lm_oOVmF0RJiODk1
- [x] 画质选择(高清画质未解锁)
- [x] 音质选择(视视频而定)
- [x] 解码格式选择(视视频而定)
- - [ ] 弹幕
+ - [x] 弹幕
- [ ] 字幕
- [x] 记忆播放
- [x] 视频比例:高度/宽度适应、填充、包含等
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 1198b6fc..3dc4f82a 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -58,11 +58,10 @@ android {
applicationId "com.guozhigq.pilipala"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
- // minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
- minSdkVersion 19
+ minSdkVersion 21
multiDexEnabled true
}
@@ -95,3 +94,14 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
+
+ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
+import com.android.build.OutputFile
+android.applicationVariants.all { variant ->
+ variant.outputs.each { output ->
+ def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
+ if (abiVersionCode != null) {
+ output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
+ }
+ }
+}
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2e3896e1..c52d8447 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -67,9 +67,9 @@
-
+
@@ -223,6 +223,10 @@
android:pathPattern="/mobile/video/.*" />
+
+
diff --git a/assets/images/live/default_bg.webp b/assets/images/live/default_bg.webp
new file mode 100644
index 00000000..a58259de
Binary files /dev/null and b/assets/images/live/default_bg.webp differ
diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg
new file mode 100644
index 00000000..9f48027b
--- /dev/null
+++ b/assets/images/video/danmu_close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/video/danmu_open.svg b/assets/images/video/danmu_open.svg
new file mode 100644
index 00000000..24e8d7a9
--- /dev/null
+++ b/assets/images/video/danmu_open.svg
@@ -0,0 +1 @@
+Layer 1
\ No newline at end of file
diff --git a/assets/sreenshot/174shots_so.png b/assets/screenshots/174shots_so.png
similarity index 100%
rename from assets/sreenshot/174shots_so.png
rename to assets/screenshots/174shots_so.png
diff --git a/assets/sreenshot/510shots_so.png b/assets/screenshots/510shots_so.png
similarity index 100%
rename from assets/sreenshot/510shots_so.png
rename to assets/screenshots/510shots_so.png
diff --git a/assets/sreenshot/850shots_so.png b/assets/screenshots/850shots_so.png
similarity index 100%
rename from assets/sreenshot/850shots_so.png
rename to assets/screenshots/850shots_so.png
diff --git a/assets/sreenshot/bangumi.png b/assets/screenshots/bangumi.png
similarity index 100%
rename from assets/sreenshot/bangumi.png
rename to assets/screenshots/bangumi.png
diff --git a/assets/sreenshot/bangumi_detail.png b/assets/screenshots/bangumi_detail.png
similarity index 100%
rename from assets/sreenshot/bangumi_detail.png
rename to assets/screenshots/bangumi_detail.png
diff --git a/assets/sreenshot/dynamic.png b/assets/screenshots/dynamic.png
similarity index 100%
rename from assets/sreenshot/dynamic.png
rename to assets/screenshots/dynamic.png
diff --git a/assets/sreenshot/home.png b/assets/screenshots/home.png
similarity index 100%
rename from assets/sreenshot/home.png
rename to assets/screenshots/home.png
diff --git a/assets/sreenshot/main_screen.png b/assets/screenshots/main_screen.png
similarity index 100%
rename from assets/sreenshot/main_screen.png
rename to assets/screenshots/main_screen.png
diff --git a/assets/sreenshot/media.png b/assets/screenshots/media.png
similarity index 100%
rename from assets/sreenshot/media.png
rename to assets/screenshots/media.png
diff --git a/assets/sreenshot/member.png b/assets/screenshots/member.png
similarity index 100%
rename from assets/sreenshot/member.png
rename to assets/screenshots/member.png
diff --git a/assets/sreenshot/search.png b/assets/screenshots/search.png
similarity index 100%
rename from assets/sreenshot/search.png
rename to assets/screenshots/search.png
diff --git a/assets/sreenshot/set.png b/assets/screenshots/set.png
similarity index 100%
rename from assets/sreenshot/set.png
rename to assets/screenshots/set.png
diff --git a/change_log/1.0.15.0101.md b/change_log/1.0.15.0101.md
new file mode 100644
index 00000000..184a6b3c
--- /dev/null
+++ b/change_log/1.0.15.0101.md
@@ -0,0 +1,22 @@
+## 1.0.15
+
+元旦快乐~ 🎉
+
+### 功能
++ 转发动态评论展示
++ 推荐、最热、收藏视频增肌日期显示
+
+### 修复
++ 全屏播放相关问题
++ 评论区@用户展示问题
++ 登录状态闪退问题
++ pip意外触发问题
++ 动态页tab切换样式问题
+
+### 优化
++ 首页默认使用web端推荐
++ 取消iOS路由切换效果
++ 视频分享中添加Up主
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.16.0102.md b/change_log/1.0.16.0102.md
new file mode 100644
index 00000000..b0a85a0f
--- /dev/null
+++ b/change_log/1.0.16.0102.md
@@ -0,0 +1,15 @@
+## 1.0.16
+
+
+### 功能
++ toast 背景支持透明度调节
+
+### 修复
++ web端推荐未展示【已关注】
++ up主动态页异常
++ 未打开自动播放时,视频详情页异常
++ 视频暂停状态取消自动ip
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.17.0125.md b/change_log/1.0.17.0125.md
new file mode 100644
index 00000000..dc8bcb62
--- /dev/null
+++ b/change_log/1.0.17.0125.md
@@ -0,0 +1,39 @@
+## 1.0.17
+
+
+### 功能
++ 视频全屏时隐藏进度条
++ 动态内容增加投稿跳转
++ 未开启自动播放时点击封面播放
++ 弹幕发送标识
++ 定时关闭
++ 推荐视频卡片拉黑up功能
++ 首页tabbar编辑排序
+
+### 修复
++ 连续跳转搜索页未刷新
++ 搜索结果为空时页面异常
++ 评论区链接解析
++ 视频全屏状态栏背景色
++ 私信对话气泡位置
++ 设置up关注分组样式
++ 每次推荐请求数据相同
++ iOS代理网络异常
++ 双击切换播放状态无声
++ 设置自定义倍速白屏
++ 免登录查看1080p
+
+### 优化
++ 首页web端推荐观看数展示
++ 首页web端推荐接口更新
++ 首页样式
++ 搜索页跳转
++ 弹幕资源优化
++ 图片渲染占用内存优化(部分)
++ 两次返回退出应用
++ schame 补充
+
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.18.0130.md b/change_log/1.0.18.0130.md
new file mode 100644
index 00000000..2f0b80ca
--- /dev/null
+++ b/change_log/1.0.18.0130.md
@@ -0,0 +1,16 @@
+## 1.0.18
+
+
+### 功能
+
+
+### 修复
+
+
+### 优化
+
+
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.19.0131.md b/change_log/1.0.19.0131.md
new file mode 100644
index 00000000..1fd3071b
--- /dev/null
+++ b/change_log/1.0.19.0131.md
@@ -0,0 +1,15 @@
+## 1.0.19
+
+
+### 修复
++ 视频404、评论加载错误
++ bvav转换
+
+### 优化
++ 视频详情页内存占用
+
+
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.20.0303.md b/change_log/1.0.20.0303.md
new file mode 100644
index 00000000..1d8c4e00
--- /dev/null
+++ b/change_log/1.0.20.0303.md
@@ -0,0 +1,31 @@
+## 1.0.20
+
+
+### 功能
++ 评论区增加表情
++ 首页渐变背景开关
++ 媒体库显示「我的订阅」
++ 评论区链接解析
++ 默认启动页设置
+
+### 修复
++ 评论区内容重复
++ pip相关问题
++ 播放多p视频评论不刷新
++ 视频评论翻页重复
+
+### 优化
++ url scheme优化
++ 图片预览放大
++ 图片加载速度
++ 视频评论区复制
++ 全屏显示视频标题
++ 网络异常处理
+
+
+
+
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.21.0306.md b/change_log/1.0.21.0306.md
new file mode 100644
index 00000000..3a582dbb
--- /dev/null
+++ b/change_log/1.0.21.0306.md
@@ -0,0 +1,9 @@
+## 1.0.21
+
+### 修复
++ 推荐视频全屏问题
++ 番剧全屏播放时灰屏问题
++ 评论回调导致页面卡死问题
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 00000000..1a6e2446
--- /dev/null
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,9 @@
+PiliPala is a third-party Bilibili client developed in Flutter.
+
+Top Features:
+
+* List of recommended videos
+* List of hottest videos
+* Popular live streams
+* List of bangumis
+* Block videos from blacklisted users
diff --git a/fastlane/metadata/android/en-US/images/featureGraphic.png b/fastlane/metadata/android/en-US/images/featureGraphic.png
new file mode 100644
index 00000000..1f72277e
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
new file mode 100644
index 00000000..db737743
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
new file mode 100644
index 00000000..ae00cf9f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
new file mode 100644
index 00000000..bf16b34f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
new file mode 100644
index 00000000..fbdfb88c
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 00000000..429e00fb
--- /dev/null
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+A third-party Bilibili client developed in Flutter
diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 00000000..2b0d34f3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+PiliPala
diff --git a/fastlane/metadata/android/zh-CN/changelogs/2001.txt b/fastlane/metadata/android/zh-CN/changelogs/2001.txt
new file mode 100644
index 00000000..a5e2c0a4
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/changelogs/2001.txt
@@ -0,0 +1,21 @@
+修复
+
+* 全屏弹幕消失
+* iOS 全屏/退出全屏视频暂停
+* 个人主页关注状态
+* 视频合集向下滑动UI问题
+* 媒体库滑动底栏不隐藏
+* 个人主页动态加载问题 * 2
+* 未登录状态访问个人主页异常
+* 视频搜索标题特殊字符转义
+* iOS 闪退
+* 消息页面夜间模式异常
+* 消息页面含有撤回消息时异常
+* 弹幕速度
+
+优化
+
+* 全屏播放方案优化
+* 弹幕加载逻辑优化
+* 点赞、投币逻辑优化
+* 进度条及播放时间渲染优化
diff --git a/fastlane/metadata/android/zh-CN/full_description.txt b/fastlane/metadata/android/zh-CN/full_description.txt
new file mode 100644
index 00000000..361386e6
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/full_description.txt
@@ -0,0 +1,9 @@
+PiliPala 是使用 Flutter 开发的 BiliBili 第三方客户端。
+
+主要功能:
+
+* 推荐视频列表 (app 端)
+* 最热视频列表
+* 热门直播
+* 番剧列表
+* 屏蔽黑名单内用户视频
diff --git a/fastlane/metadata/android/zh-CN/images/featureGraphic.png b/fastlane/metadata/android/zh-CN/images/featureGraphic.png
new file mode 100644
index 00000000..1f72277e
Binary files /dev/null and b/fastlane/metadata/android/zh-CN/images/featureGraphic.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/icon.png b/fastlane/metadata/android/zh-CN/images/icon.png
new file mode 100644
index 00000000..db737743
Binary files /dev/null and b/fastlane/metadata/android/zh-CN/images/icon.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png
new file mode 100644
index 00000000..ae00cf9f
Binary files /dev/null and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png
new file mode 100644
index 00000000..bf16b34f
Binary files /dev/null and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png
new file mode 100644
index 00000000..fbdfb88c
Binary files /dev/null and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/zh-CN/short_description.txt b/fastlane/metadata/android/zh-CN/short_description.txt
new file mode 100644
index 00000000..683129cc
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/short_description.txt
@@ -0,0 +1 @@
+使用 Flutter 开发的 BiliBili 第三方客户端
diff --git a/fastlane/metadata/android/zh-CN/title.txt b/fastlane/metadata/android/zh-CN/title.txt
new file mode 100644
index 00000000..2b0d34f3
--- /dev/null
+++ b/fastlane/metadata/android/zh-CN/title.txt
@@ -0,0 +1 @@
+PiliPala
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 5f855b9d..2c1a635b 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -9,12 +9,18 @@ PODS:
- Flutter
- connectivity_plus (0.0.1):
- Flutter
+ - FlutterMacOS
- ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
+ - flutter_mailer (0.0.1):
+ - Flutter
- flutter_volume_controller (0.0.1):
- Flutter
+ - fluttertoast (0.0.2):
+ - Flutter
+ - Toast
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
@@ -33,7 +39,7 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- - permission_handler_apple (9.1.1):
+ - permission_handler_apple (9.3.0):
- Flutter
- ReachabilitySwift (5.0.0)
- saver_gallery (0.0.1):
@@ -49,6 +55,7 @@ PODS:
- Flutter
- system_proxy (0.0.1):
- Flutter
+ - Toast (4.1.0)
- url_launcher_ios (0.0.1):
- Flutter
- volume_controller (0.0.1):
@@ -65,10 +72,12 @@ DEPENDENCIES:
- audio_service (from `.symlinks/plugins/audio_service/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
- - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
+ - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
+ - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
@@ -93,6 +102,7 @@ SPEC REPOS:
- FMDB
- GT3Captcha-iOS
- ReachabilitySwift
+ - Toast
EXTERNAL SOURCES:
appscheme:
@@ -104,13 +114,17 @@ EXTERNAL SOURCES:
auto_orientation:
:path: ".symlinks/plugins/auto_orientation/ios"
connectivity_plus:
- :path: ".symlinks/plugins/connectivity_plus/ios"
+ :path: ".symlinks/plugins/connectivity_plus/darwin"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
+ flutter_mailer:
+ :path: ".symlinks/plugins/flutter_mailer/ios"
flutter_volume_controller:
:path: ".symlinks/plugins/flutter_volume_controller/ios"
+ fluttertoast:
+ :path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
media_kit_libs_ios_video:
@@ -153,10 +167,12 @@ SPEC CHECKSUMS:
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
- connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
+ connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
+ fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
@@ -165,7 +181,7 @@ SPEC CHECKSUMS:
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
- permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
+ permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
@@ -173,11 +189,12 @@ SPEC CHECKSUMS:
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
+ Toast: ec33c32b8688982cecc6348adeae667c1b9938da
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
- webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
+ webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
diff --git a/lib/common/widgets/content_container.dart b/lib/common/widgets/content_container.dart
index 076a02e9..0abd4bf2 100644
--- a/lib/common/widgets/content_container.dart
+++ b/lib/common/widgets/content_container.dart
@@ -20,7 +20,7 @@ class ContentContainer extends StatelessWidget {
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
clipBehavior: childClipBehavior ?? Clip.hardEdge,
- physics: isScrollable ? null : NeverScrollableScrollPhysics(),
+ physics: isScrollable ? null : const NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
@@ -34,7 +34,7 @@ class ContentContainer extends StatelessWidget {
child: contentWidget!,
)
else
- Spacer(),
+ const Spacer(),
if (bottomWidget != null) bottomWidget!,
],
),
diff --git a/lib/common/widgets/custom_toast.dart b/lib/common/widgets/custom_toast.dart
index 9cd3461d..f732fd85 100644
--- a/lib/common/widgets/custom_toast.dart
+++ b/lib/common/widgets/custom_toast.dart
@@ -1,17 +1,27 @@
import 'package:flutter/material.dart';
+import 'package:hive/hive.dart';
+import 'package:pilipala/utils/storage.dart';
+
+Box setting = GStrorage.setting;
class CustomToast extends StatelessWidget {
+ const CustomToast({super.key, required this.msg});
+
final String msg;
- const CustomToast({Key? key, required this.msg}) : super(key: key);
@override
Widget build(BuildContext context) {
+ final double toastOpacity =
+ setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
return Container(
margin:
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 10),
decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.primaryContainer,
+ color: Theme.of(context)
+ .colorScheme
+ .primaryContainer
+ .withOpacity(toastOpacity),
borderRadius: BorderRadius.circular(20),
),
child: Text(
diff --git a/lib/common/widgets/html_render.dart b/lib/common/widgets/html_render.dart
index 2e97ceed..bf58d78c 100644
--- a/lib/common/widgets/html_render.dart
+++ b/lib/common/widgets/html_render.dart
@@ -1,45 +1,46 @@
-import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
-import 'package:pilipala/common/widgets/network_img_layer.dart';
+import 'package:get/get.dart';
+import 'network_img_layer.dart';
// ignore: must_be_immutable
class HtmlRender extends StatelessWidget {
- String? htmlContent;
- final int? imgCount;
- final List? imgList;
-
- HtmlRender({
+ const HtmlRender({
this.htmlContent,
this.imgCount,
this.imgList,
super.key,
});
+ final String? htmlContent;
+ final int? imgCount;
+ final List? imgList;
+
@override
Widget build(BuildContext context) {
return Html(
data: htmlContent,
- onLinkTap: (url, buildContext, attributes) => {},
+ onLinkTap: (String? url, Map buildContext, attributes) {},
extensions: [
TagExtension(
- tagsToExtend: {"img"},
- builder: (extensionContext) {
+ tagsToExtend: {'img'},
+ builder: (ExtensionContext extensionContext) {
try {
- Map attributes = extensionContext.attributes;
- List key = attributes.keys.toList();
- String? imgUrl = key.contains('src')
- ? attributes['src']
- : attributes['data-src'];
- if (imgUrl!.startsWith('//')) {
+ final Map attributes =
+ extensionContext.attributes;
+ final List key = attributes.keys.toList();
+ String imgUrl = key.contains('src')
+ ? attributes['src'] as String
+ : attributes['data-src'] as String;
+ if (imgUrl.startsWith('//')) {
imgUrl = 'https:$imgUrl';
}
if (imgUrl.startsWith('http://')) {
imgUrl = imgUrl.replaceAll('http://', 'https://');
}
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
- bool isEmote = imgUrl.contains('/emote/');
- bool isMall = imgUrl.contains('/mall/');
+ final bool isEmote = imgUrl.contains('/emote/');
+ final bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return const SizedBox();
}
@@ -58,38 +59,37 @@ class HtmlRender extends StatelessWidget {
src: imgUrl,
);
} catch (err) {
- print(err);
return const SizedBox();
}
},
),
],
style: {
- "html": Style(
+ 'html': Style(
fontSize: FontSize.medium,
lineHeight: LineHeight.percent(140),
),
- "body": Style(margin: Margins.zero, padding: HtmlPaddings.zero),
- "a": Style(
+ 'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
+ 'a': Style(
color: Theme.of(context).colorScheme.primary,
textDecoration: TextDecoration.none,
),
- "p": Style(
+ 'p': Style(
margin: Margins.only(bottom: 10),
),
- "span": Style(
+ 'span': Style(
fontSize: FontSize.medium,
height: Height(1.65),
),
- "div": Style(height: Height.auto()),
- "li > p": Style(
+ 'div': Style(height: Height.auto()),
+ 'li > p': Style(
display: Display.inline,
),
- "li": Style(
+ 'li': Style(
padding: HtmlPaddings.only(bottom: 4),
textAlign: TextAlign.justify,
),
- "img": Style(margin: Margins.only(top: 4, bottom: 4)),
+ 'img': Style(margin: Margins.only(top: 4, bottom: 4)),
},
);
}
diff --git a/lib/common/widgets/http_error.dart b/lib/common/widgets/http_error.dart
index b02182c6..cbc6659b 100644
--- a/lib/common/widgets/http_error.dart
+++ b/lib/common/widgets/http_error.dart
@@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
"assets/images/error.svg",
height: 200,
),
- const SizedBox(height: 20),
+ const SizedBox(height: 30),
Text(
errMsg ?? '请求异常',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
),
- const SizedBox(height: 30),
- OutlinedButton.icon(
+ const SizedBox(height: 20),
+ FilledButton.tonal(
onPressed: () {
fn!();
},
- icon: const Icon(Icons.arrow_forward_outlined, size: 20),
- label: Text(btnText ?? '点击重试'),
- )
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.resolveWith((states) {
+ return Theme.of(context).colorScheme.primary.withAlpha(20);
+ }),
+ ),
+ child: Text(
+ btnText ?? '点击重试',
+ style: TextStyle(color: Theme.of(context).colorScheme.primary),
+ ),
+ ),
],
),
),
diff --git a/lib/common/widgets/live_card.dart b/lib/common/widgets/live_card.dart
index 01d0bf32..4034756d 100644
--- a/lib/common/widgets/live_card.dart
+++ b/lib/common/widgets/live_card.dart
@@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/common/widgets/network_img_layer.dart';
-import 'package:pilipala/utils/utils.dart';
+import '../../utils/utils.dart';
+import '../constants.dart';
+import 'network_img_layer.dart';
class LiveCard extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
- final liveItem;
+ final dynamic liveItem;
const LiveCard({
Key? key,
@@ -14,7 +14,7 @@ class LiveCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
- String heroTag = Utils.makeHeroTag(liveItem.roomid);
+ final String heroTag = Utils.makeHeroTag(liveItem.roomid);
return Card(
elevation: 0,
@@ -23,7 +23,6 @@ class LiveCard extends StatelessWidget {
borderRadius: BorderRadius.circular(0),
side: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
- width: 1,
),
),
margin: EdgeInsets.zero,
@@ -33,15 +32,16 @@ class LiveCard extends StatelessWidget {
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
- child: LayoutBuilder(builder: (context, boxConstraints) {
- double maxWidth = boxConstraints.maxWidth;
- double maxHeight = boxConstraints.maxHeight;
+ child: LayoutBuilder(builder:
+ (BuildContext context, BoxConstraints boxConstraints) {
+ final double maxWidth = boxConstraints.maxWidth;
+ final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
- src: liveItem.cover,
+ src: liveItem.cover as String,
type: 'emote',
width: maxWidth,
height: maxHeight,
@@ -58,7 +58,7 @@ class LiveCard extends StatelessWidget {
// view: liveItem.stat.view,
// danmaku: liveItem.stat.danmaku,
// duration: liveItem.duration,
- online: liveItem.online,
+ online: liveItem.online as int,
),
),
),
@@ -90,7 +90,7 @@ class LiveContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
- liveItem.title,
+ liveItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
maxLines: 2,
@@ -99,7 +99,7 @@ class LiveContent extends StatelessWidget {
SizedBox(
width: double.infinity,
child: Text(
- liveItem.uname,
+ liveItem.uname as String,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
@@ -114,9 +114,9 @@ class LiveContent extends StatelessWidget {
}
class LiveStat extends StatelessWidget {
- final int? online;
+ const LiveStat({super.key, required this.online});
- const LiveStat({Key? key, required this.online}) : super(key: key);
+ final int? online;
@override
Widget build(BuildContext context) {
@@ -136,7 +136,7 @@ class LiveStat extends StatelessWidget {
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
+ children: [
// Row(
// children: [
// StatView(
diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart
index c44bd0e7..06c35974 100644
--- a/lib/common/widgets/network_img_layer.dart
+++ b/lib/common/widgets/network_img_layer.dart
@@ -1,78 +1,104 @@
-import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/utils/storage.dart';
+import 'package:pilipala/utils/extension.dart';
+import 'package:pilipala/utils/global_data.dart';
+import '../../utils/storage.dart';
+import '../constants.dart';
-Box setting = GStrorage.setting;
+Box setting = GStrorage.setting;
class NetworkImgLayer extends StatelessWidget {
- final String? src;
- final double? width;
- final double? height;
- final double? cacheW;
- final double? cacheH;
- final String? type;
- final Duration? fadeOutDuration;
- final Duration? fadeInDuration;
- final int? quality;
-
const NetworkImgLayer({
- Key? key,
+ super.key,
this.src,
required this.width,
required this.height,
- this.cacheW,
- this.cacheH,
this.type,
this.fadeOutDuration,
this.fadeInDuration,
// 图片质量 默认1%
this.quality,
- }) : super(key: key);
+ this.origAspectRatio,
+ });
+
+ final String? src;
+ final double width;
+ final double height;
+ final String? type;
+ final Duration? fadeOutDuration;
+ final Duration? fadeInDuration;
+ final int? quality;
+ final double? origAspectRatio;
@override
Widget build(BuildContext context) {
- double pr = MediaQuery.of(context).devicePixelRatio;
- int picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
+ final int defaultImgQuality = GlobalData().imgQuality;
+ final String imageUrl =
+ '${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp';
+ print(imageUrl);
+ int? memCacheWidth, memCacheHeight;
+ double aspectRatio = (width / height).toDouble();
- // double pr = 2;
- return src != ''
+ void setMemCacheSizes() {
+ if (aspectRatio > 1) {
+ memCacheHeight = height.cacheSize(context);
+ } else if (aspectRatio < 1) {
+ memCacheWidth = width.cacheSize(context);
+ } else {
+ if (origAspectRatio != null && origAspectRatio! > 1) {
+ memCacheWidth = width.cacheSize(context);
+ } else if (origAspectRatio != null && origAspectRatio! < 1) {
+ memCacheHeight = height.cacheSize(context);
+ } else {
+ memCacheWidth = width.cacheSize(context);
+ memCacheHeight = height.cacheSize(context);
+ }
+ }
+ }
+
+ setMemCacheSizes();
+
+ if (memCacheWidth == null && memCacheHeight == null) {
+ memCacheWidth = width.toInt();
+ }
+
+ return src != '' && src != null
? ClipRRect(
- clipBehavior: Clip.hardEdge,
- borderRadius: BorderRadius.circular(type == 'avatar'
- ? 50
- : type == 'emote'
- ? 0
- : StyleString.imgRadius.x),
+ clipBehavior: Clip.antiAlias,
+ borderRadius: BorderRadius.circular(
+ type == 'avatar'
+ ? 50
+ : type == 'emote'
+ ? 0
+ : StyleString.imgRadius.x,
+ ),
child: CachedNetworkImage(
- imageUrl:
- '${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? picQuality}q.webp',
- width: width ?? double.infinity,
- height: height ?? double.infinity,
- alignment: Alignment.center,
- maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
- // maxHeightDiskCache: (cacheH ?? height!).toInt(),
- memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
- // memCacheHeight: (cacheH ?? height!).toInt(),
+ imageUrl: imageUrl,
+ width: width,
+ height: height,
+ memCacheWidth: memCacheWidth,
+ memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
fadeOutDuration:
- fadeOutDuration ?? const Duration(milliseconds: 200),
+ fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration:
- fadeInDuration ?? const Duration(milliseconds: 200),
- // filterQuality: FilterQuality.high,
- errorWidget: (context, url, error) => placeholder(context),
- placeholder: (context, url) => placeholder(context),
+ fadeInDuration ?? const Duration(milliseconds: 120),
+ filterQuality: FilterQuality.low,
+ errorWidget: (BuildContext context, String url, Object error) =>
+ placeholder(context),
+ placeholder: (BuildContext context, String url) =>
+ placeholder(context),
),
)
: placeholder(context);
}
- Widget placeholder(context) {
+ Widget placeholder(BuildContext context) {
return Container(
- width: width ?? double.infinity,
- height: height ?? double.infinity,
- clipBehavior: Clip.hardEdge,
+ width: width,
+ height: height,
+ clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: BorderRadius.circular(type == 'avatar'
@@ -81,14 +107,19 @@ class NetworkImgLayer extends StatelessWidget {
? 0
: StyleString.imgRadius.x),
),
- child: Center(
- child: Image.asset(
- type == 'avatar'
- ? 'assets/images/noface.jpeg'
- : 'assets/images/loading.png',
- width: 300,
- height: 300,
- )),
+ child: type == 'bg'
+ ? const SizedBox()
+ : Center(
+ child: Image.asset(
+ type == 'avatar'
+ ? 'assets/images/noface.jpeg'
+ : 'assets/images/loading.png',
+ width: width,
+ height: height,
+ cacheWidth: width.cacheSize(context),
+ cacheHeight: height.cacheSize(context),
+ ),
+ ),
);
}
}
diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart
index 53d4c9a1..fe9b9377 100644
--- a/lib/common/widgets/overlay_pop.dart
+++ b/lib/common/widgets/overlay_pop.dart
@@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/common/widgets/network_img_layer.dart';
-import 'package:pilipala/utils/download.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;
- const OverlayPop({super.key, this.videoItem, this.closeFn});
@override
Widget build(BuildContext context) {
- double imgWidth = MediaQuery.of(context).size.width - 8 * 2;
+ final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
@@ -19,7 +20,6 @@ class OverlayPop extends StatelessWidget {
),
child: Column(
mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
@@ -27,7 +27,7 @@ class OverlayPop extends StatelessWidget {
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
- src: videoItem.pic!,
+ src: videoItem.pic! as String,
quality: 100,
),
Positioned(
@@ -61,7 +61,7 @@ class OverlayPop extends StatelessWidget {
children: [
Expanded(
child: Text(
- videoItem.title!,
+ videoItem.title! as String,
),
),
const SizedBox(width: 4),
@@ -69,7 +69,10 @@ class OverlayPop extends StatelessWidget {
tooltip: '保存封面图',
onPressed: () async {
await DownloadUtils.downloadImg(
- videoItem.pic ?? videoItem.cover);
+ videoItem.pic != null
+ ? videoItem.pic as String
+ : videoItem.cover as String,
+ );
// closeFn!();
},
icon: const Icon(Icons.download, size: 20),
diff --git a/lib/common/widgets/pull_to_refresh_header.dart b/lib/common/widgets/pull_to_refresh_header.dart
index 3333a0a6..46db5138 100644
--- a/lib/common/widgets/pull_to_refresh_header.dart
+++ b/lib/common/widgets/pull_to_refresh_header.dart
@@ -17,8 +17,8 @@ class PullToRefreshHeader extends StatelessWidget {
this.info,
this.lastRefreshTime, {
this.color,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
final PullToRefreshScrollNotificationInfo? info;
final DateTime? lastRefreshTime;
@@ -28,7 +28,7 @@ class PullToRefreshHeader extends StatelessWidget {
Widget build(BuildContext context) {
final PullToRefreshScrollNotificationInfo? infos = info;
if (infos == null) {
- return Container();
+ return const SizedBox();
}
String text = '';
if (infos.mode == PullToRefreshIndicatorMode.armed) {
@@ -65,7 +65,6 @@ class PullToRefreshHeader extends StatelessWidget {
top: top,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
- crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Container(
diff --git a/lib/common/widgets/stat/danmu.dart b/lib/common/widgets/stat/danmu.dart
index 44f662a9..c1c439db 100644
--- a/lib/common/widgets/stat/danmu.dart
+++ b/lib/common/widgets/stat/danmu.dart
@@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatDanMu extends StatelessWidget {
final String? theme;
- final int? danmu;
+ final dynamic danmu;
final String? size;
const StatDanMu({Key? key, this.theme, this.danmu, this.size})
diff --git a/lib/common/widgets/stat/view.dart b/lib/common/widgets/stat/view.dart
index 8b97b605..2665e2d4 100644
--- a/lib/common/widgets/stat/view.dart
+++ b/lib/common/widgets/stat/view.dart
@@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatView extends StatelessWidget {
final String? theme;
- final int? view;
+ final dynamic view;
final String? size;
const StatView({Key? key, this.theme, this.view, this.size})
diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart
index 551f4063..99059a9e 100644
--- a/lib/common/widgets/video_card_h.dart
+++ b/lib/common/widgets/video_card_h.dart
@@ -1,17 +1,29 @@
+import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
-import 'package:flutter/material.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/common/widgets/badge.dart';
-import 'package:pilipala/common/widgets/stat/danmu.dart';
-import 'package:pilipala/common/widgets/stat/view.dart';
-import 'package:pilipala/http/search.dart';
-import 'package:pilipala/http/user.dart';
-import 'package:pilipala/utils/utils.dart';
-import 'package:pilipala/common/widgets/network_img_layer.dart';
+import '../../http/search.dart';
+import '../../http/user.dart';
+import '../../http/video.dart';
+import '../../utils/utils.dart';
+import '../constants.dart';
+import 'badge.dart';
+import 'network_img_layer.dart';
+import 'stat/danmu.dart';
+import 'stat/view.dart';
// 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget {
+ const VideoCardH({
+ super.key,
+ required this.videoItem,
+ this.longPress,
+ this.longPressEnd,
+ this.source = 'normal',
+ this.showOwner = true,
+ this.showView = true,
+ this.showDanmaku = true,
+ this.showPubdate = false,
+ });
// ignore: prefer_typing_uninitialized_variables
final videoItem;
final Function()? longPress;
@@ -22,23 +34,15 @@ class VideoCardH extends StatelessWidget {
final bool showDanmaku;
final bool showPubdate;
- const VideoCardH({
- Key? key,
- required this.videoItem,
- this.longPress,
- this.longPressEnd,
- this.source = 'normal',
- this.showOwner = true,
- this.showView = true,
- this.showDanmaku = true,
- this.showPubdate = false,
- }) : super(key: key);
-
@override
Widget build(BuildContext context) {
- int aid = videoItem.aid;
- String bvid = videoItem.bvid;
- String heroTag = Utils.makeHeroTag(aid);
+ final int aid = videoItem.aid;
+ final String bvid = videoItem.bvid;
+ String type = 'video';
+ try {
+ type = videoItem.type;
+ } catch (_) {}
+ final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector(
onLongPress: () {
if (longPress != null) {
@@ -53,7 +57,11 @@ class VideoCardH extends StatelessWidget {
child: InkWell(
onTap: () async {
try {
- int cid =
+ if (type == 'ketang') {
+ SmartDialog.showToast('课堂视频暂不支持播放');
+ return;
+ }
+ final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid',
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
@@ -65,11 +73,11 @@ class VideoCardH extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(
- builder: (context, boxConstraints) {
- double width = (boxConstraints.maxWidth -
+ builder: (BuildContext context, BoxConstraints boxConstraints) {
+ final double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
- MediaQuery.of(context).textScaleFactor) /
+ MediaQuery.textScalerOf(context).scale(1.0)) /
2;
return Container(
constraints: const BoxConstraints(minHeight: 88),
@@ -77,31 +85,38 @@ class VideoCardH extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
- children: [
+ children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
- builder: (context, boxConstraints) {
- double maxWidth = boxConstraints.maxWidth;
- double maxHeight = boxConstraints.maxHeight;
+ builder: (BuildContext context,
+ BoxConstraints boxConstraints) {
+ final double maxWidth = boxConstraints.maxWidth;
+ final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
- src: videoItem.pic,
+ src: videoItem.pic as String,
width: maxWidth,
height: maxHeight,
),
),
- PBadge(
- text: Utils.timeFormat(videoItem.duration!),
- top: null,
- right: 6.0,
- bottom: 6.0,
- left: null,
- type: 'gray',
- ),
+ if (videoItem.duration != 0)
+ PBadge(
+ text: Utils.timeFormat(videoItem.duration!),
+ right: 6.0,
+ bottom: 6.0,
+ type: 'gray',
+ ),
+ if (type != 'video')
+ PBadge(
+ text: type,
+ left: 6.0,
+ bottom: 6.0,
+ type: 'primary',
+ ),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
@@ -159,7 +174,7 @@ class VideoContent extends StatelessWidget {
children: [
if (videoItem.title is String) ...[
Text(
- videoItem.title,
+ videoItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.w500,
@@ -172,9 +187,9 @@ class VideoContent extends StatelessWidget {
maxLines: 2,
text: TextSpan(
children: [
- for (var i in videoItem.title) ...[
+ for (final i in videoItem.title) ...[
TextSpan(
- text: i['text'],
+ text: i['text'] as String,
style: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
@@ -216,7 +231,7 @@ class VideoContent extends StatelessWidget {
Row(
children: [
Text(
- videoItem.owner.name,
+ videoItem.owner.name as String,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
@@ -230,14 +245,14 @@ class VideoContent extends StatelessWidget {
if (showView) ...[
StatView(
theme: 'gray',
- view: videoItem.stat.view,
+ view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
StatDanMu(
theme: 'gray',
- danmu: videoItem.stat.danmaku,
+ danmu: videoItem.stat.danmaku as int,
),
const Spacer(),
@@ -267,7 +282,6 @@ class VideoContent extends StatelessWidget {
height: 24,
child: PopupMenuButton(
padding: EdgeInsets.zero,
- tooltip: '稍后再看',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
@@ -281,11 +295,11 @@ class VideoContent extends StatelessWidget {
PopupMenuItem(
onTap: () async {
var res = await UserHttp.toViewLater(
- bvid: videoItem.bvid);
+ bvid: videoItem.bvid as String);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
- height: 35,
+ height: 40,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
@@ -294,6 +308,60 @@ class VideoContent extends StatelessWidget {
],
),
),
+ const PopupMenuDivider(),
+ PopupMenuItem(
+ onTap: () async {
+ SmartDialog.show(
+ useSystem: true,
+ animationType:
+ SmartAnimationType.centerFade_otherSlide,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('提示'),
+ content: Text(
+ '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
+ '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'),
+ actions: [
+ TextButton(
+ onPressed: () => SmartDialog.dismiss(),
+ child: Text(
+ '点错了',
+ style: TextStyle(
+ color: Theme.of(context)
+ .colorScheme
+ .outline),
+ ),
+ ),
+ TextButton(
+ onPressed: () async {
+ var res = await VideoHttp.relationMod(
+ mid: videoItem.owner.mid,
+ act: 5,
+ reSrc: 11,
+ );
+ SmartDialog.dismiss();
+ SmartDialog.showToast(res['code'] == 0
+ ? '成功'
+ : res['msg']);
+ },
+ child: const Text('确认'),
+ )
+ ],
+ );
+ },
+ );
+ },
+ value: 'pause',
+ height: 40,
+ child: Row(
+ children: [
+ const Icon(Icons.block, size: 16),
+ const SizedBox(width: 6),
+ Text('拉黑:${videoItem.owner.name}',
+ style: const TextStyle(fontSize: 13))
+ ],
+ ),
+ ),
],
),
),
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index fa15a75c..0d96f7b7 100644
--- a/lib/common/widgets/video_card_v.dart
+++ b/lib/common/widgets/video_card_v.dart
@@ -1,17 +1,19 @@
+import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
-import 'package:flutter/material.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/common/widgets/badge.dart';
-import 'package:pilipala/common/widgets/stat/danmu.dart';
-import 'package:pilipala/common/widgets/stat/view.dart';
-import 'package:pilipala/http/dynamics.dart';
-import 'package:pilipala/http/search.dart';
-import 'package:pilipala/http/user.dart';
-import 'package:pilipala/models/common/search_type.dart';
-import 'package:pilipala/utils/id_utils.dart';
-import 'package:pilipala/utils/utils.dart';
-import 'package:pilipala/common/widgets/network_img_layer.dart';
+import '../../models/model_rec_video_item.dart';
+import 'stat/danmu.dart';
+import 'stat/view.dart';
+import '../../http/dynamics.dart';
+import '../../http/search.dart';
+import '../../http/user.dart';
+import '../../http/video.dart';
+import '../../models/common/search_type.dart';
+import '../../utils/id_utils.dart';
+import '../../utils/utils.dart';
+import '../constants.dart';
+import 'badge.dart';
+import 'network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
@@ -159,12 +161,12 @@ class VideoCardV extends StatelessWidget {
height: maxHeight,
),
),
- if (videoItem.duration != null)
+ if (videoItem.duration > 0)
if (crossAxisCount == 1) ...[
PBadge(
bottom: 10,
right: 10,
- text: videoItem.duration,
+ text: Utils.timeFormat(videoItem.duration),
)
] else ...[
PBadge(
@@ -172,7 +174,7 @@ class VideoCardV extends StatelessWidget {
right: 7,
size: 'small',
type: 'gray',
- text: videoItem.duration,
+ text: Utils.timeFormat(videoItem.duration),
)
],
],
@@ -217,15 +219,10 @@ class VideoContent extends StatelessWidget {
),
if (videoItem.goto == 'av' && crossAxisCount == 1) ...[
const SizedBox(width: 10),
- WatchLater(
+ VideoPopupMenu(
size: 32,
iconSize: 18,
- callFn: () async {
- int aid = videoItem.param;
- var res =
- await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
- SmartDialog.showToast(res['msg']);
- },
+ videoItem: videoItem,
),
],
],
@@ -234,6 +231,7 @@ class VideoContent extends StatelessWidget {
const SizedBox(height: 2),
VideoStat(
videoItem: videoItem,
+ crossAxisCount: crossAxisCount,
),
],
if (crossAxisCount == 1) const SizedBox(height: 4),
@@ -266,6 +264,14 @@ class VideoContent extends StatelessWidget {
fs: 9,
)
],
+ if (videoItem.isFollowed == 1) ...[
+ const PBadge(
+ text: '已关注',
+ stack: 'normal',
+ size: 'small',
+ type: 'color',
+ )
+ ],
Expanded(
flex: crossAxisCount == 1 ? 0 : 1,
child: Text(
@@ -289,19 +295,15 @@ class VideoContent extends StatelessWidget {
),
VideoStat(
videoItem: videoItem,
+ crossAxisCount: crossAxisCount,
),
const Spacer(),
],
if (videoItem.goto == 'av' && crossAxisCount != 1) ...[
- WatchLater(
+ VideoPopupMenu(
size: 24,
iconSize: 14,
- callFn: () async {
- int aid = videoItem.param;
- var res =
- await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
- SmartDialog.showToast(res['msg']);
- },
+ videoItem: videoItem,
),
] else ...[
const SizedBox(height: 24)
@@ -317,42 +319,55 @@ class VideoContent extends StatelessWidget {
class VideoStat extends StatelessWidget {
final dynamic videoItem;
+ final int crossAxisCount;
const VideoStat({
Key? key,
required this.videoItem,
+ required this.crossAxisCount,
}) : super(key: key);
@override
Widget build(BuildContext context) {
- return RichText(
- maxLines: 1,
- text: TextSpan(
- style: TextStyle(
- fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
- color: Theme.of(context).colorScheme.outline,
+ return Row(
+ children: [
+ StatView(
+ theme: 'gray',
+ view: videoItem.stat.view,
),
- children: [
- if (videoItem.stat.view != '-')
- TextSpan(text: '${videoItem.stat.view}观看'),
- if (videoItem.stat.danmu != '-')
- TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
- ],
- ),
+ const SizedBox(width: 8),
+ StatDanMu(
+ theme: 'gray',
+ danmu: videoItem.stat.danmu,
+ ),
+ if (videoItem is RecVideoItemModel) ...[
+ crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
+ RichText(
+ maxLines: 1,
+ text: TextSpan(
+ style: TextStyle(
+ fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
+ color: Theme.of(context).colorScheme.outline,
+ ),
+ text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
+ ),
+ const SizedBox(width: 4),
+ ]
+ ],
);
}
}
-class WatchLater extends StatelessWidget {
+class VideoPopupMenu extends StatelessWidget {
final double? size;
final double? iconSize;
- final Function? callFn;
+ final dynamic videoItem;
- const WatchLater({
+ const VideoPopupMenu({
Key? key,
required this.size,
required this.iconSize,
- this.callFn,
+ required this.videoItem,
}) : super(key: key);
@override
@@ -362,7 +377,6 @@ class WatchLater extends StatelessWidget {
height: size,
child: PopupMenuButton(
padding: EdgeInsets.zero,
- tooltip: '稍后再看',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
@@ -373,9 +387,13 @@ class WatchLater extends StatelessWidget {
onSelected: (String type) {},
itemBuilder: (BuildContext context) => >[
PopupMenuItem(
- onTap: () => callFn!(),
+ onTap: () async {
+ var res =
+ await UserHttp.toViewLater(bvid: videoItem.bvid as String);
+ SmartDialog.showToast(res['msg']);
+ },
value: 'pause',
- height: 35,
+ height: 40,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
@@ -384,6 +402,55 @@ class WatchLater extends StatelessWidget {
],
),
),
+ const PopupMenuDivider(),
+ PopupMenuItem(
+ onTap: () async {
+ SmartDialog.show(
+ useSystem: true,
+ animationType: SmartAnimationType.centerFade_otherSlide,
+ builder: (BuildContext context) {
+ return AlertDialog(
+ title: const Text('提示'),
+ content: Text(
+ '确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
+ '\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'),
+ actions: [
+ TextButton(
+ onPressed: () => SmartDialog.dismiss(),
+ child: Text(
+ '点错了',
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.outline),
+ ),
+ ),
+ TextButton(
+ onPressed: () async {
+ var res = await VideoHttp.relationMod(
+ mid: videoItem.owner.mid,
+ act: 5,
+ reSrc: 11,
+ );
+ SmartDialog.dismiss();
+ SmartDialog.showToast(res['msg'] ?? '成功');
+ },
+ child: const Text('确认'),
+ )
+ ],
+ );
+ },
+ );
+ },
+ value: 'pause',
+ height: 40,
+ child: Row(
+ children: [
+ const Icon(Icons.block, size: 16),
+ const SizedBox(width: 6),
+ Text('拉黑:${videoItem.owner.name}',
+ style: const TextStyle(fontSize: 13))
+ ],
+ ),
+ ),
],
),
);
diff --git a/lib/http/api.dart b/lib/http/api.dart
index 75a121ac..fa4cc1e8 100644
--- a/lib/http/api.dart
+++ b/lib/http/api.dart
@@ -1,15 +1,17 @@
+import 'constants.dart';
+
class Api {
// 推荐视频
static const String recommendListApp =
- 'https://app.bilibili.com/x/v2/feed/index';
- static const String recommendList = '/x/web-interface/index/top/feed/rcmd';
+ '${HttpString.appBaseUrl}/x/v2/feed/index';
+ static const String recommendListWeb = '/x/web-interface/index/top/feed/rcmd';
// 热门视频
static const String hotList = '/x/web-interface/popular';
// 视频流
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/videostream_url.md
- static const String videoUrl = '/x/player/playurl';
+ static const String videoUrl = '/x/player/wbi/playurl';
// 视频详情
// 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921
@@ -152,7 +154,7 @@ class Api {
// 动态点赞
static const String likeDynamic =
- 'https://api.vc.bilibili.com/dynamic_like/v1/dynamic_like/thumb';
+ '${HttpString.tUrl}/dynamic_like/v1/dynamic_like/thumb';
// 获取稍后再看
static const String seeYouLater = '/x/v2/history/toview';
@@ -183,7 +185,7 @@ class Api {
static const String searchDefault = '/x/web-interface/wbi/search/default';
// 搜索关键词
- static const String serachSuggest =
+ static const String searchSuggest =
'https://s.search.bilibili.com/main/suggest';
// 分类搜索
@@ -212,6 +214,9 @@ class Api {
// https://api.bilibili.com/x/relation/tags
static const String followingsClass = '/x/relation/tags';
+ // 搜索follow
+ static const followSearch = '/x/relation/followings/search';
+
// 粉丝
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
// order_type 排序规则 最近访问传空,最常访问传 attention
@@ -220,13 +225,17 @@ class Api {
// 直播
// ?page=1&page_size=30&platform=web
static const String liveList =
- 'https://api.live.bilibili.com/xlive/web-interface/v1/second/getUserRecommend';
+ '${HttpString.liveBaseUrl}/xlive/web-interface/v1/second/getUserRecommend';
// 直播间详情
// cid roomId
// qn 80:流畅,150:高清,400:蓝光,10000:原画,20000:4K, 30000:杜比
static const String liveRoomInfo =
- 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo';
+ '${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo';
+
+ // 直播间详情 H5
+ static const String liveRoomInfoH5 =
+ '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getH5InfoByRoom';
// 用户信息 需要Wbi签名
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482
@@ -338,13 +347,13 @@ class Api {
/// wts=1697305010
static const String sessionList =
- 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions';
+ '${HttpString.tUrl}/session_svr/v1/session_svr/get_sessions';
/// 私聊用户信息
/// uids
/// build=0&mobi_app=web
static const String sessionAccountList =
- 'https://api.vc.bilibili.com/account/v1/user/cards';
+ '${HttpString.tUrl}/account/v1/user/cards';
/// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs?
/// talker_id=400787461&
@@ -358,7 +367,7 @@ class Api {
/// wts=1697350697
static const String sessionMsg =
- 'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs';
+ '${HttpString.tUrl}/svr_sync/v1/svr_sync/fetch_session_msgs';
/// 标记已读 POST
/// talker_id:
@@ -369,7 +378,7 @@ class Api {
/// csrf_token:
/// csrf:
static const String updateAck =
- 'https://api.vc.bilibili.com/session_svr/v1/session_svr/update_ack';
+ '${HttpString.tUrl}/session_svr/v1/session_svr/update_ack';
// 获取某个动态详情
// timezone_offset=-480
@@ -388,11 +397,11 @@ class Api {
// captcha验证码
static const String getCaptcha =
- 'https://passport.bilibili.com/x/passport-login/captcha?source=main_web';
+ '${HttpString.passBaseUrl}/x/passport-login/captcha?source=main_web';
// web端短信验证码
static const String smsCode =
- 'https://passport.bilibili.com/x/passport-login/web/sms/send';
+ '${HttpString.passBaseUrl}/x/passport-login/web/sms/send';
// web端验证码登录
@@ -400,7 +409,7 @@ class Api {
// app端短信验证码
static const String appSmsCode =
- 'https://passport.bilibili.com/x/passport-login/sms/send';
+ '${HttpString.passBaseUrl}/x/passport-login/sms/send';
// app端验证码登录
@@ -414,17 +423,16 @@ class Api {
/// key
/// rhash
static const String loginInByPwdApi =
- 'https://passport.bilibili.com/x/passport-login/oauth2/login';
+ '${HttpString.passBaseUrl}/x/passport-login/oauth2/login';
/// 密码加密密钥
/// disable_rcmd
/// local_id
- static const getWebKey =
- 'https://passport.bilibili.com/x/passport-login/web/key';
+ static const getWebKey = '${HttpString.passBaseUrl}/x/passport-login/web/key';
/// cookie转access_key
static const cookieToKey =
- 'https://passport.bilibili.com/x/passport-tv-login/h5/qrcode/confirm';
+ '${HttpString.passBaseUrl}/x/passport-tv-login/h5/qrcode/confirm';
/// 申请二维码(TV端)
static const getTVCode =
@@ -432,7 +440,7 @@ class Api {
///扫码登录(TV端)
static const qrcodePoll =
- 'https://passport.bilibili.com/x/passport-tv-login/qrcode/poll';
+ '${HttpString.passBaseUrl}/x/passport-tv-login/qrcode/poll';
/// 置顶视频
static const getTopVideoApi = '/x/space/top/arc';
@@ -466,4 +474,38 @@ class Api {
/// page_size
static const getSeasonDetailApi =
'/x/polymer/web-space/seasons_archives_list';
+
+ /// 获取未读动态数
+ static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
+
+ /// 用户动态主页
+ static const dynamicSpmPrefix = 'https://space.bilibili.com/1/dynamic';
+
+ /// 激活buvid3
+ static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
+
+ /// 获取字幕配置
+ static const getSubtitleConfig = '/x/player/v2';
+
+ /// 我的订阅
+ static const userSubFolder = '/x/v3/fav/folder/collected/list';
+
+ /// 我的订阅详情
+ static const userSubFolderDetail = '/x/space/fav/season/list';
+
+ /// 表情
+ static const emojiList = '/x/emote/user/panel/web';
+
+ /// 已读标记
+ static const String ackSessionMsg =
+ '${HttpString.tUrl}/session_svr/v1/session_svr/update_ack';
+
+ /// 发送私信
+ static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
+
+ /// 排行榜
+ static const String getRankApi = "/x/web-interface/ranking/v2";
+
+ /// 取消订阅
+ static const String cancelSub = '/x/v3/fav/season/unfav';
}
diff --git a/lib/http/bangumi.dart b/lib/http/bangumi.dart
index bd20366c..91508682 100644
--- a/lib/http/bangumi.dart
+++ b/lib/http/bangumi.dart
@@ -1,5 +1,5 @@
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/bangumi/list.dart';
+import '../models/bangumi/list.dart';
+import 'index.dart';
class BangumiHttp {
static Future bangumiList({int? page}) async {
diff --git a/lib/http/black.dart b/lib/http/black.dart
index 81a7c0c9..0c6a63ab 100644
--- a/lib/http/black.dart
+++ b/lib/http/black.dart
@@ -1,5 +1,5 @@
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/user/black.dart';
+import '../models/user/black.dart';
+import 'index.dart';
class BlackHttp {
static Future blackList({required int pn, int? ps}) async {
diff --git a/lib/http/common.dart b/lib/http/common.dart
new file mode 100644
index 00000000..d711a7e7
--- /dev/null
+++ b/lib/http/common.dart
@@ -0,0 +1,17 @@
+import 'index.dart';
+
+class CommonHttp {
+ static Future unReadDynamic() async {
+ var res = await Request().get(Api.getUnreadDynamic,
+ data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
+ if (res.data['code'] == 0) {
+ return {'status': true, 'data': res.data['data']['dyn_basic_infos']};
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+}
diff --git a/lib/http/constants.dart b/lib/http/constants.dart
index cf10a606..3d749ee8 100644
--- a/lib/http/constants.dart
+++ b/lib/http/constants.dart
@@ -1,7 +1,10 @@
class HttpString {
static const String baseUrl = 'https://www.bilibili.com';
- static const String baseApiUrl = 'https://api.bilibili.com';
+ static const String apiBaseUrl = 'https://api.bilibili.com';
static const String tUrl = 'https://api.vc.bilibili.com';
+ static const String appBaseUrl = 'https://app.bilibili.com';
+ static const String liveBaseUrl = 'https://api.live.bilibili.com';
+ static const String passBaseUrl = 'https://passport.bilibili.com';
static const List validateStatusCodes = [
302,
304,
diff --git a/lib/http/danmaku.dart b/lib/http/danmaku.dart
index e34320e7..0b108755 100644
--- a/lib/http/danmaku.dart
+++ b/lib/http/danmaku.dart
@@ -1,9 +1,6 @@
import 'package:dio/dio.dart';
-import 'package:flutter/foundation.dart';
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/danmaku/dm.pb.dart';
-
-import 'constants.dart';
+import '../models/danmaku/dm.pb.dart';
+import 'index.dart';
class DanmakaHttp {
// 获取视频弹幕
@@ -24,21 +21,23 @@ class DanmakaHttp {
);
return DmSegMobileReply.fromBuffer(response.data);
}
+
static Future shootDanmaku({
- int type = 1,//弹幕类选择(1:视频弹幕 2:漫画弹幕)
- required int oid,// 视频cid
- required String msg,//弹幕文本(长度小于 100 字符)
- int mode = 1,// 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
+ int type = 1, //弹幕类选择(1:视频弹幕 2:漫画弹幕)
+ required int oid, // 视频cid
+ required String msg, //弹幕文本(长度小于 100 字符)
+ int mode =
+ 1, // 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
// String? aid,// 稿件avid
// String? bvid,// bvid与aid必须有一个
required String bvid,
- int? progress,// 弹幕出现在视频内的时间(单位为毫秒,默认为0)
- int? color,// 弹幕颜色(默认白色,16777215)
- int? fontsize,// 弹幕字号(默认25)
- int? pool,// 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0)
+ int? progress, // 弹幕出现在视频内的时间(单位为毫秒,默认为0)
+ int? color, // 弹幕颜色(默认白色,16777215)
+ int? fontsize, // 弹幕字号(默认25)
+ int? pool, // 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0)
//int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s)
- int? colorful,//60001:专属渐变彩色(需要会员)
- int? checkbox_type,//是否带 UP 身份标识(0:普通;4:带有标识)
+ int? colorful, //60001:专属渐变彩色(需要会员)
+ int? checkbox_type, //是否带 UP 身份标识(0:普通;4:带有标识)
// String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要
// String? access_key,// APP 登录 Token APP 方式必要
}) async {
diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart
index 7a22ab13..d62de12f 100644
--- a/lib/http/dynamics.dart
+++ b/lib/http/dynamics.dart
@@ -1,6 +1,6 @@
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/dynamics/result.dart';
-import 'package:pilipala/models/dynamics/up.dart';
+import '../models/dynamics/result.dart';
+import '../models/dynamics/up.dart';
+import 'index.dart';
class DynamicsHttp {
static Future followDynamic({
diff --git a/lib/http/fan.dart b/lib/http/fan.dart
index 932cc79f..a69f58c8 100644
--- a/lib/http/fan.dart
+++ b/lib/http/fan.dart
@@ -1,5 +1,5 @@
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/fans/result.dart';
+import '../models/fans/result.dart';
+import 'index.dart';
class FanHttp {
static Future fans({int? vmid, int? pn, int? ps, String? orderType}) async {
diff --git a/lib/http/follow.dart b/lib/http/follow.dart
index f50762c4..316aa95a 100644
--- a/lib/http/follow.dart
+++ b/lib/http/follow.dart
@@ -1,5 +1,5 @@
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/follow/result.dart';
+import '../models/follow/result.dart';
+import 'index.dart';
class FollowHttp {
static Future followings(
diff --git a/lib/http/html.dart b/lib/http/html.dart
index 41570d0a..100887e5 100644
--- a/lib/http/html.dart
+++ b/lib/http/html.dart
@@ -1,6 +1,6 @@
import 'package:html/dom.dart';
import 'package:html/parser.dart';
-import 'package:pilipala/http/index.dart';
+import 'index.dart';
class HtmlHttp {
// article
@@ -15,7 +15,7 @@ class HtmlHttp {
Match match = regex.firstMatch(response.data)!;
String matchedString = match.group(0)!;
response = await Request().get(
- 'https:$matchedString' + '/',
+ 'https:$matchedString/',
extra: {'ua': 'pc'},
);
}
@@ -40,9 +40,13 @@ class HtmlHttp {
//
String opusContent =
opusDetail.querySelector('.opus-module-content')!.innerHtml;
- String test = opusDetail
- .querySelector('.horizontal-scroll-album__pic__img')!
- .innerHtml;
+ String? test;
+ try {
+ test = opusDetail
+ .querySelector('.horizontal-scroll-album__pic__img')!
+ .innerHtml;
+ } catch (_) {}
+
String commentId = opusDetail
.querySelector('.bili-comment-container')!
.className
@@ -54,7 +58,7 @@ class HtmlHttp {
'avatar': avatar,
'uname': uname,
'updateTime': updateTime,
- 'content': test + opusContent,
+ 'content': (test ?? '') + opusContent,
'commentId': int.parse(commentId)
};
} catch (err) {
diff --git a/lib/http/init.dart b/lib/http/init.dart
index 1e55be38..a0b36369 100644
--- a/lib/http/init.dart
+++ b/lib/http/init.dart
@@ -1,17 +1,21 @@
// ignore_for_file: avoid_print
+import 'dart:async';
+import 'dart:convert';
import 'dart:developer';
import 'dart:io';
-import 'dart:async';
-import 'package:dio/dio.dart';
+import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart';
+import 'package:dio/dio.dart';
import 'package:dio/io.dart';
-import 'package:dio_http2_adapter/dio_http2_adapter.dart';
-import 'package:hive/hive.dart';
-import 'package:pilipala/utils/storage.dart';
-import 'package:pilipala/utils/utils.dart';
-import 'package:pilipala/http/constants.dart';
-import 'package:pilipala/http/interceptor.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
+// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
+import 'package:hive/hive.dart';
+import 'package:pilipala/utils/id_utils.dart';
+import '../utils/storage.dart';
+import '../utils/utils.dart';
+import 'api.dart';
+import 'constants.dart';
+import 'interceptor.dart';
class Request {
static final Request _instance = Request._internal();
@@ -20,25 +24,26 @@ class Request {
factory Request() => _instance;
Box setting = GStrorage.setting;
static Box localCache = GStrorage.localCache;
- late dynamic enableSystemProxy;
+ late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
+ static final RegExp spmPrefixExp = RegExp(r' ');
/// 设置cookie
static setCookie() async {
Box userInfoCache = GStrorage.userInfo;
- var cookiePath = await Utils.getCookiePath();
- var cookieJar = PersistCookieJar(
+ final String cookiePath = await Utils.getCookiePath();
+ final PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true,
storage: FileStorage(cookiePath),
);
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
- var cookie = await cookieManager.cookieJar
+ final List cookie = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
- var userInfo = userInfoCache.get('userInfoCache');
+ final userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null && userInfo.mid != null) {
- var cookie2 = await cookieManager.cookieJar
+ final List cookie2 = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.tUrl));
if (cookie2.isEmpty) {
try {
@@ -50,22 +55,22 @@ class Request {
}
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
- if (cookie.isEmpty) {
- try {
- await Request().get(HttpString.baseUrl);
- } catch (e) {
- log("setCookie, ${e.toString()}");
- }
+ try {
+ await buvidActivate();
+ } catch (e) {
+ log("setCookie, ${e.toString()}");
}
- var cookieString =
- cookie.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
+
+ final String cookieString = cookie
+ .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
+ .join('; ');
dio.options.headers['cookie'] = cookieString;
}
// 从cookie中获取 csrf token
static Future getCsrf() async {
- var cookies = await cookieManager.cookieJar
- .loadForRequest(Uri.parse(HttpString.baseApiUrl));
+ List cookies = await cookieManager.cookieJar
+ .loadForRequest(Uri.parse(HttpString.apiBaseUrl));
String token = '';
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
@@ -73,17 +78,45 @@ class Request {
return token;
}
- static setOptionsHeaders(userInfo, status) {
+ static setOptionsHeaders(userInfo, bool status) {
if (status) {
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
+ dio.options.headers['x-bili-aurora-eid'] =
+ IdUtils.genAuroraEid(userInfo.mid);
}
dio.options.headers['env'] = 'prod';
dio.options.headers['app-key'] = 'android64';
- dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
dio.options.headers['referer'] = 'https://www.bilibili.com/';
}
+ static Future buvidActivate() async {
+ var html = await Request().get(Api.dynamicSpmPrefix);
+ String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
+ Random rand = Random();
+ String rand_png_end = base64.encode(
+ List.generate(32, (_) => rand.nextInt(256)) +
+ List.filled(4, 0) +
+ [73, 69, 78, 68] +
+ List.generate(4, (_) => rand.nextInt(256))
+ );
+
+ String jsonData = json.encode({
+ '3064': 1,
+ '39c8': '${spmPrefix}.fp.risk',
+ '3c43': {
+ 'adca': 'Linux',
+ 'bfe9': rand_png_end.substring(rand_png_end.length - 50),
+ },
+ });
+
+ await Request().post(
+ Api.activateBuvidApi,
+ data: {'payload': jsonData},
+ options: Options(contentType: 'application/json')
+ );
+ }
+
/*
* config it and create
*/
@@ -91,7 +124,7 @@ class Request {
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
BaseOptions options = BaseOptions(
//请求基地址,可以包含子路径
- baseUrl: HttpString.baseApiUrl,
+ baseUrl: HttpString.apiBaseUrl,
//连接服务器超时时间,单位是毫秒.
connectTimeout: const Duration(milliseconds: 12000),
//响应流上前后两次接受到数据的间隔,单位为毫秒。
@@ -100,30 +133,31 @@ class Request {
headers: {},
);
- enableSystemProxy =
- setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
+ enableSystemProxy = setting.get(SettingBoxKey.enableSystemProxy,
+ defaultValue: false) as bool;
systemProxyHost =
localCache.get(LocalCacheKey.systemProxyHost, defaultValue: '');
systemProxyPort =
localCache.get(LocalCacheKey.systemProxyPort, defaultValue: '');
- dio = Dio(options)
+ dio = Dio(options);
- /// fix 第三方登录 302重定向 跟iOS代理问题冲突
- ..httpClientAdapter = Http2Adapter(
- ConnectionManager(
- idleTimeout: const Duration(milliseconds: 10000),
- onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
- ),
- );
+ /// fix 第三方登录 302重定向 跟iOS代理问题冲突
+ // ..httpClientAdapter = Http2Adapter(
+ // ConnectionManager(
+ // idleTimeout: const Duration(milliseconds: 10000),
+ // onClientCreate: (_, ClientSetting config) =>
+ // config.onBadCertificate = (_) => true,
+ // ),
+ // );
/// 设置代理
if (enableSystemProxy) {
dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
- final client = HttpClient();
+ final HttpClient client = HttpClient();
// Config the client.
- client.findProxy = (uri) {
+ client.findProxy = (Uri uri) {
// return 'PROXY host:port';
return 'PROXY $systemProxyHost:$systemProxyPort';
};
@@ -145,7 +179,7 @@ class Request {
));
dio.transformer = BackgroundTransformer();
- dio.options.validateStatus = (status) {
+ dio.options.validateStatus = (int? status) {
return status! >= 200 && status < 300 ||
HttpString.validateStatusCodes.contains(status);
};
@@ -156,7 +190,7 @@ class Request {
*/
get(url, {data, options, cancelToken, extra}) async {
Response response;
- Options options = Options();
+ final Options options = Options();
ResponseType resType = ResponseType.json;
if (extra != null) {
resType = extra!['resType'] ?? ResponseType.json;
@@ -175,8 +209,14 @@ class Request {
);
return response;
} on DioException catch (e) {
- print('get error: $e');
- return Future.error(await ApiInterceptor.dioError(e));
+ Response errResponse = Response(
+ data: {
+ 'message': await ApiInterceptor.dioError(e)
+ }, // 将自定义 Map 数据赋值给 Response 的 data 属性
+ statusCode: 200,
+ requestOptions: RequestOptions(),
+ );
+ return errResponse;
}
}
@@ -197,8 +237,14 @@ class Request {
// print('post success: ${response.data}');
return response;
} on DioException catch (e) {
- print('post error: $e');
- return Future.error(await ApiInterceptor.dioError(e));
+ Response errResponse = Response(
+ data: {
+ 'message': await ApiInterceptor.dioError(e)
+ }, // 将自定义 Map 数据赋值给 Response 的 data 属性
+ statusCode: 200,
+ requestOptions: RequestOptions(),
+ );
+ return errResponse;
}
}
diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart
index 7b398caa..a5359283 100644
--- a/lib/http/interceptor.dart
+++ b/lib/http/interceptor.dart
@@ -1,11 +1,10 @@
// ignore_for_file: avoid_print
-import 'package:dio/dio.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
+import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
-import 'package:pilipala/utils/storage.dart';
-// import 'package:get/get.dart' hide Response;
+import '../utils/storage.dart';
class ApiInterceptor extends Interceptor {
@override
@@ -21,16 +20,16 @@ class ApiInterceptor extends Interceptor {
void onResponse(Response response, ResponseInterceptorHandler handler) {
try {
if (response.statusCode == 302) {
- List locations = response.headers['location']!;
+ final List locations = response.headers['location']!;
if (locations.isNotEmpty) {
if (locations.first.startsWith('https://www.mcbbs.net')) {
- final uri = Uri.parse(locations.first);
- final accessKey = uri.queryParameters['access_key'];
- final mid = uri.queryParameters['mid'];
+ final Uri uri = Uri.parse(locations.first);
+ final String? accessKey = uri.queryParameters['access_key'];
+ final String? mid = uri.queryParameters['mid'];
try {
Box localCache = GStrorage.localCache;
- localCache.put(
- LocalCacheKey.accessKey, {'mid': mid, 'value': accessKey});
+ localCache.put(LocalCacheKey.accessKey,
+ {'mid': mid, 'value': accessKey});
} catch (_) {}
}
}
@@ -46,54 +45,57 @@ class ApiInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误
// handler.next(err);
- SmartDialog.showToast(
- await dioError(err),
- displayType: SmartToastType.onlyRefresh,
- );
+ String url = err.requestOptions.uri.toString();
+ if (!url.contains('heartBeat')) {
+ SmartDialog.showToast(
+ await dioError(err),
+ displayType: SmartToastType.onlyRefresh,
+ );
+ }
super.onError(err, handler);
}
- static Future dioError(DioException error) async {
+ static Future dioError(DioException error) async {
switch (error.type) {
case DioExceptionType.badCertificate:
return '证书有误!';
case DioExceptionType.badResponse:
return '服务器异常,请稍后重试!';
case DioExceptionType.cancel:
- return "请求已被取消,请重新请求";
+ return '请求已被取消,请重新请求';
case DioExceptionType.connectionError:
return '连接错误,请检查网络设置';
case DioExceptionType.connectionTimeout:
- return "网络连接超时,请检查网络设置";
+ return '网络连接超时,请检查网络设置';
case DioExceptionType.receiveTimeout:
- return "响应超时,请稍后重试!";
+ return '响应超时,请稍后重试!';
case DioExceptionType.sendTimeout:
- return "发送请求超时,请检查网络设置";
+ return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown:
- var res = await checkConect();
- return res + " \n 网络异常,请稍后重试!";
- default:
- return "Dio异常";
+ final String res = await checkConnect();
+ return '$res,网络异常!';
}
}
- static Future checkConect() async {
- final connectivityResult = await (Connectivity().checkConnectivity());
- if (connectivityResult == ConnectivityResult.mobile) {
- return 'connected with mobile network';
- } else if (connectivityResult == ConnectivityResult.wifi) {
- return 'connected with wifi network';
- } else if (connectivityResult == ConnectivityResult.ethernet) {
- // I am connected to a ethernet network.
- } else if (connectivityResult == ConnectivityResult.vpn) {
- // I am connected to a vpn network.
- // Note for iOS and macOS:
- // There is no separate network interface type for [vpn].
- // It returns [other] on any device (also simulator)
- } else if (connectivityResult == ConnectivityResult.other) {
- // I am connected to a network which is not in the above mentioned networks.
- } else if (connectivityResult == ConnectivityResult.none) {
- return 'not connected to any network';
+ static Future checkConnect() async {
+ final List connectivityResult =
+ await Connectivity().checkConnectivity();
+ if (connectivityResult.contains(ConnectivityResult.mobile)) {
+ return '正在使用移动流量';
+ } else if (connectivityResult.contains(ConnectivityResult.wifi)) {
+ return '正在使用wifi';
+ } else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
+ return '正在使用局域网';
+ } else if (connectivityResult.contains(ConnectivityResult.vpn)) {
+ return '正在使用代理网络';
+ } else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
+ return '正在使用蓝牙网络';
+ } else if (connectivityResult.contains(ConnectivityResult.other)) {
+ return '正在使用其他网络';
+ } else if (connectivityResult.contains(ConnectivityResult.none)) {
+ return '未连接到任何网络';
+ } else {
+ return '';
}
}
}
diff --git a/lib/http/live.dart b/lib/http/live.dart
index 2ae9aad7..e624120e 100644
--- a/lib/http/live.dart
+++ b/lib/http/live.dart
@@ -1,7 +1,8 @@
-import 'package:pilipala/http/api.dart';
-import 'package:pilipala/http/init.dart';
-import 'package:pilipala/models/live/item.dart';
-import 'package:pilipala/models/live/room_info.dart';
+import '../models/live/item.dart';
+import '../models/live/room_info.dart';
+import '../models/live/room_info_h5.dart';
+import 'api.dart';
+import 'init.dart';
class LiveHttp {
static Future liveList(
@@ -46,4 +47,22 @@ class LiveHttp {
};
}
}
+
+ static Future liveRoomInfoH5({roomId, qn}) async {
+ var res = await Request().get(Api.liveRoomInfoH5, data: {
+ 'room_id': roomId,
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': RoomInfoH5Model.fromJson(res.data['data'])
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
}
diff --git a/lib/http/login.dart b/lib/http/login.dart
index 8d2a254e..ff3fee23 100644
--- a/lib/http/login.dart
+++ b/lib/http/login.dart
@@ -1,13 +1,12 @@
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
-
import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart';
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/login/index.dart';
-import 'package:pilipala/utils/login.dart';
import 'package:uuid/uuid.dart';
+import '../models/login/index.dart';
+import '../utils/login.dart';
+import 'index.dart';
class LoginHttp {
static Future queryCaptcha() async {
diff --git a/lib/http/member.dart b/lib/http/member.dart
index 20826451..1af0f9a4 100644
--- a/lib/http/member.dart
+++ b/lib/http/member.dart
@@ -1,17 +1,17 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/dynamics/result.dart';
-import 'package:pilipala/models/follow/result.dart';
-import 'package:pilipala/models/member/archive.dart';
-import 'package:pilipala/models/member/coin.dart';
-import 'package:pilipala/models/member/info.dart';
-import 'package:pilipala/models/member/seasons.dart';
-import 'package:pilipala/models/member/tags.dart';
-import 'package:pilipala/utils/storage.dart';
-import 'package:pilipala/utils/utils.dart';
-import 'package:pilipala/utils/wbi_sign.dart';
+import '../common/constants.dart';
+import '../models/dynamics/result.dart';
+import '../models/follow/result.dart';
+import '../models/member/archive.dart';
+import '../models/member/coin.dart';
+import '../models/member/info.dart';
+import '../models/member/seasons.dart';
+import '../models/member/tags.dart';
+import '../utils/storage.dart';
+import '../utils/utils.dart';
+import '../utils/wbi_sign.dart';
+import 'index.dart';
class MemberHttp {
static Future memberInfo({
@@ -79,6 +79,8 @@ class MemberHttp {
String order = 'pubdate',
bool orderAvoided = true,
}) async {
+ String dmImgStr = Utils.base64EncodeRandomString(16, 64);
+ String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
Map params = await WbiSign().makSign({
'mid': mid,
'ps': ps,
@@ -88,7 +90,11 @@ class MemberHttp {
'order': order,
'platform': 'web',
'web_location': 1550101,
- 'order_avoided': orderAvoided
+ 'order_avoided': orderAvoided,
+ 'dm_img_list': '[]',
+ 'dm_img_str': dmImgStr.substring(0, dmImgStr.length - 2),
+ 'dm_cover_img_str': dmCoverImgStr.substring(0, dmCoverImgStr.length - 2),
+ 'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
});
var res = await Request().get(
Api.memberArchive,
@@ -101,10 +107,13 @@ class MemberHttp {
'data': MemberArchiveDataModel.fromJson(res.data['data'])
};
} else {
+ Map errMap = {
+ -352: '风控校验失败,请检查登录状态',
+ };
return {
'status': false,
'data': [],
- 'msg': res.data['message'],
+ 'msg': errMap[res.data['code']] ?? res.data['message'],
};
}
}
@@ -123,10 +132,13 @@ class MemberHttp {
'data': DynamicsDataModel.fromJson(res.data['data']),
};
} else {
+ Map errMap = {
+ -352: '风控校验失败,请检查登录状态',
+ };
return {
'status': false,
'data': [],
- 'msg': res.data['message'],
+ 'msg': errMap[res.data['code']] ?? res.data['message'],
};
}
}
@@ -461,4 +473,41 @@ class MemberHttp {
};
}
}
+
+ // 搜索follow
+ static Future getfollowSearch({
+ required int mid,
+ required int ps,
+ required int pn,
+ required String name,
+ }) async {
+ Map data = {
+ 'vmid': mid,
+ 'pn': pn,
+ 'ps': ps,
+ 'order': 'desc',
+ 'order_type': 'attention',
+ 'gaia_source': 'main_web',
+ 'name': name,
+ 'web_location': 333.999,
+ };
+ Map params = await WbiSign().makSign(data);
+ var res = await Request().get(Api.followSearch, data: {
+ ...data,
+ 'w_rid': params['w_rid'],
+ 'wts': params['wts'],
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': FollowDataModel.fromJson(res.data['data'])
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
}
diff --git a/lib/http/msg.dart b/lib/http/msg.dart
index 7055d260..d1d31958 100644
--- a/lib/http/msg.dart
+++ b/lib/http/msg.dart
@@ -1,8 +1,9 @@
-import 'package:pilipala/http/api.dart';
-import 'package:pilipala/http/init.dart';
-import 'package:pilipala/models/msg/account.dart';
-import 'package:pilipala/models/msg/session.dart';
-import 'package:pilipala/utils/wbi_sign.dart';
+import 'dart:math';
+import '../models/msg/account.dart';
+import '../models/msg/session.dart';
+import '../utils/wbi_sign.dart';
+import 'api.dart';
+import 'init.dart';
class MsgHttp {
// 会话列表
@@ -22,14 +23,22 @@ class MsgHttp {
Map signParams = await WbiSign().makSign(params);
var res = await Request().get(Api.sessionList, data: signParams);
if (res.data['code'] == 0) {
- return {
- 'status': true,
- 'data': SessionDataModel.fromJson(res.data['data']),
- };
+ try {
+ return {
+ 'status': true,
+ 'data': SessionDataModel.fromJson(res.data['data']),
+ };
+ } catch (err) {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': err.toString(),
+ };
+ }
} else {
return {
'status': false,
- 'date': [],
+ 'data': [],
'msg': res.data['message'],
};
}
@@ -42,12 +51,16 @@ class MsgHttp {
'mobi_app': 'web',
});
if (res.data['code'] == 0) {
- return {
- 'status': true,
- 'data': res.data['data']
- .map((e) => AccountListModel.fromJson(e))
- .toList(),
- };
+ try {
+ return {
+ 'status': true,
+ 'data': res.data['data']
+ .map((e) => AccountListModel.fromJson(e))
+ .toList(),
+ };
+ } catch (err) {
+ print('err🔟: $err');
+ }
} else {
return {
'status': false,
@@ -86,4 +99,125 @@ class MsgHttp {
};
}
}
+
+ // 消息标记已读
+ static Future ackSessionMsg({
+ int? talkerId,
+ int? ackSeqno,
+ }) async {
+ String csrf = await Request.getCsrf();
+ Map params = await WbiSign().makSign({
+ 'talker_id': talkerId,
+ 'session_type': 1,
+ 'ack_seqno': ackSeqno,
+ 'build': 0,
+ 'mobi_app': 'web',
+ 'csrf_token': csrf,
+ 'csrf': csrf
+ });
+ var res = await Request().get(Api.ackSessionMsg, data: params);
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': res.data['data'],
+ };
+ } else {
+ return {
+ 'status': false,
+ 'date': [],
+ 'msg': "message: ${res.data['message']},"
+ " msg: ${res.data['msg']},"
+ " code: ${res.data['code']}",
+ };
+ }
+ }
+
+ // 发送私信
+ static Future sendMsg({
+ int? senderUid,
+ int? receiverId,
+ int? receiverType,
+ int? msgType,
+ dynamic content,
+ }) async {
+ String csrf = await Request.getCsrf();
+ Map params = await WbiSign().makSign({
+ 'msg[sender_uid]': senderUid,
+ 'msg[receiver_id]': receiverId,
+ 'msg[receiver_type]': receiverType ?? 1,
+ 'msg[msg_type]': msgType ?? 1,
+ 'msg[msg_status]': 0,
+ 'msg[dev_id]': getDevId(),
+ 'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
+ 'msg[new_face_version]': 0,
+ 'msg[content]': content,
+ 'from_firework': 0,
+ 'build': 0,
+ 'mobi_app': 'web',
+ 'csrf_token': csrf,
+ 'csrf': csrf,
+ });
+ var res =
+ await Request().post(Api.sendMsg, queryParameters: {
+ ...params,
+ 'csrf_token': csrf,
+ 'csrf': csrf,
+ }, data: {
+ 'w_sender_uid': params['msg[sender_uid]'],
+ 'w_receiver_id': params['msg[receiver_id]'],
+ 'w_dev_id': params['msg[dev_id]'],
+ 'w_rid': params['w_rid'],
+ 'wts': params['wts'],
+ 'csrf_token': csrf,
+ 'csrf': csrf,
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': res.data['data'],
+ };
+ } else {
+ return {
+ 'status': false,
+ 'date': [],
+ 'msg': "message: ${res.data['message']},"
+ " msg: ${res.data['msg']},"
+ " code: ${res.data['code']}",
+ };
+ }
+ }
+
+ static String getDevId() {
+ final List b = [
+ '0',
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ 'A',
+ 'B',
+ 'C',
+ 'D',
+ 'E',
+ 'F'
+ ];
+ final List s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split('');
+ for (int i = 0; i < s.length; i++) {
+ if ('-' == s[i] || '4' == s[i]) {
+ continue;
+ }
+ final int randomInt = Random().nextInt(16);
+ if ('x' == s[i]) {
+ s[i] = b[randomInt];
+ } else {
+ s[i] = b[3 & randomInt | 8];
+ }
+ }
+ return s.join();
+ }
}
diff --git a/lib/http/reply.dart b/lib/http/reply.dart
index 790a017f..f080ed51 100644
--- a/lib/http/reply.dart
+++ b/lib/http/reply.dart
@@ -1,6 +1,7 @@
-import 'package:pilipala/http/api.dart';
-import 'package:pilipala/http/init.dart';
-import 'package:pilipala/models/video/reply/data.dart';
+import '../models/video/reply/data.dart';
+import '../models/video/reply/emote.dart';
+import 'api.dart';
+import 'init.dart';
class ReplyHttp {
static Future replyList({
@@ -100,4 +101,23 @@ class ReplyHttp {
};
}
}
+
+ static Future getEmoteList({String? business}) async {
+ var res = await Request().get(Api.emojiList, data: {
+ 'business': business ?? 'reply',
+ 'web_location': '333.1245',
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': EmoteModelData.fromJson(res.data['data']),
+ };
+ } else {
+ return {
+ 'status': false,
+ 'date': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
}
diff --git a/lib/http/search.dart b/lib/http/search.dart
index b94ace2c..18481ea8 100644
--- a/lib/http/search.dart
+++ b/lib/http/search.dart
@@ -1,13 +1,12 @@
import 'dart:convert';
-
import 'package:hive/hive.dart';
-import 'package:pilipala/http/index.dart';
-import 'package:pilipala/models/bangumi/info.dart';
-import 'package:pilipala/models/common/search_type.dart';
-import 'package:pilipala/models/search/hot.dart';
-import 'package:pilipala/models/search/result.dart';
-import 'package:pilipala/models/search/suggest.dart';
-import 'package:pilipala/utils/storage.dart';
+import '../models/bangumi/info.dart';
+import '../models/common/search_type.dart';
+import '../models/search/hot.dart';
+import '../models/search/result.dart';
+import '../models/search/suggest.dart';
+import '../utils/storage.dart';
+import 'index.dart';
class SearchHttp {
static Box setting = GStrorage.setting;
@@ -37,7 +36,7 @@ class SearchHttp {
// 获取搜索建议
static Future searchSuggest({required term}) async {
- var res = await Request().get(Api.serachSuggest,
+ var res = await Request().get(Api.searchSuggest,
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
if (res.data is String) {
Map resultMap = json.decode(res.data);
@@ -129,25 +128,28 @@ class SearchHttp {
}
}
- static Future ab2c({int? aid, String? bvid}) async {
+ static Future ab2c({int? aid, String? bvid}) async {
Map data = {};
if (aid != null) {
data['aid'] = aid;
} else if (bvid != null) {
data['bvid'] = bvid;
}
- var res = await Request().get(Api.ab2c, data: {...data});
+ final dynamic res =
+ await Request().get(Api.ab2c, data: {...data});
return res.data['data'].first['cid'];
}
- static Future bangumiInfo({int? seasonId, int? epId}) async {
- Map data = {};
+ static Future> bangumiInfo(
+ {int? seasonId, int? epId}) async {
+ final Map data = {};
if (seasonId != null) {
data['season_id'] = seasonId;
} else if (epId != null) {
data['ep_id'] = epId;
}
- var res = await Request().get(Api.bangumiInfo, data: {...data});
+ final dynamic res =
+ await Request().get(Api.bangumiInfo, data: {...data});
if (res.data['code'] == 0) {
return {
'status': true,
diff --git a/lib/http/user.dart b/lib/http/user.dart
index 45da72e4..bae61720 100644
--- a/lib/http/user.dart
+++ b/lib/http/user.dart
@@ -1,14 +1,15 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/http/api.dart';
-import 'package:pilipala/http/init.dart';
-import 'package:pilipala/models/model_hot_video_item.dart';
-import 'package:pilipala/models/user/fav_detail.dart';
-import 'package:pilipala/models/user/fav_folder.dart';
-import 'package:pilipala/models/user/history.dart';
-import 'package:pilipala/models/user/info.dart';
-import 'package:pilipala/models/user/stat.dart';
-import 'package:pilipala/utils/wbi_sign.dart';
+import '../common/constants.dart';
+import '../models/model_hot_video_item.dart';
+import '../models/user/fav_detail.dart';
+import '../models/user/fav_folder.dart';
+import '../models/user/history.dart';
+import '../models/user/info.dart';
+import '../models/user/stat.dart';
+import '../models/user/sub_detail.dart';
+import '../models/user/sub_folder.dart';
+import 'api.dart';
+import 'init.dart';
class UserHttp {
static Future userStat({required int mid}) async {
@@ -306,4 +307,63 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']};
}
}
+
+ // 我的订阅
+ static Future userSubFolder({
+ required int mid,
+ required int pn,
+ required int ps,
+ }) async {
+ var res = await Request().get(Api.userSubFolder, data: {
+ 'up_mid': mid,
+ 'ps': ps,
+ 'pn': pn,
+ 'platform': 'web',
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': SubFolderModelData.fromJson(res.data['data'])
+ };
+ } else {
+ return {'status': false, 'msg': res.data['message']};
+ }
+ }
+
+ static Future userSubFolderDetail({
+ required int seasonId,
+ required int pn,
+ required int ps,
+ }) async {
+ var res = await Request().get(Api.userSubFolderDetail, data: {
+ 'season_id': seasonId,
+ 'ps': ps,
+ 'pn': pn,
+ });
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': SubDetailModelData.fromJson(res.data['data'])
+ };
+ } else {
+ return {'status': false, 'msg': res.data['message']};
+ }
+ }
+
+ // 取消订阅
+ 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']};
+ }
+ }
}
diff --git a/lib/http/video.dart b/lib/http/video.dart
index 9429a04b..d43656b2 100644
--- a/lib/http/video.dart
+++ b/lib/http/video.dart
@@ -1,19 +1,21 @@
import 'dart:developer';
-
import 'package:hive/hive.dart';
-import 'package:pilipala/common/constants.dart';
-import 'package:pilipala/http/api.dart';
-import 'package:pilipala/http/init.dart';
-import 'package:pilipala/models/common/reply_type.dart';
-import 'package:pilipala/models/home/rcmd/result.dart';
-import 'package:pilipala/models/model_hot_video_item.dart';
-import 'package:pilipala/models/model_rec_video_item.dart';
-import 'package:pilipala/models/user/fav_folder.dart';
-import 'package:pilipala/models/video/ai.dart';
-import 'package:pilipala/models/video/play/url.dart';
-import 'package:pilipala/models/video_detail_res.dart';
-import 'package:pilipala/utils/storage.dart';
-import 'package:pilipala/utils/wbi_sign.dart';
+import '../common/constants.dart';
+import '../models/common/reply_type.dart';
+import '../models/home/rcmd/result.dart';
+import '../models/model_hot_video_item.dart';
+import '../models/model_rec_video_item.dart';
+import '../models/user/fav_folder.dart';
+import '../models/video/ai.dart';
+import '../models/video/play/url.dart';
+import '../models/video/subTitile/result.dart';
+import '../models/video_detail_res.dart';
+import '../utils/recommend_filter.dart';
+import '../utils/storage.dart';
+import '../utils/subtitle.dart';
+import '../utils/wbi_sign.dart';
+import 'api.dart';
+import 'init.dart';
/// res.data['code'] == 0 请求正常返回结果
/// res.data['data'] 为结果
@@ -30,30 +32,44 @@ class VideoHttp {
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
try {
var res = await Request().get(
- Api.recommendList,
+ Api.recommendListWeb,
data: {
'version': 1,
- 'feed_version': 'V3',
+ 'feed_version': 'V8',
+ 'homepage_ver': 1,
'ps': ps,
'fresh_idx': freshIdx,
- 'fresh_type': 999999
+ 'brush': freshIdx,
+ 'fresh_type': 4
},
);
if (res.data['code'] == 0) {
List list = [];
+ List blackMidsList =
+ setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
for (var i in res.data['data']['item']) {
- list.add(RecVideoItemModel.fromJson(i));
+ //过滤掉live与ad,以及拉黑用户
+ if (i['goto'] == 'av' &&
+ (i['owner'] != null &&
+ !blackMidsList.contains(i['owner']['mid']))) {
+ RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
+ if (!RecommendFilter.filter(videoItem)) {
+ list.add(videoItem);
+ }
+ }
}
return {'status': true, 'data': list};
} else {
- return {'status': false, 'data': [], 'msg': ''};
+ return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err.toString()};
}
}
- static Future rcmdVideoListApp({int? ps, required int freshIdx}) async {
+ // 添加额外的loginState变量模拟未登录状态
+ static Future rcmdVideoListApp(
+ {bool loginStatus = true, required int freshIdx}) async {
try {
var res = await Request().get(
Api.recommendListApp,
@@ -66,9 +82,11 @@ class VideoHttp {
'device_name': 'vivo',
'pull': freshIdx == 0 ? 'true' : 'false',
'appkey': Constants.appKey,
- 'access_key': localCache
- .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
- ''
+ 'access_key': loginStatus
+ ? (localCache.get(LocalCacheKey.accessKey,
+ defaultValue: {})['value'] ??
+ '')
+ : ''
},
);
if (res.data['code'] == 0) {
@@ -81,12 +99,15 @@ class VideoHttp {
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
(i['args'] != null &&
!blackMidsList.contains(i['args']['up_mid']))) {
- list.add(RecVideoItemAppModel.fromJson(i));
+ RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
+ if (!RecommendFilter.filter(videoItem)) {
+ list.add(videoItem);
+ }
}
}
return {'status': true, 'data': list};
} else {
- return {'status': false, 'data': [], 'msg': ''};
+ return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err.toString()};
@@ -111,7 +132,7 @@ class VideoHttp {
}
return {'status': true, 'data': list};
} else {
- return {'status': false, 'data': []};
+ return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err};
@@ -122,27 +143,34 @@ class VideoHttp {
static Future videoUrl(
{int? avid, String? bvid, required int cid, int? qn}) async {
Map data = {
- // 'avid': avid,
- 'bvid': bvid,
'cid': cid,
- // 'qn': qn ?? 80,
+ 'qn': qn ?? 80,
// 获取所有格式的视频
'fnval': 4048,
- // 'fnver': '',
- 'fourk': 1,
- // 'session': '',
- // 'otype': '',
- // 'type': '',
- // 'platform': '',
- // 'high_quality': ''
};
+ if (avid != null) {
+ data['avid'] = avid;
+ }
+ if (bvid != null) {
+ data['bvid'] = bvid;
+ }
+
// 免登录查看1080p
if (userInfoCache.get('userInfoCache') == null &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
+
+ Map params = await WbiSign().makSign({
+ ...data,
+ 'fourk': 1,
+ 'voice_balance': 1,
+ 'gaia_source': 'pre-load',
+ 'web_location': 1550101,
+ });
+
try {
- var res = await Request().get(Api.videoUrl, data: data);
+ var res = await Request().get(Api.videoUrl, data: params);
if (res.data['code'] == 0) {
return {
'status': true,
@@ -190,7 +218,10 @@ class VideoHttp {
if (res.data['code'] == 0) {
List list = [];
for (var i in res.data['data']) {
- list.add(HotVideoItemModel.fromJson(i));
+ HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i);
+ if (!RecommendFilter.filter(videoItem, relatedVideos: true)) {
+ list.add(videoItem);
+ }
}
return {'status': true, 'data': list};
} else {
@@ -211,10 +242,11 @@ class VideoHttp {
// 获取投币状态
static Future hasCoinVideo({required String bvid}) async {
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
+ print('res: $res');
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
- return {'status': true, 'data': []};
+ return {'status': false, 'data': []};
}
}
@@ -292,7 +324,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
- return {'status': false, 'data': []};
+ return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
@@ -348,7 +380,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
- return {'status': true, 'data': []};
+ return {'status': false, 'data': []};
}
}
@@ -364,7 +396,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
- return {'status': true, 'data': []};
+ return {'status': false, 'data': []};
}
}
@@ -420,6 +452,8 @@ class VideoHttp {
});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
+ } else {
+ return {'status': false, 'data': null, 'msg': res.data['message']};
}
}
@@ -434,11 +468,63 @@ class VideoHttp {
'up_mid': upMid,
});
var res = await Request().get(Api.aiConclusion, data: params);
- if (res.data['code'] == 0) {
+ if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
return {
'status': true,
'data': AiConclusionModel.fromJson(res.data['data']),
};
+ } else {
+ return {'status': false, 'data': []};
}
}
+
+ 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 {
+ try {
+ var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
+ var res = await Request().get(rankApi);
+ if (res.data['code'] == 0) {
+ List list = [];
+ List blackMidsList =
+ setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
+ for (var i in res.data['data']['list']) {
+ if (!blackMidsList.contains(i['owner']['mid'])) {
+ list.add(HotVideoItemModel.fromJson(i));
+ }
+ }
+ return {'status': true, 'data': list};
+ } else {
+ return {'status': false, 'data': [], 'msg': res.data['message']};
+ }
+ } catch (err) {
+ return {'status': false, 'data': [], 'msg': err};
+ }
+ }
+
+ // 获取字幕内容
+ static Future> 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};
+ }
}
diff --git a/lib/main.dart b/lib/main.dart
index 20a5b569..7fdaeeb0 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -16,11 +16,15 @@ import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/router/app_pages.dart';
import 'package:pilipala/pages/main/view.dart';
+import 'package:pilipala/services/disable_battery_opt.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
+import 'package:pilipala/utils/recommend_filter.dart';
+import 'package:catcher_2/catcher_2.dart';
+import './services/loggeer.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -30,7 +34,36 @@ void main() async {
.then((_) async {
await GStrorage.init();
await setupServiceLocator();
- runApp(const MyApp());
+ clearLogs();
+ Request();
+ await Request.setCookie();
+ RecommendFilter();
+
+ // 异常捕获 logo记录
+ final Catcher2Options debugConfig = Catcher2Options(
+ SilentReportMode(),
+ [
+ FileHandler(await getLogsPath()),
+ ConsoleHandler(
+ enableDeviceParameters: false,
+ enableApplicationParameters: false,
+ )
+ ],
+ );
+
+ final Catcher2Options releaseConfig = Catcher2Options(
+ SilentReportMode(),
+ [FileHandler(await getLogsPath())],
+ );
+
+ Catcher2(
+ debugConfig: debugConfig,
+ releaseConfig: releaseConfig,
+ runAppFunction: () {
+ runApp(const MyApp());
+ },
+ );
+
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
@@ -38,10 +71,9 @@ void main() async {
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
- await Request.setCookie();
Data.init();
- GStrorage.lazyInit();
PiliSchame.init();
+ DisableBatteryOpt();
});
}
@@ -112,6 +144,13 @@ class MyApp extends StatelessWidget {
? darkColorScheme
: lightColorScheme,
useMaterial3: true,
+ snackBarTheme: SnackBarThemeData(
+ actionTextColor: lightColorScheme.primary,
+ backgroundColor: lightColorScheme.secondaryContainer,
+ closeIconColor: lightColorScheme.secondary,
+ contentTextStyle: TextStyle(color: lightColorScheme.secondary),
+ elevation: 20,
+ ),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: ZoomPageTransitionsBuilder(
@@ -126,6 +165,13 @@ class MyApp extends StatelessWidget {
? lightColorScheme
: darkColorScheme,
useMaterial3: true,
+ snackBarTheme: SnackBarThemeData(
+ actionTextColor: darkColorScheme.primary,
+ backgroundColor: darkColorScheme.secondaryContainer,
+ closeIconColor: darkColorScheme.secondary,
+ contentTextStyle: TextStyle(color: darkColorScheme.secondary),
+ elevation: 20,
+ ),
),
localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate,
@@ -141,9 +187,8 @@ class MyApp extends StatelessWidget {
return FlutterSmartDialog(
toastBuilder: (String msg) => CustomToast(msg: msg),
child: MediaQuery(
- data: MediaQuery.of(context).copyWith(
- textScaleFactor:
- MediaQuery.of(context).textScaleFactor * textScale),
+ data: MediaQuery.of(context)
+ .copyWith(textScaler: TextScaler.linear(textScale)),
child: child!,
),
);
diff --git a/lib/models/common/dynamic_badge_mode.dart b/lib/models/common/dynamic_badge_mode.dart
new file mode 100644
index 00000000..2609c5e2
--- /dev/null
+++ b/lib/models/common/dynamic_badge_mode.dart
@@ -0,0 +1,9 @@
+enum DynamicBadgeMode { hidden, point, number }
+
+extension DynamicBadgeModeDesc on DynamicBadgeMode {
+ String get description => ['隐藏', '红点', '数字'][index];
+}
+
+extension DynamicBadgeModeCode on DynamicBadgeMode {
+ int get code => [0, 1, 2][index];
+}
diff --git a/lib/models/common/dynamics_type.dart b/lib/models/common/dynamics_type.dart
index 337f6aec..f4e20a4b 100644
--- a/lib/models/common/dynamics_type.dart
+++ b/lib/models/common/dynamics_type.dart
@@ -7,5 +7,5 @@ enum DynamicsType {
extension BusinessTypeExtension on DynamicsType {
String get values => ['all', 'video', 'pgc', 'article'][index];
- String get labels => ['全部', '视频', '追番', '专栏'][index];
+ String get labels => ['全部', '投稿', '番剧', '专栏'][index];
}
diff --git a/lib/models/common/gesture_mode.dart b/lib/models/common/gesture_mode.dart
new file mode 100644
index 00000000..1149ae12
--- /dev/null
+++ b/lib/models/common/gesture_mode.dart
@@ -0,0 +1,12 @@
+enum FullScreenGestureMode {
+ /// 从上滑到下
+ fromToptoBottom,
+
+ /// 从下滑到上
+ fromBottomtoTop,
+}
+
+extension FullScreenGestureModeExtension on FullScreenGestureMode {
+ String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
+ String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
+}
diff --git a/lib/models/common/index.dart b/lib/models/common/index.dart
new file mode 100644
index 00000000..89a05076
--- /dev/null
+++ b/lib/models/common/index.dart
@@ -0,0 +1,4 @@
+library commonn_model;
+
+export './business_type.dart';
+export './gesture_mode.dart';
diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart
new file mode 100644
index 00000000..9ebe8e6f
--- /dev/null
+++ b/lib/models/common/nav_bar_config.dart
@@ -0,0 +1,56 @@
+import 'package:flutter/material.dart';
+
+List defaultNavigationBars = [
+ {
+ 'id': 0,
+ 'icon': const Icon(
+ Icons.home_outlined,
+ size: 21,
+ ),
+ 'selectIcon': const Icon(
+ Icons.home,
+ size: 21,
+ ),
+ 'label': "首页",
+ 'count': 0,
+ },
+ {
+ 'id': 1,
+ 'icon': const Icon(
+ Icons.trending_up,
+ size: 21,
+ ),
+ 'selectIcon': const Icon(
+ Icons.trending_up_outlined,
+ size: 21,
+ ),
+ 'label': "排行榜",
+ 'count': 0,
+ },
+ {
+ 'id': 2,
+ 'icon': const Icon(
+ Icons.motion_photos_on_outlined,
+ size: 21,
+ ),
+ 'selectIcon': const Icon(
+ Icons.motion_photos_on,
+ size: 21,
+ ),
+ 'label': "动态",
+ 'count': 0,
+ },
+ {
+ 'id': 3,
+ 'icon': const Icon(
+ Icons.video_collection_outlined,
+ size: 20,
+ ),
+ 'selectIcon': const Icon(
+ Icons.video_collection,
+ size: 21,
+ ),
+ 'label': "媒体库",
+ 'count': 0,
+ }
+];
diff --git a/lib/models/common/rank_type.dart b/lib/models/common/rank_type.dart
new file mode 100644
index 00000000..2ce6d3b5
--- /dev/null
+++ b/lib/models/common/rank_type.dart
@@ -0,0 +1,240 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:pilipala/pages/rank/zone/index.dart';
+
+enum RandType {
+ all,
+ creation,
+ animation,
+ music,
+ dance,
+ game,
+ knowledge,
+ technology,
+ sport,
+ car,
+ life,
+ food,
+ animal,
+ madness,
+ fashion,
+ entertainment,
+ film,
+ origin,
+ rookie
+}
+
+extension RankTypeDesc on RandType {
+ String get description => [
+ '全站',
+ '国创相关',
+ '动画',
+ '音乐',
+ '舞蹈',
+ '游戏',
+ '知识',
+ '科技',
+ '运动',
+ '汽车',
+ '生活',
+ '美食',
+ '动物圈',
+ '鬼畜',
+ '时尚',
+ '娱乐',
+ '影视'
+ ][index];
+
+ String get id => [
+ 'all',
+ 'creation',
+ 'animation',
+ 'music',
+ 'dance',
+ 'game',
+ 'knowledge',
+ 'technology',
+ 'sport',
+ 'car',
+ 'life',
+ 'food',
+ 'animal',
+ 'madness',
+ 'fashion',
+ 'entertainment',
+ 'film'
+ ][index];
+}
+
+List tabsConfig = [
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '全站',
+ 'type': RandType.all,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 0),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '国创相关',
+ 'type': RandType.creation,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 168),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '动画',
+ 'type': RandType.animation,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 1),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '音乐',
+ 'type': RandType.music,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 3),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '舞蹈',
+ 'type': RandType.dance,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 129),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '游戏',
+ 'type': RandType.game,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 4),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '知识',
+ 'type': RandType.knowledge,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 36),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '科技',
+ 'type': RandType.technology,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 188),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '运动',
+ 'type': RandType.sport,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 234),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '汽车',
+ 'type': RandType.car,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 223),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '生活',
+ 'type': RandType.life,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 160),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '美食',
+ 'type': RandType.food,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 211),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '动物圈',
+ 'type': RandType.animal,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 217),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '鬼畜',
+ 'type': RandType.madness,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 119),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '时尚',
+ 'type': RandType.fashion,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 155),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '娱乐',
+ 'type': RandType.entertainment,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 5),
+ },
+ {
+ 'icon': const Icon(
+ Icons.live_tv_outlined,
+ size: 15,
+ ),
+ 'label': '影视',
+ 'type': RandType.film,
+ 'ctr': Get.put,
+ 'page': const ZonePage(rid: 181),
+ }
+];
diff --git a/lib/models/common/rcmd_type.dart b/lib/models/common/rcmd_type.dart
new file mode 100644
index 00000000..2dfdad1c
--- /dev/null
+++ b/lib/models/common/rcmd_type.dart
@@ -0,0 +1,7 @@
+// 首页推荐类型
+enum RcmdType { web, app, notLogin }
+
+extension RcmdTypeExtension on RcmdType {
+ String get values => ['web', 'app', 'notLogin'][index];
+ String get labels => ['web端', 'app端', '游客模式'][index];
+}
diff --git a/lib/models/common/reply_sort_type.dart b/lib/models/common/reply_sort_type.dart
index 7c203c13..89da82b3 100644
--- a/lib/models/common/reply_sort_type.dart
+++ b/lib/models/common/reply_sort_type.dart
@@ -1,6 +1,6 @@
-enum ReplySortType { time, like, reply }
+enum ReplySortType { time, like }
extension ReplySortTypeExtension on ReplySortType {
- String get titles => ['最新评论', '最热评论', '回复最多'][index];
- String get labels => ['最新', '最热', '最多回复'][index];
+ String get titles => ['最新评论', '最热评论'][index];
+ String get labels => ['最新', '最热'][index];
}
diff --git a/lib/models/common/subtitle_type.dart b/lib/models/common/subtitle_type.dart
new file mode 100644
index 00000000..11716351
--- /dev/null
+++ b/lib/models/common/subtitle_type.dart
@@ -0,0 +1,47 @@
+enum SubtitleType {
+ // 中文(中国)
+ zhCN,
+ // 中文(自动翻译)
+ aizh,
+ // 英语(自动生成)
+ aien,
+}
+
+extension SubtitleTypeExtension on SubtitleType {
+ String get description {
+ switch (this) {
+ case SubtitleType.zhCN:
+ return '中文(中国)';
+ case SubtitleType.aizh:
+ return '中文(自动翻译)';
+ case SubtitleType.aien:
+ 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';
+ }
+ }
+}
+
+extension SubtitleCodeExtension on SubtitleType {
+ int get code {
+ switch (this) {
+ case SubtitleType.zhCN:
+ return 1;
+ case SubtitleType.aizh:
+ return 2;
+ case SubtitleType.aien:
+ return 3;
+ }
+ }
+}
diff --git a/lib/models/common/tab_type.dart b/lib/models/common/tab_type.dart
index 90d19029..e530d7e5 100644
--- a/lib/models/common/tab_type.dart
+++ b/lib/models/common/tab_type.dart
@@ -9,6 +9,7 @@ enum TabType { live, rcmd, hot, bangumi }
extension TabTypeDesc on TabType {
String get description => ['直播', '推荐', '热门', '番剧'][index];
+ String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
}
List tabsConfig = [
diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart
index d8aff7b5..2f7c2d40 100644
--- a/lib/models/dynamics/result.dart
+++ b/lib/models/dynamics/result.dart
@@ -78,12 +78,14 @@ class ItemModulesModel {
this.moduleDynamic,
// this.moduleInter,
this.moduleStat,
+ this.moduleTag,
});
ModuleAuthorModel? moduleAuthor;
ModuleDynamicModel? moduleDynamic;
// ModuleInterModel? moduleInter;
ModuleStatModel? moduleStat;
+ Map? moduleTag;
ItemModulesModel.fromJson(Map json) {
moduleAuthor = json['module_author'] != null
@@ -96,6 +98,7 @@ class ItemModulesModel {
moduleStat = json['module_stat'] != null
? ModuleStatModel.fromJson(json['module_stat'])
: null;
+ moduleTag = json['module_tag'];
}
}
diff --git a/lib/models/dynamics/up.dart b/lib/models/dynamics/up.dart
index cfd1fa7d..9bb82f70 100644
--- a/lib/models/dynamics/up.dart
+++ b/lib/models/dynamics/up.dart
@@ -2,18 +2,28 @@ class FollowUpModel {
FollowUpModel({
this.liveUsers,
this.upList,
+ this.liveList,
+ this.myInfo,
});
LiveUsers? liveUsers;
List? upList;
+ List? liveList;
+ MyInfo? myInfo;
FollowUpModel.fromJson(Map json) {
liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users'])
: null;
+ liveList = json['live_users'] != null
+ ? json['live_users']['items']
+ .map((e) => LiveUserItem.fromJson(e))
+ .toList()
+ : [];
upList = json['up_list'] != null
? json['up_list'].map((e) => UpItem.fromJson(e)).toList()
: [];
+ myInfo = json['my_info'] != null ? MyInfo.fromJson(json['my_info']) : null;
}
}
@@ -93,3 +103,21 @@ class UpItem {
uname = json['uname'];
}
}
+
+class MyInfo {
+ MyInfo({
+ this.face,
+ this.mid,
+ this.name,
+ });
+
+ String? face;
+ int? mid;
+ String? name;
+
+ MyInfo.fromJson(Map json) {
+ face = json['face'];
+ mid = json['mid'];
+ name = json['name'];
+ }
+}
diff --git a/lib/models/github/latest.dart b/lib/models/github/latest.dart
index 8730a4ba..c4b88b63 100644
--- a/lib/models/github/latest.dart
+++ b/lib/models/github/latest.dart
@@ -17,8 +17,9 @@ class LatestDataModel {
url = json['url'];
tagName = json['tag_name'];
createdAt = json['created_at'];
- assets =
- json['assets'].map((e) => AssetItem.fromJson(e)).toList();
+ assets = json['assets'] != null
+ ? json['assets'].map((e) => AssetItem.fromJson(e)).toList()
+ : [];
body = json['body'];
}
}
diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart
index a2a8006d..78747d1a 100644
--- a/lib/models/home/rcmd/result.dart
+++ b/lib/models/home/rcmd/result.dart
@@ -1,8 +1,5 @@
-import 'package:hive/hive.dart';
+import 'package:pilipala/utils/id_utils.dart';
-part 'result.g.dart';
-
-@HiveType(typeId: 0)
class RecVideoItemAppModel {
RecVideoItemAppModel({
this.id,
@@ -27,47 +24,27 @@ class RecVideoItemAppModel {
this.adInfo,
});
- @HiveField(0)
int? id;
- @HiveField(1)
int? aid;
- @HiveField(2)
String? bvid;
- @HiveField(3)
int? cid;
- @HiveField(4)
String? pic;
- @HiveField(5)
RcmdStat? stat;
- @HiveField(6)
- String? duration;
- @HiveField(7)
+ int? duration;
String? title;
- @HiveField(8)
int? isFollowed;
- @HiveField(9)
RcmdOwner? owner;
- @HiveField(10)
RcmdReason? rcmdReason;
- @HiveField(11)
String? goto;
- @HiveField(12)
int? param;
- @HiveField(13)
String? uri;
- @HiveField(14)
String? talkBack;
// 番剧
- @HiveField(15)
String? bangumiView;
- @HiveField(16)
String? bangumiFollow;
- @HiveField(17)
String? bangumiBadge;
- @HiveField(18)
String? cardType;
- @HiveField(19)
Map? adInfo;
RecVideoItemAppModel.fromJson(Map json) {
@@ -75,17 +52,32 @@ class RecVideoItemAppModel {
? json['player_args']['aid']
: int.parse(json['param'] ?? '-1');
aid = json['player_args'] != null ? json['player_args']['aid'] : -1;
- bvid = null;
+ bvid = json['player_args'] != null
+ ? IdUtils.av2bv(json['player_args']['aid'])
+ : '';
cid = json['player_args'] != null ? json['player_args']['cid'] : -1;
pic = json['cover'];
stat = RcmdStat.fromJson(json);
- duration = json['cover_right_text'];
+ // 改用player_args中的duration作为原始数据(秒数)
+ duration =
+ json['player_args'] != null ? json['player_args']['duration'] : -1;
+ //duration = json['cover_right_text'];
title = json['title'];
- isFollowed = 0;
owner = RcmdOwner.fromJson(json);
rcmdReason = json['rcmd_reason_style'] != null
? RcmdReason.fromJson(json['rcmd_reason_style'])
: null;
+ // 由于app端api并不会直接返回与owner的关注状态
+ // 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效
+ isFollowed = rcmdReason != null &&
+ rcmdReason!.content != null &&
+ rcmdReason!.content!.contains('关注')
+ ? 1
+ : 0;
+ // 如果是,就无需再显示推荐原因,交由view统一处理即可
+ if (isFollowed == 1) {
+ rcmdReason = null;
+ }
goto = json['goto'];
param = int.parse(json['param']);
uri = json['uri'];
@@ -102,18 +94,14 @@ class RecVideoItemAppModel {
}
}
-@HiveType(typeId: 1)
class RcmdStat {
RcmdStat({
this.view,
this.like,
this.danmu,
});
- @HiveField(0)
String? view;
- @HiveField(1)
String? like;
- @HiveField(2)
String? danmu;
RcmdStat.fromJson(Map json) {
@@ -122,13 +110,10 @@ class RcmdStat {
}
}
-@HiveType(typeId: 2)
class RcmdOwner {
RcmdOwner({this.name, this.mid});
- @HiveField(0)
String? name;
- @HiveField(1)
int? mid;
RcmdOwner.fromJson(Map json) {
@@ -141,13 +126,11 @@ class RcmdOwner {
}
}
-@HiveType(typeId: 8)
class RcmdReason {
RcmdReason({
this.content,
});
- @HiveField(0)
String? content;
RcmdReason.fromJson(Map json) {
diff --git a/lib/models/home/rcmd/result.g.dart b/lib/models/home/rcmd/result.g.dart
deleted file mode 100644
index 43bf4bcf..00000000
--- a/lib/models/home/rcmd/result.g.dart
+++ /dev/null
@@ -1,209 +0,0 @@
-// GENERATED CODE - DO NOT MODIFY BY HAND
-
-part of 'result.dart';
-
-// **************************************************************************
-// TypeAdapterGenerator
-// **************************************************************************
-
-class RecVideoItemAppModelAdapter extends TypeAdapter {
- @override
- final int typeId = 0;
-
- @override
- RecVideoItemAppModel read(BinaryReader reader) {
- final numOfFields = reader.readByte();
- final fields = {
- for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
- };
- return RecVideoItemAppModel(
- id: fields[0] as int?,
- aid: fields[1] as int?,
- bvid: fields[2] as String?,
- cid: fields[3] as int?,
- pic: fields[4] as String?,
- stat: fields[5] as RcmdStat?,
- duration: fields[6] as String?,
- title: fields[7] as String?,
- isFollowed: fields[8] as int?,
- owner: fields[9] as RcmdOwner?,
- rcmdReason: fields[10] as RcmdReason?,
- goto: fields[11] as String?,
- param: fields[12] as int?,
- uri: fields[13] as String?,
- talkBack: fields[14] as String?,
- bangumiView: fields[15] as String?,
- bangumiFollow: fields[16] as String?,
- bangumiBadge: fields[17] as String?,
- cardType: fields[18] as String?,
- adInfo: (fields[19] as Map?)?.cast(),
- );
- }
-
- @override
- void write(BinaryWriter writer, RecVideoItemAppModel obj) {
- writer
- ..writeByte(20)
- ..writeByte(0)
- ..write(obj.id)
- ..writeByte(1)
- ..write(obj.aid)
- ..writeByte(2)
- ..write(obj.bvid)
- ..writeByte(3)
- ..write(obj.cid)
- ..writeByte(4)
- ..write(obj.pic)
- ..writeByte(5)
- ..write(obj.stat)
- ..writeByte(6)
- ..write(obj.duration)
- ..writeByte(7)
- ..write(obj.title)
- ..writeByte(8)
- ..write(obj.isFollowed)
- ..writeByte(9)
- ..write(obj.owner)
- ..writeByte(10)
- ..write(obj.rcmdReason)
- ..writeByte(11)
- ..write(obj.goto)
- ..writeByte(12)
- ..write(obj.param)
- ..writeByte(13)
- ..write(obj.uri)
- ..writeByte(14)
- ..write(obj.talkBack)
- ..writeByte(15)
- ..write(obj.bangumiView)
- ..writeByte(16)
- ..write(obj.bangumiFollow)
- ..writeByte(17)
- ..write(obj.bangumiBadge)
- ..writeByte(18)
- ..write(obj.cardType)
- ..writeByte(19)
- ..write(obj.adInfo);
- }
-
- @override
- int get hashCode => typeId.hashCode;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is RecVideoItemAppModelAdapter &&
- runtimeType == other.runtimeType &&
- typeId == other.typeId;
-}
-
-class RcmdStatAdapter extends TypeAdapter {
- @override
- final int typeId = 1;
-
- @override
- RcmdStat read(BinaryReader reader) {
- final numOfFields = reader.readByte();
- final fields = {
- for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
- };
- return RcmdStat(
- view: fields[0] as String?,
- like: fields[1] as String?,
- danmu: fields[2] as String?,
- );
- }
-
- @override
- void write(BinaryWriter writer, RcmdStat obj) {
- writer
- ..writeByte(3)
- ..writeByte(0)
- ..write(obj.view)
- ..writeByte(1)
- ..write(obj.like)
- ..writeByte(2)
- ..write(obj.danmu);
- }
-
- @override
- int get hashCode => typeId.hashCode;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is RcmdStatAdapter &&
- runtimeType == other.runtimeType &&
- typeId == other.typeId;
-}
-
-class RcmdOwnerAdapter extends TypeAdapter {
- @override
- final int typeId = 2;
-
- @override
- RcmdOwner read(BinaryReader reader) {
- final numOfFields = reader.readByte();
- final fields = {
- for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
- };
- return RcmdOwner(
- name: fields[0] as String?,
- mid: fields[1] as int?,
- );
- }
-
- @override
- void write(BinaryWriter writer, RcmdOwner obj) {
- writer
- ..writeByte(2)
- ..writeByte(0)
- ..write(obj.name)
- ..writeByte(1)
- ..write(obj.mid);
- }
-
- @override
- int get hashCode => typeId.hashCode;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is RcmdOwnerAdapter &&
- runtimeType == other.runtimeType &&
- typeId == other.typeId;
-}
-
-class RcmdReasonAdapter extends TypeAdapter {
- @override
- final int typeId = 8;
-
- @override
- RcmdReason read(BinaryReader reader) {
- final numOfFields = reader.readByte();
- final fields = {
- for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
- };
- return RcmdReason(
- content: fields[0] as String?,
- );
- }
-
- @override
- void write(BinaryWriter writer, RcmdReason obj) {
- writer
- ..writeByte(1)
- ..writeByte(0)
- ..write(obj.content);
- }
-
- @override
- int get hashCode => typeId.hashCode;
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is RcmdReasonAdapter &&
- runtimeType == other.runtimeType &&
- typeId == other.typeId;
-}
diff --git a/lib/models/live/quality.dart b/lib/models/live/quality.dart
new file mode 100644
index 00000000..677d615b
--- /dev/null
+++ b/lib/models/live/quality.dart
@@ -0,0 +1,43 @@
+enum LiveQuality {
+ dolby,
+ super4K,
+ origin,
+ bluRay,
+ superHD,
+ smooth,
+ flunt,
+}
+
+extension LiveQualityCode on LiveQuality {
+ static final List _codeList = [
+ 30000,
+ 20000,
+ 10000,
+ 400,
+ 250,
+ 150,
+ 80,
+ ];
+ int get code => _codeList[index];
+
+ static LiveQuality? fromCode(int code) {
+ final index = _codeList.indexOf(code);
+ if (index != -1) {
+ return LiveQuality.values[index];
+ }
+ return null;
+ }
+}
+
+extension VideoQualityDesc on LiveQuality {
+ static final List _descList = [
+ '杜比',
+ '4K',
+ '原画',
+ '蓝光',
+ '超清',
+ '高清',
+ '流畅',
+ ];
+ get description => _descList[index];
+}
diff --git a/lib/models/live/room_info_h5.dart b/lib/models/live/room_info_h5.dart
new file mode 100644
index 00000000..a0c19621
--- /dev/null
+++ b/lib/models/live/room_info_h5.dart
@@ -0,0 +1,130 @@
+class RoomInfoH5Model {
+ RoomInfoH5Model({
+ this.roomInfo,
+ this.anchorInfo,
+ this.isRoomFeed,
+ this.watchedShow,
+ this.likeInfoV3,
+ this.blockInfo,
+ });
+
+ RoomInfo? roomInfo;
+ AnchorInfo? anchorInfo;
+ int? isRoomFeed;
+ Map? watchedShow;
+ LikeInfoV3? likeInfoV3;
+ Map? blockInfo;
+
+ RoomInfoH5Model.fromJson(Map json) {
+ roomInfo = RoomInfo.fromJson(json['room_info']);
+ anchorInfo = AnchorInfo.fromJson(json['anchor_info']);
+ isRoomFeed = json['is_room_feed'];
+ watchedShow = json['watched_show'];
+ likeInfoV3 = LikeInfoV3.fromJson(json['like_info_v3']);
+ blockInfo = json['block_info'];
+ }
+}
+
+class RoomInfo {
+ RoomInfo({
+ this.uid,
+ this.roomId,
+ this.title,
+ this.cover,
+ this.description,
+ this.liveStatus,
+ this.liveStartTime,
+ this.areaId,
+ this.areaName,
+ this.parentAreaId,
+ this.parentAreaName,
+ this.online,
+ this.background,
+ this.appBackground,
+ this.liveId,
+ });
+
+ int? uid;
+ int? roomId;
+ String? title;
+ String? cover;
+ String? description;
+ int? liveStatus;
+ int? liveStartTime;
+ int? areaId;
+ String? areaName;
+ int? parentAreaId;
+ String? parentAreaName;
+ int? online;
+ String? background;
+ String? appBackground;
+ String? liveId;
+
+ RoomInfo.fromJson(Map json) {
+ uid = json['uid'];
+ roomId = json['room_id'];
+ title = json['title'];
+ cover = json['cover'];
+ description = json['description'];
+ liveStatus = json['liveS_satus'];
+ liveStartTime = json['live_start_time'];
+ areaId = json['area_id'];
+ areaName = json['area_name'];
+ parentAreaId = json['parent_area_id'];
+ parentAreaName = json['parent_area_name'];
+ online = json['online'];
+ background = json['background'];
+ appBackground = json['app_background'];
+ liveId = json['live_id'];
+ }
+}
+
+class AnchorInfo {
+ AnchorInfo({
+ this.baseInfo,
+ this.relationInfo,
+ });
+
+ BaseInfo? baseInfo;
+ RelationInfo? relationInfo;
+
+ AnchorInfo.fromJson(Map json) {
+ baseInfo = BaseInfo.fromJson(json['base_info']);
+ relationInfo = RelationInfo.fromJson(json['relation_info']);
+ }
+}
+
+class BaseInfo {
+ BaseInfo({
+ this.uname,
+ this.face,
+ });
+
+ String? uname;
+ String? face;
+
+ BaseInfo.fromJson(Map json) {
+ uname = json['uname'];
+ face = json['face'];
+ }
+}
+
+class RelationInfo {
+ RelationInfo({this.attention});
+
+ int? attention;
+
+ RelationInfo.fromJson(Map json) {
+ attention = json['attention'];
+ }
+}
+
+class LikeInfoV3 {
+ LikeInfoV3({this.totalLikes});
+
+ int? totalLikes;
+
+ LikeInfoV3.fromJson(Map json) {
+ totalLikes = json['total_likes'];
+ }
+}
diff --git a/lib/models/member/archive.dart b/lib/models/member/archive.dart
index 5d2ea77e..d735ab7c 100644
--- a/lib/models/member/archive.dart
+++ b/lib/models/member/archive.dart
@@ -142,7 +142,7 @@ class Stat {
Stat.fromJson(Map json) {
view = json["play"];
- danmaku = json['comment'];
+ danmaku = json['video_review'];
}
}
diff --git a/lib/models/member/info.dart b/lib/models/member/info.dart
index 789131ee..83f94c54 100644
--- a/lib/models/member/info.dart
+++ b/lib/models/member/info.dart
@@ -47,18 +47,23 @@ class Vip {
this.status,
this.dueDate,
this.label,
+ this.nicknameColor,
});
int? type;
int? status;
int? dueDate;
Map? label;
+ int? nicknameColor;
Vip.fromJson(Map json) {
type = json['type'];
status = json['status'];
dueDate = json['due_date'];
label = json['label'];
+ nicknameColor = json['nickname_color'] == ''
+ ? null
+ : int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
}
}
diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart
index f8c1731b..1503f192 100644
--- a/lib/models/model_rec_video_item.dart
+++ b/lib/models/model_rec_video_item.dart
@@ -36,7 +36,7 @@ class RecVideoItemModel {
@HiveField(6)
String? title = '';
@HiveField(7)
- String? duration = '';
+ int? duration = -1;
@HiveField(8)
int? pubdate = -1;
@HiveField(9)
@@ -56,7 +56,7 @@ class RecVideoItemModel {
uri = json["uri"];
pic = json["pic"];
title = json["title"];
- duration = json["duration"].toString();
+ duration = json["duration"];
pubdate = json["pubdate"];
owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json["stat"]);
@@ -82,6 +82,7 @@ class Stat {
int? danmu;
Stat.fromJson(Map json) {
+ // 无需在model中转换以保留原始数据,在view层处理即可
view = json["view"];
like = json["like"];
danmu = json['danmaku'];
diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart
index 99f096c2..dc614354 100644
--- a/lib/models/model_rec_video_item.g.dart
+++ b/lib/models/model_rec_video_item.g.dart
@@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter {
uri: fields[4] as String?,
pic: fields[5] as String?,
title: fields[6] as String?,
- duration: fields[7] as String?,
+ duration: fields[7] as int?,
pubdate: fields[8] as int?,
owner: fields[9] as Owner?,
stat: fields[10] as Stat?,
diff --git a/lib/models/msg/session.dart b/lib/models/msg/session.dart
index 1fa05cb0..b6c1b6a6 100644
--- a/lib/models/msg/session.dart
+++ b/lib/models/msg/session.dart
@@ -8,7 +8,7 @@ class SessionDataModel {
this.hasMore,
});
- List? sessionList;
+ List? sessionList;
int? hasMore;
SessionDataModel.fromJson(Map json) {
@@ -121,35 +121,37 @@ class LastMsg {
this.msgKey,
this.msgStatus,
this.notifyCode,
- this.newFaceVersion,
+ // this.newFaceVersion,
});
int? senderIid;
int? receiverType;
int? receiverId;
int? msgType;
- Map? content;
+ dynamic content;
int? msgSeqno;
int? timestamp;
String? atUids;
int? msgKey;
int? msgStatus;
String? notifyCode;
- int? newFaceVersion;
+ // int? newFaceVersion;
LastMsg.fromJson(Map json) {
senderIid = json['sender_uid'];
receiverType = json['receiver_type'];
receiverId = json['receiver_id'];
msgType = json['msg_type'];
- content = jsonDecode(json['content']);
+ content = json['content'] != null && json['content'] != ''
+ ? jsonDecode(json['content'])
+ : '';
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];
msgKey = json['msg_key'];
msgStatus = json['msg_status'];
notifyCode = json['notify_code'];
- newFaceVersion = json['new_face_version'];
+ // newFaceVersion = json['new_face_version'];
}
}
@@ -166,7 +168,7 @@ class SessionMsgDataModel {
int? hasMore;
int? minSeqno;
int? maxSeqno;
- List? eInfos;
+ List? eInfos;
SessionMsgDataModel.fromJson(Map json) {
messages = json['messages']
@@ -214,7 +216,9 @@ class MessageItem {
receiverId = json['receiver_id'];
// 1 文本 2 图片 18 系统提示 10 系统通知 5 撤回的消息
msgType = json['msg_type'];
- content = jsonDecode(json['content']);
+ content = json['content'] != null && json['content'] != ''
+ ? jsonDecode(json['content'])
+ : '';
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];
diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart
index 3d381ed9..418fb99d 100644
--- a/lib/models/search/result.dart
+++ b/lib/models/search/result.dart
@@ -85,7 +85,9 @@ class SearchVideoItemModel {
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
title = Em.regTitle(json['title']);
description = json['description'];
- pic = 'https:${json['pic']}';
+ pic = json['pic'] != null && json['pic'].startsWith('//')
+ ? 'https:${json['pic']}'
+ : json['pic'] ?? '';
videoReview = json['video_review'];
pubdate = json['pubdate'];
senddate = json['senddate'];
@@ -435,7 +437,8 @@ class SearchArticleItemModel {
pubTime = json['pub_time'];
like = json['like'];
title = Em.regTitle(json['title']);
- subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
+ subTitle =
+ Em.decodeHtmlEntities(json['title'].replaceAll(RegExp(r'<[^>]*>'), ''));
rankOffset = json['rank_offset'];
mid = json['mid'];
imageUrls = json['image_urls'];
diff --git a/lib/models/user/fav_folder.dart b/lib/models/user/fav_folder.dart
index 6d3f9975..c45e2de9 100644
--- a/lib/models/user/fav_folder.dart
+++ b/lib/models/user/fav_folder.dart
@@ -15,7 +15,7 @@ class FavFolderData {
? json['list']
.map((e) => FavFolderItemData.fromJson(e))
.toList()
- : [FavFolderItemData()];
+ : [];
hasMore = json['has_more'];
}
}
diff --git a/lib/models/user/sub_detail.dart b/lib/models/user/sub_detail.dart
new file mode 100644
index 00000000..a1e52e55
--- /dev/null
+++ b/lib/models/user/sub_detail.dart
@@ -0,0 +1,123 @@
+class SubDetailModelData {
+ DetailInfo? info;
+ List? medias;
+
+ SubDetailModelData({this.info, this.medias});
+
+ SubDetailModelData.fromJson(Map json) {
+ info = DetailInfo.fromJson(json['info']);
+ if (json['medias'] != null) {
+ medias = [];
+ json['medias'].forEach((v) {
+ medias!.add(SubDetailMediaItem.fromJson(v));
+ });
+ }
+ }
+}
+
+class SubDetailMediaItem {
+ int? id;
+ String? title;
+ String? cover;
+ String? pic;
+ int? duration;
+ int? pubtime;
+ String? bvid;
+ Map? upper;
+ Map? cntInfo;
+ int? enableVt;
+ String? vtDisplay;
+
+ SubDetailMediaItem({
+ this.id,
+ this.title,
+ this.cover,
+ this.pic,
+ this.duration,
+ this.pubtime,
+ this.bvid,
+ this.upper,
+ this.cntInfo,
+ this.enableVt,
+ this.vtDisplay,
+ });
+
+ SubDetailMediaItem.fromJson(Map json) {
+ id = json['id'];
+ title = json['title'];
+ cover = json['cover'];
+ pic = json['cover'];
+ duration = json['duration'];
+ pubtime = json['pubtime'];
+ bvid = json['bvid'];
+ upper = json['upper'];
+ cntInfo = json['cnt_info'];
+ enableVt = json['enable_vt'];
+ vtDisplay = json['vt_display'];
+ }
+
+ Map toJson() {
+ final data = {};
+ data['id'] = id;
+ data['title'] = title;
+ data['cover'] = cover;
+ data['duration'] = duration;
+ data['pubtime'] = pubtime;
+ data['bvid'] = bvid;
+ data['upper'] = upper;
+ data['cnt_info'] = cntInfo;
+ data['enable_vt'] = enableVt;
+ data['vt_display'] = vtDisplay;
+ return data;
+ }
+}
+
+class DetailInfo {
+ int? id;
+ int? seasonType;
+ String? title;
+ String? cover;
+ Map? upper;
+ Map? cntInfo;
+ int? mediaCount;
+ String? intro;
+ int? enableVt;
+
+ DetailInfo({
+ this.id,
+ this.seasonType,
+ this.title,
+ this.cover,
+ this.upper,
+ this.cntInfo,
+ this.mediaCount,
+ this.intro,
+ this.enableVt,
+ });
+
+ DetailInfo.fromJson(Map json) {
+ id = json['id'];
+ seasonType = json['season_type'];
+ title = json['title'];
+ cover = json['cover'];
+ upper = json['upper'];
+ cntInfo = json['cnt_info'];
+ mediaCount = json['media_count'];
+ intro = json['intro'];
+ enableVt = json['enable_vt'];
+ }
+
+ Map toJson() {
+ final data = {};
+ data['id'] = id;
+ data['season_type'] = seasonType;
+ data['title'] = title;
+ data['cover'] = cover;
+ data['upper'] = upper;
+ data['cnt_info'] = cntInfo;
+ data['media_count'] = mediaCount;
+ data['intro'] = intro;
+ data['enable_vt'] = enableVt;
+ return data;
+ }
+}
diff --git a/lib/models/user/sub_folder.dart b/lib/models/user/sub_folder.dart
new file mode 100644
index 00000000..d496a1cf
--- /dev/null
+++ b/lib/models/user/sub_folder.dart
@@ -0,0 +1,111 @@
+class SubFolderModelData {
+ final int? count;
+ final List? list;
+
+ SubFolderModelData({
+ this.count,
+ this.list,
+ });
+
+ factory SubFolderModelData.fromJson(Map json) {
+ return SubFolderModelData(
+ count: json['count'],
+ list: json['list'] != null
+ ? (json['list'] as List)
+ .map((i) => SubFolderItemData.fromJson(i))
+ .toList()
+ : null,
+ );
+ }
+}
+
+class SubFolderItemData {
+ final int? id;
+ final int? fid;
+ final int? mid;
+ final int? attr;
+ final String? title;
+ final String? cover;
+ final Upper? upper;
+ final int? coverType;
+ final String? intro;
+ final int? ctime;
+ final int? mtime;
+ final int? state;
+ final int? favState;
+ final int? mediaCount;
+ final int? viewCount;
+ final int? vt;
+ final int? playSwitch;
+ final int? type;
+ final String? link;
+ final String? bvid;
+
+ SubFolderItemData({
+ this.id,
+ this.fid,
+ this.mid,
+ this.attr,
+ this.title,
+ this.cover,
+ this.upper,
+ this.coverType,
+ this.intro,
+ this.ctime,
+ this.mtime,
+ this.state,
+ this.favState,
+ this.mediaCount,
+ this.viewCount,
+ this.vt,
+ this.playSwitch,
+ this.type,
+ this.link,
+ this.bvid,
+ });
+
+ factory SubFolderItemData.fromJson(Map json) {
+ return SubFolderItemData(
+ id: json['id'],
+ fid: json['fid'],
+ mid: json['mid'],
+ attr: json['attr'],
+ title: json['title'],
+ cover: json['cover'],
+ upper: json['upper'] != null ? Upper.fromJson(json['upper']) : null,
+ coverType: json['cover_type'],
+ intro: json['intro'],
+ ctime: json['ctime'],
+ mtime: json['mtime'],
+ state: json['state'],
+ favState: json['fav_state'],
+ mediaCount: json['media_count'],
+ viewCount: json['view_count'],
+ vt: json['vt'],
+ playSwitch: json['play_switch'],
+ type: json['type'],
+ link: json['link'],
+ bvid: json['bvid'],
+ );
+ }
+}
+
+class Upper {
+ final int? mid;
+ final String? name;
+ final String? face;
+
+ Upper({
+ this.mid,
+ this.name,
+ this.face,
+ });
+
+ factory Upper.fromJson(Map json) {
+ return Upper(
+ mid: json['mid'],
+ name: json['name'],
+ face: json['face'],
+ );
+ }
+}
diff --git a/lib/models/video/play/url.dart b/lib/models/video/play/url.dart
index 4c43cb00..792cd50d 100644
--- a/lib/models/video/play/url.dart
+++ b/lib/models/video/play/url.dart
@@ -34,6 +34,7 @@ class PlayUrlModel {
String? seekParam;
String? seekType;
Dash? dash;
+ List? durl;
List? supportFormats;
// String? highFormat;
int? lastPlayTime;
@@ -52,7 +53,8 @@ class PlayUrlModel {
videoCodecid = json['video_codecid'];
seekParam = json['seek_param'];
seekType = json['seek_type'];
- dash = Dash.fromJson(json['dash']);
+ dash = json['dash'] != null ? Dash.fromJson(json['dash']) : null;
+ durl = json['durl']?.map((e) => Durl.fromJson(e)).toList();
supportFormats = json['support_formats'] != null
? json['support_formats']
.map((e) => FormatItem.fromJson(e))
@@ -250,3 +252,30 @@ class Flac {
audio = json['audio'] != null ? AudioItem.fromJson(json['audio']) : null;
}
}
+
+class Durl {
+ Durl({
+ this.order,
+ this.length,
+ this.size,
+ this.ahead,
+ this.vhead,
+ this.url,
+ });
+
+ int? order;
+ int? length;
+ int? size;
+ String? ahead;
+ String? vhead;
+ String? url;
+
+ Durl.fromJson(Map json) {
+ order = json['order'];
+ length = json['length'];
+ size = json['size'];
+ ahead = json['ahead'];
+ vhead = json['vhead'];
+ url = json['url'];
+ }
+}
diff --git a/lib/models/video/reply/content.dart b/lib/models/video/reply/content.dart
index ad1759ac..d62a4bca 100644
--- a/lib/models/video/reply/content.dart
+++ b/lib/models/video/reply/content.dart
@@ -2,24 +2,26 @@ class ReplyContent {
ReplyContent({
this.message,
this.atNameToMid, // @的用户的mid null
- this.memebers, // 被@的用户List 如果有的话 []
+ this.members, // 被@的用户List 如果有的话 []
this.emote, // 表情包 如果有的话 null
this.jumpUrl, // {}
this.pictures, // {}
this.vote,
this.richText,
this.isText,
+ this.topicsMeta,
});
String? message;
Map? atNameToMid;
- List? memebers;
+ List? members;
Map? emote;
Map? jumpUrl;
List? pictures;
Map? vote;
Map? richText;
bool? isText;
+ Map? topicsMeta;
ReplyContent.fromJson(Map json) {
message = json['message']
@@ -27,7 +29,11 @@ class ReplyContent {
.replaceAll('"', '"')
.replaceAll(''', "'");
atNameToMid = json['at_name_to_mid'] ?? {};
- memebers = json['memebers'] ?? [];
+ members = json['members'] != null
+ ? json['members']
+ .map((e) => MemberItemModel.fromJson(e))
+ .toList()
+ : [];
emote = json['emote'] ?? {};
jumpUrl = json['jump_url'] ?? {};
pictures = json['pictures'] ?? [];
@@ -35,5 +41,21 @@ class ReplyContent {
richText = json['rich_text'] ?? {};
// 不包含@ 笔记 图片的时候,文字可折叠
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
+ topicsMeta = json['topics_meta'] ?? {};
+ }
+}
+
+class MemberItemModel {
+ MemberItemModel({
+ required this.mid,
+ required this.uname,
+ });
+
+ late String mid;
+ late String uname;
+
+ MemberItemModel.fromJson(Map json) {
+ mid = json['mid'];
+ uname = json['uname'];
}
}
diff --git a/lib/models/video/reply/emote.dart b/lib/models/video/reply/emote.dart
new file mode 100644
index 00000000..b4071826
--- /dev/null
+++ b/lib/models/video/reply/emote.dart
@@ -0,0 +1,120 @@
+class EmoteModelData {
+ final List? packages;
+
+ EmoteModelData({
+ required this.packages,
+ });
+
+ factory EmoteModelData.fromJson(Map jsonRes) {
+ final List? packages =
+ jsonRes['packages'] is List ? [] : null;
+ if (packages != null) {
+ for (final dynamic item in jsonRes['packages']!) {
+ if (item != null) {
+ try {
+ packages.add(PackageItem.fromJson(item));
+ } catch (_) {}
+ }
+ }
+ }
+ return EmoteModelData(
+ packages: packages,
+ );
+ }
+}
+
+class PackageItem {
+ final int? id;
+ final String? text;
+ final String? url;
+ final int? mtime;
+ final int? type;
+ final int? attr;
+ final Meta? meta;
+ final List? emote;
+
+ PackageItem({
+ required this.id,
+ required this.text,
+ required this.url,
+ required this.mtime,
+ required this.type,
+ required this.attr,
+ required this.meta,
+ required this.emote,
+ });
+
+ factory PackageItem.fromJson(Map jsonRes) {
+ final List? emote = jsonRes['emote'] is List ? [] : null;
+ if (emote != null) {
+ for (final dynamic item in jsonRes['emote']!) {
+ if (item != null) {
+ try {
+ emote.add(Emote.fromJson(item));
+ } catch (_) {}
+ }
+ }
+ }
+ return PackageItem(
+ id: jsonRes['id'],
+ text: jsonRes['text'],
+ url: jsonRes['url'],
+ mtime: jsonRes['mtime'],
+ type: jsonRes['type'],
+ attr: jsonRes['attr'],
+ meta: Meta.fromJson(jsonRes['meta']),
+ emote: emote,
+ );
+ }
+}
+
+class Meta {
+ final int? size;
+ final List? suggest;
+
+ Meta({
+ required this.size,
+ required this.suggest,
+ });
+
+ factory Meta.fromJson(Map jsonRes) => Meta(
+ size: jsonRes['size'],
+ suggest: jsonRes['suggest'] is List ? [] : null,
+ );
+}
+
+class Emote {
+ final int? id;
+ final int? packageId;
+ final String? text;
+ final String? url;
+ final int? mtime;
+ final int? type;
+ final int? attr;
+ final Meta? meta;
+ final dynamic activity;
+
+ Emote({
+ required this.id,
+ required this.packageId,
+ required this.text,
+ required this.url,
+ required this.mtime,
+ required this.type,
+ required this.attr,
+ required this.meta,
+ required this.activity,
+ });
+
+ factory Emote.fromJson(Map jsonRes) => Emote(
+ id: jsonRes['id'],
+ packageId: jsonRes['package_id'],
+ text: jsonRes['text'],
+ url: jsonRes['url'],
+ mtime: jsonRes['mtime'],
+ type: jsonRes['type'],
+ attr: jsonRes['attr'],
+ meta: Meta.fromJson(jsonRes['meta']),
+ activity: jsonRes['activity'],
+ );
+}
diff --git a/lib/models/video/subTitile/content.dart b/lib/models/video/subTitile/content.dart
new file mode 100644
index 00000000..b18098a4
--- /dev/null
+++ b/lib/models/video/subTitile/content.dart
@@ -0,0 +1,20 @@
+class SubTitileContentModel {
+ double? from;
+ double? to;
+ int? location;
+ String? content;
+
+ SubTitileContentModel({
+ this.from,
+ this.to,
+ this.location,
+ this.content,
+ });
+
+ SubTitileContentModel.fromJson(Map json) {
+ from = json['from'];
+ to = json['to'];
+ location = json['location'];
+ content = json['content'];
+ }
+}
diff --git a/lib/models/video/subTitile/result.dart b/lib/models/video/subTitile/result.dart
new file mode 100644
index 00000000..d3e32e55
--- /dev/null
+++ b/lib/models/video/subTitile/result.dart
@@ -0,0 +1,89 @@
+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? subtitles;
+
+ factory SubTitlteModel.fromJson(Map 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((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 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: [],
+ );
+}
diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart
index 17eabee5..b381691a 100644
--- a/lib/pages/about/index.dart
+++ b/lib/pages/about/index.dart
@@ -7,6 +7,7 @@ import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/github/latest.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:url_launcher/url_launcher.dart';
+import '../../utils/cache_manage.dart';
class AboutPage extends StatefulWidget {
const AboutPage({super.key});
@@ -17,10 +18,23 @@ class AboutPage extends StatefulWidget {
class _AboutPageState extends State {
final AboutController _aboutController = Get.put(AboutController());
+ String cacheSize = '';
+
+ @override
+ void initState() {
+ super.initState();
+ // 读取缓存占用
+ getCacheSize();
+ }
+
+ Future getCacheSize() async {
+ final res = await CacheManage().loadApplicationCache();
+ setState(() => cacheSize = res);
+ }
@override
Widget build(BuildContext context) {
- Color outline = Theme.of(context).colorScheme.outline;
+ final Color outline = Theme.of(context).colorScheme.outline;
TextStyle subTitleStyle =
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
return Scaffold(
@@ -29,7 +43,6 @@ class _AboutPageState extends State {
),
body: SingleChildScrollView(
child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo/logo_android_2.png',
@@ -40,29 +53,54 @@ class _AboutPageState extends State {
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 6),
- Text(
- '使用Flutter开发的哔哩哔哩第三方客户端',
- style: TextStyle(color: Theme.of(context).colorScheme.outline),
- ),
- const SizedBox(height: 20),
Obx(
- () => ListTile(
- title: const Text("当前版本"),
- trailing: Text(_aboutController.currentVersion.value,
- style: subTitleStyle),
- ),
- ),
- Obx(
- () => ListTile(
- onTap: () => _aboutController.onUpdate(),
- title: const Text('最新版本'),
- trailing: Text(
- _aboutController.isLoading.value
- ? '正在获取'
- : _aboutController.isUpdate.value
- ? '有新版本 ❤️${_aboutController.remoteVersion.value}'
- : '当前已是最新版',
- style: subTitleStyle,
+ () => Badge(
+ isLabelVisible: _aboutController.isLoading.value
+ ? false
+ : _aboutController.isUpdate.value,
+ label: const Text('New'),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(0, 0, 0, 30),
+ child: FilledButton.tonal(
+ onPressed: () {
+ showModalBottomSheet(
+ context: context,
+ builder: (context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ListTile(
+ onTap: () => _aboutController.githubRelease(),
+ title: const Text('Github下载'),
+ ),
+ ListTile(
+ onTap: () => _aboutController.panDownload(),
+ title: const Text('网盘下载'),
+ ),
+ ListTile(
+ onTap: () => _aboutController.webSiteUrl(),
+ title: const Text('官网下载'),
+ ),
+ ListTile(
+ onTap: () => _aboutController.qimiao(),
+ title: const Text('奇妙应用'),
+ ),
+ SizedBox(
+ height:
+ MediaQuery.of(context).padding.bottom +
+ 20)
+ ],
+ );
+ },
+ );
+ },
+ child: Text(
+ 'V${_aboutController.currentVersion.value}',
+ style: subTitleStyle.copyWith(
+ color: Theme.of(context).primaryColor,
+ ),
+ ),
+ ),
),
),
),
@@ -74,19 +112,22 @@ class _AboutPageState extends State {
// size: 16,
// ),
// ),
- Divider(
- thickness: 1,
- height: 30,
- color: Theme.of(context).colorScheme.outlineVariant,
- ),
ListTile(
onTap: () => _aboutController.githubUrl(),
- title: const Text('Github'),
+ title: const Text('开源地址'),
trailing: Text(
'github.com/guozhigq/pilipala',
style: subTitleStyle,
),
),
+ ListTile(
+ onTap: () => _aboutController.webSiteUrl(),
+ title: const Text('访问官网'),
+ trailing: Text(
+ 'https://pilipalanet.mysxl.cn',
+ style: subTitleStyle,
+ ),
+ ),
ListTile(
onTap: () => _aboutController.panDownload(),
title: const Text('网盘下载'),
@@ -108,24 +149,64 @@ class _AboutPageState extends State {
),
),
ListTile(
- onTap: () => _aboutController.qqChanel(),
- title: const Text('QQ群'),
+ onTap: () {
+ showModalBottomSheet(
+ context: context,
+ builder: (context) {
+ return Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ListTile(
+ onTap: () => _aboutController.qqChanel(),
+ title: const Text('QQ群'),
+ trailing: Text(
+ '616150809',
+ style: subTitleStyle,
+ ),
+ ),
+ ListTile(
+ onTap: () => _aboutController.tgChanel(),
+ title: const Text('TG频道'),
+ trailing: Text(
+ 'https://t.me/+lm_oOVmF0RJiODk1',
+ style: subTitleStyle,
+ ),
+ ),
+ SizedBox(
+ height: MediaQuery.of(context).padding.bottom + 20)
+ ],
+ );
+ },
+ );
+ },
+ title: const Text('交流社区'),
trailing: Icon(
Icons.arrow_forward_ios,
size: 16,
color: outline,
),
),
- ListTile(
- onTap: () => _aboutController.tgChanel(),
- title: const Text('TG频道'),
- trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
- ),
ListTile(
onTap: () => _aboutController.aPay(),
title: const Text('赞助'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
+ ListTile(
+ onTap: () => _aboutController.logs(),
+ title: const Text('错误日志'),
+ trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
+ ),
+ ListTile(
+ onTap: () async {
+ var cleanStatus = await CacheManage().clearCacheAll();
+ if (cleanStatus) {
+ getCacheSize();
+ }
+ },
+ title: const Text('清除缓存'),
+ subtitle: Text('图片及网络缓存 $cacheSize', style: subTitleStyle),
+ ),
+ SizedBox(height: MediaQuery.of(context).padding.bottom + 20)
],
),
),
@@ -172,12 +253,16 @@ class AboutController extends GetxController {
// 获取远程版本
Future getRemoteApp() async {
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
+ isLoading.value = false;
+ if (result.data == null || result.data.isEmpty) {
+ SmartDialog.showToast('获取远程版本失败,请检查网络');
+ return;
+ }
data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data;
remoteVersion.value = data.tagName!;
isUpdate.value =
Utils.needUpdate(currentVersion.value, remoteVersion.value);
- isLoading.value = false;
}
// 跳转下载/本地更新
@@ -193,11 +278,26 @@ class AboutController extends GetxController {
);
}
+ githubRelease() {
+ launchUrl(
+ Uri.parse('https://github.com/guozhigq/pilipala/releases'),
+ mode: LaunchMode.externalApplication,
+ );
+ }
+
// 从网盘下载
panDownload() {
- launchUrl(
- Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
- mode: LaunchMode.externalApplication,
+ Clipboard.setData(
+ const ClipboardData(text: 'pili'),
+ );
+ SmartDialog.showToast(
+ '已复制提取码:pili',
+ displayTime: const Duration(milliseconds: 500),
+ ).then(
+ (value) => launchUrl(
+ Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
+ mode: LaunchMode.externalApplication,
+ ),
);
}
@@ -213,7 +313,7 @@ class AboutController extends GetxController {
// qq频道
qqChanel() {
Clipboard.setData(
- const ClipboardData(text: '489981949'),
+ const ClipboardData(text: '616150809'),
);
SmartDialog.showToast('已复制QQ群号');
}
@@ -245,4 +345,24 @@ class AboutController extends GetxController {
print(e);
}
}
+
+ // 官网
+ webSiteUrl() {
+ launchUrl(
+ Uri.parse('https://pilipalanet.mysxl.cn'),
+ mode: LaunchMode.externalApplication,
+ );
+ }
+
+ qimiao() {
+ launchUrl(
+ Uri.parse('https://www.magicalapk.com/home'),
+ mode: LaunchMode.externalApplication,
+ );
+ }
+
+ // 日志
+ logs() {
+ Get.toNamed('/logs');
+ }
}
diff --git a/lib/pages/bangumi/controller.dart b/lib/pages/bangumi/controller.dart
index 09afc43a..e5748d6c 100644
--- a/lib/pages/bangumi/controller.dart
+++ b/lib/pages/bangumi/controller.dart
@@ -7,8 +7,8 @@ import 'package:pilipala/utils/storage.dart';
class BangumiController extends GetxController {
final ScrollController scrollController = ScrollController();
- RxList bangumiList = [BangumiListItemModel()].obs;
- RxList bangumiFollowList = [BangumiListItemModel()].obs;
+ RxList bangumiList = [].obs;
+ RxList bangumiFollowList = [].obs;
int _currentPage = 1;
bool isLoadingMore = true;
Box userInfoCache = GStrorage.userInfo;
diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart
index f37a3310..12f0c053 100644
--- a/lib/pages/bangumi/introduction/controller.dart
+++ b/lib/pages/bangumi/introduction/controller.dart
@@ -25,13 +25,6 @@ class BangumiIntroController extends GetxController {
? int.tryParse(Get.parameters['epId']!)
: null;
- // 是否预渲染 骨架屏
- bool preRender = false;
-
- // 视频详情 上个页面传入
- Map? videoItem = {};
- BangumiInfoModel? bangumiItem;
-
// 请求状态
RxBool isLoading = false.obs;
@@ -63,27 +56,6 @@ class BangumiIntroController extends GetxController {
@override
void onInit() {
super.onInit();
- if (Get.arguments.isNotEmpty) {
- if (Get.arguments.containsKey('bangumiItem')) {
- preRender = true;
- bangumiItem = Get.arguments['bangumiItem'];
- // bangumiItem!['pic'] = args.pic;
- // if (args.title is String) {
- // videoItem!['title'] = args.title;
- // } else {
- // String str = '';
- // for (Map map in args.title) {
- // str += map['text'];
- // }
- // videoItem!['title'] = str;
- // }
- // if (args.stat != null) {
- // videoItem!['stat'] = args.stat;
- // }
- // videoItem!['pubdate'] = args.pubdate;
- // videoItem!['owner'] = args.owner;
- }
- }
userInfo = userInfoCache.get('userInfoCache');
userLogin = userInfo != null;
}
@@ -183,20 +155,21 @@ class BangumiIntroController extends GetxController {
actions: [
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
TextButton(
- onPressed: () async {
- var res = await VideoHttp.coinVideo(
- bvid: bvid, multiply: _tempThemeValue);
- if (res['status']) {
- SmartDialog.showToast('投币成功 👏');
- hasCoin.value = true;
- bangumiDetail.value.stat!['coins'] =
- bangumiDetail.value.stat!['coins'] + _tempThemeValue;
- } else {
- SmartDialog.showToast(res['msg']);
- }
- Get.back();
- },
- child: const Text('确定'))
+ onPressed: () async {
+ var res = await VideoHttp.coinVideo(
+ bvid: bvid, multiply: _tempThemeValue);
+ if (res['status']) {
+ SmartDialog.showToast('投币成功 👏');
+ hasCoin.value = true;
+ bangumiDetail.value.stat!['coins'] =
+ bangumiDetail.value.stat!['coins'] + _tempThemeValue;
+ } else {
+ SmartDialog.showToast(res['msg']);
+ }
+ Get.back();
+ },
+ child: const Text('确定'),
+ )
],
);
});
@@ -218,14 +191,12 @@ class BangumiIntroController extends GetxController {
addIds: addMediaIdsNew.join(','),
delIds: delMediaIdsNew.join(','));
if (result['status']) {
- if (result['data']['prompt']) {
- addMediaIdsNew = [];
- delMediaIdsNew = [];
- Get.back();
- // 重新获取收藏状态
- queryHasFavVideo();
- SmartDialog.showToast('✅ 操作成功');
- }
+ addMediaIdsNew = [];
+ delMediaIdsNew = [];
+ // 重新获取收藏状态
+ queryHasFavVideo();
+ SmartDialog.showToast('✅ 操作成功');
+ Get.back();
}
}
diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart
index 63c66515..6255ffda 100644
--- a/lib/pages/bangumi/introduction/view.dart
+++ b/lib/pages/bangumi/introduction/view.dart
@@ -12,11 +12,10 @@ import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
-import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
-
+import '../../../common/widgets/http_error.dart';
import 'controller.dart';
import 'widgets/intro_detail.dart';
@@ -51,12 +50,8 @@ class _BangumiIntroPanelState extends State
cid = widget.cid!;
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find(tag: heroTag);
- bangumiIntroController.bangumiDetail.listen((value) {
- bangumiDetail = value;
- });
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
- videoDetailCtr.cid.listen((p0) {
- print('🐶🐶$p0');
+ videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
});
@@ -67,29 +62,34 @@ class _BangumiIntroPanelState extends State
super.build(context);
return FutureBuilder(
future: _futureBuilderFuture,
- builder: (context, snapshot) {
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
+ if (snapshot.data == null) {
+ return const SliverToBoxAdapter(child: SizedBox());
+ }
if (snapshot.data['status']) {
// 请求成功
-
- return BangumiInfo(
- loadingStatus: false,
- bangumiDetail: bangumiDetail,
- cid: cid,
+ return Obx(
+ () => BangumiInfo(
+ bangumiDetail: bangumiIntroController.bangumiDetail.value,
+ cid: cid,
+ ),
);
} else {
// 请求错误
- // return HttpError(
- // errMsg: snapshot.data['msg'],
- // fn: () => Get.back(),
- // );
- return SizedBox();
+ return HttpError(
+ errMsg: snapshot.data['msg'],
+ fn: () => Get.back(),
+ );
}
} else {
- return BangumiInfo(
- loadingStatus: true,
- bangumiDetail: bangumiDetail,
- cid: cid,
+ return const SliverToBoxAdapter(
+ child: SizedBox(
+ height: 100,
+ child: Center(
+ child: CircularProgressIndicator(),
+ ),
+ ),
);
}
},
@@ -98,16 +98,14 @@ class _BangumiIntroPanelState extends State
}
class BangumiInfo extends StatefulWidget {
- final bool loadingStatus;
- final BangumiInfoModel? bangumiDetail;
- final int? cid;
-
const BangumiInfo({
- Key? key,
- this.loadingStatus = false,
+ super.key,
this.bangumiDetail,
this.cid,
- }) : super(key: key);
+ });
+
+ final BangumiInfoModel? bangumiDetail;
+ final int? cid;
@override
State createState() => _BangumiInfoState();
@@ -118,29 +116,28 @@ class _BangumiInfoState extends State {
late final BangumiIntroController bangumiIntroController;
late final VideoDetailController videoDetailCtr;
Box localCache = GStrorage.localCache;
- late final BangumiInfoModel? bangumiItem;
late double sheetHeight;
int? cid;
bool isProcessing = false;
void Function()? handleState(Future Function() action) {
- return isProcessing ? null : () async {
- setState(() => isProcessing = true);
- await action();
- setState(() => isProcessing = false);
- };
+ return isProcessing
+ ? null
+ : () async {
+ setState(() => isProcessing = true);
+ await action();
+ setState(() => isProcessing = false);
+ };
}
+
@override
void initState() {
super.initState();
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find(tag: heroTag);
- bangumiItem = bangumiIntroController.bangumiItem;
sheetHeight = localCache.get('sheetHeight');
cid = widget.cid!;
- print('cid: $cid');
videoDetailCtr.cid.listen((p0) {
cid = p0;
- print('cid: $cid');
setState(() {});
});
}
@@ -155,7 +152,7 @@ class _BangumiInfoState extends State {
context: context,
useRootNavigator: true,
isScrollControlled: true,
- builder: (context) {
+ builder: (BuildContext context) {
return FavPanel(ctr: bangumiIntroController);
},
);
@@ -175,218 +172,166 @@ class _BangumiInfoState extends State {
@override
Widget build(BuildContext context) {
- ThemeData t = Theme.of(context);
+ final ThemeData t = Theme.of(context);
return SliverPadding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20),
sliver: SliverToBoxAdapter(
- child: !widget.loadingStatus || bangumiItem != null
- ? Column(
- crossAxisAlignment: CrossAxisAlignment.start,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Stack(
children: [
- Row(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Stack(
- children: [
- NetworkImgLayer(
- width: 105,
- height: 160,
- src: !widget.loadingStatus
- ? widget.bangumiDetail!.cover!
- : bangumiItem!.cover!,
- ),
- if (bangumiItem != null &&
- bangumiItem!.rating != null)
- PBadge(
- text:
- '评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
- top: null,
- right: 6,
- bottom: 6,
- left: null,
+ NetworkImgLayer(
+ width: 105,
+ height: 160,
+ src: widget.bangumiDetail!.cover!,
+ ),
+ PBadge(
+ text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
+ top: null,
+ right: 6,
+ bottom: 6,
+ left: null,
+ ),
+ ],
+ ),
+ const SizedBox(width: 10),
+ Expanded(
+ child: InkWell(
+ onTap: () => showIntroDetail(),
+ child: SizedBox(
+ height: 158,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ widget.bangumiDetail!.title!,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.w500,
+ ),
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
),
- ],
- ),
- const SizedBox(width: 10),
- Expanded(
- child: InkWell(
- onTap: () => showIntroDetail(),
- child: SizedBox(
- height: 158,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisSize: MainAxisSize.min,
- children: [
- Row(
- children: [
- Expanded(
- child: Text(
- !widget.loadingStatus
- ? widget.bangumiDetail!.title!
- : bangumiItem!.title!,
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- const SizedBox(width: 20),
- SizedBox(
- width: 34,
- height: 34,
- child: IconButton(
- style: ButtonStyle(
- padding: MaterialStateProperty.all(
- EdgeInsets.zero),
- backgroundColor:
- MaterialStateProperty.resolveWith(
- (states) {
- return t
- .colorScheme.primaryContainer
- .withOpacity(0.7);
- }),
- ),
- onPressed: () =>
- bangumiIntroController.bangumiAdd(),
- icon: Icon(
- Icons.favorite_border_rounded,
- color: t.colorScheme.primary,
- size: 22,
- ),
- ),
- ),
- ],
+ const SizedBox(width: 20),
+ SizedBox(
+ width: 34,
+ height: 34,
+ child: IconButton(
+ style: ButtonStyle(
+ padding: MaterialStateProperty.all(
+ EdgeInsets.zero),
+ backgroundColor:
+ MaterialStateProperty.resolveWith(
+ (Set states) {
+ return t.colorScheme.primaryContainer
+ .withOpacity(0.7);
+ }),
),
- Row(
- children: [
- StatView(
- theme: 'gray',
- view: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['views']
- : bangumiItem!.stat!['views'],
- size: 'medium',
- ),
- const SizedBox(width: 6),
- StatDanMu(
- theme: 'gray',
- danmu: !widget.loadingStatus
- ? widget
- .bangumiDetail!.stat!['danmakus']
- : bangumiItem!.stat!['danmakus'],
- size: 'medium',
- ),
- ],
+ onPressed: () =>
+ bangumiIntroController.bangumiAdd(),
+ icon: Icon(
+ Icons.favorite_border_rounded,
+ color: t.colorScheme.primary,
+ size: 22,
),
- const SizedBox(height: 6),
- Row(
- children: [
- Text(
- !widget.loadingStatus
- ? (widget.bangumiDetail!.areas!
- .isNotEmpty
- ? widget.bangumiDetail!.areas!
- .first['name']
- : '')
- : (bangumiItem!.areas!.isNotEmpty
- ? bangumiItem!
- .areas!.first['name']
- : ''),
- style: TextStyle(
- fontSize: 12,
- color: t.colorScheme.outline,
- ),
- ),
- const SizedBox(width: 6),
- Text(
- !widget.loadingStatus
- ? widget.bangumiDetail!
- .publish!['pub_time_show']
- : bangumiItem!
- .publish!['pub_time_show'],
- style: TextStyle(
- fontSize: 12,
- color: t.colorScheme.outline,
- ),
- ),
- ],
- ),
- // const SizedBox(height: 4),
- Text(
- !widget.loadingStatus
- ? widget.bangumiDetail!.newEp!['desc']
- : bangumiItem!.newEp!['desc'],
- style: TextStyle(
- fontSize: 12,
- color: t.colorScheme.outline,
- ),
- ),
- // const SizedBox(height: 10),
- const Spacer(),
- Text(
- '简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
- maxLines: 3,
- overflow: TextOverflow.ellipsis,
- style: TextStyle(
- fontSize: 13,
- color: t.colorScheme.outline,
- ),
- ),
- ],
+ ),
),
+ ],
+ ),
+ Row(
+ children: [
+ StatView(
+ theme: 'gray',
+ view: widget.bangumiDetail!.stat!['views'],
+ size: 'medium',
+ ),
+ const SizedBox(width: 6),
+ StatDanMu(
+ theme: 'gray',
+ danmu: widget.bangumiDetail!.stat!['danmakus'],
+ size: 'medium',
+ ),
+ ],
+ ),
+ const SizedBox(height: 6),
+ Row(
+ children: [
+ Text(
+ (widget.bangumiDetail!.areas!.isNotEmpty
+ ? widget.bangumiDetail!.areas!.first['name']
+ : ''),
+ style: TextStyle(
+ fontSize: 12,
+ color: t.colorScheme.outline,
+ ),
+ ),
+ const SizedBox(width: 6),
+ Text(
+ widget.bangumiDetail!.publish!['pub_time_show'],
+ style: TextStyle(
+ fontSize: 12,
+ color: t.colorScheme.outline,
+ ),
+ ),
+ ],
+ ),
+ Text(
+ widget.bangumiDetail!.newEp!['desc'],
+ style: TextStyle(
+ fontSize: 12,
+ color: t.colorScheme.outline,
),
),
- ),
- ],
+ const Spacer(),
+ Text(
+ '简介:${widget.bangumiDetail!.evaluate!}',
+ maxLines: 3,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 13,
+ color: t.colorScheme.outline,
+ ),
+ ),
+ ],
+ ),
),
- const SizedBox(height: 6),
- // 点赞收藏转发 布局样式1
- // SingleChildScrollView(
- // padding: const EdgeInsets.only(top: 7, bottom: 7),
- // scrollDirection: Axis.horizontal,
- // child: actionRow(
- // context,
- // bangumiIntroController,
- // videoDetailCtr,
- // ),
- // ),
- // 点赞收藏转发 布局样式2
- actionGrid(context, bangumiIntroController),
- // 番剧分p
- if ((!widget.loadingStatus &&
- widget.bangumiDetail!.episodes!.isNotEmpty) ||
- bangumiItem != null &&
- bangumiItem!.episodes!.isNotEmpty) ...[
- BangumiPanel(
- pages: bangumiItem != null
- ? bangumiItem!.episodes!
- : widget.bangumiDetail!.episodes!,
- cid: cid ??
- (bangumiItem != null
- ? bangumiItem!.episodes!.first.cid
- : widget.bangumiDetail!.episodes!.first.cid),
- sheetHeight: sheetHeight,
- changeFuc: (bvid, cid, aid) => bangumiIntroController
- .changeSeasonOrbangu(bvid, cid, aid),
- )
- ],
- ],
- )
- : const SizedBox(
- height: 100,
- child: Center(
- child: CircularProgressIndicator(),
),
),
- ),
+ ],
+ ),
+ const SizedBox(height: 6),
+
+ /// 点赞收藏转发
+ actionGrid(context, bangumiIntroController),
+ // 番剧分p
+ if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[
+ BangumiPanel(
+ pages: widget.bangumiDetail!.episodes!,
+ cid: cid ?? widget.bangumiDetail!.episodes!.first.cid,
+ sheetHeight: sheetHeight,
+ changeFuc: (bvid, cid, aid) =>
+ bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
+ bangumiDetail: bangumiIntroController.bangumiDetail.value,
+ )
+ ],
+ ],
+ )),
);
}
Widget actionGrid(BuildContext context, bangumiIntroController) {
- return LayoutBuilder(builder: (context, constraints) {
+ return LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints constraints) {
return Material(
child: Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
@@ -394,61 +339,50 @@ class _BangumiInfoState extends State {
height: constraints.maxWidth / 5 * 0.8,
child: GridView.count(
primary: false,
- padding: const EdgeInsets.all(0),
+ padding: EdgeInsets.zero,
crossAxisCount: 5,
childAspectRatio: 1.25,
children: [
Obx(
() => ActionItem(
- icon: const Icon(FontAwesomeIcons.thumbsUp),
- selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
- onTap: handleState(bangumiIntroController.actionLikeVideo),
- selectStatus: bangumiIntroController.hasLike.value,
- loadingStatus: false,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['likes']!.toString()
- : bangumiItem!.stat!['likes']!.toString()),
+ icon: const Icon(FontAwesomeIcons.thumbsUp),
+ selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
+ onTap: handleState(bangumiIntroController.actionLikeVideo),
+ selectStatus: bangumiIntroController.hasLike.value,
+ text: widget.bangumiDetail!.stat!['likes']!.toString(),
+ ),
),
Obx(
() => ActionItem(
- icon: const Icon(FontAwesomeIcons.b),
- selectIcon: const Icon(FontAwesomeIcons.b),
- onTap: handleState(bangumiIntroController.actionCoinVideo),
- selectStatus: bangumiIntroController.hasCoin.value,
- loadingStatus: false,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['coins']!.toString()
- : bangumiItem!.stat!['coins']!.toString()),
+ icon: const Icon(FontAwesomeIcons.b),
+ selectIcon: const Icon(FontAwesomeIcons.b),
+ onTap: handleState(bangumiIntroController.actionCoinVideo),
+ selectStatus: bangumiIntroController.hasCoin.value,
+ text: widget.bangumiDetail!.stat!['coins']!.toString(),
+ ),
),
Obx(
() => ActionItem(
- icon: const Icon(FontAwesomeIcons.star),
- selectIcon: const Icon(FontAwesomeIcons.solidStar),
- onTap: () => showFavBottomSheet(),
- selectStatus: bangumiIntroController.hasFav.value,
- loadingStatus: false,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['favorite']!.toString()
- : bangumiItem!.stat!['favorite']!.toString()),
+ icon: const Icon(FontAwesomeIcons.star),
+ selectIcon: const Icon(FontAwesomeIcons.solidStar),
+ onTap: () => showFavBottomSheet(),
+ selectStatus: bangumiIntroController.hasFav.value,
+ text: widget.bangumiDetail!.stat!['favorite']!.toString(),
+ ),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.comment),
selectIcon: const Icon(FontAwesomeIcons.reply),
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
selectStatus: false,
- loadingStatus: false,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['reply']!.toString()
- : bangumiItem!.stat!['reply']!.toString(),
+ text: widget.bangumiDetail!.stat!['reply']!.toString(),
),
ActionItem(
- icon: const Icon(FontAwesomeIcons.shareFromSquare),
- onTap: () => bangumiIntroController.actionShareVideo(),
- selectStatus: false,
- loadingStatus: false,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['share']!.toString()
- : bangumiItem!.stat!['share']!.toString()),
+ icon: const Icon(FontAwesomeIcons.shareFromSquare),
+ onTap: () => bangumiIntroController.actionShareVideo(),
+ selectStatus: false,
+ text: widget.bangumiDetail!.stat!['share']!.toString(),
+ ),
],
),
),
@@ -456,63 +390,4 @@ class _BangumiInfoState extends State {
);
});
}
-
- Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
- return Row(children: [
- Obx(
- () => ActionRowItem(
- icon: const Icon(FontAwesomeIcons.thumbsUp),
- onTap: handleState(videoIntroController.actionLikeVideo),
- selectStatus: videoIntroController.hasLike.value,
- loadingStatus: widget.loadingStatus,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['likes']!.toString()
- : '-',
- ),
- ),
- const SizedBox(width: 8),
- Obx(
- () => ActionRowItem(
- icon: const Icon(FontAwesomeIcons.b),
- onTap: handleState(videoIntroController.actionCoinVideo),
- selectStatus: videoIntroController.hasCoin.value,
- loadingStatus: widget.loadingStatus,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['coins']!.toString()
- : '-',
- ),
- ),
- const SizedBox(width: 8),
- Obx(
- () => ActionRowItem(
- icon: const Icon(FontAwesomeIcons.heart),
- onTap: () => showFavBottomSheet(),
- selectStatus: videoIntroController.hasFav.value,
- loadingStatus: widget.loadingStatus,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['favorite']!.toString()
- : '-',
- ),
- ),
- const SizedBox(width: 8),
- ActionRowItem(
- icon: const Icon(FontAwesomeIcons.comment),
- onTap: () {
- videoDetailCtr.tabCtr.animateTo(1);
- },
- selectStatus: false,
- loadingStatus: widget.loadingStatus,
- text: !widget.loadingStatus
- ? widget.bangumiDetail!.stat!['reply']!.toString()
- : '-',
- ),
- const SizedBox(width: 8),
- ActionRowItem(
- icon: const Icon(FontAwesomeIcons.share),
- onTap: () => videoIntroController.actionShareVideo(),
- selectStatus: false,
- loadingStatus: widget.loadingStatus,
- text: '转发'),
- ]);
- }
}
diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart
index a2e8ae0f..f59f94a2 100644
--- a/lib/pages/bangumi/view.dart
+++ b/lib/pages/bangumi/view.dart
@@ -4,11 +4,11 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
+import 'package:nil/nil.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart';
-import 'package:pilipala/pages/rcmd/view.dart';
import 'controller.dart';
import 'widgets/bangumu_card_v.dart';
@@ -74,7 +74,7 @@ class _BangumiPageState extends State
super.build(context);
return RefreshIndicator(
onRefresh: () async {
- await _bangumidController.queryBangumiListFeed(type: 'init');
+ await _bangumidController.queryBangumiListFeed();
return _bangumidController.queryBangumiFollow();
},
child: CustomScrollView(
@@ -112,10 +112,11 @@ class _BangumiPageState extends State
),
),
SizedBox(
- height: 258,
+ height: 268,
child: FutureBuilder(
future: _futureBuilderFutureFollow,
- builder: (context, snapshot) {
+ builder:
+ (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
@@ -156,10 +157,10 @@ class _BangumiPageState extends State
),
);
} else {
- return const SizedBox();
+ return nil;
}
} else {
- return const SizedBox();
+ return nil;
}
},
),
@@ -188,7 +189,7 @@ class _BangumiPageState extends State
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
- builder: (context, snapshot) {
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
@@ -197,7 +198,10 @@ class _BangumiPageState extends State
} else {
return HttpError(
errMsg: data['msg'],
- fn: () => {},
+ fn: () {
+ _futureBuilderFuture =
+ _bangumidController.queryBangumiListFeed();
+ },
);
}
} else {
@@ -206,7 +210,6 @@ class _BangumiPageState extends State
},
),
),
- LoadingMore()
],
),
);
@@ -222,13 +225,13 @@ class _BangumiPageState extends State
// 列数
crossAxisCount: 3,
mainAxisExtent: Get.size.width / 3 / 0.65 +
- 32 * MediaQuery.of(context).textScaleFactor,
+ MediaQuery.textScalerOf(context).scale(32.0),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return bangumiList!.isNotEmpty
? BangumiCardV(bangumiItem: bangumiList[index])
- : const SizedBox();
+ : nil;
},
childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10,
),
diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart
index 6948172f..05fd814c 100644
--- a/lib/pages/bangumi/widgets/bangumi_panel.dart
+++ b/lib/pages/bangumi/widgets/bangumi_panel.dart
@@ -8,19 +8,21 @@ import 'package:pilipala/utils/storage.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class BangumiPanel extends StatefulWidget {
- final List pages;
- final int? cid;
- final double? sheetHeight;
- final Function? changeFuc;
-
const BangumiPanel({
super.key,
required this.pages,
this.cid,
this.sheetHeight,
this.changeFuc,
+ this.bangumiDetail,
});
+ final List pages;
+ final int? cid;
+ final double? sheetHeight;
+ final Function? changeFuc;
+ final BangumiInfoModel? bangumiDetail;
+
@override
State createState() => _BangumiPanelState();
}
@@ -50,10 +52,10 @@ class _BangumiPanelState extends State {
}
videoDetailCtr = Get.find(tag: heroTag);
- videoDetailCtr.cid.listen((p0) {
+ videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
- currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
+ currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
scrollToIndex();
});
}
@@ -65,6 +67,47 @@ class _BangumiPanelState extends State {
super.dispose();
}
+ Widget buildPageListItem(
+ EpisodeItem page,
+ int index,
+ bool isCurrentIndex,
+ ) {
+ Color primary = Theme.of(context).colorScheme.primary;
+ return ListTile(
+ onTap: () {
+ Get.back();
+ setState(() {
+ changeFucCall(page, index);
+ });
+ },
+ dense: false,
+ leading: isCurrentIndex
+ ? Image.asset(
+ 'assets/images/live.gif',
+ color: primary,
+ height: 12,
+ )
+ : null,
+ title: Text(
+ '第${page.title}话 ${page.longTitle!}',
+ style: TextStyle(
+ fontSize: 14,
+ color: isCurrentIndex
+ ? primary
+ : Theme.of(context).colorScheme.onSurface,
+ ),
+ ),
+ trailing: page.badge != null
+ ? Text(
+ page.badge!,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ )
+ : const SizedBox(),
+ );
+ }
+
void showBangumiPanel() {
showBottomSheet(
context: context,
@@ -105,37 +148,22 @@ class _BangumiPanelState extends State {
Expanded(
child: Material(
child: ScrollablePositionedList.builder(
- itemCount: widget.pages.length,
- itemBuilder: (context, index) => ListTile(
- onTap: () {
- setState(() {
- changeFucCall(widget.pages[index], index);
- });
- },
- dense: false,
- leading: index == currentIndex
- ? Image.asset(
- 'assets/images/live.gif',
- color: Theme.of(context).colorScheme.primary,
- height: 12,
+ itemCount: widget.pages.length + 1,
+ itemBuilder: (BuildContext context, int index) {
+ bool isLastItem = index == widget.pages.length;
+ bool isCurrentIndex = currentIndex == index;
+ return isLastItem
+ ? SizedBox(
+ height:
+ MediaQuery.of(context).padding.bottom +
+ 20,
)
- : null,
- title: Text(
- '第${index + 1}话 ${widget.pages[index].longTitle!}',
- style: TextStyle(
- fontSize: 14,
- color: index == currentIndex
- ? Theme.of(context).colorScheme.primary
- : Theme.of(context).colorScheme.onSurface,
- ),
- ),
- trailing: widget.pages[index].badge != null
- ? Image.asset(
- 'assets/images/big-vip.png',
- height: 20,
- )
- : const SizedBox(),
- ),
+ : buildPageListItem(
+ widget.pages[index],
+ index,
+ isCurrentIndex,
+ );
+ },
itemScrollController: itemScrollController,
),
),
@@ -150,7 +178,7 @@ class _BangumiPanelState extends State {
}
void changeFucCall(item, i) async {
- if (item.badge != null && vipStatus != 1) {
+ if (item.badge != null && item.badge == '会员' && vipStatus != 1) {
SmartDialog.showToast('需要大会员');
return;
}
@@ -177,11 +205,11 @@ class _BangumiPanelState extends State {
return Column(
children: [
Padding(
- padding: const EdgeInsets.only(top: 10, bottom: 6),
+ padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- const Text('合集 '),
+ const Text('选集 '),
Expanded(
child: Text(
' 正在播放:${widget.pages[currentIndex].longTitle}',
@@ -201,7 +229,7 @@ class _BangumiPanelState extends State {
),
onPressed: () => showBangumiPanel(),
child: Text(
- '全${widget.pages.length}话',
+ '${widget.bangumiDetail!.newEp!['desc']}',
style: const TextStyle(fontSize: 13),
),
),
@@ -212,78 +240,79 @@ class _BangumiPanelState extends State {
SizedBox(
height: 60,
child: ListView.builder(
- controller: listViewScrollCtr,
- scrollDirection: Axis.horizontal,
- itemCount: widget.pages.length,
- itemExtent: 150,
- itemBuilder: ((context, i) {
- return Container(
- width: 150,
- margin: const EdgeInsets.only(right: 10),
- child: Material(
- color: Theme.of(context).colorScheme.onInverseSurface,
- borderRadius: BorderRadius.circular(6),
- clipBehavior: Clip.hardEdge,
- child: InkWell(
- onTap: () => changeFucCall(widget.pages[i], i),
- child: Padding(
- padding: const EdgeInsets.symmetric(
- vertical: 8, horizontal: 10),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- children: [
- if (i == currentIndex) ...[
- Image.asset(
- 'assets/images/live.png',
+ controller: listViewScrollCtr,
+ scrollDirection: Axis.horizontal,
+ itemCount: widget.pages.length,
+ itemExtent: 150,
+ itemBuilder: (BuildContext context, int i) {
+ return Container(
+ width: 150,
+ margin: const EdgeInsets.only(right: 10),
+ child: Material(
+ color: Theme.of(context).colorScheme.onInverseSurface,
+ borderRadius: BorderRadius.circular(6),
+ clipBehavior: Clip.hardEdge,
+ child: InkWell(
+ onTap: () => changeFucCall(widget.pages[i], i),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 8, horizontal: 10),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ if (i == currentIndex) ...[
+ Image.asset(
+ 'assets/images/live.png',
+ color: Theme.of(context).colorScheme.primary,
+ height: 12,
+ ),
+ const SizedBox(width: 6)
+ ],
+ Text(
+ '第${i + 1}话',
+ style: TextStyle(
+ fontSize: 13,
+ color: i == currentIndex
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context)
+ .colorScheme
+ .onSurface),
+ ),
+ const SizedBox(width: 2),
+ if (widget.pages[i].badge != null) ...[
+ const Spacer(),
+ Text(
+ widget.pages[i].badge!,
+ style: TextStyle(
+ fontSize: 12,
color:
Theme.of(context).colorScheme.primary,
- height: 12,
),
- const SizedBox(width: 6)
- ],
- Text(
- '第${i + 1}话',
- style: TextStyle(
- fontSize: 13,
- color: i == currentIndex
- ? Theme.of(context)
- .colorScheme
- .primary
- : Theme.of(context)
- .colorScheme
- .onSurface),
),
- const SizedBox(width: 2),
- if (widget.pages[i].badge != null) ...[
- Image.asset(
- 'assets/images/big-vip.png',
- height: 16,
- ),
- ],
- ],
- ),
- const SizedBox(height: 3),
- Text(
- widget.pages[i].longTitle!,
- maxLines: 1,
- style: TextStyle(
- fontSize: 13,
- color: i == currentIndex
- ? Theme.of(context).colorScheme.primary
- : Theme.of(context)
- .colorScheme
- .onSurface),
- overflow: TextOverflow.ellipsis,
- )
- ],
- ),
+ ]
+ ],
+ ),
+ const SizedBox(height: 3),
+ Text(
+ widget.pages[i].longTitle!,
+ maxLines: 1,
+ style: TextStyle(
+ fontSize: 13,
+ color: i == currentIndex
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context).colorScheme.onSurface),
+ overflow: TextOverflow.ellipsis,
+ )
+ ],
),
),
),
- );
- })),
+ ),
+ );
+ },
+ ),
)
],
);
diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart
index 937d9d40..c1233ddf 100644
--- a/lib/pages/bangumi/widgets/bangumu_card_v.dart
+++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart
@@ -11,17 +11,16 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class BangumiCardV extends StatelessWidget {
- // ignore: prefer_typing_uninitialized_variables
- final bangumiItem;
- final Function()? longPress;
- final Function()? longPressEnd;
-
const BangumiCardV({
- Key? key,
+ super.key,
required this.bangumiItem,
this.longPress,
this.longPressEnd,
- }) : super(key: key);
+ });
+
+ final bangumiItem;
+ final Function()? longPress;
+ final Function()? longPressEnd;
@override
Widget build(BuildContext context) {
@@ -43,9 +42,9 @@ class BangumiCardV extends StatelessWidget {
// },
child: InkWell(
onTap: () async {
- int seasonId = bangumiItem.seasonId;
+ final int seasonId = bangumiItem.seasonId;
SmartDialog.showLoading(msg: '获取中...');
- var res = await SearchHttp.bangumiInfo(seasonId: seasonId);
+ final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
SmartDialog.dismiss().then((value) {
if (res['status']) {
if (res['data'].episodes.isEmpty) {
@@ -81,8 +80,8 @@ class BangumiCardV extends StatelessWidget {
child: AspectRatio(
aspectRatio: 0.65,
child: LayoutBuilder(builder: (context, boxConstraints) {
- double maxWidth = boxConstraints.maxWidth;
- double maxHeight = boxConstraints.maxHeight;
+ final double maxWidth = boxConstraints.maxWidth;
+ final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
@@ -124,9 +123,9 @@ class BangumiCardV extends StatelessWidget {
}
class BangumiContent extends StatelessWidget {
+ const BangumiContent({super.key, required this.bangumiItem});
// ignore: prefer_typing_uninitialized_variables
final bangumiItem;
- const BangumiContent({Key? key, required this.bangumiItem}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(
diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart
index 09cbaee8..402790f5 100644
--- a/lib/pages/blacklist/index.dart
+++ b/lib/pages/blacklist/index.dart
@@ -70,7 +70,7 @@ class _BlackListPageState extends State {
onRefresh: () async => await _blackListController.queryBlacklist(),
child: FutureBuilder(
future: _futureBuilderFuture,
- builder: (context, snapshot) {
+ builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {
@@ -139,7 +139,7 @@ class BlackListController extends GetxController {
int currentPage = 1;
int pageSize = 50;
RxInt total = 0.obs;
- RxList blackList = [BlackListItem()].obs;
+ RxList blackList = [].obs;
Future queryBlacklist({type = 'init'}) async {
if (type == 'init') {
diff --git a/lib/pages/danmaku/controller.dart b/lib/pages/danmaku/controller.dart
index c7d627a8..11e097e1 100644
--- a/lib/pages/danmaku/controller.dart
+++ b/lib/pages/danmaku/controller.dart
@@ -1,26 +1,23 @@
import 'package:pilipala/http/danmaku.dart';
import 'package:pilipala/models/danmaku/dm.pb.dart';
-import 'package:pilipala/plugin/pl_player/index.dart';
class PlDanmakuController {
PlDanmakuController(this.cid);
final int cid;
- Map> dmSegMap = {};
+ Map> dmSegMap = {};
// 已请求的段落标记
List requestedSeg = [];
bool get initiated => requestedSeg.isNotEmpty;
- static int SEGMENT_LENGTH = 60 * 6 * 1000;
+ static int segmentLength = 60 * 6 * 1000;
void initiate(int videoDuration, int progress) {
if (requestedSeg.isEmpty) {
- int segCount = (videoDuration / SEGMENT_LENGTH).ceil();
+ int segCount = (videoDuration / segmentLength).ceil();
requestedSeg = List.generate(segCount, (index) => false);
}
- queryDanmaku(
- calcSegment(progress)
- );
+ queryDanmaku(calcSegment(progress));
}
void dispose() {
@@ -29,17 +26,17 @@ class PlDanmakuController {
}
int calcSegment(int progress) {
- return progress ~/ SEGMENT_LENGTH;
+ return progress ~/ segmentLength;
}
void queryDanmaku(int segmentIndex) async {
assert(requestedSeg[segmentIndex] == false);
requestedSeg[segmentIndex] = true;
- DmSegMobileReply result =
- await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segmentIndex + 1);
+ final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
+ cid: cid, segmentIndex: segmentIndex + 1);
if (result.elems.isNotEmpty) {
for (var element in result.elems) {
- int pos = element.progress ~/ 100;//每0.1秒存储一次
+ int pos = element.progress ~/ 100; //每0.1秒存储一次
if (dmSegMap[pos] == null) {
dmSegMap[pos] = [];
}
diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart
index 027b9dfa..109f0206 100644
--- a/lib/pages/danmaku/view.dart
+++ b/lib/pages/danmaku/view.dart
@@ -1,4 +1,3 @@
-import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@@ -36,6 +35,7 @@ class _PlDanmakuState extends State {
late double opacityVal;
late double fontSizeVal;
late double danmakuDurationVal;
+ late double strokeWidth;
int latestAddedPosition = -1;
@override
@@ -43,15 +43,13 @@ class _PlDanmakuState extends State {
super.initState();
enableShowDanmaku =
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
- _plDanmakuController =
- PlDanmakuController(widget.cid);
+ _plDanmakuController = PlDanmakuController(widget.cid);
if (mounted) {
playerController = widget.playerController;
if (enableShowDanmaku || playerController.isOpenDanmu.value) {
_plDanmakuController.initiate(
playerController.duration.value.inMilliseconds,
- playerController.position.value.inMilliseconds
- );
+ playerController.position.value.inMilliseconds);
}
playerController
..addStatusLister(playerListener)
@@ -61,14 +59,14 @@ class _PlDanmakuState extends State {
if (p0 && !_plDanmakuController.initiated) {
_plDanmakuController.initiate(
playerController.duration.value.inMilliseconds,
- playerController.position.value.inMilliseconds
- );
+ playerController.position.value.inMilliseconds);
}
});
blockTypes = playerController.blockTypes;
showArea = playerController.showArea;
opacityVal = playerController.opacityVal;
fontSizeVal = playerController.fontSizeVal;
+ strokeWidth = playerController.strokeWidth;
danmakuDurationVal = playerController.danmakuDurationVal;
}
@@ -87,7 +85,7 @@ class _PlDanmakuState extends State {
return;
}
int currentPosition = position.inMilliseconds;
- currentPosition -= currentPosition % 100;//取整百的毫秒数
+ currentPosition -= currentPosition % 100; //取整百的毫秒数
if (currentPosition == latestAddedPosition) {
return;
@@ -98,17 +96,18 @@ class _PlDanmakuState extends State {
_plDanmakuController.getCurrentDanmaku(currentPosition);
if (currentDanmakuList != null) {
- Color? defaultColor = playerController.blockTypes.contains(6) ?
- DmUtils.decimalToColor(16777215) : null;
+ Color? defaultColor = playerController.blockTypes.contains(6)
+ ? DmUtils.decimalToColor(16777215)
+ : null;
- _controller!.addItems(
- currentDanmakuList.map((e) => DanmakuItem(
- e.content,
- color: defaultColor ?? DmUtils.decimalToColor(e.color),
- time: e.progress,
- type: DmUtils.getPosition(e.mode),
- )).toList()
- );
+ _controller!.addItems(currentDanmakuList
+ .map((e) => DanmakuItem(
+ e.content,
+ color: defaultColor ?? DmUtils.decimalToColor(e.color),
+ time: e.progress,
+ type: DmUtils.getPosition(e.mode),
+ ))
+ .toList());
}
}
@@ -128,7 +127,7 @@ class _PlDanmakuState extends State {
duration: const Duration(milliseconds: 100),
child: DanmakuView(
createdController: (DanmakuController e) async {
- widget.playerController.danmakuController = _controller = e;
+ playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: 15 * fontSizeVal,
@@ -137,7 +136,9 @@ class _PlDanmakuState extends State {
hideTop: blockTypes.contains(5),
hideScroll: blockTypes.contains(2),
hideBottom: blockTypes.contains(4),
- duration: danmakuDurationVal / widget.playerController.playbackSpeed,
+ duration:
+ danmakuDurationVal / playerController.playbackSpeed,
+ strokeWidth: strokeWidth,
// initDuration /
// (danmakuSpeedVal * widget.playerController.playbackSpeed),
),
diff --git a/lib/pages/dynamics/controller.dart b/lib/pages/dynamics/controller.dart
index 26ba2b22..b7676663 100644
--- a/lib/pages/dynamics/controller.dart
+++ b/lib/pages/dynamics/controller.dart
@@ -20,7 +20,7 @@ import 'package:pilipala/utils/utils.dart';
class DynamicsController extends GetxController {
int page = 1;
String? offset = '';
- RxList dynamicsList = [DynamicItemModel()].obs;
+ RxList dynamicsList = [].obs;
Rx dynamicsType = DynamicsType.values[0].obs;
RxString dynamicsTypeLabel = '全部'.obs;
final ScrollController scrollController = ScrollController();
@@ -105,7 +105,7 @@ class DynamicsController extends GetxController {
onSelectType(value) async {
dynamicsType.value = filterTypeList[value]['value'];
- dynamicsList.value = [DynamicItemModel()];
+ dynamicsList.value = [];
page = 1;
initialValue.value = value;
await queryFollowDynamic();
@@ -249,8 +249,8 @@ class DynamicsController extends GetxController {
return {'status': false, 'msg': '账号未登录'};
}
if (type == 'init') {
- upData.value.upList = [];
- upData.value.liveUsers = LiveUsers();
+ upData.value.upList = [];
+ upData.value.liveList = [];
}
var res = await DynamicsHttp.followUp();
if (res['status']) {
@@ -258,20 +258,23 @@ class DynamicsController extends GetxController {
if (upData.value.upList!.isEmpty) {
mid.value = -1;
}
+ upData.value.upList!.insertAll(0, [
+ UpItem(face: '', uname: '全部动态', mid: -1),
+ UpItem(face: userInfo.face, uname: '我', mid: userInfo.mid),
+ ]);
}
return res;
}
onSelectUp(mid) async {
dynamicsType.value = DynamicsType.values[0];
- dynamicsList.value = [DynamicItemModel()];
+ dynamicsList.value = [];
page = 1;
queryFollowDynamic();
}
onRefresh() async {
page = 1;
- print('onRefresh');
await queryFollowUp();
await queryFollowDynamic();
}
@@ -293,7 +296,7 @@ class DynamicsController extends GetxController {
dynamicsType.value = DynamicsType.values[0];
initialValue.value = 0;
SmartDialog.showToast('还原默认加载');
- dynamicsList.value = [DynamicItemModel()];
+ dynamicsList.value = [];
queryFollowDynamic();
}
}
diff --git a/lib/pages/dynamics/deatil/controller.dart b/lib/pages/dynamics/detail/controller.dart
similarity index 88%
rename from lib/pages/dynamics/deatil/controller.dart
rename to lib/pages/dynamics/detail/controller.dart
index 62f0245d..8e117383 100644
--- a/lib/pages/dynamics/deatil/controller.dart
+++ b/lib/pages/dynamics/detail/controller.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
+import 'package:pilipala/http/html.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
@@ -16,7 +17,7 @@ class DynamicDetailController extends GetxController {
int currentPage = 0;
bool isLoadingMore = false;
RxString noMore = ''.obs;
- RxList replyList = [ReplyItemModel()].obs;
+ RxList replyList = [].obs;
RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController();
@@ -36,6 +37,10 @@ class DynamicDetailController extends GetxController {
}
int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
+ if (deaultReplySortIndex == 2) {
+ setting.put(SettingBoxKey.replySortType, 0);
+ deaultReplySortIndex = 0;
+ }
_sortType = ReplySortType.values[deaultReplySortIndex];
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
@@ -91,9 +96,6 @@ class DynamicDetailController extends GetxController {
_sortType = ReplySortType.like;
break;
case ReplySortType.like:
- _sortType = ReplySortType.reply;
- break;
- case ReplySortType.reply:
_sortType = ReplySortType.time;
break;
default:
@@ -103,4 +105,10 @@ class DynamicDetailController extends GetxController {
replyList.clear();
queryReplyList(reqType: 'init');
}
+
+ // 根据jumpUrl获取动态html
+ reqHtmlByOpusId(int id) async {
+ var res = await HtmlHttp.reqHtml(id, 'opus');
+ oid = res['commentId'];
+ }
}
diff --git a/lib/pages/dynamics/deatil/index.dart b/lib/pages/dynamics/detail/index.dart
similarity index 100%
rename from lib/pages/dynamics/deatil/index.dart
rename to lib/pages/dynamics/detail/index.dart
diff --git a/lib/pages/dynamics/deatil/view.dart b/lib/pages/dynamics/detail/view.dart
similarity index 88%
rename from lib/pages/dynamics/deatil/view.dart
rename to lib/pages/dynamics/detail/view.dart
index 116e0d27..840cd33f 100644
--- a/lib/pages/dynamics/deatil/view.dart
+++ b/lib/pages/dynamics/detail/view.dart
@@ -7,11 +7,12 @@ import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/common/reply_type.dart';
-import 'package:pilipala/pages/dynamics/deatil/index.dart';
+import 'package:pilipala/models/dynamics/result.dart';
+import 'package:pilipala/pages/dynamics/detail/index.dart';
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
-import 'package:pilipala/pages/video/detail/replyNew/index.dart';
-import 'package:pilipala/pages/video/detail/replyReply/index.dart';
+import 'package:pilipala/pages/video/detail/reply_new/index.dart';
+import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';
@@ -35,39 +36,17 @@ class _DynamicDetailPageState extends State
bool _visibleTitle = false;
String? action;
// 回复类型
- late int type;
+ late int replyType;
bool _isFabVisible = true;
+ int oid = 0;
+ int? opusId;
+ bool isOpusId = false;
@override
void initState() {
super.initState();
- int oid = 0;
// floor 1原创 2转发
- if (Get.arguments['floor'] == 1) {
- oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
- print(oid);
- } else {
- try {
- String type = Get.arguments['item'].modules.moduleDynamic.major.type;
-
- /// TODO
- if (type == 'MAJOR_TYPE_OPUS') {
- } else {
- oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
- }
- } catch (_) {}
- }
- int commentType = 11;
- try {
- commentType = Get.arguments['item'].basic!['comment_type'];
- } catch (_) {}
- type = (commentType == 0) ? 11 : commentType;
-
- action =
- Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
- _dynamicDetailController =
- Get.put(DynamicDetailController(oid, type), tag: oid.toString());
- _futureBuilderFuture = _dynamicDetailController.queryReplyList();
+ init();
titleStreamC = StreamController();
if (action == 'comment') {
_visibleTitle = true;
@@ -83,6 +62,49 @@ class _DynamicDetailPageState extends State
scrollListener();
}
+ // 页面初始化
+ void init() async {
+ Map args = Get.arguments;
+ // 楼层
+ int floor = args['floor'];
+ // 从action栏点击进入
+ action = args.containsKey('action') ? args['action'] : null;
+ // 评论类型
+ int commentType = args['item'].basic!['comment_type'] ?? 11;
+ replyType = (commentType == 0) ? 11 : commentType;
+
+ if (floor == 1) {
+ oid = int.parse(args['item'].basic!['comment_id_str']);
+ } else {
+ try {
+ ModuleDynamicModel moduleDynamic = args['item'].modules.moduleDynamic;
+ String majorType = moduleDynamic.major!.type!;
+
+ if (majorType == 'MAJOR_TYPE_OPUS') {
+ // 转发的动态
+ String jumpUrl = moduleDynamic.major!.opus!.jumpUrl!;
+ opusId = int.parse(jumpUrl.split('/').last);
+ if (opusId != null) {
+ isOpusId = true;
+ _dynamicDetailController = Get.put(
+ DynamicDetailController(oid, replyType),
+ tag: opusId.toString());
+ await _dynamicDetailController.reqHtmlByOpusId(opusId!);
+ setState(() {});
+ }
+ } else {
+ oid = moduleDynamic.major!.draw!.id!;
+ }
+ } catch (_) {}
+ }
+ if (!isOpusId) {
+ _dynamicDetailController =
+ Get.put(DynamicDetailController(oid, replyType), tag: oid.toString());
+ }
+ _futureBuilderFuture = _dynamicDetailController.queryReplyList();
+ }
+
+ // 查看二级评论
void replyReply(replyItem) {
int oid = replyItem.oid;
int rpid = replyItem.rpid!;
@@ -100,13 +122,14 @@ class _DynamicDetailPageState extends State
oid: oid,
rpid: rpid,
source: 'dynamic',
- replyType: ReplyType.values[type],
+ replyType: ReplyType.values[replyType],
firstFloor: replyItem,
),
),
);
}
+ // 滑动事件监听
void scrollListener() {
scrollController = _dynamicDetailController.scrollController;
scrollController.addListener(
@@ -307,7 +330,8 @@ class _DynamicDetailPageState extends State
replyLevel: '1',
replyReply: (replyItem) =>
replyReply(replyItem),
- replyType: ReplyType.values[type],
+ replyType:
+ ReplyType.values[replyType],
addReply: (replyItem) {
_dynamicDetailController
.replyList[index].replies!
@@ -365,7 +389,7 @@ class _DynamicDetailPageState extends State
IdUtils.bv2av(Get.parameters['bvid']!),
root: 0,
parent: 0,
- replyType: ReplyType.values[type],
+ replyType: ReplyType.values[replyType],
);
},
).then(
diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart
index 575c8767..fe594a43 100644
--- a/lib/pages/dynamics/view.dart
+++ b/lib/pages/dynamics/view.dart
@@ -14,6 +14,7 @@ import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
+import '../mine/controller.dart';
import 'controller.dart';
import 'widgets/dynamic_panel.dart';
import 'widgets/up_panel.dart';
@@ -28,6 +29,7 @@ class DynamicsPage extends StatefulWidget {
class _DynamicsPageState extends State
with AutomaticKeepAliveClientMixin {
final DynamicsController _dynamicsController = Get.put(DynamicsController());
+ final MineController mineController = Get.put(MineController());
late Future _futureBuilderFuture;
late Future _futureBuilderFutureUp;
Box userInfoCache = GStrorage.userInfo;
@@ -192,22 +194,6 @@ class _DynamicsPageState extends State
)
],
),
- // Obx(
- // () => Visibility(
- // visible: _dynamicsController.userLogin.value,
- // child: Positioned(
- // right: 4,
- // top: 0,
- // bottom: 0,
- // child: IconButton(
- // padding: EdgeInsets.zero,
- // onPressed: () =>
- // {feedBack(), _dynamicsController.resetSearch()},
- // icon: const Icon(Icons.history, size: 21),
- // ),
- // ),
- // ),
- // ),
],
),
),
@@ -229,7 +215,8 @@ class _DynamicsPageState extends State
return Obx(() => UpPanel(_dynamicsController.upData.value));
} else {
return const SliverToBoxAdapter(
- child: SizedBox(height: 80));
+ child: SizedBox(height: 80),
+ );
}
} else {
return const SliverToBoxAdapter(
@@ -240,15 +227,6 @@ class _DynamicsPageState extends State
}
},
),
- SliverToBoxAdapter(
- child: Container(
- height: 6,
- color: Theme.of(context)
- .colorScheme
- .onInverseSurface
- .withOpacity(0.5),
- ),
- ),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
@@ -280,6 +258,14 @@ class _DynamicsPageState extends State
}
},
);
+ } else if (data['msg'] == "账号未登录") {
+ return HttpError(
+ errMsg: data['msg'],
+ btnText: "去登录",
+ fn: () {
+ mineController.onLogin();
+ },
+ );
} else {
return HttpError(
errMsg: data['msg'],
diff --git a/lib/pages/dynamics/widgets/article_panel.dart b/lib/pages/dynamics/widgets/article_panel.dart
index e68d966d..19707435 100644
--- a/lib/pages/dynamics/widgets/article_panel.dart
+++ b/lib/pages/dynamics/widgets/article_panel.dart
@@ -34,25 +34,25 @@ Widget articlePanel(item, context, {floor = 1}) {
),
const SizedBox(height: 8),
],
- Text(
- item.modules.moduleDynamic.major.opus.title,
- style: Theme.of(context)
- .textTheme
- .titleMedium!
- .copyWith(fontWeight: FontWeight.bold),
- ),
- const SizedBox(height: 2),
- if (item.modules.moduleDynamic.major.opus.summary.text !=
- 'undefined') ...[
- Text(
- item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
- .text,
- maxLines: 4,
- style: const TextStyle(height: 1.55),
- overflow: TextOverflow.ellipsis,
- ),
- const SizedBox(height: 2),
- ],
+ // Text(
+ // item.modules.moduleDynamic.major.opus.title,
+ // style: Theme.of(context)
+ // .textTheme
+ // .titleMedium!
+ // .copyWith(fontWeight: FontWeight.bold),
+ // ),
+ // const SizedBox(height: 2),
+ // if (item.modules.moduleDynamic.major.opus.summary.text !=
+ // 'undefined') ...[
+ // Text(
+ // item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
+ // .text,
+ // maxLines: 4,
+ // style: const TextStyle(height: 1.55),
+ // overflow: TextOverflow.ellipsis,
+ // ),
+ // const SizedBox(height: 2),
+ // ],
picWidget(item, context)
],
),
diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart
index b6ea5eb9..0d3baecd 100644
--- a/lib/pages/dynamics/widgets/author_panel.dart
+++ b/lib/pages/dynamics/widgets/author_panel.dart
@@ -44,15 +44,19 @@ class AuthorPanel extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(
- item.modules.moduleAuthor.name,
- style: TextStyle(
- color: item.modules.moduleAuthor!.vip != null &&
- item.modules.moduleAuthor!.vip['status'] > 0
- ? const Color.fromARGB(255, 251, 100, 163)
- : Theme.of(context).colorScheme.onBackground,
- fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
- ),
+ Row(
+ children: [
+ Text(
+ item.modules.moduleAuthor.name,
+ style: TextStyle(
+ color: item.modules.moduleAuthor!.vip != null &&
+ item.modules.moduleAuthor!.vip['status'] > 0
+ ? const Color.fromARGB(255, 251, 100, 163)
+ : Theme.of(context).colorScheme.onBackground,
+ fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
+ ),
+ ),
+ ],
),
DefaultTextStyle.merge(
style: TextStyle(
diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart
index 680d21a2..e1beaeb2 100644
--- a/lib/pages/dynamics/widgets/content_panel.dart
+++ b/lib/pages/dynamics/widgets/content_panel.dart
@@ -1,5 +1,7 @@
// 内容
import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/preview/index.dart';
@@ -43,11 +45,20 @@ class _ContentState extends State {
if (len == 1) {
OpusPicsModel pictureItem = pics.first;
picList.add(pictureItem.url!);
- spanChilds.add(const TextSpan(text: '\n'));
+
+ /// 图片上方的空白间隔
+ // spanChilds.add(const TextSpan(text: '\n'));
spanChilds.add(
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
+ double maxWidth = box.maxWidth.truncateToDouble();
+ double maxHeight = box.maxWidth * 0.6; // 设置最大高度
+ double height = maxWidth *
+ 0.5 *
+ (pictureItem.height != null && pictureItem.width != null
+ ? pictureItem.height! / pictureItem.width!
+ : 1);
return GestureDetector(
onTap: () {
showDialog(
@@ -58,18 +69,29 @@ class _ContentState extends State {
},
);
},
- child: Padding(
- padding: const EdgeInsets.only(top: 4),
- child: NetworkImgLayer(
- src: pictureItem.url,
+ child: Container(
+ padding: const EdgeInsets.only(top: 4),
+ constraints: BoxConstraints(maxHeight: maxHeight),
width: box.maxWidth / 2,
- height: box.maxWidth *
- 0.5 *
- (pictureItem.height != null && pictureItem.width != null
- ? pictureItem.height! / pictureItem.width!
- : 1),
- ),
- ),
+ height: height,
+ child: Stack(
+ children: [
+ Positioned.fill(
+ child: NetworkImgLayer(
+ src: pictureItem.url,
+ width: maxWidth / 2,
+ height: height,
+ ),
+ ),
+ height > Get.size.height * 0.9
+ ? const PBadge(
+ text: '长图',
+ right: 8,
+ bottom: 8,
+ )
+ : const SizedBox(),
+ ],
+ )),
);
},
),
@@ -83,6 +105,7 @@ class _ContentState extends State {
list.add(
LayoutBuilder(
builder: (context, BoxConstraints box) {
+ double maxWidth = box.maxWidth.truncateToDouble();
return GestureDetector(
onTap: () {
showDialog(
@@ -95,8 +118,10 @@ class _ContentState extends State {
},
child: NetworkImgLayer(
src: pics[i].url,
- width: box.maxWidth,
- height: box.maxWidth,
+ width: maxWidth,
+ height: maxWidth,
+ origAspectRatio:
+ pics[i].width!.toInt() / pics[i].height!.toInt(),
),
);
},
@@ -107,7 +132,7 @@ class _ContentState extends State {
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
- double maxWidth = box.maxWidth;
+ double maxWidth = box.maxWidth.truncateToDouble();
double crossCount = len < 3 ? 2 : 3;
double height = maxWidth /
crossCount *
diff --git a/lib/pages/dynamics/widgets/pic_panel.dart b/lib/pages/dynamics/widgets/pic_panel.dart
index 25b22c21..4e94e6fd 100644
--- a/lib/pages/dynamics/widgets/pic_panel.dart
+++ b/lib/pages/dynamics/widgets/pic_panel.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@@ -87,7 +88,7 @@ Widget picWidget(item, context) {
childAspectRatio: aspectRatio,
children: list,
),
- if (len == 1 && origAspectRatio < 0.4)
+ if (len == 1 && height > Get.size.height * 0.9)
const PBadge(
text: '长图',
top: null,
diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart
index 8b7dcd69..5ffee5f1 100644
--- a/lib/pages/dynamics/widgets/rich_node_panel.dart
+++ b/lib/pages/dynamics/widgets/rich_node_panel.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
+import 'package:pilipala/http/search.dart';
// 富文本
InlineSpan richNode(item, context) {
@@ -17,6 +19,17 @@ InlineSpan richNode(item, context) {
// 动态页面 richTextNodes 层级可能与主页动态层级不同
richTextNodes =
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
+ if (item.modules.moduleDynamic.major.opus.title != null) {
+ spanChilds.add(
+ TextSpan(
+ text: item.modules.moduleDynamic.major.opus.title + '\n',
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(fontWeight: FontWeight.bold),
+ ),
+ );
+ }
}
if (richTextNodes == null || richTextNodes.isEmpty) {
return spacer;
@@ -191,6 +204,39 @@ InlineSpan richNode(item, context) {
),
);
}
+ // 投稿
+ if (i.type == 'RICH_TEXT_NODE_TYPE_BV') {
+ spanChilds.add(
+ WidgetSpan(
+ alignment: PlaceholderAlignment.middle,
+ child: Icon(
+ Icons.play_circle_outline_outlined,
+ size: 16,
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ ),
+ );
+ spanChilds.add(
+ WidgetSpan(
+ alignment: PlaceholderAlignment.middle,
+ child: GestureDetector(
+ onTap: () async {
+ try {
+ int cid = await SearchHttp.ab2c(bvid: i.rid);
+ Get.toNamed('/video?bvid=${i.rid}&cid=$cid',
+ arguments: {'pic': null, 'heroTag': i.rid});
+ } catch (err) {
+ SmartDialog.showToast(err.toString());
+ }
+ },
+ child: Text(
+ '${i.text} ',
+ style: authorStyle,
+ ),
+ ),
+ ),
+ );
+ }
}
// if (contentType == 'major' &&
// item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart
index 8a2c5dac..fd0ae642 100644
--- a/lib/pages/dynamics/widgets/up_panel.dart
+++ b/lib/pages/dynamics/widgets/up_panel.dart
@@ -1,16 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/up.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/pages/dynamics/controller.dart';
import 'package:pilipala/utils/feed_back.dart';
-import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
class UpPanel extends StatefulWidget {
- final FollowUpModel? upData;
+ final FollowUpModel upData;
const UpPanel(this.upData, {Key? key}) : super(key: key);
@override
@@ -24,39 +22,22 @@ class _UpPanelState extends State {
List upList = [];
List liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
- Box userInfoCache = GStrorage.userInfo;
- var userInfo;
+ late MyInfo userInfo;
- @override
- void initState() {
- super.initState();
- upList = widget.upData!.upList!;
- if (widget.upData!.liveUsers != null) {
- liveList = widget.upData!.liveUsers!.items!;
- }
- upList.insert(
- 0,
- UpItem(
- face: 'https://files.catbox.moe/8uc48f.png', uname: '全部动态', mid: -1),
- );
- userInfo = userInfoCache.get('userInfoCache');
- upList.insert(
- 1,
- UpItem(
- face: userInfo.face,
- uname: '我',
- mid: userInfo.mid,
- ),
- );
+ void listFormat() {
+ userInfo = widget.upData.myInfo!;
+ upList = widget.upData.upList!;
+ liveList = widget.upData.liveList!;
}
@override
Widget build(BuildContext context) {
+ listFormat();
return SliverPersistentHeader(
floating: true,
pinned: false,
delegate: _SliverHeaderDelegate(
- height: 124,
+ height: liveList.isNotEmpty || upList.isNotEmpty ? 126 : 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -91,7 +72,7 @@ class _UpPanelState extends State {
color: Theme.of(context).colorScheme.background,
child: Row(
children: [
- Expanded(
+ Flexible(
child: ListView(
scrollDirection: Axis.horizontal,
controller: scrollController,
@@ -121,6 +102,13 @@ class _UpPanelState extends State