merge main
This commit is contained in:
@ -40,9 +40,13 @@
|
|||||||
android:label="PiliPala"
|
android:label="PiliPala"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:enableOnBackInvokedCallback="true">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:fullBackupContent="false"
|
||||||
|
tools:replace="android:allowBackup">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
@ -244,6 +248,24 @@
|
|||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service
|
||||||
|
android:name="com.ryanheise.audioservice.AudioService"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
@ -256,6 +278,8 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
<!--
|
<!--
|
||||||
Media access permissions.
|
Media access permissions.
|
||||||
Android 13 or higher.
|
Android 13 or higher.
|
||||||
|
|||||||
BIN
android/app/src/main/res/drawable-hdpi/ic_notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-hdpi/ic_notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 528 B |
BIN
android/app/src/main/res/drawable-mdpi/ic_notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-mdpi/ic_notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 337 B |
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification_icon.png
Normal file
BIN
android/app/src/main/res/drawable-xhdpi/ic_notification_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 648 B |
Binary file not shown.
|
After Width: | Height: | Size: 962 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M18,13c0,3.31 -2.69,6 -6,6s-6,-2.69 -6,-6s2.69,-6 6,-6v4l5,-5l-5,-5v4c-4.42,0 -8,3.58 -8,8c0,4.42 3.58,8 8,8s8,-3.58 8,-8H18z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M10.86,15.94l0,-4.27l-0.09,0l-1.77,0.63l0,0.69l1.01,-0.31l0,3.26z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12.25,13.44v0.74c0,1.9 1.31,1.82 1.44,1.82c0.14,0 1.44,0.09 1.44,-1.82v-0.74c0,-1.9 -1.31,-1.82 -1.44,-1.82C13.55,11.62 12.25,11.53 12.25,13.44zM14.29,13.32v0.97c0,0.77 -0.21,1.03 -0.59,1.03c-0.38,0 -0.6,-0.26 -0.6,-1.03v-0.97c0,-0.75 0.22,-1.01 0.59,-1.01C14.07,12.3 14.29,12.57 14.29,13.32z"/>
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M11.99,5V1l-5,5l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6s-6,-2.69 -6,-6h-2c0,4.42 3.58,8 8,8s8,-3.58 8,-8S16.41,5 11.99,5z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M10.89,16h-0.85v-3.26l-1.01,0.31v-0.69l1.77,-0.63h0.09V16z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M15.17,14.24c0,0.32 -0.03,0.6 -0.1,0.82s-0.17,0.42 -0.29,0.57s-0.28,0.26 -0.45,0.33s-0.37,0.1 -0.59,0.1s-0.41,-0.03 -0.59,-0.1s-0.33,-0.18 -0.46,-0.33s-0.23,-0.34 -0.3,-0.57s-0.11,-0.5 -0.11,-0.82V13.5c0,-0.32 0.03,-0.6 0.1,-0.82s0.17,-0.42 0.29,-0.57s0.28,-0.26 0.45,-0.33s0.37,-0.1 0.59,-0.1s0.41,0.03 0.59,0.1c0.18,0.07 0.33,0.18 0.46,0.33s0.23,0.34 0.3,0.57s0.11,0.5 0.11,0.82V14.24zM14.32,13.38c0,-0.19 -0.01,-0.35 -0.04,-0.48s-0.07,-0.23 -0.12,-0.31s-0.11,-0.14 -0.19,-0.17s-0.16,-0.05 -0.25,-0.05s-0.18,0.02 -0.25,0.05s-0.14,0.09 -0.19,0.17s-0.09,0.18 -0.12,0.31s-0.04,0.29 -0.04,0.48v0.97c0,0.19 0.01,0.35 0.04,0.48s0.07,0.24 0.12,0.32s0.11,0.14 0.19,0.17s0.16,0.05 0.25,0.05s0.18,-0.02 0.25,-0.05s0.14,-0.09 0.19,-0.17s0.09,-0.19 0.11,-0.32s0.04,-0.29 0.04,-0.48V13.38z"/>
|
||||||
|
</vector>
|
||||||
@ -2,4 +2,5 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
3
android/app/src/main/res/raw/keep.xml
Normal file
3
android/app/src/main/res/raw/keep.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:keep="@drawable/*" />
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
assets/images/ai.png
Normal file
BIN
assets/images/ai.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
4
change_log/1.0.10.1016.md
Normal file
4
change_log/1.0.10.1016.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
## 1.0.10
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 长按倍速抬起后未恢复默认倍速
|
||||||
26
change_log/1.0.11.1112.md
Normal file
26
change_log/1.0.11.1112.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
## 1.0.11
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 适配了原生媒体通知栏 @Daydreamer-riri
|
||||||
|
+ 视频主题图标 @Daydreamer-riri
|
||||||
|
+ 关闭软件后自动画中画播放
|
||||||
|
+ UP主分组管理
|
||||||
|
+ md2样式底栏
|
||||||
|
+
|
||||||
|
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 历史记录记忆播放
|
||||||
|
+ 部分类型视频连播
|
||||||
|
+ 播放速度选择框不支持返回手势
|
||||||
|
+ 播放速度选择框不支持返回手势
|
||||||
|
+ 视频播放速度总是显示1.0X
|
||||||
|
+ 评论页面计数错误
|
||||||
|
+ 退出视频还有声音
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 视频加载速度
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
22
change_log/1.0.7.0908.md
Normal file
22
change_log/1.0.7.0908.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
## 1.0.7
|
||||||
|
|
||||||
|
默认倍速、直播弹幕、专栏等功能开发中
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 弹幕设置、屏蔽功能
|
||||||
|
+ 不是很完美的后台播放功能
|
||||||
|
+ 不是很完美的画中画(pip)功能(Android端)
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 动态页面加载异常
|
||||||
|
+ 网络异常时页面空白
|
||||||
|
+ 竖屏全屏状态栏问题
|
||||||
|
+ iOS端代理请求异常
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 图片预览
|
||||||
|
+ 全屏播放时自动旋转
|
||||||
|
+ 转发内容增加视频标题
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
24
change_log/1.0.8.0917.md
Normal file
24
change_log/1.0.8.0917.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
## 1.0.8
|
||||||
|
|
||||||
|
直播弹幕、循环播放等功能开发中
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 用户拉黑功能
|
||||||
|
+ gif图片保存
|
||||||
|
+ 删除已看历史记录
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 弹幕数量较少
|
||||||
|
+ 弹幕屏蔽设置自动记忆
|
||||||
|
+ 动态页面渲染
|
||||||
|
+ 用户主页数据错乱
|
||||||
|
+ 大家都在搜空白
|
||||||
|
+ 默认自动全屏,顶部操作栏丢失
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 全屏状态栏区域显示优化
|
||||||
|
+ 图片保存至PiliPala文件夹
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
28
change_log/1.0.9.1015.md
Normal file
28
change_log/1.0.9.1015.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
## 1.0.9
|
||||||
|
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 自定义倍速、默认倍速
|
||||||
|
+ 历史记录搜索
|
||||||
|
+ 收藏夹搜索
|
||||||
|
+ 历史记录多选删除
|
||||||
|
+ 视频循环播放
|
||||||
|
+ 免登录看1080P
|
||||||
|
+ 评论区视频链接跳转
|
||||||
|
+ up主分组
|
||||||
|
+ up主投稿搜索
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 搜索视频标题乱码
|
||||||
|
+ 屏幕帧率
|
||||||
|
+ 动态页面渲染
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 快进手势
|
||||||
|
+ 视频简介链接匹配
|
||||||
|
+ 视频全屏时安全区域
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -16,10 +16,15 @@ PODS:
|
|||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- image_gallery_saver (2.0.2):
|
- gt3_flutter_plugin (0.0.8):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
<<<<<<< HEAD
|
||||||
- just_audio (0.0.1):
|
- just_audio (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
=======
|
||||||
|
- GT3Captcha-iOS
|
||||||
|
- GT3Captcha-iOS (0.15.8.3)
|
||||||
|
>>>>>>> main
|
||||||
- media_kit_libs_ios_video (1.0.4):
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_native_event_loop (1.0.0):
|
- media_kit_native_event_loop (1.0.0):
|
||||||
@ -34,6 +39,8 @@ PODS:
|
|||||||
- permission_handler_apple (9.1.1):
|
- permission_handler_apple (9.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
|
- saver_gallery (0.0.1):
|
||||||
|
- Flutter
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
@ -43,6 +50,8 @@ PODS:
|
|||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
- status_bar_control (3.2.1):
|
- status_bar_control (3.2.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- system_proxy (0.0.1):
|
||||||
|
- Flutter
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
@ -62,18 +71,24 @@ DEPENDENCIES:
|
|||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
|
<<<<<<< HEAD
|
||||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||||
- just_audio (from `.symlinks/plugins/just_audio/ios`)
|
- just_audio (from `.symlinks/plugins/just_audio/ios`)
|
||||||
|
=======
|
||||||
|
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
|
||||||
|
>>>>>>> main
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- status_bar_control (from `.symlinks/plugins/status_bar_control/ios`)
|
- status_bar_control (from `.symlinks/plugins/status_bar_control/ios`)
|
||||||
|
- system_proxy (from `.symlinks/plugins/system_proxy/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
@ -83,6 +98,7 @@ DEPENDENCIES:
|
|||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- FMDB
|
- FMDB
|
||||||
|
- GT3Captcha-iOS
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
@ -100,10 +116,15 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_volume_controller:
|
flutter_volume_controller:
|
||||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
|
<<<<<<< HEAD
|
||||||
image_gallery_saver:
|
image_gallery_saver:
|
||||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||||
just_audio:
|
just_audio:
|
||||||
:path: ".symlinks/plugins/just_audio/ios"
|
:path: ".symlinks/plugins/just_audio/ios"
|
||||||
|
=======
|
||||||
|
gt3_flutter_plugin:
|
||||||
|
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
||||||
|
>>>>>>> main
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
@ -116,6 +137,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
saver_gallery:
|
||||||
|
:path: ".symlinks/plugins/saver_gallery/ios"
|
||||||
screen_brightness_ios:
|
screen_brightness_ios:
|
||||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
@ -124,6 +147,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
status_bar_control:
|
status_bar_control:
|
||||||
:path: ".symlinks/plugins/status_bar_control/ios"
|
:path: ".symlinks/plugins/status_bar_control/ios"
|
||||||
|
system_proxy:
|
||||||
|
:path: ".symlinks/plugins/system_proxy/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
volume_controller:
|
volume_controller:
|
||||||
@ -144,8 +169,13 @@ SPEC CHECKSUMS:
|
|||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
|
<<<<<<< HEAD
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
|
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
|
||||||
|
=======
|
||||||
|
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
||||||
|
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||||
|
>>>>>>> main
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
@ -153,10 +183,12 @@ SPEC CHECKSUMS:
|
|||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
|
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
||||||
|
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
|
|||||||
@ -140,6 +140,7 @@
|
|||||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
5A372F23F3CF0118D6526BAC /* [CP] Embed Pods Frameworks */,
|
5A372F23F3CF0118D6526BAC /* [CP] Embed Pods Frameworks */,
|
||||||
|
B78851E7B29A4C3961AC483C /* [CP] Copy Pods Resources */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
@ -268,6 +269,23 @@
|
|||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
};
|
};
|
||||||
|
B78851E7B29A4C3961AC483C /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
|||||||
@ -103,7 +103,10 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<<<<<<< HEAD
|
||||||
<!-- audio service配置 -->
|
<!-- audio service配置 -->
|
||||||
|
=======
|
||||||
|
>>>>>>> main
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
|
|||||||
96
lib/common/widgets/html_render.dart
Normal file
96
lib/common/widgets/html_render.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
// ignore: must_be_immutable
|
||||||
|
class HtmlRender extends StatelessWidget {
|
||||||
|
String? htmlContent;
|
||||||
|
final int? imgCount;
|
||||||
|
final List? imgList;
|
||||||
|
|
||||||
|
HtmlRender({
|
||||||
|
this.htmlContent,
|
||||||
|
this.imgCount,
|
||||||
|
this.imgList,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Html(
|
||||||
|
data: htmlContent,
|
||||||
|
onLinkTap: (url, buildContext, attributes) => {},
|
||||||
|
extensions: [
|
||||||
|
TagExtension(
|
||||||
|
tagsToExtend: {"img"},
|
||||||
|
builder: (extensionContext) {
|
||||||
|
try {
|
||||||
|
Map attributes = extensionContext.attributes;
|
||||||
|
List key = attributes.keys.toList();
|
||||||
|
String? imgUrl = key.contains('src')
|
||||||
|
? attributes['src']
|
||||||
|
: attributes['data-src'];
|
||||||
|
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/');
|
||||||
|
if (isMall) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
// bool inTable =
|
||||||
|
// extensionContext.element!.previousElementSibling == null ||
|
||||||
|
// extensionContext.element!.nextElementSibling == null;
|
||||||
|
// imgUrl = Utils().imageUrl(imgUrl!);
|
||||||
|
// return Image.network(
|
||||||
|
// imgUrl,
|
||||||
|
// width: isEmote ? 22 : null,
|
||||||
|
// height: isEmote ? 22 : null,
|
||||||
|
// );
|
||||||
|
return NetworkImgLayer(
|
||||||
|
width: isEmote ? 22 : Get.size.width - 24,
|
||||||
|
height: isEmote ? 22 : 200,
|
||||||
|
src: imgUrl,
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: {
|
||||||
|
"html": Style(
|
||||||
|
fontSize: FontSize.medium,
|
||||||
|
lineHeight: LineHeight.percent(140),
|
||||||
|
),
|
||||||
|
"body": Style(margin: Margins.zero, padding: HtmlPaddings.zero),
|
||||||
|
"a": Style(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
textDecoration: TextDecoration.none,
|
||||||
|
),
|
||||||
|
"p": Style(
|
||||||
|
margin: Margins.only(bottom: 10),
|
||||||
|
),
|
||||||
|
"span": Style(
|
||||||
|
fontSize: FontSize.medium,
|
||||||
|
height: Height(1.65),
|
||||||
|
),
|
||||||
|
"div": Style(height: Height.auto()),
|
||||||
|
"li > p": Style(
|
||||||
|
display: Display.inline,
|
||||||
|
),
|
||||||
|
"li": Style(
|
||||||
|
padding: HtmlPaddings.only(bottom: 4),
|
||||||
|
textAlign: TextAlign.justify,
|
||||||
|
),
|
||||||
|
"img": Style(margin: Margins.only(top: 4, bottom: 4)),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import 'package:pilipala/common/constants.dart';
|
|||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
@ -27,6 +28,11 @@ class VideoCardV extends StatelessWidget {
|
|||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool isStringNumeric(String str) {
|
||||||
|
RegExp numericRegex = RegExp(r'^\d+$');
|
||||||
|
return numericRegex.hasMatch(str);
|
||||||
|
}
|
||||||
|
|
||||||
void onPushDetail(heroTag) async {
|
void onPushDetail(heroTag) async {
|
||||||
String goto = videoItem.goto;
|
String goto = videoItem.goto;
|
||||||
switch (goto) {
|
switch (goto) {
|
||||||
@ -62,6 +68,47 @@ class VideoCardV extends StatelessWidget {
|
|||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
// 动态
|
||||||
|
case 'picture':
|
||||||
|
try {
|
||||||
|
String dynamicType = 'picture';
|
||||||
|
String uri = videoItem.uri;
|
||||||
|
String id = '';
|
||||||
|
if (videoItem.uri.startsWith('bilibili://article/')) {
|
||||||
|
// https://www.bilibili.com/read/cv27063554
|
||||||
|
dynamicType = 'read';
|
||||||
|
RegExp regex = RegExp(r'\d+');
|
||||||
|
Match match = regex.firstMatch(videoItem.uri)!;
|
||||||
|
String matchedNumber = match.group(0)!;
|
||||||
|
videoItem.param = int.parse(matchedNumber);
|
||||||
|
id = 'cv${videoItem.param}';
|
||||||
|
}
|
||||||
|
if (uri.startsWith('http')) {
|
||||||
|
String path = Uri.parse(uri).path;
|
||||||
|
if (isStringNumeric(path.split('/')[1])) {
|
||||||
|
// 请求接口
|
||||||
|
var res =
|
||||||
|
await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
|
||||||
|
if (res['status']) {
|
||||||
|
Get.toNamed('/dynamicDetail', arguments: {
|
||||||
|
'item': res['data'],
|
||||||
|
'floor': 1,
|
||||||
|
'action': 'detail'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
|
'url': uri,
|
||||||
|
'title': videoItem.title,
|
||||||
|
'id': id,
|
||||||
|
'dynamicType': dynamicType
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
SmartDialog.showToast(videoItem.goto);
|
SmartDialog.showToast(videoItem.goto);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
@ -112,12 +159,22 @@ class VideoCardV extends StatelessWidget {
|
|||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (crossAxisCount == 1 && videoItem.duration != null)
|
if (videoItem.duration != null)
|
||||||
PBadge(
|
if (crossAxisCount == 1) ...[
|
||||||
bottom: 10,
|
PBadge(
|
||||||
right: 10,
|
bottom: 10,
|
||||||
text: videoItem.duration,
|
right: 10,
|
||||||
)
|
text: videoItem.duration,
|
||||||
|
)
|
||||||
|
] else ...[
|
||||||
|
PBadge(
|
||||||
|
bottom: 6,
|
||||||
|
right: 7,
|
||||||
|
size: 'small',
|
||||||
|
type: 'gray',
|
||||||
|
text: videoItem.duration,
|
||||||
|
)
|
||||||
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -174,7 +231,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (crossAxisCount > 1) ...[
|
if (crossAxisCount > 1) ...[
|
||||||
const SizedBox(height: 3),
|
const SizedBox(height: 2),
|
||||||
VideoStat(
|
VideoStat(
|
||||||
videoItem: videoItem,
|
videoItem: videoItem,
|
||||||
),
|
),
|
||||||
@ -247,7 +304,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
const SizedBox(height: 26)
|
const SizedBox(height: 24)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -268,23 +325,18 @@ class VideoStat extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return RichText(
|
||||||
children: [
|
maxLines: 1,
|
||||||
Text(
|
text: TextSpan(
|
||||||
'${videoItem.stat.view}观看',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Text(
|
children: [
|
||||||
' • ${videoItem.stat.danmu}弹幕',
|
TextSpan(text: '${videoItem.stat.view}观看'),
|
||||||
style: TextStyle(
|
TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
],
|
||||||
color: Theme.of(context).colorScheme.outline,
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,6 +97,9 @@ class Api {
|
|||||||
// 操作用户关系
|
// 操作用户关系
|
||||||
static const String relationMod = '/x/relation/modify';
|
static const String relationMod = '/x/relation/modify';
|
||||||
|
|
||||||
|
// 相互关系查询
|
||||||
|
static const String relationSearch = '/x/space/wbi/acc/relation';
|
||||||
|
|
||||||
// 评论列表
|
// 评论列表
|
||||||
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
||||||
static const String replyList = '/x/v2/reply';
|
static const String replyList = '/x/v2/reply';
|
||||||
@ -126,12 +129,14 @@ class Api {
|
|||||||
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
||||||
|
|
||||||
/// 收藏夹 详情
|
/// 收藏夹 详情
|
||||||
/// media_id int 收藏夹id
|
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||||
/// pn int 当前页
|
/// pn int 当前页
|
||||||
/// ps int pageSize
|
/// ps int pageSize
|
||||||
/// keyword String 搜索词
|
/// keyword String 搜索词
|
||||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||||
/// tid int 分区id
|
/// tid int 分区id
|
||||||
|
/// platform web
|
||||||
|
/// type 0 当前收藏夹 1 全部收藏夹
|
||||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||||
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
||||||
|
|
||||||
@ -164,6 +169,12 @@ class Api {
|
|||||||
// 清空历史记录
|
// 清空历史记录
|
||||||
static const String clearHistory = '/x/v2/history/clear';
|
static const String clearHistory = '/x/v2/history/clear';
|
||||||
|
|
||||||
|
// 删除某条历史记录
|
||||||
|
static const String delHistory = '/x/v2/history/delete';
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static const String searchHistory = '/x/web-goblin/history/search';
|
||||||
|
|
||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
'https://s.search.bilibili.com/main/hotword';
|
'https://s.search.bilibili.com/main/hotword';
|
||||||
@ -239,6 +250,9 @@ class Api {
|
|||||||
// wts=1689767832
|
// wts=1689767832
|
||||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||||
|
|
||||||
|
// 用户动态搜索
|
||||||
|
static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||||
|
|
||||||
// 用户动态
|
// 用户动态
|
||||||
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
||||||
|
|
||||||
@ -285,6 +299,9 @@ class Api {
|
|||||||
// 黑名单
|
// 黑名单
|
||||||
static const String blackLst = '/x/relation/blacks';
|
static const String blackLst = '/x/relation/blacks';
|
||||||
|
|
||||||
|
// 移除黑名单
|
||||||
|
static const String removeBlack = '/x/relation/modify';
|
||||||
|
|
||||||
// github 获取最新版
|
// github 获取最新版
|
||||||
static const String latestApp =
|
static const String latestApp =
|
||||||
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
||||||
@ -294,4 +311,65 @@ class Api {
|
|||||||
static const String onlineTotal = '/x/player/online/total';
|
static const String onlineTotal = '/x/player/online/total';
|
||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
// up主分组
|
||||||
|
static const String followUpTag = '/x/relation/tags';
|
||||||
|
|
||||||
|
// 设置Up主分组
|
||||||
|
// 0 添加至默认分组 否则使用,分割tagid
|
||||||
|
static const String addUsers = '/x/relation/tags/addUsers';
|
||||||
|
|
||||||
|
// 获取指定分组下的up
|
||||||
|
static const String followUpGroup = '/x/relation/tag';
|
||||||
|
|
||||||
|
// 获取某个动态详情
|
||||||
|
// timezone_offset=-480
|
||||||
|
// id=849312409672744983
|
||||||
|
// features=itemOpusStyle
|
||||||
|
static const String dynamicDetail = '/x/polymer/web-dynamic/v1/detail';
|
||||||
|
|
||||||
|
// AI总结
|
||||||
|
/// https://api.bilibili.com/x/web-interface/view/conclusion/get?
|
||||||
|
/// bvid=BV1ju4y1s7kn&
|
||||||
|
/// cid=1296086601&
|
||||||
|
/// up_mid=4641697&
|
||||||
|
/// w_rid=1607c6c5a4a35a1297e31992220900ae&
|
||||||
|
/// wts=1697033079
|
||||||
|
static const String aiConclusion = '/x/web-interface/view/conclusion/get';
|
||||||
|
|
||||||
|
// captcha验证码
|
||||||
|
static const String getCaptcha =
|
||||||
|
'https://passport.bilibili.com/x/passport-login/captcha?source=main_web';
|
||||||
|
|
||||||
|
// web端短信验证码
|
||||||
|
static const String smsCode =
|
||||||
|
'https://passport.bilibili.com/x/passport-login/web/sms/send';
|
||||||
|
|
||||||
|
// web端验证码登录
|
||||||
|
|
||||||
|
// web端密码登录
|
||||||
|
|
||||||
|
// app端短信验证码
|
||||||
|
static const String appSmsCode =
|
||||||
|
'https://passport.bilibili.com/x/passport-login/sms/send';
|
||||||
|
|
||||||
|
// app端验证码登录
|
||||||
|
|
||||||
|
// 获取短信验证码
|
||||||
|
// static const String appSafeSmsCode =
|
||||||
|
// 'https://passport.bilibili.com/x/safecenter/common/sms/send';
|
||||||
|
|
||||||
|
/// app端密码登录
|
||||||
|
/// username
|
||||||
|
/// password
|
||||||
|
/// key
|
||||||
|
/// rhash
|
||||||
|
static const String loginInByPwdApi =
|
||||||
|
'https://passport.bilibili.com/x/passport-login/oauth2/login';
|
||||||
|
|
||||||
|
/// 密码加密密钥
|
||||||
|
/// disable_rcmd
|
||||||
|
/// local_id
|
||||||
|
static const getWebKey =
|
||||||
|
'https://passport.bilibili.com/x/passport-login/web/key';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,4 +23,31 @@ class BlackHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 移除黑名单
|
||||||
|
static Future removeBlack({required int fid}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.removeBlack,
|
||||||
|
queryParameters: {
|
||||||
|
'act': 6,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
'fid': fid,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
're_src': 116,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': [],
|
||||||
|
'msg': '操作成功',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,4 +2,37 @@ class HttpString {
|
|||||||
static const String baseUrl = 'https://www.bilibili.com';
|
static const String baseUrl = 'https://www.bilibili.com';
|
||||||
static const String baseApiUrl = 'https://api.bilibili.com';
|
static const String baseApiUrl = 'https://api.bilibili.com';
|
||||||
static const String tUrl = 'https://api.vc.bilibili.com';
|
static const String tUrl = 'https://api.vc.bilibili.com';
|
||||||
|
static const List<int> validateStatusCodes = [
|
||||||
|
302,
|
||||||
|
304,
|
||||||
|
307,
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
404,
|
||||||
|
405,
|
||||||
|
409,
|
||||||
|
412,
|
||||||
|
500,
|
||||||
|
503,
|
||||||
|
504,
|
||||||
|
509,
|
||||||
|
616,
|
||||||
|
617,
|
||||||
|
625,
|
||||||
|
626,
|
||||||
|
628,
|
||||||
|
629,
|
||||||
|
632,
|
||||||
|
643,
|
||||||
|
650,
|
||||||
|
652,
|
||||||
|
658,
|
||||||
|
662,
|
||||||
|
688,
|
||||||
|
689,
|
||||||
|
701,
|
||||||
|
799,
|
||||||
|
8888
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,17 +17,11 @@ class DanmakaHttp {
|
|||||||
'oid': cid,
|
'oid': cid,
|
||||||
'segment_index': segmentIndex,
|
'segment_index': segmentIndex,
|
||||||
};
|
};
|
||||||
|
var response = await Request().get(
|
||||||
// 计算函数
|
Api.webDanmaku,
|
||||||
Future<DmSegMobileReply> computeTask(Map<String, int> params) async {
|
data: params,
|
||||||
var response = await Request().get(
|
extra: {'resType': ResponseType.bytes},
|
||||||
Api.webDanmaku,
|
);
|
||||||
data: params,
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
extra: {'resType': ResponseType.bytes},
|
|
||||||
);
|
|
||||||
return DmSegMobileReply.fromBuffer(response.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await compute(computeTask, params);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class DynamicsHttp {
|
|||||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
'data': [],
|
||||||
@ -85,4 +86,35 @@ class DynamicsHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
static Future dynamicDetail({
|
||||||
|
String? id,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(Api.dynamicDetail, data: {
|
||||||
|
'timezone_offset': -480,
|
||||||
|
'id': id,
|
||||||
|
'features': 'itemOpusStyle',
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicItemModel.fromJson(res.data['data']['item']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': err.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
lib/http/html.dart
Normal file
103
lib/http/html.dart
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import 'package:html/dom.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
|
||||||
|
class HtmlHttp {
|
||||||
|
// article
|
||||||
|
static Future reqHtml(id, dynamicType) async {
|
||||||
|
var response = await Request().get(
|
||||||
|
"https://www.bilibili.com/opus/$id",
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.data.contains('Redirecting to')) {
|
||||||
|
RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)');
|
||||||
|
Match match = regex.firstMatch(response.data)!;
|
||||||
|
String matchedString = match.group(0)!;
|
||||||
|
response = await Request().get(
|
||||||
|
'https:$matchedString' + '/',
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Document rootTree = parse(response.data);
|
||||||
|
// log(response.data.body.toString());
|
||||||
|
Element body = rootTree.body!;
|
||||||
|
Element appDom = body.querySelector('#app')!;
|
||||||
|
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
||||||
|
// 头像
|
||||||
|
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
|
||||||
|
avatar = 'https:${avatar.split('@')[0]}';
|
||||||
|
String uname = authorHeader
|
||||||
|
.querySelector('.fixed-author-header__author__name')!
|
||||||
|
.text;
|
||||||
|
|
||||||
|
// 动态详情
|
||||||
|
Element opusDetail = appDom.querySelector('.opus-detail')!;
|
||||||
|
// 发布时间
|
||||||
|
String updateTime =
|
||||||
|
opusDetail.querySelector('.opus-module-author__pub__text')!.text;
|
||||||
|
//
|
||||||
|
String opusContent =
|
||||||
|
opusDetail.querySelector('.opus-module-content')!.innerHtml;
|
||||||
|
String test = opusDetail
|
||||||
|
.querySelector('.horizontal-scroll-album__pic__img')!
|
||||||
|
.innerHtml;
|
||||||
|
String commentId = opusDetail
|
||||||
|
.querySelector('.bili-comment-container')!
|
||||||
|
.className
|
||||||
|
.split(' ')[1]
|
||||||
|
.split('-')[2];
|
||||||
|
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'avatar': avatar,
|
||||||
|
'uname': uname,
|
||||||
|
'updateTime': updateTime,
|
||||||
|
'content': test + opusContent,
|
||||||
|
'commentId': int.parse(commentId)
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
print('err: $err');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read
|
||||||
|
static Future reqReadHtml(id, dynamicType) async {
|
||||||
|
var response = await Request().get(
|
||||||
|
"https://www.bilibili.com/$dynamicType/$id/",
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
|
);
|
||||||
|
Document rootTree = parse(response.data);
|
||||||
|
Element body = rootTree.body!;
|
||||||
|
Element appDom = body.querySelector('#app')!;
|
||||||
|
Element authorHeader = appDom.querySelector('.up-left')!;
|
||||||
|
// 头像
|
||||||
|
// String avatar =
|
||||||
|
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
|
||||||
|
// print(avatar);
|
||||||
|
// avatar = 'https:${avatar.split('@')[0]}';
|
||||||
|
String uname = authorHeader.querySelector('.up-name')!.text.trim();
|
||||||
|
// 动态详情
|
||||||
|
Element opusDetail = appDom.querySelector('.article-content')!;
|
||||||
|
// 发布时间
|
||||||
|
// String updateTime =
|
||||||
|
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
|
||||||
|
// print(updateTime);
|
||||||
|
|
||||||
|
//
|
||||||
|
String opusContent =
|
||||||
|
opusDetail.querySelector('#read-article-holder')!.innerHtml;
|
||||||
|
RegExp digitRegExp = RegExp(r'\d+');
|
||||||
|
Iterable<Match> matches = digitRegExp.allMatches(id);
|
||||||
|
String number = matches.first.group(0)!;
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'avatar': '',
|
||||||
|
'uname': uname,
|
||||||
|
'updateTime': '',
|
||||||
|
'content': opusContent,
|
||||||
|
'commentId': int.parse(number)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,12 +4,13 @@ import 'dart:io';
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:cookie_jar/cookie_jar.dart';
|
import 'package:cookie_jar/cookie_jar.dart';
|
||||||
|
import 'package:dio/io.dart';
|
||||||
|
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
import 'package:pilipala/http/interceptor.dart';
|
import 'package:pilipala/http/interceptor.dart';
|
||||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
|
||||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||||
|
|
||||||
class Request {
|
class Request {
|
||||||
@ -17,6 +18,11 @@ class Request {
|
|||||||
static late CookieManager cookieManager;
|
static late CookieManager cookieManager;
|
||||||
static late final Dio dio;
|
static late final Dio dio;
|
||||||
factory Request() => _instance;
|
factory Request() => _instance;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
static Box localCache = GStrorage.localCache;
|
||||||
|
late dynamic enableSystemProxy;
|
||||||
|
late String systemProxyHost;
|
||||||
|
late String systemProxyPort;
|
||||||
|
|
||||||
/// 设置cookie
|
/// 设置cookie
|
||||||
static setCookie() async {
|
static setCookie() async {
|
||||||
@ -41,8 +47,8 @@ class Request {
|
|||||||
log("setCookie, ${e.toString()}");
|
log("setCookie, ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setOptionsHeaders(userInfo);
|
|
||||||
}
|
}
|
||||||
|
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
|
||||||
|
|
||||||
if (cookie.isEmpty) {
|
if (cookie.isEmpty) {
|
||||||
try {
|
try {
|
||||||
@ -60,9 +66,6 @@ class Request {
|
|||||||
static Future<String> getCsrf() async {
|
static Future<String> getCsrf() async {
|
||||||
var cookies = await cookieManager.cookieJar
|
var cookies = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
|
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
|
||||||
// for (var i in cookies) {
|
|
||||||
// print(i);
|
|
||||||
// }
|
|
||||||
String token = '';
|
String token = '';
|
||||||
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
|
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
|
||||||
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
||||||
@ -70,8 +73,10 @@ class Request {
|
|||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setOptionsHeaders(userInfo) {
|
static setOptionsHeaders(userInfo, status) {
|
||||||
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
if (status) {
|
||||||
|
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||||
|
}
|
||||||
dio.options.headers['env'] = 'prod';
|
dio.options.headers['env'] = 'prod';
|
||||||
dio.options.headers['app-key'] = 'android64';
|
dio.options.headers['app-key'] = 'android64';
|
||||||
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
||||||
@ -92,18 +97,47 @@ class Request {
|
|||||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||||
receiveTimeout: const Duration(milliseconds: 12000),
|
receiveTimeout: const Duration(milliseconds: 12000),
|
||||||
//Http请求头.
|
//Http请求头.
|
||||||
headers: {
|
headers: {},
|
||||||
// 'cookie': '',
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
enableSystemProxy =
|
||||||
|
setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
|
||||||
|
systemProxyHost =
|
||||||
|
localCache.get(LocalCacheKey.systemProxyHost, defaultValue: '');
|
||||||
|
systemProxyPort =
|
||||||
|
localCache.get(LocalCacheKey.systemProxyPort, defaultValue: '');
|
||||||
|
|
||||||
dio = Dio(options)
|
dio = Dio(options)
|
||||||
|
|
||||||
|
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
|
||||||
..httpClientAdapter = Http2Adapter(
|
..httpClientAdapter = Http2Adapter(
|
||||||
ConnectionManager(
|
ConnectionManager(
|
||||||
idleTimeout: const Duration(milliseconds: 10000),
|
idleTimeout: const Duration(milliseconds: 10000),
|
||||||
// Ignore bad certificate
|
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/// 设置代理
|
||||||
|
..httpClientAdapter = IOHttpClientAdapter(
|
||||||
|
createHttpClient: () {
|
||||||
|
final client = HttpClient();
|
||||||
|
// Config the client.
|
||||||
|
client.findProxy = (uri) {
|
||||||
|
if (enableSystemProxy) {
|
||||||
|
print('🌹:$systemProxyHost');
|
||||||
|
print('🌹:$systemProxyPort');
|
||||||
|
|
||||||
|
// return 'PROXY host:port';
|
||||||
|
return 'PROXY $systemProxyHost:$systemProxyPort';
|
||||||
|
} else {
|
||||||
|
// 不设置代理
|
||||||
|
return 'DIRECT';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
client.badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
return client;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
@ -118,30 +152,26 @@ class Request {
|
|||||||
|
|
||||||
dio.transformer = BackgroundTransformer();
|
dio.transformer = BackgroundTransformer();
|
||||||
dio.options.validateStatus = (status) {
|
dio.options.validateStatus = (status) {
|
||||||
return status! >= 200 && status < 300 || status == 304 || status == 302;
|
return status! >= 200 && status < 300 ||
|
||||||
|
HttpString.validateStatusCodes.contains(status);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get请求
|
* get请求
|
||||||
*/
|
*/
|
||||||
get(url, {data, cacheOptions, options, cancelToken, extra}) async {
|
get(url, {data, options, cancelToken, extra}) async {
|
||||||
Response response;
|
Response response;
|
||||||
Options options;
|
Options options = Options();
|
||||||
String ua = 'pc';
|
|
||||||
ResponseType resType = ResponseType.json;
|
ResponseType resType = ResponseType.json;
|
||||||
if (extra != null) {
|
if (extra != null) {
|
||||||
ua = extra!['ua'] ?? 'pc';
|
|
||||||
resType = extra!['resType'] ?? ResponseType.json;
|
resType = extra!['resType'] ?? ResponseType.json;
|
||||||
|
if (extra['ua'] != null) {
|
||||||
|
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (cacheOptions != null) {
|
options.responseType = resType;
|
||||||
cacheOptions.headers = {'user-agent': headerUa(ua)};
|
|
||||||
options = cacheOptions;
|
|
||||||
} else {
|
|
||||||
options = Options();
|
|
||||||
options.headers = {'user-agent': headerUa(ua)};
|
|
||||||
options.responseType = resType;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
response = await dio.get(
|
response = await dio.get(
|
||||||
url,
|
url,
|
||||||
@ -208,15 +238,19 @@ class Request {
|
|||||||
token.cancel("cancelled");
|
token.cancel("cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
String headerUa(ua) {
|
String headerUa({type = 'mob'}) {
|
||||||
String headerUa = '';
|
String headerUa = '';
|
||||||
if (ua == 'mob') {
|
if (type == 'mob') {
|
||||||
headerUa = Platform.isIOS
|
if (Platform.isIOS) {
|
||||||
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
|
headerUa =
|
||||||
: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
|
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1';
|
||||||
|
} else {
|
||||||
|
headerUa =
|
||||||
|
'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
headerUa =
|
headerUa =
|
||||||
'Mozilla/5.0 (MaciMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36';
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
|
||||||
}
|
}
|
||||||
return headerUa;
|
return headerUa;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,10 @@ class ApiInterceptor extends Interceptor {
|
|||||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
// 处理网络请求错误
|
// 处理网络请求错误
|
||||||
// handler.next(err);
|
// handler.next(err);
|
||||||
SmartDialog.showToast(await dioError(err));
|
SmartDialog.showToast(
|
||||||
|
await dioError(err),
|
||||||
|
displayType: SmartToastType.onlyRefresh,
|
||||||
|
);
|
||||||
super.onError(err, handler);
|
super.onError(err, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
177
lib/http/login.dart
Normal file
177
lib/http/login.dart
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class LoginHttp {
|
||||||
|
static Future queryCaptcha() async {
|
||||||
|
var res = await Request().get(Api.getCaptcha);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': CaptchaDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': res.message};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future sendSmsCode({
|
||||||
|
int? cid,
|
||||||
|
required int tel,
|
||||||
|
required String token,
|
||||||
|
required String challenge,
|
||||||
|
required String validate,
|
||||||
|
required String seccode,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.appSmsCode,
|
||||||
|
data: {
|
||||||
|
'cid': cid,
|
||||||
|
'tel': tel,
|
||||||
|
"source": "main_web",
|
||||||
|
'token': token,
|
||||||
|
'challenge': challenge,
|
||||||
|
'validate': validate,
|
||||||
|
'seccode': seccode,
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
// headers: {'user-agent': ApiConstants.userAgent}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端验证码
|
||||||
|
static Future sendWebSmsCode({
|
||||||
|
int? cid,
|
||||||
|
required int tel,
|
||||||
|
required String token,
|
||||||
|
required String challenge,
|
||||||
|
required String validate,
|
||||||
|
required String seccode,
|
||||||
|
}) async {
|
||||||
|
Map data = {
|
||||||
|
'cid': cid,
|
||||||
|
'tel': tel,
|
||||||
|
'token': token,
|
||||||
|
'challenge': challenge,
|
||||||
|
'validate': validate,
|
||||||
|
'seccode': seccode,
|
||||||
|
};
|
||||||
|
FormData formData = FormData.fromMap({...data});
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.smsCode,
|
||||||
|
data: formData,
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// web端验证码登录
|
||||||
|
static Future loginInByWebSmsCode() async {}
|
||||||
|
|
||||||
|
// web端密码登录
|
||||||
|
static Future liginInByWebPwd() async {}
|
||||||
|
|
||||||
|
// app端验证码
|
||||||
|
static Future sendAppSmsCode({
|
||||||
|
int? cid,
|
||||||
|
required int tel,
|
||||||
|
required String token,
|
||||||
|
required String challenge,
|
||||||
|
required String validate,
|
||||||
|
required String seccode,
|
||||||
|
}) async {
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'cid': cid,
|
||||||
|
'tel': tel,
|
||||||
|
'login_session_id': const Uuid().v4().replaceAll('-', ''),
|
||||||
|
'recaptcha_token': token,
|
||||||
|
'gee_challenge': challenge,
|
||||||
|
'gee_validate': validate,
|
||||||
|
'gee_seccode': seccode,
|
||||||
|
'channel': 'bili',
|
||||||
|
'buvid': buvid(),
|
||||||
|
'local_id': buvid(),
|
||||||
|
// 'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
'statistics': {
|
||||||
|
"appId": 1,
|
||||||
|
"platform": 3,
|
||||||
|
"version": "7.52.0",
|
||||||
|
"abtest": ""
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// FormData formData = FormData.fromMap({...data});
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.appSmsCode,
|
||||||
|
data: data,
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String buvid() {
|
||||||
|
var mac = <String>[];
|
||||||
|
var random = Random();
|
||||||
|
|
||||||
|
for (var i = 0; i < 6; i++) {
|
||||||
|
var min = 0;
|
||||||
|
var max = 0xff;
|
||||||
|
var num = (random.nextInt(max - min + 1) + min).toRadixString(16);
|
||||||
|
mac.add(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
var md5Str = md5.convert(utf8.encode(mac.join(':'))).toString();
|
||||||
|
var md5Arr = md5Str.split('');
|
||||||
|
return 'XY${md5Arr[2]}${md5Arr[12]}${md5Arr[22]}$md5Str';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取盐hash跟PubKey
|
||||||
|
static Future getWebKey() async {
|
||||||
|
var res = await Request().get(Api.getWebKey,
|
||||||
|
data: {'disable_rcmd': 0, 'local_id': LoginUtils.generateBuvid()});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': {}, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// app端密码登录
|
||||||
|
static Future loginInByMobPwd({
|
||||||
|
required String tel,
|
||||||
|
required String password,
|
||||||
|
required String key,
|
||||||
|
required String rhash,
|
||||||
|
}) async {
|
||||||
|
dynamic publicKey = RSAKeyParser().parse(key);
|
||||||
|
String passwordEncryptyed =
|
||||||
|
Encrypter(RSA(publicKey: publicKey)).encrypt(rhash + password).base64;
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
'username': tel,
|
||||||
|
'password': passwordEncryptyed,
|
||||||
|
'local_id': LoginUtils.generateBuvid(),
|
||||||
|
'disable_rcmd': "0",
|
||||||
|
};
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.loginInByPwdApi,
|
||||||
|
data: data,
|
||||||
|
options: Options(
|
||||||
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
print(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.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/archive.dart';
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
import 'package:pilipala/utils/wbi_sign.dart';
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
@ -18,6 +20,7 @@ class MemberHttp {
|
|||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberInfo,
|
Api.memberInfo,
|
||||||
data: params,
|
data: params,
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -65,7 +68,7 @@ class MemberHttp {
|
|||||||
int ps = 30,
|
int ps = 30,
|
||||||
int tid = 0,
|
int tid = 0,
|
||||||
int? pn,
|
int? pn,
|
||||||
String keyword = '',
|
String? keyword,
|
||||||
String order = 'pubdate',
|
String order = 'pubdate',
|
||||||
bool orderAvoided = true,
|
bool orderAvoided = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -74,7 +77,7 @@ class MemberHttp {
|
|||||||
'ps': ps,
|
'ps': ps,
|
||||||
'tid': tid,
|
'tid': tid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'keyword': keyword,
|
'keyword': keyword ?? '',
|
||||||
'order': order,
|
'order': order,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
@ -83,6 +86,7 @@ class MemberHttp {
|
|||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberArchive,
|
Api.memberArchive,
|
||||||
data: params,
|
data: params,
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -119,4 +123,96 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索用户动态
|
||||||
|
static Future memberDynamicSearch({int? pn, int? ps, int? mid}) async {
|
||||||
|
var res = await Request().get(Api.memberDynamic, data: {
|
||||||
|
'keyword': '海拔',
|
||||||
|
'mid': mid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
'platform': 'web'
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询分组
|
||||||
|
static Future followUpTags() async {
|
||||||
|
var res = await Request().get(Api.followUpTag);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']
|
||||||
|
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置分组
|
||||||
|
static Future addUsers(int? fids, String? tagids) async {
|
||||||
|
var res = await Request().post(Api.addUsers, queryParameters: {
|
||||||
|
'fids': fids,
|
||||||
|
'tagids': tagids ?? '0',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
}, data: {
|
||||||
|
'cross_domain': true
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取某分组下的up
|
||||||
|
static Future followUpGroup(
|
||||||
|
int? mid,
|
||||||
|
int? tagid,
|
||||||
|
int? pn,
|
||||||
|
int? ps,
|
||||||
|
) async {
|
||||||
|
var res = await Request().get(Api.followUpGroup, data: {
|
||||||
|
'mid': mid,
|
||||||
|
'tagid': tagid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
// FollowItemModel
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']
|
||||||
|
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ class ReplyHttp {
|
|||||||
Map errMap = {
|
Map errMap = {
|
||||||
-400: '请求错误',
|
-400: '请求错误',
|
||||||
-404: '无此项',
|
-404: '无此项',
|
||||||
12002: '当前页面评论功能已关闭"',
|
12002: '当前页面评论功能已关闭',
|
||||||
12009: '评论主体的type不合法',
|
12009: '评论主体的type不合法',
|
||||||
12061: 'UP主已关闭评论区',
|
12061: 'UP主已关闭评论区',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,37 +1,63 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/search/hot.dart';
|
import 'package:pilipala/models/search/hot.dart';
|
||||||
import 'package:pilipala/models/search/result.dart';
|
import 'package:pilipala/models/search/result.dart';
|
||||||
import 'package:pilipala/models/search/suggest.dart';
|
import 'package:pilipala/models/search/suggest.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class SearchHttp {
|
class SearchHttp {
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
static Future hotSearchList() async {
|
static Future hotSearchList() async {
|
||||||
var res = await Request().get(Api.hotSearchList);
|
var res = await Request().get(Api.hotSearchList);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
|
if (resultMap['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': HotSearchModel.fromJson(resultMap),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (res.data is Map<String, dynamic> && res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': HotSearchModel.fromJson(res.data),
|
'data': HotSearchModel.fromJson(res.data),
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': '请求错误 🙅',
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取搜索建议
|
// 获取搜索建议
|
||||||
static Future searchSuggest({required term}) async {
|
static Future searchSuggest({required term}) async {
|
||||||
var res = await Request().get(Api.serachSuggest,
|
var res = await Request().get(Api.serachSuggest,
|
||||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
res.data['result']['term'] = term;
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
return {
|
if (resultMap['code'] == 0) {
|
||||||
'status': true,
|
if (resultMap['result'] is Map) {
|
||||||
'data': SearchSuggestModel.fromJson(res.data['result']),
|
resultMap['result']['term'] = term;
|
||||||
};
|
}
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': resultMap['result'] is Map
|
||||||
|
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
@ -61,29 +87,44 @@ class SearchHttp {
|
|||||||
var res = await Request().get(Api.searchByType, data: reqData);
|
var res = await Request().get(Api.searchByType, data: reqData);
|
||||||
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
||||||
Object data;
|
Object data;
|
||||||
switch (searchType) {
|
try {
|
||||||
case SearchType.video:
|
switch (searchType) {
|
||||||
data = SearchVideoModel.fromJson(res.data['data']);
|
case SearchType.video:
|
||||||
break;
|
List<int> blackMidsList =
|
||||||
case SearchType.live_room:
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
data = SearchLiveModel.fromJson(res.data['data']);
|
for (var i in res.data['data']['result']) {
|
||||||
break;
|
// 屏蔽推广和拉黑用户
|
||||||
case SearchType.bili_user:
|
i['available'] = !blackMidsList.contains(i['mid']);
|
||||||
data = SearchUserModel.fromJson(res.data['data']);
|
}
|
||||||
break;
|
data = SearchVideoModel.fromJson(res.data['data']);
|
||||||
case SearchType.media_bangumi:
|
break;
|
||||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
case SearchType.live_room:
|
||||||
break;
|
data = SearchLiveModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.bili_user:
|
||||||
|
data = SearchUserModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.media_bangumi:
|
||||||
|
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
case SearchType.article:
|
||||||
|
data = SearchArticleModel.fromJson(res.data['data']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': data,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
}
|
}
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': data,
|
|
||||||
};
|
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
'data': [],
|
||||||
'msg': res.data['data']['numPages'] == 0 ? '没有相关数据' : '请求错误 🙅',
|
'msg': res.data['data'] != null && res.data['data']['numPages'] == 0
|
||||||
|
? '没有相关数据'
|
||||||
|
: res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import 'package:pilipala/models/user/fav_folder.dart';
|
|||||||
import 'package:pilipala/models/user/history.dart';
|
import 'package:pilipala/models/user/history.dart';
|
||||||
import 'package:pilipala/models/user/info.dart';
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/models/user/stat.dart';
|
import 'package:pilipala/models/user/stat.dart';
|
||||||
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
class UserHttp {
|
class UserHttp {
|
||||||
static Future<dynamic> userStat({required int mid}) async {
|
static Future<dynamic> userStat({required int mid}) async {
|
||||||
@ -70,14 +71,15 @@ class UserHttp {
|
|||||||
required int pn,
|
required int pn,
|
||||||
required int ps,
|
required int ps,
|
||||||
String keyword = '',
|
String keyword = '',
|
||||||
String order = 'mtime'}) async {
|
String order = 'mtime',
|
||||||
|
int type = 0}) async {
|
||||||
var res = await Request().get(Api.userFavFolderDetail, data: {
|
var res = await Request().get(Api.userFavFolderDetail, data: {
|
||||||
'media_id': mediaId,
|
'media_id': mediaId,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'ps': ps,
|
'ps': ps,
|
||||||
'keyword': keyword,
|
'keyword': keyword,
|
||||||
'order': order,
|
'order': order,
|
||||||
'type': 0,
|
'type': type,
|
||||||
'tid': 0,
|
'tid': 0,
|
||||||
'platform': 'web'
|
'platform': 'web'
|
||||||
});
|
});
|
||||||
@ -231,4 +233,64 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除历史记录
|
||||||
|
static Future delHistory(kid) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.delHistory,
|
||||||
|
queryParameters: {
|
||||||
|
'kid': kid,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '已删除'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 相互关系查询
|
||||||
|
static Future relationSearch(int mid) async {
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'mid': mid,
|
||||||
|
'token': '',
|
||||||
|
'platform': 'web',
|
||||||
|
'web_location': 1550101,
|
||||||
|
});
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.relationSearch,
|
||||||
|
data: {
|
||||||
|
'mid': mid,
|
||||||
|
'w_rid': params['w_rid'],
|
||||||
|
'wts': params['wts'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
// relation 主动状态
|
||||||
|
// 被动状态
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static Future searchHistory(
|
||||||
|
{required int pn, required String keyword}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.searchHistory,
|
||||||
|
data: {
|
||||||
|
'pn': pn,
|
||||||
|
'keyword': keyword,
|
||||||
|
'business': 'all',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,9 +9,11 @@ import 'package:pilipala/models/home/rcmd/result.dart';
|
|||||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||||
import 'package:pilipala/models/user/fav_folder.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/play/url.dart';
|
||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
/// res.data['code'] == 0 请求正常返回结果
|
/// res.data['code'] == 0 请求正常返回结果
|
||||||
/// res.data['data'] 为结果
|
/// res.data['data'] 为结果
|
||||||
@ -20,6 +22,9 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class VideoHttp {
|
class VideoHttp {
|
||||||
static Box localCache = GStrorage.localCache;
|
static Box localCache = GStrorage.localCache;
|
||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStrorage.setting;
|
||||||
|
static bool enableRcmdDynamic =
|
||||||
|
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||||
|
static Box userInfoCache = GStrorage.userInfo;
|
||||||
|
|
||||||
// 首页推荐视频
|
// 首页推荐视频
|
||||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||||
@ -73,6 +78,7 @@ class VideoHttp {
|
|||||||
for (var i in res.data['data']['items']) {
|
for (var i in res.data['data']['items']) {
|
||||||
// 屏蔽推广和拉黑用户
|
// 屏蔽推广和拉黑用户
|
||||||
if (i['card_goto'] != 'ad_av' &&
|
if (i['card_goto'] != 'ad_av' &&
|
||||||
|
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||||
(i['args'] != null &&
|
(i['args'] != null &&
|
||||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||||
list.add(RecVideoItemAppModel.fromJson(i));
|
list.add(RecVideoItemAppModel.fromJson(i));
|
||||||
@ -130,6 +136,11 @@ class VideoHttp {
|
|||||||
// 'platform': '',
|
// 'platform': '',
|
||||||
// 'high_quality': ''
|
// 'high_quality': ''
|
||||||
};
|
};
|
||||||
|
// 免登录查看1080p
|
||||||
|
if (userInfoCache.get('userInfoCache') == null &&
|
||||||
|
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||||
|
data['try_look'] = 1;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
var res = await Request().get(Api.videoUrl, data: data);
|
var res = await Request().get(Api.videoUrl, data: data);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
@ -411,4 +422,23 @@ class VideoHttp {
|
|||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future aiConclusion({
|
||||||
|
String? bvid,
|
||||||
|
int? cid,
|
||||||
|
int? upMid,
|
||||||
|
}) async {
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'bvid': bvid,
|
||||||
|
'cid': cid,
|
||||||
|
'up_mid': upMid,
|
||||||
|
});
|
||||||
|
var res = await Request().get(Api.aiConclusion, data: params);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': AiConclusionModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
=======
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
>>>>>>> main
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -14,6 +20,7 @@ import 'package:pilipala/pages/search/index.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/router/app_pages.dart';
|
import 'package:pilipala/router/app_pages.dart';
|
||||||
import 'package:pilipala/pages/main/view.dart';
|
import 'package:pilipala/pages/main/view.dart';
|
||||||
|
import 'package:pilipala/services/service_locator.dart';
|
||||||
import 'package:pilipala/utils/app_scheme.dart';
|
import 'package:pilipala/utils/app_scheme.dart';
|
||||||
import 'package:pilipala/utils/data.dart';
|
import 'package:pilipala/utils/data.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -26,6 +33,7 @@ void main() async {
|
|||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
||||||
.then((_) async {
|
.then((_) async {
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
await AudioService.init<AudioHandler>(
|
await AudioService.init<AudioHandler>(
|
||||||
builder: () => MyAudioHandler(),
|
builder: () => MyAudioHandler(),
|
||||||
@ -38,6 +46,9 @@ void main() async {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
=======
|
||||||
|
await setupServiceLocator();
|
||||||
|
>>>>>>> main
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
// 小白条、导航栏沉浸
|
// 小白条、导航栏沉浸
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
@ -74,6 +85,23 @@ class MyApp extends StatelessWidget {
|
|||||||
double textScale =
|
double textScale =
|
||||||
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||||
|
|
||||||
|
// 强制设置高帧率
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
late List modes;
|
||||||
|
FlutterDisplayMode.supported.then((value) {
|
||||||
|
modes = value;
|
||||||
|
var storageDisplay = setting.get(SettingBoxKey.displayMode);
|
||||||
|
DisplayMode f = DisplayMode.auto;
|
||||||
|
if (storageDisplay != null) {
|
||||||
|
f = modes.firstWhere((e) => e.toString() == storageDisplay);
|
||||||
|
}
|
||||||
|
DisplayMode preferred = modes.toList().firstWhere((el) => el == f);
|
||||||
|
FlutterDisplayMode.setPreferredMode(preferred);
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||||
ColorScheme? lightColorScheme;
|
ColorScheme? lightColorScheme;
|
||||||
|
|||||||
@ -12,20 +12,20 @@ enum SearchType {
|
|||||||
live_room,
|
live_room,
|
||||||
// 主播:live_user
|
// 主播:live_user
|
||||||
// live_user,
|
// live_user,
|
||||||
// 专栏:article
|
|
||||||
// article,
|
|
||||||
// 话题:topic
|
// 话题:topic
|
||||||
// topic,
|
// topic,
|
||||||
// 用户:bili_user
|
// 用户:bili_user
|
||||||
bili_user,
|
bili_user,
|
||||||
|
// 专栏:article
|
||||||
|
article,
|
||||||
// 相簿:photo
|
// 相簿:photo
|
||||||
// photo
|
// photo
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SearchTypeExtension on SearchType {
|
extension SearchTypeExtension on SearchType {
|
||||||
String get type =>
|
String get type =>
|
||||||
['video', 'media_bangumi', 'live_room', 'bili_user'][index];
|
['video', 'media_bangumi', 'live_room', 'bili_user', 'article'][index];
|
||||||
String get label => ['视频', '番剧', '直播间', '用户'][index];
|
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索类型为视频、专栏及相簿时
|
// 搜索类型为视频、专栏及相簿时
|
||||||
|
|||||||
@ -244,7 +244,9 @@ class Vote {
|
|||||||
choiceCnt = json['choice_cnt'];
|
choiceCnt = json['choice_cnt'];
|
||||||
share = json['share'];
|
share = json['share'];
|
||||||
defaultShare = json['default_share'];
|
defaultShare = json['default_share'];
|
||||||
endTime = json['end_time'];
|
endTime = json['end_time'] is int
|
||||||
|
? json['end_time']
|
||||||
|
: int.parse(json['end_time']);
|
||||||
joinNum = json['join_num'];
|
joinNum = json['join_num'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class FollowDataModel {
|
|||||||
List<FollowItemModel>? list;
|
List<FollowItemModel>? list;
|
||||||
|
|
||||||
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
total = json['total'];
|
total = json['total'] ?? 0;
|
||||||
list = json['list']
|
list = json['list']
|
||||||
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
@ -19,7 +19,7 @@ class FollowItemModel {
|
|||||||
FollowItemModel({
|
FollowItemModel({
|
||||||
this.mid,
|
this.mid,
|
||||||
this.attribute,
|
this.attribute,
|
||||||
this.mtime,
|
// this.mtime,
|
||||||
this.tag,
|
this.tag,
|
||||||
this.special,
|
this.special,
|
||||||
this.uname,
|
this.uname,
|
||||||
@ -30,7 +30,7 @@ class FollowItemModel {
|
|||||||
|
|
||||||
int? mid;
|
int? mid;
|
||||||
int? attribute;
|
int? attribute;
|
||||||
int? mtime;
|
// int? mtime;
|
||||||
List? tag;
|
List? tag;
|
||||||
int? special;
|
int? special;
|
||||||
String? uname;
|
String? uname;
|
||||||
@ -41,7 +41,7 @@ class FollowItemModel {
|
|||||||
FollowItemModel.fromJson(Map<String, dynamic> json) {
|
FollowItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
attribute = json['attribute'];
|
attribute = json['attribute'];
|
||||||
mtime = json['mtime'];
|
// mtime = json['mtime'];
|
||||||
tag = json['tag'];
|
tag = json['tag'];
|
||||||
special = json['special'];
|
special = json['special'];
|
||||||
uname = json['uname'];
|
uname = json['uname'];
|
||||||
|
|||||||
49
lib/models/login/index.dart
Normal file
49
lib/models/login/index.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
class CaptchaDataModel {
|
||||||
|
CaptchaDataModel({
|
||||||
|
this.type,
|
||||||
|
this.token,
|
||||||
|
this.geetest,
|
||||||
|
this.tencent,
|
||||||
|
this.validate,
|
||||||
|
this.seccode,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? type;
|
||||||
|
String? token;
|
||||||
|
GeetestData? geetest;
|
||||||
|
Tencent? tencent;
|
||||||
|
String? validate;
|
||||||
|
String? seccode;
|
||||||
|
|
||||||
|
CaptchaDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json["type"];
|
||||||
|
token = json["token"];
|
||||||
|
geetest =
|
||||||
|
json["geetest"] != null ? GeetestData.fromJson(json["geetest"]) : null;
|
||||||
|
tencent =
|
||||||
|
json["tencent"] != null ? Tencent.fromJson(json["tencent"]) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GeetestData {
|
||||||
|
GeetestData({
|
||||||
|
this.challenge,
|
||||||
|
this.gt,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? challenge;
|
||||||
|
String? gt;
|
||||||
|
|
||||||
|
GeetestData.fromJson(Map<String, dynamic> json) {
|
||||||
|
challenge = json["challenge"];
|
||||||
|
gt = json["gt"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tencent {
|
||||||
|
Tencent({this.appid});
|
||||||
|
String? appid;
|
||||||
|
Tencent.fromJson(Map<String, dynamic> json) {
|
||||||
|
appid = json["appid"];
|
||||||
|
}
|
||||||
|
}
|
||||||
23
lib/models/member/tags.dart
Normal file
23
lib/models/member/tags.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
class MemberTagItemModel {
|
||||||
|
MemberTagItemModel({
|
||||||
|
this.count,
|
||||||
|
this.name,
|
||||||
|
this.tagid,
|
||||||
|
this.tip,
|
||||||
|
this.checked,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? count;
|
||||||
|
String? name;
|
||||||
|
int? tagid;
|
||||||
|
String? tip;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
|
MemberTagItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
count = json['count'];
|
||||||
|
name = json['name'];
|
||||||
|
tagid = json['tagid'];
|
||||||
|
tip = json['tip'];
|
||||||
|
checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ class SearchVideoModel {
|
|||||||
List<SearchVideoItemModel>? list;
|
List<SearchVideoItemModel>? list;
|
||||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||||
list = json['result']
|
list = json['result']
|
||||||
|
.where((e) => e['available'] == true)
|
||||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -17,7 +18,7 @@ class SearchVideoItemModel {
|
|||||||
this.id,
|
this.id,
|
||||||
this.cid,
|
this.cid,
|
||||||
// this.author,
|
// this.author,
|
||||||
// this.mid,
|
this.mid,
|
||||||
// this.typeid,
|
// this.typeid,
|
||||||
// this.typename,
|
// this.typename,
|
||||||
this.arcurl,
|
this.arcurl,
|
||||||
@ -47,7 +48,7 @@ class SearchVideoItemModel {
|
|||||||
int? id;
|
int? id;
|
||||||
int? cid;
|
int? cid;
|
||||||
// String? author;
|
// String? author;
|
||||||
// String? mid;
|
int? mid;
|
||||||
// String? typeid;
|
// String? typeid;
|
||||||
// String? typename;
|
// String? typename;
|
||||||
String? arcurl;
|
String? arcurl;
|
||||||
@ -80,6 +81,7 @@ class SearchVideoItemModel {
|
|||||||
arcurl = json['arcurl'];
|
arcurl = json['arcurl'];
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
mid = json['mid'];
|
||||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
@ -376,3 +378,75 @@ class SearchMBangumiItemModel {
|
|||||||
indexShow = json['index_show'];
|
indexShow = json['index_show'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SearchArticleModel {
|
||||||
|
SearchArticleModel({this.list});
|
||||||
|
|
||||||
|
List<SearchArticleItemModel>? list;
|
||||||
|
|
||||||
|
SearchArticleModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
list = json['result'] != null
|
||||||
|
? json['result']
|
||||||
|
.map<SearchArticleItemModel>(
|
||||||
|
(e) => SearchArticleItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchArticleItemModel {
|
||||||
|
SearchArticleItemModel({
|
||||||
|
this.pubTime,
|
||||||
|
this.like,
|
||||||
|
this.title,
|
||||||
|
this.subTitle,
|
||||||
|
this.rankOffset,
|
||||||
|
this.mid,
|
||||||
|
this.imageUrls,
|
||||||
|
this.id,
|
||||||
|
this.categoryId,
|
||||||
|
this.view,
|
||||||
|
this.reply,
|
||||||
|
this.desc,
|
||||||
|
this.rankScore,
|
||||||
|
this.type,
|
||||||
|
this.templateId,
|
||||||
|
this.categoryName,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? pubTime;
|
||||||
|
int? like;
|
||||||
|
List? title;
|
||||||
|
String? subTitle;
|
||||||
|
int? rankOffset;
|
||||||
|
int? mid;
|
||||||
|
List? imageUrls;
|
||||||
|
int? id;
|
||||||
|
int? categoryId;
|
||||||
|
int? view;
|
||||||
|
int? reply;
|
||||||
|
String? desc;
|
||||||
|
int? rankScore;
|
||||||
|
String? type;
|
||||||
|
int? templateId;
|
||||||
|
String? categoryName;
|
||||||
|
|
||||||
|
SearchArticleItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
pubTime = json['pub_time'];
|
||||||
|
like = json['like'];
|
||||||
|
title = Em.regTitle(json['title']);
|
||||||
|
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
|
rankOffset = json['rank_offset'];
|
||||||
|
mid = json['mid'];
|
||||||
|
imageUrls = json['image_urls'];
|
||||||
|
id = json['id'];
|
||||||
|
categoryId = json['category_id'];
|
||||||
|
view = json['view'];
|
||||||
|
reply = json['reply'];
|
||||||
|
desc = json['desc'];
|
||||||
|
rankScore = json['rank_score'];
|
||||||
|
type = json['type'];
|
||||||
|
templateId = json['templateId'];
|
||||||
|
categoryName = json['category_name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,17 +3,23 @@ class HistoryData {
|
|||||||
this.cursor,
|
this.cursor,
|
||||||
this.tab,
|
this.tab,
|
||||||
this.list,
|
this.list,
|
||||||
|
this.page,
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor? cursor;
|
Cursor? cursor;
|
||||||
List<HisTabItem>? tab;
|
List<HisTabItem>? tab;
|
||||||
List<HisListItem>? list;
|
List<HisListItem>? list;
|
||||||
|
Map? page;
|
||||||
|
|
||||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||||
cursor = Cursor.fromJson(json['cursor']);
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
tab = json['tab'] != null
|
||||||
list =
|
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
: [];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
page = json['page'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ class HisListItem {
|
|||||||
this.kid,
|
this.kid,
|
||||||
this.tagName,
|
this.tagName,
|
||||||
this.liveStatus,
|
this.liveStatus,
|
||||||
|
this.checked,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? title;
|
String? title;
|
||||||
@ -105,6 +112,7 @@ class HisListItem {
|
|||||||
int? kid;
|
int? kid;
|
||||||
String? tagName;
|
String? tagName;
|
||||||
int? liveStatus;
|
int? liveStatus;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
@ -131,6 +139,7 @@ class HisListItem {
|
|||||||
kid = json['kid'];
|
kid = json['kid'];
|
||||||
tagName = json['tag_name'];
|
tagName = json['tag_name'];
|
||||||
liveStatus = json['live_status'];
|
liveStatus = json['live_status'];
|
||||||
|
checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
80
lib/models/video/ai.dart
Normal file
80
lib/models/video/ai.dart
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
class AiConclusionModel {
|
||||||
|
AiConclusionModel({
|
||||||
|
this.code,
|
||||||
|
this.modelResult,
|
||||||
|
this.stid,
|
||||||
|
this.status,
|
||||||
|
this.likeNum,
|
||||||
|
this.dislikeNum,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? code;
|
||||||
|
ModelResult? modelResult;
|
||||||
|
String? stid;
|
||||||
|
int? status;
|
||||||
|
int? likeNum;
|
||||||
|
int? dislikeNum;
|
||||||
|
|
||||||
|
AiConclusionModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
code = json['code'];
|
||||||
|
modelResult = ModelResult.fromJson(json['model_result']);
|
||||||
|
stid = json['stid'];
|
||||||
|
status = json['status'];
|
||||||
|
likeNum = json['like_num'];
|
||||||
|
dislikeNum = json['dislike_num'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModelResult {
|
||||||
|
ModelResult({
|
||||||
|
this.resultType,
|
||||||
|
this.summary,
|
||||||
|
this.outline,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? resultType;
|
||||||
|
String? summary;
|
||||||
|
List<OutlineItem>? outline;
|
||||||
|
|
||||||
|
ModelResult.fromJson(Map<String, dynamic> json) {
|
||||||
|
resultType = json['result_type'];
|
||||||
|
summary = json['summary'];
|
||||||
|
outline = json['result_type'] == 2
|
||||||
|
? json['outline']
|
||||||
|
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: <OutlineItem>[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OutlineItem {
|
||||||
|
OutlineItem({
|
||||||
|
this.title,
|
||||||
|
this.partOutline,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? title;
|
||||||
|
List<PartOutline>? partOutline;
|
||||||
|
|
||||||
|
OutlineItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
title = json['title'];
|
||||||
|
partOutline = json['part_outline']
|
||||||
|
.map<PartOutline>((e) => PartOutline.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PartOutline {
|
||||||
|
PartOutline({
|
||||||
|
this.timestamp,
|
||||||
|
this.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? timestamp;
|
||||||
|
String? content;
|
||||||
|
|
||||||
|
PartOutline.fromJson(Map<String, dynamic> json) {
|
||||||
|
timestamp = json['timestamp'];
|
||||||
|
content = json['content'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -184,7 +184,7 @@ class AboutController extends GetxController {
|
|||||||
|
|
||||||
// 获取远程版本
|
// 获取远程版本
|
||||||
Future getRemoteApp() async {
|
Future getRemoteApp() async {
|
||||||
var result = await Request().get(Api.latestApp);
|
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
|
||||||
data = LatestDataModel.fromJson(result.data);
|
data = LatestDataModel.fromJson(result.data);
|
||||||
remoteAppInfo = data;
|
remoteAppInfo = data;
|
||||||
remoteVersion.value = data.tagName!;
|
remoteVersion.value = data.tagName!;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -21,7 +22,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
? int.parse(Get.parameters['seasonId']!)
|
? int.parse(Get.parameters['seasonId']!)
|
||||||
: null;
|
: null;
|
||||||
var epId = Get.parameters['epId'] != null
|
var epId = Get.parameters['epId'] != null
|
||||||
? int.parse(Get.parameters['epId']!)
|
? int.tryParse(Get.parameters['epId']!)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// 是否预渲染 骨架屏
|
// 是否预渲染 骨架屏
|
||||||
@ -257,7 +258,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
@ -292,4 +293,31 @@ class BangumiIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
if (bangumiDetail.value.episodes != null) {
|
||||||
|
episodes = bangumiDetail.value.episodes!;
|
||||||
|
}
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
int currentIndex =
|
||||||
|
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
// 列表循环
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
if (nextIndex == episodes.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextIndex <= episodes.length - 1 &&
|
||||||
|
platRepeat == PlayRepeat.listOrder) {}
|
||||||
|
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
|
int aid = episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,10 +34,12 @@ class BangumiIntroPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final BangumiIntroController bangumiIntroController =
|
late BangumiIntroController bangumiIntroController;
|
||||||
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
late VideoDetailController videoDetailCtr;
|
||||||
BangumiInfoModel? bangumiDetail;
|
BangumiInfoModel? bangumiDetail;
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
|
late int cid;
|
||||||
|
late String heroTag;
|
||||||
|
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
@ -46,10 +48,19 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
heroTag = Get.arguments['heroTag'];
|
||||||
|
cid = widget.cid!;
|
||||||
|
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||||
|
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
bangumiIntroController.bangumiDetail.listen((value) {
|
bangumiIntroController.bangumiDetail.listen((value) {
|
||||||
bangumiDetail = value;
|
bangumiDetail = value;
|
||||||
});
|
});
|
||||||
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
print('🐶🐶$p0');
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -61,22 +72,25 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
// 请求成功
|
// 请求成功
|
||||||
|
|
||||||
return BangumiInfo(
|
return BangumiInfo(
|
||||||
loadingStatus: false,
|
loadingStatus: false,
|
||||||
bangumiDetail: bangumiDetail,
|
bangumiDetail: bangumiDetail,
|
||||||
|
cid: cid,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 请求错误
|
// 请求错误
|
||||||
return HttpError(
|
// return HttpError(
|
||||||
errMsg: snapshot.data['msg'],
|
// errMsg: snapshot.data['msg'],
|
||||||
fn: () => Get.back(),
|
// fn: () => Get.back(),
|
||||||
);
|
// );
|
||||||
|
return SizedBox();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return BangumiInfo(
|
return BangumiInfo(
|
||||||
loadingStatus: true,
|
loadingStatus: true,
|
||||||
bangumiDetail: bangumiDetail,
|
bangumiDetail: bangumiDetail,
|
||||||
cid: widget.cid,
|
cid: cid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -117,6 +131,12 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
bangumiItem = bangumiIntroController.bangumiItem;
|
bangumiItem = bangumiIntroController.bangumiItem;
|
||||||
sheetHeight = localCache.get('sheetHeight');
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
cid = widget.cid!;
|
cid = widget.cid!;
|
||||||
|
print('cid: $cid');
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
print('cid: $cid');
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -260,9 +280,15 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
!widget.loadingStatus
|
!widget.loadingStatus
|
||||||
? widget.bangumiDetail!.areas!
|
? (widget.bangumiDetail!.areas!
|
||||||
.first['name']
|
.isNotEmpty
|
||||||
: bangumiItem!.areas!.first['name'],
|
? widget.bangumiDetail!.areas!
|
||||||
|
.first['name']
|
||||||
|
: '')
|
||||||
|
: (bangumiItem!.areas!.isNotEmpty
|
||||||
|
? bangumiItem!
|
||||||
|
.areas!.first['name']
|
||||||
|
: ''),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: t.colorScheme.outline,
|
color: t.colorScheme.outline,
|
||||||
|
|||||||
@ -113,6 +113,9 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState ==
|
if (snapshot.connectionState ==
|
||||||
ConnectionState.done) {
|
ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
Map data = snapshot.data as Map;
|
Map data = snapshot.data as Map;
|
||||||
List list = _bangumidController.bangumiFollowList;
|
List list = _bangumidController.bangumiFollowList;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
@ -198,7 +201,7 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const LoadingMore()
|
LoadingMore()
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class BangumiPanel extends StatefulWidget {
|
class BangumiPanel extends StatefulWidget {
|
||||||
@ -30,16 +32,28 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
dynamic userInfo;
|
dynamic userInfo;
|
||||||
// 默认未开通
|
// 默认未开通
|
||||||
int vipStatus = 0;
|
int vipStatus = 0;
|
||||||
|
late int cid;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
late final VideoDetailController videoDetailCtr;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
cid = widget.cid!;
|
||||||
|
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo != null) {
|
if (userInfo != null) {
|
||||||
vipStatus = userInfo.vipStatus;
|
vipStatus = userInfo.vipStatus;
|
||||||
}
|
}
|
||||||
|
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
|
||||||
|
scrollToIndex();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -60,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'黑名单管理 (${_blackListController.blackList.length} / 5000)',
|
'黑名单管理 - ${_blackListController.total.value}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -104,10 +105,11 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
dense: true,
|
dense: true,
|
||||||
// trailing: TextButton(
|
trailing: TextButton(
|
||||||
// onPressed: () {},
|
onPressed: () => _blackListController
|
||||||
// child: const Text('移除'),
|
.removeBlack(list[index].mid),
|
||||||
// ),
|
child: const Text('移除'),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -136,6 +138,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
class BlackListController extends GetxController {
|
class BlackListController extends GetxController {
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int pageSize = 50;
|
int pageSize = 50;
|
||||||
|
RxInt total = 0.obs;
|
||||||
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||||
|
|
||||||
Future queryBlacklist({type = 'init'}) async {
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
@ -146,6 +149,7 @@ class BlackListController extends GetxController {
|
|||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
blackList.value = result['data'].list;
|
blackList.value = result['data'].list;
|
||||||
|
total.value = result['data'].total;
|
||||||
} else {
|
} else {
|
||||||
blackList.addAll(result['data'].list);
|
blackList.addAll(result['data'].list);
|
||||||
}
|
}
|
||||||
@ -154,4 +158,13 @@ class BlackListController extends GetxController {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future removeBlack(mid) async {
|
||||||
|
var result = await BlackHttp.removeBlack(fid: mid);
|
||||||
|
if (result['status']) {
|
||||||
|
blackList.removeWhere((e) => e.mid == mid);
|
||||||
|
total.value = total.value - 1;
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,22 +10,34 @@ class PlDanmakuController {
|
|||||||
// 按 6min 分段
|
// 按 6min 分段
|
||||||
int segCount = 0;
|
int segCount = 0;
|
||||||
List<DmSegMobileReply> dmSegList = [];
|
List<DmSegMobileReply> dmSegList = [];
|
||||||
int currentSegIndex = 0;
|
// 已请求的段落标记
|
||||||
|
List<int> hasrequestSeg = [];
|
||||||
|
int currentSegIndex = 1;
|
||||||
int currentDmIndex = 0;
|
int currentDmIndex = 0;
|
||||||
|
|
||||||
void calcSegment() {
|
void calcSegment() {
|
||||||
|
dmSegList.clear();
|
||||||
|
// 视频分段数
|
||||||
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
|
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
|
||||||
|
dmSegList = List<DmSegMobileReply>.generate(
|
||||||
|
segCount < 1 ? 1 : segCount, (index) => DmSegMobileReply());
|
||||||
|
// 当前分段
|
||||||
|
try {
|
||||||
|
currentSegIndex =
|
||||||
|
(playerController.position.value.inSeconds / (60 * 6)).ceil();
|
||||||
|
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DmSegMobileReply>> queryDanmaku() async {
|
Future<List<DmSegMobileReply>> queryDanmaku() async {
|
||||||
dmSegList.clear();
|
// dmSegList.clear();
|
||||||
for (int segIndex = 1; segIndex <= segCount; segIndex++) {
|
DmSegMobileReply result =
|
||||||
DmSegMobileReply result =
|
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: currentSegIndex);
|
||||||
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segIndex);
|
if (result.elems.isNotEmpty) {
|
||||||
if (result.elems.isNotEmpty) {
|
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
|
||||||
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
|
// dmSegList.add(result);
|
||||||
dmSegList.add(result);
|
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
|
||||||
}
|
dmSegList[currentSegIndex - 1] = result;
|
||||||
}
|
}
|
||||||
if (dmSegList.isNotEmpty) {
|
if (dmSegList.isNotEmpty) {
|
||||||
findClosestPositionIndex(playerController.position.value.inMilliseconds);
|
findClosestPositionIndex(playerController.position.value.inMilliseconds);
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -29,6 +30,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
bool danmuPlayStatus = true;
|
bool danmuPlayStatus = true;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
late bool enableShowDanmaku;
|
late bool enableShowDanmaku;
|
||||||
|
late List blockTypes;
|
||||||
|
late double showArea;
|
||||||
|
late double opacityVal;
|
||||||
|
late double fontSizeVal;
|
||||||
|
late double danmakuSpeedVal;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -58,6 +64,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
showArea = playerController.showArea;
|
||||||
|
opacityVal = playerController.opacityVal;
|
||||||
|
fontSizeVal = playerController.fontSizeVal;
|
||||||
|
danmakuSpeedVal = playerController.danmakuSpeedVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 播放器状态监听
|
// 播放器状态监听
|
||||||
@ -75,12 +86,23 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
_controller!.onResume();
|
_controller!.onResume();
|
||||||
danmuPlayStatus = true;
|
danmuPlayStatus = true;
|
||||||
}
|
}
|
||||||
PlDanmakuController ctr = _plDanmakuController;
|
|
||||||
int currentPosition = position.inMilliseconds;
|
|
||||||
|
|
||||||
if (!playerController.isOpenDanmu.value) {
|
if (!playerController.isOpenDanmu.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
PlDanmakuController ctr = _plDanmakuController;
|
||||||
|
int currentPosition = position.inMilliseconds;
|
||||||
|
blockTypes = playerController.blockTypes;
|
||||||
|
// 根据position判断是否有已缓存弹幕。没有则请求对应段
|
||||||
|
int segIndex = (currentPosition / (6 * 60 * 1000)).ceil();
|
||||||
|
segIndex = segIndex < 1 ? 1 : segIndex;
|
||||||
|
if (ctr.dmSegList[segIndex - 1].elems.isEmpty &&
|
||||||
|
!ctr.hasrequestSeg.contains(segIndex - 1)) {
|
||||||
|
ctr.hasrequestSeg.add(segIndex - 1);
|
||||||
|
ctr.currentSegIndex = segIndex;
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
ctr.queryDanmaku();
|
||||||
|
});
|
||||||
|
}
|
||||||
// 超出分段数返回
|
// 超出分段数返回
|
||||||
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
|
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
|
||||||
return;
|
return;
|
||||||
@ -99,14 +121,17 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
var delta = currentPosition - element.progress;
|
var delta = currentPosition - element.progress;
|
||||||
|
|
||||||
if (delta >= 0 && delta < 200) {
|
if (delta >= 0 && delta < 200) {
|
||||||
_controller!.addItems([
|
// 屏蔽彩色弹幕
|
||||||
DanmakuItem(
|
if (blockTypes.contains(6) ? element.color == 16777215 : true) {
|
||||||
element.content,
|
_controller!.addItems([
|
||||||
color: DmUtils.decimalToColor(element.color),
|
DanmakuItem(
|
||||||
time: element.progress,
|
element.content,
|
||||||
type: DmUtils.getPosition(element.mode),
|
color: DmUtils.decimalToColor(element.color),
|
||||||
)
|
time: element.progress,
|
||||||
]);
|
type: DmUtils.getPosition(element.mode),
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
ctr.currentDmIndex++;
|
ctr.currentDmIndex++;
|
||||||
} else {
|
} else {
|
||||||
if (!playerController.isOpenDanmu.value) {
|
if (!playerController.isOpenDanmu.value) {
|
||||||
@ -126,22 +151,30 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Obx(
|
return LayoutBuilder(builder: (context, box) {
|
||||||
() => AnimatedOpacity(
|
double initDuration = box.maxWidth / 12;
|
||||||
opacity: playerController.isOpenDanmu.value ? 1 : 0,
|
return Obx(
|
||||||
duration: const Duration(milliseconds: 100),
|
() => AnimatedOpacity(
|
||||||
child: DanmakuView(
|
opacity: playerController.isOpenDanmu.value ? 1 : 0,
|
||||||
createdController: (DanmakuController e) async {
|
duration: const Duration(milliseconds: 100),
|
||||||
widget.playerController.danmakuController = _controller = e;
|
child: DanmakuView(
|
||||||
},
|
createdController: (DanmakuController e) async {
|
||||||
option: DanmakuOption(
|
widget.playerController.danmakuController = _controller = e;
|
||||||
fontSize: 15,
|
},
|
||||||
area: 0.5,
|
option: DanmakuOption(
|
||||||
duration: 5,
|
fontSize: 15 * fontSizeVal,
|
||||||
|
area: showArea,
|
||||||
|
opacity: opacityVal,
|
||||||
|
hideTop: blockTypes.contains(5),
|
||||||
|
hideScroll: blockTypes.contains(2),
|
||||||
|
hideBottom: blockTypes.contains(4),
|
||||||
|
duration: initDuration /
|
||||||
|
(danmakuSpeedVal * widget.playerController.playbackSpeed),
|
||||||
|
),
|
||||||
|
statusChanged: (isPlaying) {},
|
||||||
),
|
),
|
||||||
statusChanged: (isPlaying) {},
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,10 +149,30 @@ class DynamicsController extends GetxController {
|
|||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
String title = item.modules.moduleDynamic.major.opus.title;
|
String title = item.modules.moduleDynamic.major.opus.title;
|
||||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
Get.toNamed(
|
if (url.contains('opus') || url.contains('read')) {
|
||||||
'/webview',
|
RegExp digitRegExp = RegExp(r'\d+');
|
||||||
parameters: {'url': 'https:$url', 'type': 'note', 'pageTitle': title},
|
Iterable<Match> matches = digitRegExp.allMatches(url);
|
||||||
);
|
String number = matches.first.group(0)!;
|
||||||
|
if (url.contains('read')) {
|
||||||
|
number = 'cv$number';
|
||||||
|
}
|
||||||
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
|
'url': url.startsWith('//') ? url.split('//').last : url,
|
||||||
|
'title': title,
|
||||||
|
'id': number,
|
||||||
|
'dynamicType': url.split('//').last.split('/')[1]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': 'https:$url',
|
||||||
|
'type': 'note',
|
||||||
|
'pageTitle': title
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
print('番剧');
|
print('番剧');
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
@ -17,6 +18,7 @@ class DynamicDetailController extends GetxController {
|
|||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
RxInt acount = 0.obs;
|
RxInt acount = 0.obs;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
ReplySortType _sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -9,7 +10,10 @@ import 'package:pilipala/models/common/reply_type.dart';
|
|||||||
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
import 'package:pilipala/pages/dynamics/deatil/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/widgets/author_panel.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/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/replyReply/index.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
|
||||||
import '../widgets/dynamic_panel.dart';
|
import '../widgets/dynamic_panel.dart';
|
||||||
|
|
||||||
@ -21,15 +25,18 @@ class DynamicDetailPage extends StatefulWidget {
|
|||||||
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||||
late DynamicDetailController? _dynamicDetailController;
|
with TickerProviderStateMixin {
|
||||||
|
late DynamicDetailController _dynamicDetailController;
|
||||||
|
late AnimationController fabAnimationCtr;
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late StreamController<bool> titleStreamC; // appBar title
|
late StreamController<bool> titleStreamC; // appBar title
|
||||||
final ScrollController scrollController = ScrollController();
|
late ScrollController scrollController;
|
||||||
bool _visibleTitle = false;
|
bool _visibleTitle = false;
|
||||||
String? action;
|
String? action;
|
||||||
// 回复类型
|
// 回复类型
|
||||||
late int type;
|
late int type;
|
||||||
|
bool _isFabVisible = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -38,39 +45,42 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
// floor 1原创 2转发
|
// floor 1原创 2转发
|
||||||
if (Get.arguments['floor'] == 1) {
|
if (Get.arguments['floor'] == 1) {
|
||||||
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
oid = int.parse(Get.arguments['item'].basic!['comment_id_str']);
|
||||||
|
print(oid);
|
||||||
} else {
|
} else {
|
||||||
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
try {
|
||||||
|
String type = Get.arguments['item'].modules.moduleDynamic.major.type;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
if (type == 'MAJOR_TYPE_OPUS') {
|
||||||
|
} else {
|
||||||
|
oid = Get.arguments['item'].modules.moduleDynamic.major.draw.id;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
int commentType = 11;
|
||||||
|
try {
|
||||||
|
commentType = Get.arguments['item'].basic!['comment_type'];
|
||||||
|
} catch (_) {}
|
||||||
type = (commentType == 0) ? 11 : commentType;
|
type = (commentType == 0) ? 11 : commentType;
|
||||||
|
|
||||||
action =
|
action =
|
||||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||||
_dynamicDetailController = Get.put(DynamicDetailController(oid, type));
|
_dynamicDetailController =
|
||||||
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
||||||
|
_futureBuilderFuture = _dynamicDetailController.queryReplyList();
|
||||||
titleStreamC = StreamController<bool>();
|
titleStreamC = StreamController<bool>();
|
||||||
scrollController.addListener(_listen);
|
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
_visibleTitle = true;
|
_visibleTitle = true;
|
||||||
titleStreamC.add(true);
|
titleStreamC.add(true);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _listen() async {
|
fabAnimationCtr = AnimationController(
|
||||||
if (scrollController.position.pixels >=
|
vsync: this,
|
||||||
scrollController.position.maxScrollExtent - 300) {
|
duration: const Duration(milliseconds: 300),
|
||||||
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
);
|
||||||
_dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
fabAnimationCtr.forward();
|
||||||
});
|
// 滚动事件监听
|
||||||
}
|
scrollListener();
|
||||||
|
|
||||||
if (scrollController.offset > 55 && !_visibleTitle) {
|
|
||||||
_visibleTitle = true;
|
|
||||||
titleStreamC.add(true);
|
|
||||||
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
|
||||||
_visibleTitle = false;
|
|
||||||
titleStreamC.add(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void replyReply(replyItem) {
|
void replyReply(replyItem) {
|
||||||
@ -97,9 +107,58 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scrollListener() {
|
||||||
|
scrollController = _dynamicDetailController.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
// 分页加载
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
||||||
|
_dynamicDetailController.queryReplyList(reqType: 'onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标题
|
||||||
|
if (scrollController.offset > 55 && !_visibleTitle) {
|
||||||
|
_visibleTitle = true;
|
||||||
|
titleStreamC.add(true);
|
||||||
|
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
||||||
|
_visibleTitle = false;
|
||||||
|
titleStreamC.add(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fab按钮
|
||||||
|
final ScrollDirection direction =
|
||||||
|
scrollController.position.userScrollDirection;
|
||||||
|
if (direction == ScrollDirection.forward) {
|
||||||
|
_showFab();
|
||||||
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
|
_hideFab();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showFab() {
|
||||||
|
if (!_isFabVisible) {
|
||||||
|
_isFabVisible = true;
|
||||||
|
fabAnimationCtr.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hideFab() {
|
||||||
|
if (_isFabVisible) {
|
||||||
|
_isFabVisible = false;
|
||||||
|
fabAnimationCtr.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.removeListener(() {});
|
scrollController.removeListener(() {});
|
||||||
|
fabAnimationCtr.dispose();
|
||||||
|
scrollController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +177,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: snapshot.data ? 1 : 0,
|
opacity: snapshot.data ? 1 : 0,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: author(_dynamicDetailController!.item, context),
|
child: AuthorPanel(item: _dynamicDetailController.item),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -126,155 +185,206 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
|||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _dynamicDetailController!.queryReplyList();
|
await _dynamicDetailController.queryReplyList();
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: Stack(
|
||||||
controller: scrollController,
|
children: [
|
||||||
slivers: [
|
CustomScrollView(
|
||||||
if (action != 'comment')
|
controller: scrollController,
|
||||||
SliverToBoxAdapter(
|
slivers: [
|
||||||
child: DynamicPanel(
|
if (action != 'comment')
|
||||||
item: _dynamicDetailController!.item,
|
SliverToBoxAdapter(
|
||||||
source: 'detail',
|
child: DynamicPanel(
|
||||||
),
|
item: _dynamicDetailController.item,
|
||||||
),
|
source: 'detail',
|
||||||
SliverPersistentHeader(
|
|
||||||
delegate: _MySliverPersistentHeaderDelegate(
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
width: 0.6,
|
|
||||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
height: 45,
|
SliverPersistentHeader(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 6),
|
delegate: _MySliverPersistentHeaderDelegate(
|
||||||
child: Row(
|
child: Container(
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Obx(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
() => AnimatedSwitcher(
|
border: Border(
|
||||||
duration: const Duration(milliseconds: 400),
|
top: BorderSide(
|
||||||
transitionBuilder:
|
width: 0.6,
|
||||||
(Widget child, Animation<double> animation) {
|
color: Theme.of(context)
|
||||||
return ScaleTransition(
|
.dividerColor
|
||||||
scale: animation, child: child);
|
.withOpacity(0.05),
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'${_dynamicDetailController!.acount.value}',
|
|
||||||
key: ValueKey<int>(
|
|
||||||
_dynamicDetailController!.acount.value),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text('条回复'),
|
height: 45,
|
||||||
const Spacer(),
|
padding: const EdgeInsets.only(left: 12, right: 6),
|
||||||
SizedBox(
|
child: Row(
|
||||||
height: 35,
|
children: [
|
||||||
child: TextButton.icon(
|
Obx(
|
||||||
onPressed: () =>
|
() => AnimatedSwitcher(
|
||||||
_dynamicDetailController!.queryBySort(),
|
duration: const Duration(milliseconds: 400),
|
||||||
icon: const Icon(Icons.sort, size: 16),
|
transitionBuilder:
|
||||||
label: Obx(() => Text(
|
(Widget child, Animation<double> animation) {
|
||||||
_dynamicDetailController!.sortTypeLabel.value,
|
return ScaleTransition(
|
||||||
style: const TextStyle(fontSize: 13),
|
scale: animation, child: child);
|
||||||
)),
|
},
|
||||||
),
|
child: Text(
|
||||||
)
|
'${_dynamicDetailController.acount.value}',
|
||||||
],
|
key: ValueKey<int>(
|
||||||
),
|
_dynamicDetailController.acount.value),
|
||||||
),
|
|
||||||
),
|
|
||||||
pinned: true,
|
|
||||||
),
|
|
||||||
FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
if (snapshot.data['status']) {
|
|
||||||
// 请求成功
|
|
||||||
return Obx(
|
|
||||||
() => _dynamicDetailController!.replyList.isEmpty &&
|
|
||||||
_dynamicDetailController!.isLoadingMore
|
|
||||||
? SliverList(
|
|
||||||
delegate:
|
|
||||||
SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoReplySkeleton();
|
|
||||||
}, childCount: 8),
|
|
||||||
)
|
|
||||||
: SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) {
|
|
||||||
if (index ==
|
|
||||||
_dynamicDetailController!
|
|
||||||
.replyList.length) {
|
|
||||||
return Container(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom),
|
|
||||||
height: MediaQuery.of(context)
|
|
||||||
.padding
|
|
||||||
.bottom +
|
|
||||||
100,
|
|
||||||
child: Center(
|
|
||||||
child: Obx(
|
|
||||||
() => Text(
|
|
||||||
_dynamicDetailController!
|
|
||||||
.noMore.value,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return ReplyItem(
|
|
||||||
replyItem: _dynamicDetailController!
|
|
||||||
.replyList[index],
|
|
||||||
showReplyRow: true,
|
|
||||||
replyLevel: '1',
|
|
||||||
replyReply: (replyItem) =>
|
|
||||||
replyReply(replyItem),
|
|
||||||
replyType: ReplyType.values[type],
|
|
||||||
addReply: (replyItem) {
|
|
||||||
_dynamicDetailController!
|
|
||||||
.replyList[index].replies!
|
|
||||||
.add(replyItem);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
childCount:
|
|
||||||
_dynamicDetailController!.replyList.length +
|
|
||||||
1,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const Text('条回复'),
|
||||||
|
const Spacer(),
|
||||||
|
SizedBox(
|
||||||
|
height: 35,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed: () =>
|
||||||
|
_dynamicDetailController.queryBySort(),
|
||||||
|
icon: const Icon(Icons.sort, size: 16),
|
||||||
|
label: Obx(() => Text(
|
||||||
|
_dynamicDetailController
|
||||||
|
.sortTypeLabel.value,
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pinned: true,
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
// 请求成功
|
||||||
|
return Obx(
|
||||||
|
() => _dynamicDetailController.replyList.isEmpty &&
|
||||||
|
_dynamicDetailController.isLoadingMore
|
||||||
|
? SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
return const VideoReplySkeleton();
|
||||||
|
}, childCount: 8),
|
||||||
|
)
|
||||||
|
: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate(
|
||||||
|
(context, index) {
|
||||||
|
if (index ==
|
||||||
|
_dynamicDetailController
|
||||||
|
.replyList.length) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
100,
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_dynamicDetailController
|
||||||
|
.noMore.value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ReplyItem(
|
||||||
|
replyItem: _dynamicDetailController
|
||||||
|
.replyList[index],
|
||||||
|
showReplyRow: true,
|
||||||
|
replyLevel: '1',
|
||||||
|
replyReply: (replyItem) =>
|
||||||
|
replyReply(replyItem),
|
||||||
|
replyType: ReplyType.values[type],
|
||||||
|
addReply: (replyItem) {
|
||||||
|
_dynamicDetailController
|
||||||
|
.replyList[index].replies!
|
||||||
|
.add(replyItem);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childCount: _dynamicDetailController
|
||||||
|
.replyList.length +
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
return const VideoReplySkeleton();
|
||||||
|
}, childCount: 8),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 14,
|
||||||
|
right: 14,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0, 2),
|
||||||
|
end: const Offset(0, 0),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: fabAnimationCtr,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
)),
|
||||||
|
child: FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return VideoReplyNewDialog(
|
||||||
|
oid: _dynamicDetailController.oid ??
|
||||||
|
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||||
|
root: 0,
|
||||||
|
parent: 0,
|
||||||
|
replyType: ReplyType.values[type],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).then(
|
||||||
|
(value) => {
|
||||||
|
// 完成评论,数据添加
|
||||||
|
if (value != null && value['data'] != null)
|
||||||
|
{
|
||||||
|
_dynamicDetailController.replyList
|
||||||
|
.add(value['data']),
|
||||||
|
_dynamicDetailController.acount.value++
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
} else {
|
},
|
||||||
// 请求错误
|
tooltip: '评论动态',
|
||||||
return HttpError(
|
child: const Icon(Icons.reply),
|
||||||
errMsg: data['msg'],
|
),
|
||||||
fn: () => setState(() {}),
|
),
|
||||||
);
|
),
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoReplySkeleton();
|
|
||||||
}, childCount: 8),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -212,6 +212,9 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
future: _futureBuilderFutureUp,
|
future: _futureBuilderFutureUp,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||||
@ -232,6 +235,9 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SliverToBoxAdapter(child: SizedBox());
|
||||||
|
}
|
||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<DynamicItemModel> list =
|
List<DynamicItemModel> list =
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
|
||||||
/// TODO 点击跳转
|
/// TODO 点击跳转
|
||||||
Widget addWidget(item, context, type, {floor = 1}) {
|
Widget addWidget(item, context, type, {floor = 1}) {
|
||||||
@ -19,8 +22,27 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
: Theme.of(context).colorScheme.background;
|
: Theme.of(context).colorScheme.background;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ADDITIONAL_TYPE_UGC':
|
case 'ADDITIONAL_TYPE_UGC':
|
||||||
|
// 转发的投稿
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {},
|
onTap: () async {
|
||||||
|
String text = dynamicProperty[type].jumpUrl;
|
||||||
|
RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
|
||||||
|
Iterable<Match> matches = bvRegex.allMatches(text);
|
||||||
|
if (matches.isNotEmpty) {
|
||||||
|
Match match = matches.first;
|
||||||
|
String bvid = match.group(0)!;
|
||||||
|
String cover = dynamicProperty[type].cover;
|
||||||
|
try {
|
||||||
|
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: {'pic': cover, 'heroTag': bvid});
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("No match found.");
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||||
@ -61,101 +83,111 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
);
|
);
|
||||||
case 'ADDITIONAL_TYPE_RESERVE':
|
case 'ADDITIONAL_TYPE_RESERVE':
|
||||||
return dynamicProperty[type].state != -1
|
return dynamicProperty[type].state != -1
|
||||||
? Padding(
|
? dynamicProperty[type].title != null
|
||||||
padding: const EdgeInsets.only(top: 8),
|
? Padding(
|
||||||
child: InkWell(
|
padding: const EdgeInsets.only(top: 8),
|
||||||
onTap: () {},
|
child: InkWell(
|
||||||
child: Container(
|
onTap: () {},
|
||||||
width: double.infinity,
|
child: Container(
|
||||||
padding: const EdgeInsets.only(
|
width: double.infinity,
|
||||||
left: 12, top: 10, right: 12, bottom: 10),
|
padding: const EdgeInsets.only(
|
||||||
color: bgColor,
|
left: 12, top: 10, right: 12, bottom: 10),
|
||||||
child: Column(
|
color: bgColor,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
dynamicProperty[type].title,
|
Text(
|
||||||
maxLines: 1,
|
dynamicProperty[type].title,
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize),
|
||||||
|
children: [
|
||||||
|
if (dynamicProperty[type].desc1 != null)
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
dynamicProperty[type].desc1['text']),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
if (dynamicProperty[type].desc2 != null)
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
dynamicProperty[type].desc2['text']),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 1),
|
// TextButton(onPressed: () {}, child: Text('123'))
|
||||||
Text.rich(
|
|
||||||
TextSpan(
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize),
|
|
||||||
children: [
|
|
||||||
TextSpan(text: dynamicProperty[type].desc1['text']),
|
|
||||||
const TextSpan(text: ' '),
|
|
||||||
TextSpan(text: dynamicProperty[type].desc2['text']),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
// TextButton(onPressed: () {}, child: Text('123'))
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox();
|
|
||||||
case 'ADDITIONAL_TYPE_GOODS':
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 6),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {},
|
|
||||||
child: Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: bgColor,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
NetworkImgLayer(
|
|
||||||
width: 75,
|
|
||||||
height: 75,
|
|
||||||
src: dynamicProperty[type].items.first.cover,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
dynamicProperty[type].items.first.name,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
dynamicProperty[type].items.first.brief,
|
|
||||||
maxLines: 1,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.fontSize,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 2),
|
|
||||||
Text(
|
|
||||||
dynamicProperty[type].items.first.price,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
)
|
||||||
),
|
: const SizedBox()
|
||||||
),
|
: const SizedBox();
|
||||||
));
|
case 'ADDITIONAL_TYPE_GOODS':
|
||||||
|
// 商品
|
||||||
|
return const SizedBox();
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 6),
|
||||||
|
// child: InkWell(
|
||||||
|
// onTap: () {},
|
||||||
|
// child: Container(
|
||||||
|
// padding:
|
||||||
|
// const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: bgColor,
|
||||||
|
// borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
|
// ),
|
||||||
|
// child: Row(
|
||||||
|
// children: [
|
||||||
|
// NetworkImgLayer(
|
||||||
|
// width: 75,
|
||||||
|
// height: 75,
|
||||||
|
// src: dynamicProperty[type].items.first.cover,
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 10),
|
||||||
|
// Expanded(
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// dynamicProperty[type].items.first.name,
|
||||||
|
// maxLines: 1,
|
||||||
|
// overflow: TextOverflow.ellipsis,
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// dynamicProperty[type].items.first.brief,
|
||||||
|
// maxLines: 1,
|
||||||
|
// style: TextStyle(
|
||||||
|
// color: Theme.of(context).colorScheme.outline,
|
||||||
|
// fontSize: Theme.of(context)
|
||||||
|
// .textTheme
|
||||||
|
// .labelMedium!
|
||||||
|
// .fontSize,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 2),
|
||||||
|
// Text(
|
||||||
|
// dynamicProperty[type].items.first.price,
|
||||||
|
// style: TextStyle(
|
||||||
|
// color: Theme.of(context).colorScheme.primary,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),);
|
||||||
case 'ADDITIONAL_TYPE_MATCH':
|
case 'ADDITIONAL_TYPE_MATCH':
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
case 'ADDITIONAL_TYPE_COMMON':
|
case 'ADDITIONAL_TYPE_COMMON':
|
||||||
|
|||||||
@ -1,65 +1,163 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget author(item, context) {
|
class AuthorPanel extends StatelessWidget {
|
||||||
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
|
final dynamic item;
|
||||||
return Row(
|
const AuthorPanel({super.key, required this.item});
|
||||||
children: [
|
|
||||||
GestureDetector(
|
@override
|
||||||
onTap: () {
|
Widget build(BuildContext context) {
|
||||||
feedBack();
|
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
|
||||||
Get.toNamed(
|
return Row(
|
||||||
'/member?mid=${item.modules.moduleAuthor.mid}',
|
children: [
|
||||||
arguments: {
|
GestureDetector(
|
||||||
'face': item.modules.moduleAuthor.face,
|
onTap: () {
|
||||||
'heroTag': heroTag
|
// 番剧
|
||||||
},
|
if (item.modules.moduleAuthor.type == 'AUTHOR_TYPE_PGC') {
|
||||||
);
|
return;
|
||||||
},
|
}
|
||||||
child: Hero(
|
feedBack();
|
||||||
tag: heroTag,
|
Get.toNamed(
|
||||||
child: NetworkImgLayer(
|
'/member?mid=${item.modules.moduleAuthor.mid}',
|
||||||
width: 40,
|
arguments: {
|
||||||
height: 40,
|
'face': item.modules.moduleAuthor.face,
|
||||||
type: 'avatar',
|
'heroTag': heroTag
|
||||||
src: item.modules.moduleAuthor.face,
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
type: 'avatar',
|
||||||
|
src: item.modules.moduleAuthor.face,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
Column(
|
||||||
Column(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Text(
|
item.modules.moduleAuthor.name,
|
||||||
item.modules.moduleAuthor.name,
|
style: TextStyle(
|
||||||
style: TextStyle(
|
color: item.modules.moduleAuthor!.vip != null &&
|
||||||
color: item.modules.moduleAuthor!.vip != null &&
|
item.modules.moduleAuthor!.vip['status'] > 0
|
||||||
item.modules.moduleAuthor!.vip['status'] > 0
|
? const Color.fromARGB(255, 251, 100, 163)
|
||||||
? const Color.fromARGB(255, 251, 100, 163)
|
: Theme.of(context).colorScheme.onBackground,
|
||||||
: Theme.of(context).colorScheme.onBackground,
|
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
),
|
||||||
|
),
|
||||||
|
DefaultTextStyle.merge(
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(item.modules.moduleAuthor.pubTime),
|
||||||
|
if (item.modules.moduleAuthor.pubTime != '' &&
|
||||||
|
item.modules.moduleAuthor.pubAction != '')
|
||||||
|
const Text(' '),
|
||||||
|
Text(item.modules.moduleAuthor.pubAction),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (item.type == 'DYNAMIC_TYPE_AV')
|
||||||
|
SizedBox(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return MorePanel(item: item);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.more_vert_outlined, size: 18),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
DefaultTextStyle.merge(
|
],
|
||||||
style: TextStyle(
|
);
|
||||||
color: Theme.of(context).colorScheme.outline,
|
}
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
}
|
||||||
|
|
||||||
|
class MorePanel extends StatelessWidget {
|
||||||
|
final dynamic item;
|
||||||
|
const MorePanel({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
// clipBehavior: Clip.hardEdge,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: Container(
|
||||||
|
height: 35,
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 32,
|
||||||
|
height: 3,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(3))),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
),
|
||||||
children: [
|
ListTile(
|
||||||
Text(item.modules.moduleAuthor.pubTime),
|
onTap: () async {
|
||||||
if (item.modules.moduleAuthor.pubTime != '' &&
|
try {
|
||||||
item.modules.moduleAuthor.pubAction != '')
|
String bvid = item.modules.moduleDynamic.major.archive.bvid;
|
||||||
const Text(' '),
|
var res = await UserHttp.toViewLater(bvid: bvid);
|
||||||
Text(item.modules.moduleAuthor.pubAction),
|
SmartDialog.showToast(res['msg']);
|
||||||
],
|
Get.back();
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast('出错了:${err.toString()}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
// dense: true,
|
||||||
|
leading: const Icon(Icons.watch_later_outlined, size: 19),
|
||||||
|
title: Text(
|
||||||
|
'稍后再看',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
const Divider(thickness: 0.1, height: 1),
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
dense: true,
|
||||||
|
title: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,40 +1,183 @@
|
|||||||
// 内容
|
// 内容
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
import 'rich_node_panel.dart';
|
import 'rich_node_panel.dart';
|
||||||
|
|
||||||
Widget content(item, context, source) {
|
// ignore: must_be_immutable
|
||||||
TextStyle authorStyle =
|
class Content extends StatefulWidget {
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
dynamic item;
|
||||||
return Container(
|
String? source;
|
||||||
width: double.infinity,
|
Content({
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
super.key,
|
||||||
child: Column(
|
this.item,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
this.source,
|
||||||
children: [
|
});
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
|
||||||
GestureDetector(
|
@override
|
||||||
child: Text(
|
State<Content> createState() => _ContentState();
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
}
|
||||||
style: authorStyle,
|
|
||||||
),
|
class _ContentState extends State<Content> {
|
||||||
),
|
late bool hasPics;
|
||||||
],
|
List<OpusPicsModel> pics = [];
|
||||||
IgnorePointer(
|
|
||||||
// 禁用SelectableRegion的触摸交互功能
|
@override
|
||||||
ignoring: source == 'detail' ? false : true,
|
void initState() {
|
||||||
child: SelectableRegion(
|
super.initState();
|
||||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
hasPics = widget.item.modules.moduleDynamic.major != null &&
|
||||||
focusNode: FocusNode(),
|
widget.item.modules.moduleDynamic.major.opus != null &&
|
||||||
selectionControls: MaterialTextSelectionControls(),
|
widget.item.modules.moduleDynamic.major.opus.pics.isNotEmpty;
|
||||||
child: Text.rich(
|
if (hasPics) {
|
||||||
richNode(item, context),
|
pics = widget.item.modules.moduleDynamic.major.opus.pics;
|
||||||
maxLines: source == 'detail' ? 999 : 3,
|
}
|
||||||
overflow: TextOverflow.ellipsis,
|
}
|
||||||
),
|
|
||||||
|
InlineSpan picsNodes() {
|
||||||
|
List<InlineSpan> spanChilds = [];
|
||||||
|
int len = pics.length;
|
||||||
|
List<String> picList = [];
|
||||||
|
|
||||||
|
if (len == 1) {
|
||||||
|
OpusPicsModel pictureItem = pics.first;
|
||||||
|
picList.add(pictureItem.url!);
|
||||||
|
spanChilds.add(const TextSpan(text: '\n'));
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pictureItem.url,
|
||||||
|
width: box.maxWidth / 2,
|
||||||
|
height: box.maxWidth *
|
||||||
|
0.5 *
|
||||||
|
(pictureItem.height != null && pictureItem.width != null
|
||||||
|
? pictureItem.height! / pictureItem.width!
|
||||||
|
: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
);
|
||||||
),
|
}
|
||||||
);
|
if (len > 1) {
|
||||||
|
List<Widget> list = [];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
picList.add(pics[i].url!);
|
||||||
|
list.add(
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pics[i].url,
|
||||||
|
width: box.maxWidth,
|
||||||
|
height: box.maxWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
double maxWidth = box.maxWidth;
|
||||||
|
double crossCount = len < 3 ? 2 : 3;
|
||||||
|
double height = maxWidth /
|
||||||
|
crossCount *
|
||||||
|
(len % crossCount == 0
|
||||||
|
? len ~/ crossCount
|
||||||
|
: len ~/ crossCount + 1) +
|
||||||
|
6;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
height: height,
|
||||||
|
child: GridView.count(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
crossAxisCount: crossCount.toInt(),
|
||||||
|
mainAxisSpacing: 4.0,
|
||||||
|
crossAxisSpacing: 4.0,
|
||||||
|
childAspectRatio: 1,
|
||||||
|
children: list,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return TextSpan(
|
||||||
|
children: spanChilds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
TextStyle authorStyle =
|
||||||
|
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (widget.item.modules.moduleDynamic.topic != null) ...[
|
||||||
|
GestureDetector(
|
||||||
|
child: Text(
|
||||||
|
'#${widget.item.modules.moduleDynamic.topic.name}',
|
||||||
|
style: authorStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
IgnorePointer(
|
||||||
|
// 禁用SelectableRegion的触摸交互功能
|
||||||
|
ignoring: widget.source == 'detail' ? false : true,
|
||||||
|
child: SelectableRegion(
|
||||||
|
magnifierConfiguration: const TextMagnifierConfiguration(),
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
selectionControls: MaterialTextSelectionControls(),
|
||||||
|
child: Text.rich(
|
||||||
|
/// fix 默认20px高度
|
||||||
|
style: const TextStyle(height: 0),
|
||||||
|
richNode(widget.item, context),
|
||||||
|
maxLines: widget.source == 'detail' ? 999 : 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (hasPics) ...[
|
||||||
|
Text.rich(picsNodes()),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,10 +39,11 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
child: author(item, context),
|
child: AuthorPanel(item: item),
|
||||||
),
|
),
|
||||||
if (item!.modules!.moduleDynamic!.desc != null)
|
if (item!.modules!.moduleDynamic!.desc != null ||
|
||||||
content(item, context, source),
|
item!.modules!.moduleDynamic!.major != null)
|
||||||
|
Content(item: item, source: source),
|
||||||
forWard(item, context, _dynamicsController, source),
|
forWard(item, context, _dynamicsController, source),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
if (source == null) ActionPanel(item: item),
|
if (source == null) ActionPanel(item: item),
|
||||||
|
|||||||
@ -44,19 +44,21 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
|
||||||
Padding(
|
/// fix #话题跟content重复
|
||||||
padding: floor == 2
|
// if (item.modules.moduleDynamic.topic != null) ...[
|
||||||
? EdgeInsets.zero
|
// Padding(
|
||||||
: const EdgeInsets.only(left: 12, right: 12),
|
// padding: floor == 2
|
||||||
child: GestureDetector(
|
// ? EdgeInsets.zero
|
||||||
child: Text(
|
// : const EdgeInsets.only(left: 12, right: 12),
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
// child: GestureDetector(
|
||||||
style: authorStyle,
|
// child: Text(
|
||||||
),
|
// '#${item.modules.moduleDynamic.topic.name}',
|
||||||
),
|
// style: authorStyle,
|
||||||
),
|
// ),
|
||||||
],
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
Text.rich(
|
Text.rich(
|
||||||
richNode(item, context),
|
richNode(item, context),
|
||||||
// 被转发状态(floor=2) 隐藏
|
// 被转发状态(floor=2) 隐藏
|
||||||
@ -71,6 +73,8 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
: const EdgeInsets.only(left: 12, right: 12),
|
: const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: picWidget(item, context),
|
child: picWidget(item, context),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/// 附加内容 商品信息、直播预约等等
|
||||||
if (item.modules.moduleDynamic.additional != null)
|
if (item.modules.moduleDynamic.additional != null)
|
||||||
addWidget(
|
addWidget(
|
||||||
item,
|
item,
|
||||||
@ -133,7 +137,12 @@ Widget forWard(item, context, ctr, source, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(item.modules.moduleDynamic.desc.text)
|
Text.rich(
|
||||||
|
richNode(item, context),
|
||||||
|
// 被转发状态(floor=2) 隐藏
|
||||||
|
maxLines: source == 'detail' && floor != 2 ? 999 : 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: item.modules.moduleDynamic.additional != null
|
: item.modules.moduleDynamic.additional != null
|
||||||
|
|||||||
@ -1,20 +1,22 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
Widget picWidget(item, context) {
|
Widget picWidget(item, context) {
|
||||||
String type = item.modules.moduleDynamic.major.type;
|
String type = item.modules.moduleDynamic.major.type;
|
||||||
List pictures = [];
|
List pictures = [];
|
||||||
if (type == 'MAJOR_TYPE_OPUS') {
|
if (type == 'MAJOR_TYPE_OPUS') {
|
||||||
pictures = item.modules.moduleDynamic.major.opus.pics;
|
/// fix 图片跟rich_node_panel重复
|
||||||
|
// pictures = item.modules.moduleDynamic.major.opus.pics;
|
||||||
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
if (type == 'MAJOR_TYPE_DRAW') {
|
if (type == 'MAJOR_TYPE_DRAW') {
|
||||||
pictures = item.modules.moduleDynamic.major.draw.items;
|
pictures = item.modules.moduleDynamic.major.draw.items;
|
||||||
}
|
}
|
||||||
int len = pictures.length;
|
int len = pictures.length;
|
||||||
List picList = [];
|
List<String> picList = [];
|
||||||
List<Widget> list = [];
|
List<Widget> list = [];
|
||||||
for (var i = 0; i < len; i++) {
|
for (var i = 0; i < len; i++) {
|
||||||
picList.add(pictures[i].src ?? pictures[i].url);
|
picList.add(pictures[i].src ?? pictures[i].url);
|
||||||
@ -23,11 +25,14 @@ Widget picWidget(item, context) {
|
|||||||
builder: (context, BoxConstraints box) {
|
builder: (context, BoxConstraints box) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed('/preview',
|
showDialog(
|
||||||
arguments: {'initialPage': i, 'imgList': picList});
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
// child: Hero(
|
|
||||||
// tag: pictures[i].src ?? pictures[i].url,
|
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
src: pictures[i].src ?? pictures[i].url,
|
src: pictures[i].src ?? pictures[i].url,
|
||||||
width: box.maxWidth,
|
width: box.maxWidth,
|
||||||
|
|||||||
@ -1,175 +1,324 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
// 富文本
|
// 富文本
|
||||||
InlineSpan richNode(item, context) {
|
InlineSpan richNode(item, context) {
|
||||||
TextStyle authorStyle =
|
final spacer = _VerticalSpaceSpan(0.0);
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
try {
|
||||||
List<InlineSpan> spanChilds = [];
|
TextStyle authorStyle =
|
||||||
for (var i in item.modules.moduleDynamic.desc.richTextNodes) {
|
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
List<InlineSpan> spanChilds = [];
|
||||||
spanChilds.add(
|
String contentType = 'desc';
|
||||||
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
|
||||||
|
dynamic richTextNodes;
|
||||||
|
if (item.modules.moduleDynamic.desc != null) {
|
||||||
|
richTextNodes = item.modules.moduleDynamic.desc.richTextNodes;
|
||||||
|
} else if (item.modules.moduleDynamic.major != null) {
|
||||||
|
contentType = 'major';
|
||||||
|
// 动态页面 richTextNodes 层级可能与主页动态层级不同
|
||||||
|
richTextNodes =
|
||||||
|
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
|
||||||
}
|
}
|
||||||
// @用户
|
if (richTextNodes == null || richTextNodes.isEmpty) {
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_AT') {
|
return spacer;
|
||||||
spanChilds.add(
|
} else {
|
||||||
WidgetSpan(
|
for (var i in richTextNodes) {
|
||||||
alignment: PlaceholderAlignment.middle,
|
/// fix 渲染专栏时内容会重复
|
||||||
child: Row(
|
// if (item.modules.moduleDynamic.major.opus.title == null &&
|
||||||
mainAxisSize: MainAxisSize.min,
|
// i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
||||||
children: [
|
if (i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
||||||
GestureDetector(
|
spanChilds.add(
|
||||||
onTap: () => Get.toNamed('/member?mid=${i.rid}',
|
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
||||||
arguments: {'face': null}),
|
}
|
||||||
|
// @用户
|
||||||
|
if (i.type == 'RICH_TEXT_NODE_TYPE_AT') {
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => Get.toNamed('/member?mid=${i.rid}',
|
||||||
|
arguments: {'face': null}),
|
||||||
|
child: Text(
|
||||||
|
' ${i.text}',
|
||||||
|
style: authorStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 话题
|
||||||
|
if (i.type == 'RICH_TEXT_NODE_TYPE_TOPIC') {
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
child: Text(
|
child: Text(
|
||||||
' ${i.text}',
|
'${i.origText}',
|
||||||
style: authorStyle,
|
style: authorStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// 话题
|
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_TOPIC') {
|
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
alignment: PlaceholderAlignment.middle,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {},
|
|
||||||
child: Text(
|
|
||||||
'${i.origText}',
|
|
||||||
style: authorStyle,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
);
|
// 网页链接
|
||||||
}
|
if (i.type == 'RICH_TEXT_NODE_TYPE_WEB') {
|
||||||
// 网页链接
|
spanChilds.add(
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_WEB') {
|
WidgetSpan(
|
||||||
spanChilds.add(
|
alignment: PlaceholderAlignment.middle,
|
||||||
WidgetSpan(
|
child: Icon(
|
||||||
alignment: PlaceholderAlignment.middle,
|
Icons.link,
|
||||||
child: Icon(
|
size: 20,
|
||||||
Icons.link,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
size: 20,
|
),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
alignment: PlaceholderAlignment.middle,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
Get.toNamed(
|
|
||||||
'/webview',
|
|
||||||
parameters: {'url': i.origText, 'type': 'url', 'pageTitle': ''},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
i.text,
|
|
||||||
style: authorStyle,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
spanChilds.add(
|
||||||
);
|
WidgetSpan(
|
||||||
}
|
alignment: PlaceholderAlignment.middle,
|
||||||
// 投票
|
child: GestureDetector(
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_VOTE') {
|
onTap: () {
|
||||||
spanChilds.add(
|
Get.toNamed(
|
||||||
WidgetSpan(
|
'/webview',
|
||||||
alignment: PlaceholderAlignment.middle,
|
parameters: {
|
||||||
child: GestureDetector(
|
'url': i.origText,
|
||||||
onTap: () {
|
'type': 'url',
|
||||||
String dynamicId = item.basic['comment_id_str'];
|
'pageTitle': ''
|
||||||
Get.toNamed(
|
},
|
||||||
'/webview',
|
);
|
||||||
parameters: {
|
|
||||||
'url':
|
|
||||||
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=$dynamicId&isWeb=1',
|
|
||||||
'type': 'vote',
|
|
||||||
'pageTitle': '投票'
|
|
||||||
},
|
},
|
||||||
);
|
child: Text(
|
||||||
},
|
i.text,
|
||||||
child: Text(
|
style: authorStyle,
|
||||||
'投票:${i.text}',
|
),
|
||||||
style: authorStyle,
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
);
|
// 投票
|
||||||
}
|
if (i.type == 'RICH_TEXT_NODE_TYPE_VOTE') {
|
||||||
// 表情
|
spanChilds.add(
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_EMOJI') {
|
WidgetSpan(
|
||||||
spanChilds.add(
|
alignment: PlaceholderAlignment.middle,
|
||||||
WidgetSpan(
|
child: GestureDetector(
|
||||||
child: NetworkImgLayer(
|
onTap: () {
|
||||||
src: i.emoji.iconUrl,
|
try {
|
||||||
type: 'emote',
|
String dynamicId = item.basic['comment_id_str'];
|
||||||
width: i.emoji.size * 20,
|
Get.toNamed(
|
||||||
height: i.emoji.size * 20,
|
'/webview',
|
||||||
),
|
parameters: {
|
||||||
),
|
'url':
|
||||||
);
|
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=$dynamicId&isWeb=1',
|
||||||
}
|
'type': 'vote',
|
||||||
// 抽奖
|
'pageTitle': '投票'
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_LOTTERY') {
|
},
|
||||||
spanChilds.add(
|
);
|
||||||
WidgetSpan(
|
} catch (_) {}
|
||||||
alignment: PlaceholderAlignment.middle,
|
},
|
||||||
child: Icon(
|
child: Text(
|
||||||
Icons.redeem_rounded,
|
'投票:${i.text}',
|
||||||
size: 16,
|
style: authorStyle,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
alignment: PlaceholderAlignment.middle,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {},
|
|
||||||
child: Text(
|
|
||||||
'${i.origText} ',
|
|
||||||
style: authorStyle,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
}
|
||||||
);
|
// 表情
|
||||||
}
|
if (i.type == 'RICH_TEXT_NODE_TYPE_EMOJI') {
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: i.emoji.iconUrl,
|
||||||
|
type: 'emote',
|
||||||
|
width: i.emoji.size * 20,
|
||||||
|
height: i.emoji.size * 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 抽奖
|
||||||
|
if (i.type == 'RICH_TEXT_NODE_TYPE_LOTTERY') {
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: Icon(
|
||||||
|
Icons.redeem_rounded,
|
||||||
|
size: 16,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: Text(
|
||||||
|
'${i.origText} ',
|
||||||
|
style: authorStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// TODO 商品
|
/// TODO 商品
|
||||||
if (i.type == 'RICH_TEXT_NODE_TYPE_GOODS') {
|
if (i.type == 'RICH_TEXT_NODE_TYPE_GOODS') {
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
WidgetSpan(
|
WidgetSpan(
|
||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.shopping_bag_outlined,
|
Icons.shopping_bag_outlined,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
spanChilds.add(
|
|
||||||
WidgetSpan(
|
|
||||||
alignment: PlaceholderAlignment.middle,
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {},
|
|
||||||
child: Text(
|
|
||||||
'${i.text} ',
|
|
||||||
style: authorStyle,
|
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
),
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
alignment: PlaceholderAlignment.middle,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: Text(
|
||||||
|
'${i.text} ',
|
||||||
|
style: authorStyle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (contentType == 'major' &&
|
||||||
|
// item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
|
||||||
|
// // 图片可能跟其他widget重复渲染
|
||||||
|
// List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
|
||||||
|
// int len = pics.length;
|
||||||
|
// List<String> picList = [];
|
||||||
|
|
||||||
|
// if (len == 1) {
|
||||||
|
// OpusPicsModel pictureItem = pics.first;
|
||||||
|
// picList.add(pictureItem.url!);
|
||||||
|
// spanChilds.add(const TextSpan(text: '\n'));
|
||||||
|
// spanChilds.add(
|
||||||
|
// WidgetSpan(
|
||||||
|
// child: LayoutBuilder(
|
||||||
|
// builder: (context, BoxConstraints box) {
|
||||||
|
// return GestureDetector(
|
||||||
|
// onTap: () {
|
||||||
|
// showDialog(
|
||||||
|
// useSafeArea: false,
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) {
|
||||||
|
// return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// child: Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 4),
|
||||||
|
// child: NetworkImgLayer(
|
||||||
|
// src: pictureItem.url,
|
||||||
|
// width: box.maxWidth / 2,
|
||||||
|
// height: box.maxWidth *
|
||||||
|
// 0.5 *
|
||||||
|
// (pictureItem.height != null &&
|
||||||
|
// pictureItem.width != null
|
||||||
|
// ? pictureItem.height! / pictureItem.width!
|
||||||
|
// : 1),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// if (len > 1) {
|
||||||
|
// List<Widget> list = [];
|
||||||
|
// for (var i = 0; i < len; i++) {
|
||||||
|
// picList.add(pics[i].url!);
|
||||||
|
// list.add(
|
||||||
|
// LayoutBuilder(
|
||||||
|
// builder: (context, BoxConstraints box) {
|
||||||
|
// return GestureDetector(
|
||||||
|
// onTap: () {
|
||||||
|
// showDialog(
|
||||||
|
// useSafeArea: false,
|
||||||
|
// context: context,
|
||||||
|
// builder: (context) {
|
||||||
|
// return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// child: NetworkImgLayer(
|
||||||
|
// src: pics[i].url,
|
||||||
|
// width: box.maxWidth,
|
||||||
|
// height: box.maxWidth,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// spanChilds.add(
|
||||||
|
// WidgetSpan(
|
||||||
|
// child: LayoutBuilder(
|
||||||
|
// builder: (context, BoxConstraints box) {
|
||||||
|
// double maxWidth = box.maxWidth;
|
||||||
|
// double crossCount = len < 3 ? 2 : 3;
|
||||||
|
// double height = maxWidth /
|
||||||
|
// crossCount *
|
||||||
|
// (len % crossCount == 0
|
||||||
|
// ? len ~/ crossCount
|
||||||
|
// : len ~/ crossCount + 1) +
|
||||||
|
// 6;
|
||||||
|
// return Container(
|
||||||
|
// padding: const EdgeInsets.only(top: 6),
|
||||||
|
// height: height,
|
||||||
|
// child: GridView.count(
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
|
// crossAxisCount: crossCount.toInt(),
|
||||||
|
// mainAxisSpacing: 4.0,
|
||||||
|
// crossAxisSpacing: 4.0,
|
||||||
|
// childAspectRatio: 1,
|
||||||
|
// children: list,
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// spanChilds.add(
|
||||||
|
// WidgetSpan(
|
||||||
|
// child: NetworkImgLayer(
|
||||||
|
// src: pics.first.url,
|
||||||
|
// type: 'emote',
|
||||||
|
// width: 100,
|
||||||
|
// height: 200,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
return TextSpan(
|
||||||
|
children: spanChilds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
print('❌rich_node_panel err: $err');
|
||||||
|
return spacer;
|
||||||
}
|
}
|
||||||
return TextSpan(
|
}
|
||||||
children: spanChilds,
|
|
||||||
);
|
class _VerticalSpaceSpan extends WidgetSpan {
|
||||||
|
_VerticalSpaceSpan(double height)
|
||||||
|
: super(child: SizedBox(height: height, width: double.infinity));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,7 +91,10 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => {feedBack(), Get.toNamed('/follow')},
|
onTap: () => {
|
||||||
|
feedBack(),
|
||||||
|
Get.toNamed('/follow?mid=${userInfo.mid}')
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
|
|||||||
@ -57,20 +57,21 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
],
|
],
|
||||||
// const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
/// fix #话题跟content重复
|
||||||
Padding(
|
// if (item.modules.moduleDynamic.topic != null) ...[
|
||||||
padding: floor == 2
|
// Padding(
|
||||||
? EdgeInsets.zero
|
// padding: floor == 2
|
||||||
: const EdgeInsets.only(left: 12, right: 12),
|
// ? EdgeInsets.zero
|
||||||
child: GestureDetector(
|
// : const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: Text(
|
// child: GestureDetector(
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
// child: Text(
|
||||||
style: authorStyle,
|
// '#${item.modules.moduleDynamic.topic.name}',
|
||||||
),
|
// style: authorStyle,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(height: 6),
|
// ),
|
||||||
],
|
// const SizedBox(height: 6),
|
||||||
|
// ],
|
||||||
if (floor == 2 && item.modules.moduleDynamic.desc != null) ...[
|
if (floor == 2 && item.modules.moduleDynamic.desc != null) ...[
|
||||||
Text.rich(richNode(item, context)),
|
Text.rich(richNode(item, context)),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|||||||
@ -16,13 +16,16 @@ class FansPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FansPageState extends State<FansPage> {
|
class _FansPageState extends State<FansPage> {
|
||||||
final FansController _fansController = Get.put(FansController());
|
late String mid;
|
||||||
|
late FansController _fansController;
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
mid = Get.parameters['mid']!;
|
||||||
|
_fansController = Get.put(FansController(), tag: mid);
|
||||||
_futureBuilderFuture = _fansController.queryFans('init');
|
_futureBuilderFuture = _fansController.queryFans('init');
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@ -44,6 +44,14 @@ class _FavPageState extends State<FavPage> {
|
|||||||
'我的收藏',
|
'我的收藏',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class FavDetailController extends GetxController {
|
|||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxMap favInfo = {}.obs;
|
RxMap favInfo = {}.obs;
|
||||||
RxList<FavDetailItemData> favList = [FavDetailItemData()].obs;
|
RxList favList = [].obs;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
int mediaCount = 0;
|
int mediaCount = 0;
|
||||||
|
|
||||||
@ -61,15 +61,13 @@ class FavDetailController extends GetxController {
|
|||||||
aid: id, addIds: '', delIds: mediaId.toString());
|
aid: id, addIds: '', delIds: mediaId.toString());
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (result['data']['prompt']) {
|
if (result['data']['prompt']) {
|
||||||
List<FavDetailItemData> dataList = favDetailData.value.medias!;
|
List dataList = favList;
|
||||||
for (var i in dataList) {
|
for (var i in dataList) {
|
||||||
if (i.id == id) {
|
if (i.id == id) {
|
||||||
dataList.remove(i);
|
dataList.remove(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
favDetailData.value.medias = dataList;
|
|
||||||
favDetailData.refresh();
|
|
||||||
SmartDialog.showToast('取消收藏');
|
SmartDialog.showToast('取消收藏');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,13 +92,18 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// actions: [
|
actions: [
|
||||||
// IconButton(
|
IconButton(
|
||||||
// onPressed: () {},
|
onPressed: () => Get.toNamed(
|
||||||
// icon: const Icon(Icons.more_vert),
|
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'),
|
||||||
// ),
|
icon: const Icon(Icons.search_outlined),
|
||||||
// const SizedBox(width: 4)
|
),
|
||||||
// ],
|
// IconButton(
|
||||||
|
// onPressed: () {},
|
||||||
|
// icon: const Icon(Icons.more_vert),
|
||||||
|
// ),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Container(
|
background: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -168,7 +173,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'共${_favDetailController.favInfo['media_count'] ?? '-'}条视频',
|
'共${_favDetailController.favList.length}条视频',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize:
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
@ -187,14 +192,20 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
if (_favDetailController.item!.mediaCount == 0) {
|
if (_favDetailController.item!.mediaCount == 0) {
|
||||||
return const NoData();
|
return const NoData();
|
||||||
} else {
|
} else {
|
||||||
|
List favList = _favDetailController.favList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => SliverList(
|
() => favList.isEmpty
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
? const SliverToBoxAdapter(child: SizedBox())
|
||||||
return FavVideoCardH(
|
: SliverList(
|
||||||
videoItem: _favDetailController.favList[index],
|
delegate:
|
||||||
);
|
SliverChildBuilderDelegate((context, index) {
|
||||||
}, childCount: _favDetailController.favList.length),
|
return FavVideoCardH(
|
||||||
),
|
videoItem: favList[index],
|
||||||
|
callFn: () => _favDetailController
|
||||||
|
.onCancelFav(favList[index].id),
|
||||||
|
);
|
||||||
|
}, childCount: favList.length),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -10,134 +10,109 @@ import 'package:pilipala/utils/id_utils.dart';
|
|||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
import '../controller.dart';
|
|
||||||
|
|
||||||
// 收藏视频卡片 - 水平布局
|
// 收藏视频卡片 - 水平布局
|
||||||
class FavVideoCardH extends StatelessWidget {
|
class FavVideoCardH extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final FavDetailController _favDetailController =
|
final Function? callFn;
|
||||||
Get.put(FavDetailController());
|
|
||||||
|
|
||||||
FavVideoCardH({Key? key, required this.videoItem}) : super(key: key);
|
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
int id = videoItem.id;
|
int id = videoItem.id;
|
||||||
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
|
||||||
String heroTag = Utils.makeHeroTag(id);
|
String heroTag = Utils.makeHeroTag(id);
|
||||||
return Dismissible(
|
return InkWell(
|
||||||
movementDuration: const Duration(milliseconds: 300),
|
onTap: () async {
|
||||||
background: Container(
|
// int? seasonId;
|
||||||
decoration: BoxDecoration(
|
String? epId;
|
||||||
color: Theme.of(context).colorScheme.errorContainer,
|
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
|
||||||
),
|
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
child: const Row(
|
// seasonId = videoItem.ogv['season_id'];
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
epId = videoItem.epId;
|
||||||
children: [
|
} else if (videoItem.page == 0 || videoItem.page > 1) {
|
||||||
Icon(Icons.clear_all_rounded),
|
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||||
SizedBox(width: 6),
|
if (result['status']) {
|
||||||
Text('取消收藏')
|
epId = result['data'].epId;
|
||||||
],
|
|
||||||
)),
|
|
||||||
direction: DismissDirection.endToStart,
|
|
||||||
key: ValueKey<int>(videoItem.id),
|
|
||||||
onDismissed: (DismissDirection direction) {
|
|
||||||
_favDetailController.onCancelFav(videoItem.id);
|
|
||||||
// widget.onDeleteNotice();
|
|
||||||
},
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
// int? seasonId;
|
|
||||||
String? epId;
|
|
||||||
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
|
|
||||||
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
|
|
||||||
// seasonId = videoItem.ogv['season_id'];
|
|
||||||
epId = videoItem.epId;
|
|
||||||
} else if (videoItem.page == 0 || videoItem.page > 1) {
|
|
||||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
|
||||||
if (result['status']) {
|
|
||||||
epId = result['data'].epId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, String> parameters = {
|
Map<String, String> parameters = {
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'cid': videoItem.cid.toString(),
|
'cid': videoItem.cid.toString(),
|
||||||
'epId': epId ?? '',
|
'epId': epId ?? '',
|
||||||
};
|
};
|
||||||
// if (seasonId != null) {
|
// if (seasonId != null) {
|
||||||
// parameters['seasonId'] = seasonId.toString();
|
// parameters['seasonId'] = seasonId.toString();
|
||||||
// }
|
// }
|
||||||
Get.toNamed('/video', parameters: parameters, arguments: {
|
Get.toNamed('/video', parameters: parameters, arguments: {
|
||||||
'videoItem': videoItem,
|
'videoItem': videoItem,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType':
|
'videoType':
|
||||||
epId != null ? SearchType.media_bangumi : SearchType.video,
|
epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double width =
|
double width =
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
tag: heroTag,
|
tag: heroTag,
|
||||||
child: NetworkImgLayer(
|
child: NetworkImgLayer(
|
||||||
src: videoItem.pic,
|
src: videoItem.pic,
|
||||||
width: maxWidth,
|
width: maxWidth,
|
||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 4,
|
||||||
|
bottom: 4,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 1, horizontal: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
color: Colors.black54.withOpacity(0.4)),
|
||||||
|
child: Text(
|
||||||
|
Utils.timeFormat(videoItem.duration!),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 11, color: Colors.white),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
)
|
||||||
right: 4,
|
],
|
||||||
bottom: 4,
|
);
|
||||||
child: Container(
|
},
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 1, horizontal: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius:
|
|
||||||
BorderRadius.circular(4),
|
|
||||||
color:
|
|
||||||
Colors.black54.withOpacity(0.4)),
|
|
||||||
child: Text(
|
|
||||||
Utils.timeFormat(videoItem.duration!),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 11, color: Colors.white),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
),
|
||||||
],
|
VideoContent(videoItem: videoItem, callFn: callFn)
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -145,7 +120,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const VideoContent({super.key, required this.videoItem});
|
final Function? callFn;
|
||||||
|
const VideoContent({super.key, required this.videoItem, this.callFn});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -173,7 +149,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
StatView(
|
StatView(
|
||||||
@ -181,7 +156,51 @@ class VideoContent extends StatelessWidget {
|
|||||||
view: videoItem.cntInfo['play'],
|
view: videoItem.cntInfo['play'],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku'])
|
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
|
||||||
|
const Spacer(),
|
||||||
|
SizedBox(
|
||||||
|
width: 26,
|
||||||
|
height: 26,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('要取消收藏吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline),
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await callFn!();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确定取消'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
75
lib/pages/fav_search/controller.dart
Normal file
75
lib/pages/fav_search/controller.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/models/user/fav_detail.dart';
|
||||||
|
|
||||||
|
class FavSearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs; // 搜索词
|
||||||
|
String hintText = '请输入已收藏视频名称'; // 默认
|
||||||
|
RxBool loadingStatus = false.obs; // 加载状态
|
||||||
|
RxString loadingText = '加载中...'.obs; // 加载提示
|
||||||
|
bool hasMore = false;
|
||||||
|
late int searchType;
|
||||||
|
late int mediaId;
|
||||||
|
|
||||||
|
int currentPage = 1; // 当前页
|
||||||
|
int count = 0; // 总数
|
||||||
|
RxList<FavDetailItemData> favList = <FavDetailItemData>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
searchType = int.parse(Get.parameters['searchType']!);
|
||||||
|
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = true;
|
||||||
|
currentPage = 1;
|
||||||
|
searchFav();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索收藏夹视频
|
||||||
|
Future searchFav({type = 'init'}) async {
|
||||||
|
var res = await await UserHttp.userFavFolderDetail(
|
||||||
|
pn: currentPage,
|
||||||
|
ps: 20,
|
||||||
|
mediaId: mediaId,
|
||||||
|
keyword: searchKeyWord.value,
|
||||||
|
type: searchType,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (currentPage == 1 && type == 'init') {
|
||||||
|
favList.value = res['data'].medias;
|
||||||
|
} else if (type == 'onLoad') {
|
||||||
|
favList.addAll(res['data'].medias);
|
||||||
|
}
|
||||||
|
hasMore = res['data'].hasMore;
|
||||||
|
}
|
||||||
|
currentPage += 1;
|
||||||
|
loadingStatus.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
if (!hasMore) return;
|
||||||
|
searchFav(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/fav_search/index.dart
Normal file
4
lib/pages/fav_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library fav_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
116
lib/pages/fav_search/view.dart
Normal file
116
lib/pages/fav_search/view.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/pages/favDetail/widget/fav_video_card.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class FavSearchPage extends StatefulWidget {
|
||||||
|
final int? sourceType;
|
||||||
|
final int? mediaId;
|
||||||
|
const FavSearchPage({super.key, this.sourceType, this.mediaId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FavSearchPage> createState() => _FavSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FavSearchPageState extends State<FavSearchPage> {
|
||||||
|
final FavSearchController _favSearchCtr = Get.put(FavSearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
scrollController = _favSearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('fav', const Duration(seconds: 1), () {
|
||||||
|
_favSearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _favSearchCtr.submit(),
|
||||||
|
icon: const Icon(Icons.search_outlined, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _favSearchCtr.searchFocusNode,
|
||||||
|
controller: _favSearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _favSearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _favSearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _favSearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _favSearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => _favSearchCtr.loadingStatus.value && _favSearchCtr.favList.isEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _favSearchCtr.favList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: _favSearchCtr.favList.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == _favSearchCtr.favList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FavVideoCardH(
|
||||||
|
videoItem: _favSearchCtr.favList[index],
|
||||||
|
callFn: () => null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/follow.dart';
|
import 'package:pilipala/http/follow.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class FollowController extends GetxController {
|
/// 查看自己的关注时,可以查看分类
|
||||||
|
/// 查看其他人的关注时,只可以看全部
|
||||||
|
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
int ps = 20;
|
int ps = 20;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
RxList<FollowItemModel> followList = [FollowItemModel()].obs;
|
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String name;
|
late String name;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
RxBool isOwner = false.obs;
|
||||||
|
late List<MemberTagItemModel> followTags;
|
||||||
|
late TabController tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -23,6 +31,7 @@ class FollowController extends GetxController {
|
|||||||
mid = Get.parameters['mid'] != null
|
mid = Get.parameters['mid'] != null
|
||||||
? int.parse(Get.parameters['mid']!)
|
? int.parse(Get.parameters['mid']!)
|
||||||
: userInfo.mid;
|
: userInfo.mid;
|
||||||
|
isOwner.value = mid == userInfo.mid;
|
||||||
name = Get.parameters['name'] ?? userInfo.uname;
|
name = Get.parameters['name'] ?? userInfo.uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,4 +65,20 @@ class FollowController extends GetxController {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当查看当前用户的关注时,请求关注分组
|
||||||
|
Future followUpTags() async {
|
||||||
|
if (userInfo != null && mid == userInfo.mid) {
|
||||||
|
var res = await MemberHttp.followUpTags();
|
||||||
|
if (res['status']) {
|
||||||
|
followTags = res['data'];
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: 0,
|
||||||
|
length: res['data'].length,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
|
||||||
import 'package:pilipala/common/widgets/no_data.dart';
|
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/follow_item.dart';
|
import 'widgets/follow_list.dart';
|
||||||
|
import 'widgets/owner_follow_list.dart';
|
||||||
|
|
||||||
class FollowPage extends StatefulWidget {
|
class FollowPage extends StatefulWidget {
|
||||||
const FollowPage({super.key});
|
const FollowPage({super.key});
|
||||||
@ -16,30 +12,15 @@ class FollowPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FollowPageState extends State<FollowPage> {
|
class _FollowPageState extends State<FollowPage> {
|
||||||
final FollowController _followController = Get.put(FollowController());
|
late String mid;
|
||||||
|
late FollowController _followController;
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_futureBuilderFuture = _followController.queryFollowings('init');
|
mid = Get.parameters['mid']!;
|
||||||
scrollController.addListener(
|
_followController = Get.put(FollowController(), tag: mid);
|
||||||
() async {
|
|
||||||
if (scrollController.position.pixels >=
|
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
|
||||||
_followController.queryFollowings('onLoad');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
scrollController.removeListener(() {});
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -51,73 +32,57 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Text(
|
title: Text(
|
||||||
'${_followController.name}的关注',
|
_followController.isOwner.value
|
||||||
|
? '我的关注'
|
||||||
|
: '${_followController.name}的关注',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: Obx(
|
||||||
onRefresh: () async =>
|
() => !_followController.isOwner.value
|
||||||
await _followController.queryFollowings('init'),
|
? FollowList(ctr: _followController)
|
||||||
child: FutureBuilder(
|
: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _followController.followUpTags(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
var data = snapshot.data;
|
var data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<FollowItemModel> list = _followController.followList;
|
return Column(
|
||||||
return Obx(
|
children: [
|
||||||
() => list.isNotEmpty
|
TabBar(
|
||||||
? ListView.builder(
|
controller: _followController.tabController,
|
||||||
controller: scrollController,
|
isScrollable: true,
|
||||||
itemCount: list.length + 1,
|
tabs: [
|
||||||
itemBuilder: (BuildContext context, int index) {
|
for (var i in data['data']) ...[
|
||||||
if (index == list.length) {
|
Tab(text: i.name),
|
||||||
return Container(
|
]
|
||||||
height:
|
]),
|
||||||
MediaQuery.of(context).padding.bottom +
|
Expanded(
|
||||||
60,
|
child: TabBarView(
|
||||||
padding: EdgeInsets.only(
|
controller: _followController.tabController,
|
||||||
bottom: MediaQuery.of(context)
|
children: [
|
||||||
.padding
|
for (var i = 0;
|
||||||
.bottom),
|
i < _followController.tabController.length;
|
||||||
child: Center(
|
i++) ...[
|
||||||
child: Obx(
|
OwnerFollowList(
|
||||||
() => Text(
|
ctr: _followController,
|
||||||
_followController.loadingText.value,
|
tagItem: _followController.followTags[i],
|
||||||
style: TextStyle(
|
)
|
||||||
color: Theme.of(context)
|
]
|
||||||
.colorScheme
|
],
|
||||||
.outline,
|
),
|
||||||
fontSize: 13),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return followItem(item: list[index]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const CustomScrollView(
|
|
||||||
slivers: [NoData()],
|
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
} else {
|
);
|
||||||
return CustomScrollView(
|
} else {
|
||||||
slivers: [
|
return const SizedBox();
|
||||||
HttpError(
|
}
|
||||||
errMsg: data['msg'],
|
} else {
|
||||||
fn: () => _followController.queryFollowings('init'),
|
return const SizedBox();
|
||||||
)
|
}
|
||||||
],
|
},
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,71 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/introduction/widgets/group_panel.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget followItem({item}) {
|
class FollowItem extends StatelessWidget {
|
||||||
String heroTag = Utils.makeHeroTag(item!.mid);
|
final FollowItemModel item;
|
||||||
return ListTile(
|
final FollowController? ctr;
|
||||||
onTap: () {
|
const FollowItem({super.key, required this.item, this.ctr});
|
||||||
feedBack();
|
|
||||||
Get.toNamed('/member?mid=${item.mid}',
|
@override
|
||||||
arguments: {'face': item.face, 'heroTag': heroTag});
|
Widget build(BuildContext context) {
|
||||||
},
|
String heroTag = Utils.makeHeroTag(item.mid);
|
||||||
leading: Hero(
|
return ListTile(
|
||||||
tag: heroTag,
|
onTap: () {
|
||||||
child: NetworkImgLayer(
|
feedBack();
|
||||||
width: 45,
|
Get.toNamed('/member?mid=${item.mid}',
|
||||||
height: 45,
|
arguments: {'face': item.face, 'heroTag': heroTag});
|
||||||
type: 'avatar',
|
},
|
||||||
src: item.face,
|
leading: Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
type: 'avatar',
|
||||||
|
src: item.face,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
title: Text(
|
||||||
title: Text(
|
item.uname!,
|
||||||
item.uname,
|
maxLines: 1,
|
||||||
maxLines: 1,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
style: const TextStyle(fontSize: 14),
|
||||||
style: const TextStyle(fontSize: 14),
|
),
|
||||||
),
|
subtitle: Text(
|
||||||
subtitle: Text(
|
item.sign!,
|
||||||
item.sign,
|
maxLines: 1,
|
||||||
maxLines: 1,
|
overflow: TextOverflow.ellipsis,
|
||||||
overflow: TextOverflow.ellipsis,
|
),
|
||||||
),
|
dense: true,
|
||||||
dense: true,
|
trailing: ctr!.isOwner.value
|
||||||
trailing: const SizedBox(width: 6),
|
? SizedBox(
|
||||||
);
|
height: 34,
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Get.bottomSheet(
|
||||||
|
GroupPanel(mid: item.mid!),
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.fromLTRB(15, 0, 15, 0),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.onInverseSurface, // 设置按钮背景色
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'已关注',
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
114
lib/pages/follow/widgets/follow_list.dart
Normal file
114
lib/pages/follow/widgets/follow_list.dart
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
|
|
||||||
|
import 'follow_item.dart';
|
||||||
|
|
||||||
|
class FollowList extends StatefulWidget {
|
||||||
|
final FollowController ctr;
|
||||||
|
const FollowList({
|
||||||
|
super.key,
|
||||||
|
required this.ctr,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FollowList> createState() => _FollowListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FollowListState extends State<FollowList> {
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = widget.ctr.queryFollowings('init');
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
widget.ctr.queryFollowings('onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async => await widget.ctr.queryFollowings('init'),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
List<FollowItemModel> list = widget.ctr.followList;
|
||||||
|
return Obx(
|
||||||
|
() => list.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: list.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == list.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
widget.ctr.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FollowItem(
|
||||||
|
item: list[index],
|
||||||
|
ctr: widget.ctr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(slivers: [NoData()]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => widget.ctr.queryFollowings('init'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
134
lib/pages/follow/widgets/owner_follow_list.dart
Normal file
134
lib/pages/follow/widgets/owner_follow_list.dart
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
|
import 'follow_item.dart';
|
||||||
|
|
||||||
|
class OwnerFollowList extends StatefulWidget {
|
||||||
|
final FollowController ctr;
|
||||||
|
final MemberTagItemModel? tagItem;
|
||||||
|
const OwnerFollowList({super.key, required this.ctr, this.tagItem});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OwnerFollowList> createState() => _OwnerFollowListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OwnerFollowListState extends State<OwnerFollowList>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
late int mid;
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
int pn = 1;
|
||||||
|
int ps = 20;
|
||||||
|
late MemberTagItemModel tagItem;
|
||||||
|
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
mid = widget.ctr.mid;
|
||||||
|
tagItem = widget.tagItem!;
|
||||||
|
_futureBuilderFuture = followUpGroup('init');
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
followUpGroup('onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分组下up
|
||||||
|
Future followUpGroup(type) async {
|
||||||
|
if (type == 'init') {
|
||||||
|
pn = 1;
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.followUpGroup(mid, tagItem.tagid, pn, ps);
|
||||||
|
if (res['status']) {
|
||||||
|
if (res['data'].isNotEmpty) {
|
||||||
|
if (type == 'init') {
|
||||||
|
followList.value = res['data'];
|
||||||
|
} else {
|
||||||
|
followList.addAll(res['data']);
|
||||||
|
}
|
||||||
|
pn += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async => await followUpGroup('init'),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => followList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: followList.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == followList.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FollowItem(
|
||||||
|
item: followList[index],
|
||||||
|
ctr: widget.ctr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(slivers: [NoData()]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => widget.ctr.queryFollowings('init'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
|
|
||||||
class HistoryController extends GetxController {
|
class HistoryController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
RxBool isLoadingMore = false.obs;
|
RxBool isLoadingMore = false.obs;
|
||||||
RxBool pauseStatus = false.obs;
|
RxBool pauseStatus = false.obs;
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
RxBool isLoading = false.obs;
|
RxBool isLoading = false.obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
RxInt checkedCount = 0.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -121,4 +123,80 @@ class HistoryController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除某条历史记录
|
||||||
|
Future delHistory(kid, business) async {
|
||||||
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
|
if (res['status']) {
|
||||||
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除已看历史记录
|
||||||
|
Future onDelHistory() async {
|
||||||
|
/// TODO 优化
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.progress == -1).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String resKid = 'archive_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('操作完成');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除选中的记录
|
||||||
|
Future onDelCheckedHistory() async {
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认删除所选历史记录吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
/// TODO 优化
|
||||||
|
await SmartDialog.dismiss();
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.checked!).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String str = 'archive';
|
||||||
|
try {
|
||||||
|
str = i.history!.business!;
|
||||||
|
} catch (_) {}
|
||||||
|
String resKid = '${str}_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
checkedCount.value = 0;
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
enableMultiple.value = false;
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user