Merge branch 'main' into feature-liveRoomRender
This commit is contained in:
3
.github/workflows/beta_ci.yml
vendored
3
.github/workflows/beta_ci.yml
vendored
@ -12,7 +12,6 @@ on:
|
|||||||
- ".idea/**"
|
- ".idea/**"
|
||||||
- "!.github/workflows/**"
|
- "!.github/workflows/**"
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update_version:
|
update_version:
|
||||||
name: Read and update version
|
name: Read and update version
|
||||||
@ -96,7 +95,7 @@ jobs:
|
|||||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
channel: any
|
channel: any
|
||||||
|
|
||||||
- name: 下载项目依赖
|
- name: 下载项目依赖
|
||||||
|
|||||||
4
.github/workflows/release_ci.yml
vendored
4
.github/workflows/release_ci.yml
vendored
@ -36,7 +36,7 @@ jobs:
|
|||||||
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
channel: any
|
channel: any
|
||||||
|
|
||||||
- name: 下载项目依赖
|
- name: 下载项目依赖
|
||||||
@ -98,7 +98,7 @@ jobs:
|
|||||||
uses: subosito/flutter-action@v2.10.0
|
uses: subosito/flutter-action@v2.10.0
|
||||||
with:
|
with:
|
||||||
cache: true
|
cache: true
|
||||||
flutter-version: 3.16.5
|
flutter-version: 3.19.6
|
||||||
|
|
||||||
- name: flutter build ipa
|
- name: flutter build ipa
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent>
|
</intent>
|
||||||
|
|
||||||
</queries>
|
</queries>
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@ -20,7 +19,6 @@
|
|||||||
"android.support.customtabs.action.CustomTabsService" />
|
"android.support.customtabs.action.CustomTabsService" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
<!-- If your app checks for http support -->
|
<!-- If your app checks for http support -->
|
||||||
<intent>
|
<intent>
|
||||||
@ -34,7 +32,6 @@
|
|||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="PiliPala"
|
android:label="PiliPala"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
@ -47,13 +44,14 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.guozhigq.pilipala.MainActivity"
|
android:name="com.guozhigq.pilipala.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
|
android:autoVerify="true"
|
||||||
>
|
>
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@ -63,10 +61,21 @@
|
|||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
|
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<!-- Deep Link -->
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="pili"/>
|
||||||
|
<data android:scheme="pilipala"/>
|
||||||
|
</intent-filter>
|
||||||
|
<!-- App Link -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
@ -132,102 +141,55 @@
|
|||||||
<data android:scheme="bilibili" android:host="assistant" />
|
<data android:scheme="bilibili" android:host="assistant" />
|
||||||
<data android:scheme="bilibili" android:host="feedback" />
|
<data android:scheme="bilibili" android:host="feedback" />
|
||||||
<data android:scheme="bilibili" android:host="auth" android:path="/launch" />
|
<data android:scheme="bilibili" android:host="auth" android:path="/launch" />
|
||||||
|
|
||||||
<data android:scheme="http" android:host="live.bilibili.com"
|
|
||||||
android:pathPattern="/live/.*" />
|
|
||||||
<data android:scheme="https" android:host="live.bilibili.com"
|
<data android:scheme="https" android:host="live.bilibili.com"
|
||||||
android:pathPattern="/live/.*" />
|
android:pathPattern="/live/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.tv"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.tv"
|
<data android:scheme="https" android:host="www.bilibili.tv"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.cn"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.cn"
|
<data android:scheme="https" android:host="www.bilibili.cn"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/mobile/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/mobile/video/.*" />
|
android:pathPattern="/mobile/video/.*" />
|
||||||
<data android:scheme="http" android:host="m.bilibili.com"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="m.bilibili.com"
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/story/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/story/.*" />
|
android:pathPattern="/story/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/bangumi/i/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/bangumi/i/.*" />
|
android:pathPattern="/bangumi/i/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/mobile/bangumi/i/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/mobile/bangumi/i/.*" />
|
android:pathPattern="/mobile/bangumi/i/.*" />
|
||||||
<data android:scheme="http" android:host="bangumi.bilibili.com"
|
|
||||||
android:pathPattern="/.*" />
|
|
||||||
<data android:scheme="https" android:host="bangumi.bilibili.com"
|
<data android:scheme="https" android:host="bangumi.bilibili.com"
|
||||||
android:pathPattern="/.*" />
|
android:pathPattern="/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/bangumi/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/bangumi/.*" />
|
android:pathPattern="/bangumi/.*" />
|
||||||
<data android:scheme="http" android:host="m.bilibili.com"
|
|
||||||
android:pathPattern="/bangumi/.*" />
|
|
||||||
<data android:scheme="https" android:host="m.bilibili.com"
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
android:pathPattern="/bangumi/.*" />
|
android:pathPattern="/bangumi/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/cheese/play/ss.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/cheese/play/ss.*" />
|
android:pathPattern="/cheese/play/ss.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/cheese/play/ep.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/cheese/play/ep.*" />
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
<data android:scheme="http" android:host="m.bilibili.com"
|
|
||||||
android:pathPattern="/bangumi/play/ss.*" />
|
|
||||||
<data android:scheme="https" android:host="m.bilibili.com"
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
android:pathPattern="/cheese/play/ss.*" />
|
android:pathPattern="/cheese/play/ss.*" />
|
||||||
<data android:scheme="http" android:host="m.bilibili.com"
|
|
||||||
android:pathPattern="/cheese/play/ep.*" />
|
|
||||||
<data android:scheme="https" android:host="m.bilibili.com"
|
<data android:scheme="https" android:host="m.bilibili.com"
|
||||||
android:pathPattern="/cheese/play/ep.*" />
|
android:pathPattern="/cheese/play/ep.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/read/cv.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/read/cv.*" />
|
android:pathPattern="/read/cv.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com" android:path="/review/" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com" android:path="/review/" />
|
<data android:scheme="https" android:host="www.bilibili.com" android:path="/review/" />
|
||||||
<data android:scheme="http" android:host="bilibili.cn"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="bilibili.cn"
|
<data android:scheme="https" android:host="bilibili.cn"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="bilibili.com"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="bilibili.com"
|
<data android:scheme="https" android:host="bilibili.com"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.cn"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.cn"
|
<data android:scheme="https" android:host="www.bilibili.cn"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/video/.*" />
|
android:pathPattern="/video/.*" />
|
||||||
<data android:scheme="http" android:host="www.bilibili.com"
|
|
||||||
android:pathPattern="/mobile/video/.*" />
|
|
||||||
<data android:scheme="https" android:host="www.bilibili.com"
|
<data android:scheme="https" android:host="www.bilibili.com"
|
||||||
android:pathPattern="/mobile/video/.*" />
|
android:pathPattern="/mobile/video/.*" />
|
||||||
<data android:scheme="https" android:host="b23.tv"
|
<data android:scheme="https" android:host="b23.tv"
|
||||||
android:pathPattern="/*" />
|
android:pathPattern="/*" />
|
||||||
<data android:scheme="https" android:host="space.bilibili.com"
|
<data android:scheme="https" android:host="space.bilibili.com"
|
||||||
android:pathPattern="/*" />
|
android:pathPattern="/*" />
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
@ -239,7 +201,6 @@
|
|||||||
<action android:name="android.media.browse.MediaBrowserService" />
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
BIN
assets/images/pay/alipay.jpg
Normal file
BIN
assets/images/pay/alipay.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
assets/images/pay/wechat.png
Normal file
BIN
assets/images/pay/wechat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
39
change_log/1.0.25.1010.md
Normal file
39
change_log/1.0.25.1010.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
## 1.0.25
|
||||||
|
|
||||||
|
### 功能
|
||||||
|
+ 直播弹幕
|
||||||
|
+ 稍后再看、收藏夹播放全部
|
||||||
|
+ 收藏夹新建、编辑
|
||||||
|
+ 评论删除
|
||||||
|
+ 评论保存为图片
|
||||||
|
+ 动态页滑动切换up
|
||||||
|
+ up投稿筛选充电视频
|
||||||
|
+ 直播tab展示关注up
|
||||||
|
+ up主页专栏展示
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 视频详情页一键三连
|
||||||
|
+ 动态页标识充电视频
|
||||||
|
+ 播放器亮度、音量调整百分比展示
|
||||||
|
+ 封面预览时视频标题可复制
|
||||||
|
+ 竖屏直播布局
|
||||||
|
+ 图片预览
|
||||||
|
+ 专栏渲染优化
|
||||||
|
+ 私信图片查看
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 收藏夹点击异常
|
||||||
|
+ 搜索up异常
|
||||||
|
+ 系统通知已读异常
|
||||||
|
+ [赞了我的]展示错误
|
||||||
|
+ 部分up合集无法打开
|
||||||
|
+ 切换合集视频投币个数未重置
|
||||||
|
+ 搜索条件筛选面板无法滚动
|
||||||
|
+ 部分机型导航条未沉浸
|
||||||
|
+ 专栏图片渲染问题
|
||||||
|
+ 专栏浏览历史记录
|
||||||
|
+ 直播间历史记录
|
||||||
|
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -1,5 +1,5 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- appscheme (1.0.4):
|
- app_links (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- audio_service (0.0.1):
|
- audio_service (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@ -27,6 +27,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- GT3Captcha-iOS
|
- GT3Captcha-iOS
|
||||||
- GT3Captcha-iOS (0.15.8.3)
|
- GT3Captcha-iOS (0.15.8.3)
|
||||||
|
- image_picker_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- 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):
|
||||||
@ -66,7 +68,7 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
||||||
@ -77,6 +79,7 @@ DEPENDENCIES:
|
|||||||
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
|
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
@ -102,8 +105,8 @@ SPEC REPOS:
|
|||||||
- Toast
|
- Toast
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
appscheme:
|
app_links:
|
||||||
:path: ".symlinks/plugins/appscheme/ios"
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
audio_service:
|
audio_service:
|
||||||
:path: ".symlinks/plugins/audio_service/ios"
|
:path: ".symlinks/plugins/audio_service/ios"
|
||||||
audio_session:
|
audio_session:
|
||||||
@ -124,6 +127,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
gt3_flutter_plugin:
|
gt3_flutter_plugin:
|
||||||
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
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:
|
||||||
@ -160,7 +165,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||||
@ -173,6 +178,7 @@ SPEC CHECKSUMS:
|
|||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
||||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||||
|
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||||
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
|
||||||
|
|||||||
@ -65,19 +65,12 @@
|
|||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleURLName</key>
|
<key>CFBundleURLName</key>
|
||||||
<string></string>
|
<string>bilibili</string>
|
||||||
<key>CFBundleURLSchemes</key>
|
<key>CFBundleURLSchemes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>http</string>
|
<string>http</string>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
</array>
|
<string>bilibili</string>
|
||||||
<key>CFBundleURLTypes</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string></string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>m.bilibili.com</string>
|
<string>m.bilibili.com</string>
|
||||||
<string>bilibili.com</string>
|
<string>bilibili.com</string>
|
||||||
<string>www.bilibili.com</string>
|
<string>www.bilibili.com</string>
|
||||||
@ -90,19 +83,11 @@
|
|||||||
<string>bangumi.bilibili.tv</string>
|
<string>bangumi.bilibili.tv</string>
|
||||||
<string>miniapp.bilibili.com</string>
|
<string>miniapp.bilibili.com</string>
|
||||||
<string>live.bilibili.com</string>
|
<string>live.bilibili.com</string>
|
||||||
|
<string>pili</string>
|
||||||
|
<string>pilipala</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
<key>FlutterDeepLinkingEnabled</key>
|
||||||
</array>
|
<false/>
|
||||||
</dict>
|
|
||||||
|
|
||||||
<!-- 当其他应用程序或系统通过 bilibili -->
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleURLName</key>
|
|
||||||
<string>bilibili</string>
|
|
||||||
<key>CFBundleURLSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>bilibili</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
|
|||||||
@ -15,6 +15,4 @@ class Constants {
|
|||||||
// 59b43e04ad6965f34319062b478f83dd TV端
|
// 59b43e04ad6965f34319062b478f83dd TV端
|
||||||
static const String appSec = '59b43e04ad6965f34319062b478f83dd';
|
static const String appSec = '59b43e04ad6965f34319062b478f83dd';
|
||||||
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
|
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
|
||||||
static const String thirdApi =
|
|
||||||
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,462 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import 'package:scrollview_observer/scrollview_observer.dart';
|
||||||
import '../models/common/video_episode_type.dart';
|
import '../models/common/video_episode_type.dart';
|
||||||
|
import 'widgets/badge.dart';
|
||||||
|
import 'widgets/stat/danmu.dart';
|
||||||
|
import 'widgets/stat/view.dart';
|
||||||
|
|
||||||
class EpisodeBottomSheet {
|
class EpisodeBottomSheet {
|
||||||
final List<dynamic> episodes;
|
final List<dynamic> episodes;
|
||||||
final int currentCid;
|
final int currentCid;
|
||||||
final dynamic dataType;
|
final dynamic dataType;
|
||||||
final BuildContext context;
|
|
||||||
final Function changeFucCall;
|
final Function changeFucCall;
|
||||||
final int? cid;
|
final int? cid;
|
||||||
final double? sheetHeight;
|
final double? sheetHeight;
|
||||||
bool isFullScreen = false;
|
bool isFullScreen = false;
|
||||||
|
final UgcSeason? ugcSeason;
|
||||||
|
final int? currentEpisodeIndex;
|
||||||
|
final int? currentIndex;
|
||||||
|
|
||||||
EpisodeBottomSheet({
|
EpisodeBottomSheet({
|
||||||
required this.episodes,
|
required this.episodes,
|
||||||
required this.currentCid,
|
required this.currentCid,
|
||||||
required this.dataType,
|
required this.dataType,
|
||||||
required this.context,
|
|
||||||
required this.changeFucCall,
|
required this.changeFucCall,
|
||||||
this.cid,
|
this.cid,
|
||||||
this.sheetHeight,
|
this.sheetHeight,
|
||||||
this.isFullScreen = false,
|
this.isFullScreen = false,
|
||||||
|
this.ugcSeason,
|
||||||
|
this.currentEpisodeIndex,
|
||||||
|
this.currentIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
Widget buildEpisodeListItem(
|
Widget buildShowContent() {
|
||||||
dynamic episode,
|
return PagesBottomSheet(
|
||||||
int index,
|
episodes: episodes,
|
||||||
bool isCurrentIndex,
|
currentCid: currentCid,
|
||||||
) {
|
dataType: dataType,
|
||||||
|
changeFucCall: changeFucCall,
|
||||||
|
cid: cid,
|
||||||
|
sheetHeight: sheetHeight,
|
||||||
|
isFullScreen: isFullScreen,
|
||||||
|
ugcSeason: ugcSeason,
|
||||||
|
currentEpisodeIndex: currentEpisodeIndex,
|
||||||
|
currentIndex: currentIndex,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PersistentBottomSheetController show(BuildContext context) {
|
||||||
|
final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return buildShowContent();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return btmSheetCtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PagesBottomSheet extends StatefulWidget {
|
||||||
|
const PagesBottomSheet({
|
||||||
|
super.key,
|
||||||
|
required this.episodes,
|
||||||
|
required this.currentCid,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
this.cid,
|
||||||
|
this.sheetHeight,
|
||||||
|
this.isFullScreen = false,
|
||||||
|
this.ugcSeason,
|
||||||
|
this.currentEpisodeIndex,
|
||||||
|
this.currentIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<dynamic> episodes;
|
||||||
|
final int currentCid;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final int? cid;
|
||||||
|
final double? sheetHeight;
|
||||||
|
final bool isFullScreen;
|
||||||
|
final UgcSeason? ugcSeason;
|
||||||
|
final int? currentEpisodeIndex;
|
||||||
|
final int? currentIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PagesBottomSheet> createState() => _PagesBottomSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PagesBottomSheetState extends State<PagesBottomSheet>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
final ScrollController _listScrollController = ScrollController();
|
||||||
|
late ListObserverController _listObserverController;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
late int currentIndex;
|
||||||
|
TabController? tabController;
|
||||||
|
List<ListObserverController>? _listObserverControllerList;
|
||||||
|
List<ScrollController>? _listScrollControllerList;
|
||||||
|
final String heroTag = Get.arguments['heroTag'];
|
||||||
|
VideoDetailController? _videoDetailController;
|
||||||
|
RxInt isSubscribe = (-1).obs;
|
||||||
|
bool isVisible = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
currentIndex = widget.currentIndex ??
|
||||||
|
widget.episodes.indexWhere((dynamic e) => e.cid == widget.currentCid);
|
||||||
|
_scrollToInit();
|
||||||
|
_scrollPositionInit();
|
||||||
|
if (widget.dataType == VideoEpidoesType.videoEpisode) {
|
||||||
|
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
_getSubscribeStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String prefix() {
|
||||||
|
switch (widget.dataType) {
|
||||||
|
case VideoEpidoesType.videoEpisode:
|
||||||
|
return '选集';
|
||||||
|
case VideoEpidoesType.videoPart:
|
||||||
|
return '分集';
|
||||||
|
case VideoEpidoesType.bangumiEpisode:
|
||||||
|
return '选集';
|
||||||
|
}
|
||||||
|
return '选集';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动器初始化
|
||||||
|
void _scrollToInit() {
|
||||||
|
/// 单个
|
||||||
|
_listObserverController =
|
||||||
|
ListObserverController(controller: _listScrollController);
|
||||||
|
|
||||||
|
if (widget.dataType == VideoEpidoesType.videoEpisode &&
|
||||||
|
widget.ugcSeason?.sections != null &&
|
||||||
|
widget.ugcSeason!.sections!.length > 1) {
|
||||||
|
tabController = TabController(
|
||||||
|
length: widget.ugcSeason!.sections!.length,
|
||||||
|
vsync: this,
|
||||||
|
initialIndex: widget.currentEpisodeIndex ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// 多tab
|
||||||
|
_listScrollControllerList = List.generate(
|
||||||
|
widget.ugcSeason!.sections!.length,
|
||||||
|
(index) {
|
||||||
|
return ScrollController();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_listObserverControllerList = List.generate(
|
||||||
|
widget.ugcSeason!.sections!.length,
|
||||||
|
(index) {
|
||||||
|
return ListObserverController(
|
||||||
|
controller: _listScrollControllerList![index],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动器位置初始化
|
||||||
|
void _scrollPositionInit() {
|
||||||
|
if (widget.dataType == VideoEpidoesType.videoEpisode) {
|
||||||
|
// 单个 多tab
|
||||||
|
if (widget.ugcSeason?.sections != null) {
|
||||||
|
if (widget.ugcSeason!.sections!.length == 1) {
|
||||||
|
_listObserverController.initialIndexModel =
|
||||||
|
ObserverIndexPositionModel(
|
||||||
|
index: currentIndex,
|
||||||
|
isFixedHeight: true,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_listObserverControllerList![widget.currentEpisodeIndex!]
|
||||||
|
.initialIndexModel = ObserverIndexPositionModel(
|
||||||
|
index: currentIndex,
|
||||||
|
isFixedHeight: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (widget.dataType != VideoEpidoesType.videoEpisode) {
|
||||||
|
double itemHeight = (widget.isFullScreen
|
||||||
|
? 400
|
||||||
|
: Get.size.width - 3 * StyleString.safeSpace) /
|
||||||
|
5.2;
|
||||||
|
double offset = ((currentIndex - 1) / 2).ceil() * itemHeight;
|
||||||
|
_scrollController.jumpTo(offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取订阅状态
|
||||||
|
void _getSubscribeStatus() async {
|
||||||
|
var res =
|
||||||
|
await VideoHttp.getSubscribeStatus(bvid: _videoDetailController!.bvid);
|
||||||
|
if (res['status']) {
|
||||||
|
isSubscribe.value = res['data']['season_fav'] ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更改订阅状态
|
||||||
|
void _changeSubscribeStatus() async {
|
||||||
|
if (isSubscribe.value == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dynamic result = await VideoHttp.seasonFav(
|
||||||
|
isFav: isSubscribe.value == 1,
|
||||||
|
seasonId: widget.ugcSeason!.id,
|
||||||
|
);
|
||||||
|
if (result['status']) {
|
||||||
|
SmartDialog.showToast(isSubscribe.value == 1 ? '取消订阅成功' : '订阅成功');
|
||||||
|
isSubscribe.value = isSubscribe.value == 1 ? 0 : 1;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更改展开状态
|
||||||
|
void _changeVisible() {
|
||||||
|
setState(() {
|
||||||
|
isVisible = !isVisible;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
try {
|
||||||
|
_listObserverController.controller?.dispose();
|
||||||
|
_listScrollController.dispose();
|
||||||
|
for (var element in _listObserverControllerList!) {
|
||||||
|
element.controller?.dispose();
|
||||||
|
}
|
||||||
|
for (var element in _listScrollControllerList!) {
|
||||||
|
element.dispose();
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
|
return SizedBox(
|
||||||
|
height: widget.sheetHeight,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TitleBar(
|
||||||
|
title: '${prefix()}(${widget.episodes.length})',
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
),
|
||||||
|
if (widget.ugcSeason != null) ...[
|
||||||
|
UgcSeasonBuild(
|
||||||
|
ugcSeason: widget.ugcSeason!,
|
||||||
|
isSubscribe: isSubscribe,
|
||||||
|
isVisible: isVisible,
|
||||||
|
changeFucCall: _changeSubscribeStatus,
|
||||||
|
changeVisible: _changeVisible,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: widget.dataType == VideoEpidoesType.videoEpisode
|
||||||
|
? (widget.ugcSeason!.sections!.length == 1
|
||||||
|
? ListViewObserver(
|
||||||
|
controller: _listObserverController,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _listScrollController,
|
||||||
|
itemCount: widget.episodes.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
bool isLastItem =
|
||||||
|
index == widget.episodes.length;
|
||||||
|
bool isCurrentIndex = currentIndex == index;
|
||||||
|
return isLastItem
|
||||||
|
? SizedBox(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
20,
|
||||||
|
)
|
||||||
|
: EpisodeListItem(
|
||||||
|
episode: widget.episodes[index],
|
||||||
|
index: index,
|
||||||
|
isCurrentIndex: isCurrentIndex,
|
||||||
|
dataType: widget.dataType,
|
||||||
|
changeFucCall: widget.changeFucCall,
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: buildTabBar())
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0), // 设置左右间距为12
|
||||||
|
child: GridView.count(
|
||||||
|
controller: _scrollController,
|
||||||
|
crossAxisCount: 2,
|
||||||
|
crossAxisSpacing: StyleString.safeSpace,
|
||||||
|
childAspectRatio: 2.6,
|
||||||
|
children: List.generate(
|
||||||
|
widget.episodes.length,
|
||||||
|
(index) {
|
||||||
|
bool isCurrentIndex = currentIndex == index;
|
||||||
|
return EpisodeGridItem(
|
||||||
|
episode: widget.episodes[index],
|
||||||
|
index: index,
|
||||||
|
isCurrentIndex: isCurrentIndex,
|
||||||
|
dataType: widget.dataType,
|
||||||
|
changeFucCall: widget.changeFucCall,
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTabBar() {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// 背景色
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: TabBar(
|
||||||
|
controller: tabController,
|
||||||
|
isScrollable: true,
|
||||||
|
indicatorSize: TabBarIndicatorSize.label,
|
||||||
|
tabAlignment: TabAlignment.start,
|
||||||
|
splashBorderRadius: BorderRadius.circular(4),
|
||||||
|
tabs: [
|
||||||
|
...widget.ugcSeason!.sections!.map((SectionItem section) {
|
||||||
|
return Tab(
|
||||||
|
text: section.title,
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: tabController,
|
||||||
|
children: [
|
||||||
|
...widget.ugcSeason!.sections!.map((SectionItem section) {
|
||||||
|
final int fIndex = widget.ugcSeason!.sections!.indexOf(section);
|
||||||
|
return ListViewObserver(
|
||||||
|
controller: _listObserverControllerList![fIndex],
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _listScrollControllerList![fIndex],
|
||||||
|
itemCount: section.episodes!.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
final bool isLastItem = index == section.episodes!.length;
|
||||||
|
return isLastItem
|
||||||
|
? SizedBox(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 20,
|
||||||
|
)
|
||||||
|
: EpisodeListItem(
|
||||||
|
episode: section.episodes![index], // 调整索引
|
||||||
|
index: index, // 调整索引
|
||||||
|
isCurrentIndex: widget.currentCid ==
|
||||||
|
section.episodes![index].cid,
|
||||||
|
dataType: widget.dataType,
|
||||||
|
changeFucCall: widget.changeFucCall,
|
||||||
|
isFullScreen: widget.isFullScreen,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList()
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TitleBar extends StatelessWidget {
|
||||||
|
final String title;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const TitleBar({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
toolbarHeight: 45,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
centerTitle: false,
|
||||||
|
elevation: 1,
|
||||||
|
scrolledUnderElevation: 1,
|
||||||
|
title: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 12),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: !isFullScreen
|
||||||
|
? [
|
||||||
|
SizedBox(
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.close, size: 20),
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpisodeListItem extends StatelessWidget {
|
||||||
|
final dynamic episode;
|
||||||
|
final int index;
|
||||||
|
final bool isCurrentIndex;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const EpisodeListItem({
|
||||||
|
Key? key,
|
||||||
|
required this.episode,
|
||||||
|
required this.index,
|
||||||
|
required this.isCurrentIndex,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
Color onSurface = Theme.of(context).colorScheme.onSurface;
|
Color onSurface = Theme.of(context).colorScheme.onSurface;
|
||||||
|
|
||||||
@ -45,128 +472,365 @@ class EpisodeBottomSheet {
|
|||||||
title = '第${episode.title}话 ${episode.longTitle!}';
|
title = '第${episode.title}话 ${episode.longTitle!}';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isFullScreen || episode?.cover == null || episode?.cover == ''
|
return isFullScreen || episode?.cover == null || episode?.cover == ''
|
||||||
? ListTile(
|
? _buildListTile(context, title, primary, onSurface)
|
||||||
|
: _buildInkWell(context, title, primary, onSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListTile(
|
||||||
|
BuildContext context, String title, Color primary, Color onSurface) {
|
||||||
|
return ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SmartDialog.showToast('切换至「$title」');
|
SmartDialog.showToast('切换至「$title」');
|
||||||
changeFucCall.call(episode, index);
|
changeFucCall.call(episode, index);
|
||||||
},
|
},
|
||||||
dense: false,
|
dense: false,
|
||||||
leading: isCurrentIndex
|
leading: isCurrentIndex
|
||||||
? Image.asset(
|
? Image.asset(
|
||||||
'assets/images/live.gif',
|
'assets/images/live.png',
|
||||||
color: primary,
|
color: primary,
|
||||||
height: 12,
|
height: 12,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
title: Text(title,
|
title: Text(
|
||||||
|
title,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: isCurrentIndex ? primary : onSurface,
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
)))
|
),
|
||||||
: InkWell(
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInkWell(
|
||||||
|
BuildContext context, String title, Color primary, Color onSurface) {
|
||||||
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
SmartDialog.showToast('切换至「$title」');
|
SmartDialog.showToast('切换至「$title」');
|
||||||
changeFucCall.call(episode, index);
|
changeFucCall.call(episode, index);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.fromLTRB(
|
||||||
const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
|
StyleString.safeSpace, 6, StyleString.safeSpace, 6),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||||
|
const double width = 160;
|
||||||
|
return Container(
|
||||||
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: StyleString.aspectRatio,
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (BuildContext context,
|
||||||
|
BoxConstraints boxConstraints) {
|
||||||
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: 130, height: 75, src: episode?.cover ?? ''),
|
src: episode?.cover ?? '',
|
||||||
const SizedBox(width: 10),
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
if (episode.duration != 0)
|
||||||
|
PBadge(
|
||||||
|
text: Utils.timeFormat(episode.duration!),
|
||||||
|
right: 6.0,
|
||||||
|
bottom: 6.0,
|
||||||
|
type: 'gray',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Padding(
|
||||||
title,
|
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
episode.title as String,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontWeight: FontWeight.w500,
|
||||||
color: isCurrentIndex ? primary : onSurface,
|
color: isCurrentIndex ? primary : onSurface,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (dataType != VideoEpidoesType.videoPart) ...[
|
||||||
|
if (episode?.pubdate != null ||
|
||||||
|
episode.pubTime != null)
|
||||||
|
Text(
|
||||||
|
Utils.dateFormat(
|
||||||
|
episode?.pubdate ?? episode.pubTime),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 2),
|
||||||
),
|
if (episode.stat != null)
|
||||||
),
|
Row(
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildTitle() {
|
|
||||||
return AppBar(
|
|
||||||
toolbarHeight: 45,
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
centerTitle: false,
|
|
||||||
title: Text(
|
|
||||||
'合集(${episodes.length})',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
actions: !isFullScreen
|
|
||||||
? [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close, size: 20),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 14),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget buildShowContent(BuildContext context) {
|
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
|
||||||
int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid);
|
|
||||||
return StatefulBuilder(
|
|
||||||
builder: (BuildContext context, StateSetter setState) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
itemScrollController.jumpTo(index: currentIndex);
|
|
||||||
});
|
|
||||||
return Container(
|
|
||||||
height: sheetHeight,
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
buildTitle(),
|
StatView(view: episode.stat.view),
|
||||||
Expanded(
|
const SizedBox(width: 8),
|
||||||
child: Material(
|
StatDanMu(danmu: episode.stat.danmaku),
|
||||||
child: PageStorage(
|
const Spacer(),
|
||||||
bucket: PageStorageBucket(),
|
],
|
||||||
child: ScrollablePositionedList.builder(
|
),
|
||||||
itemScrollController: itemScrollController,
|
const SizedBox(height: 4),
|
||||||
itemCount: episodes.length + 1,
|
]
|
||||||
itemBuilder: (BuildContext context, int index) {
|
],
|
||||||
bool isLastItem = index == episodes.length;
|
),
|
||||||
bool isCurrentIndex = currentIndex == index;
|
),
|
||||||
return isLastItem
|
|
||||||
? SizedBox(
|
|
||||||
height:
|
|
||||||
MediaQuery.of(context).padding.bottom + 20,
|
|
||||||
)
|
)
|
||||||
: buildEpisodeListItem(
|
|
||||||
episodes[index],
|
|
||||||
index,
|
|
||||||
isCurrentIndex,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The [BuildContext] of the widget that calls the bottom sheet.
|
|
||||||
PersistentBottomSheetController show(BuildContext context) {
|
|
||||||
final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return buildShowContent(context);
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EpisodeGridItem extends StatelessWidget {
|
||||||
|
final dynamic episode;
|
||||||
|
final int index;
|
||||||
|
final bool isCurrentIndex;
|
||||||
|
final dynamic dataType;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final bool isFullScreen;
|
||||||
|
|
||||||
|
const EpisodeGridItem({
|
||||||
|
Key? key,
|
||||||
|
required this.episode,
|
||||||
|
required this.index,
|
||||||
|
required this.isCurrentIndex,
|
||||||
|
required this.dataType,
|
||||||
|
required this.changeFucCall,
|
||||||
|
required this.isFullScreen,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
TextStyle textStyle = TextStyle(
|
||||||
|
color: isCurrentIndex ? colorScheme.primary : colorScheme.onSurface,
|
||||||
|
fontSize: 14,
|
||||||
|
);
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const EdgeInsets.only(top: StyleString.safeSpace),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isCurrentIndex
|
||||||
|
? colorScheme.primaryContainer.withOpacity(0.6)
|
||||||
|
: colorScheme.onInverseSurface.withOpacity(0.6),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: isCurrentIndex
|
||||||
|
? colorScheme.primary.withOpacity(0.8)
|
||||||
|
: Colors.transparent,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
onTap: () {
|
||||||
|
if (isCurrentIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SmartDialog.showToast('切换至「${episode.title}」');
|
||||||
|
changeFucCall.call(episode, index);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
dataType == VideoEpidoesType.bangumiEpisode
|
||||||
|
? '第${index + 1}话'
|
||||||
|
: '第${index + 1}p',
|
||||||
|
style: textStyle),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
episode.title,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: textStyle,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (dataType == VideoEpidoesType.bangumiEpisode &&
|
||||||
|
episode.badge != '' &&
|
||||||
|
episode.badge != null)
|
||||||
|
Positioned(
|
||||||
|
right: 8,
|
||||||
|
top: 18,
|
||||||
|
child: Text(
|
||||||
|
episode.badge,
|
||||||
|
style: const TextStyle(fontSize: 11, color: Color(0xFFFF6699)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UgcSeasonBuild extends StatelessWidget {
|
||||||
|
final UgcSeason ugcSeason;
|
||||||
|
final RxInt isSubscribe;
|
||||||
|
final bool isVisible;
|
||||||
|
final Function changeFucCall;
|
||||||
|
final Function changeVisible;
|
||||||
|
|
||||||
|
const UgcSeasonBuild({
|
||||||
|
Key? key,
|
||||||
|
required this.ugcSeason,
|
||||||
|
required this.isSubscribe,
|
||||||
|
required this.isVisible,
|
||||||
|
required this.changeFucCall,
|
||||||
|
required this.changeVisible,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
final Color outline = theme.colorScheme.outline;
|
||||||
|
final Color surface = theme.colorScheme.surface;
|
||||||
|
final Color primary = theme.colorScheme.primary;
|
||||||
|
final Color onPrimary = theme.colorScheme.onPrimary;
|
||||||
|
final Color onInverseSurface = theme.colorScheme.onInverseSurface;
|
||||||
|
final TextStyle titleMedium = theme.textTheme.titleMedium!;
|
||||||
|
final TextStyle labelMedium = theme.textTheme.labelMedium!;
|
||||||
|
final Color dividerColor = theme.dividerColor.withOpacity(0.1);
|
||||||
|
|
||||||
|
return isVisible
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||||
|
color: surface,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Divider(height: 1, thickness: 1, color: dividerColor),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'合集:${ugcSeason.title}',
|
||||||
|
style: titleMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Obx(
|
||||||
|
() => isSubscribe.value == -1
|
||||||
|
? const SizedBox(height: 32)
|
||||||
|
: SizedBox(
|
||||||
|
height: 32,
|
||||||
|
child: FilledButton.tonal(
|
||||||
|
onPressed: () => changeFucCall.call(),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(left: 8, right: 8),
|
||||||
|
foregroundColor: isSubscribe.value == 1
|
||||||
|
? outline
|
||||||
|
: onPrimary,
|
||||||
|
backgroundColor: isSubscribe.value == 1
|
||||||
|
? onInverseSurface
|
||||||
|
: primary,
|
||||||
|
),
|
||||||
|
child:
|
||||||
|
Text(isSubscribe.value == 1 ? '已订阅' : '订阅'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (ugcSeason.intro != null && ugcSeason.intro != '') ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
ugcSeason.intro!,
|
||||||
|
style: TextStyle(color: outline, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: labelMedium.fontSize, color: outline),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'),
|
||||||
|
const TextSpan(text: ' · '),
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
'${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Material(
|
||||||
|
color: surface,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => changeVisible.call(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10, horizontal: 0),
|
||||||
|
child: Text(
|
||||||
|
'收起简介',
|
||||||
|
style: TextStyle(color: primary, fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(height: 1, thickness: 1, color: dividerColor),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => changeVisible.call(),
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 10, horizontal: 0),
|
||||||
|
child: Text(
|
||||||
|
'展开简介',
|
||||||
|
style: TextStyle(color: primary, fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return btmSheetCtr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,9 @@ import 'package:pilipala/common/constants.dart';
|
|||||||
|
|
||||||
import 'skeleton.dart';
|
import 'skeleton.dart';
|
||||||
|
|
||||||
class MediaBangumiSkeleton extends StatefulWidget {
|
class MediaBangumiSkeleton extends StatelessWidget {
|
||||||
const MediaBangumiSkeleton({super.key});
|
const MediaBangumiSkeleton({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<MediaBangumiSkeleton> createState() => _MediaBangumiSkeletonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||||
@ -35,25 +30,25 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor,
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 20,
|
height: 20,
|
||||||
margin: const EdgeInsets.only(bottom: 15),
|
margin: const EdgeInsets.only(bottom: 15),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor,
|
||||||
width: 150,
|
width: 150,
|
||||||
height: 13,
|
height: 13,
|
||||||
margin: const EdgeInsets.only(bottom: 5),
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor,
|
||||||
width: 150,
|
width: 150,
|
||||||
height: 13,
|
height: 13,
|
||||||
margin: const EdgeInsets.only(bottom: 5),
|
margin: const EdgeInsets.only(bottom: 5),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor,
|
||||||
width: 150,
|
width: 150,
|
||||||
height: 13,
|
height: 13,
|
||||||
),
|
),
|
||||||
@ -64,7 +59,7 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius:
|
borderRadius:
|
||||||
const BorderRadius.all(Radius.circular(20)),
|
const BorderRadius.all(Radius.circular(20)),
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: bgColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
42
lib/common/skeleton/user_list.dart
Normal file
42
lib/common/skeleton/user_list.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../constants.dart';
|
||||||
|
|
||||||
|
class UserListSkeleton extends StatelessWidget {
|
||||||
|
const UserListSkeleton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: StyleString.safeSpace, vertical: 7),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: Container(width: 42, height: 42, color: bgColor),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(color: bgColor, width: 60, height: 13),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Container(color: bgColor, width: 40, height: 13),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Container(
|
||||||
|
color: bgColor,
|
||||||
|
width: 100,
|
||||||
|
height: 13,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
126
lib/common/skeleton/video_intro.dart
Normal file
126
lib/common/skeleton/video_intro.dart
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../constants.dart';
|
||||||
|
import 'skeleton.dart';
|
||||||
|
|
||||||
|
class VideoIntroSkeleton extends StatelessWidget {
|
||||||
|
const VideoIntroSkeleton({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||||
|
return Skeleton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 20,
|
||||||
|
margin: const EdgeInsets.only(bottom: 6),
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 220,
|
||||||
|
height: 20,
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 45,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
width: 45,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
width: 45,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
width: 35,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
LayoutBuilder(builder: (context, constraints) {
|
||||||
|
// 并列5个正方形
|
||||||
|
double width = (constraints.maxWidth - 30) / 5;
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
return Container(
|
||||||
|
width: width - 24,
|
||||||
|
height: width - 24,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 30,
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: Container(
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Container(
|
||||||
|
width: 50,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Container(
|
||||||
|
width: 35,
|
||||||
|
height: 14,
|
||||||
|
color: bgColor,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Container(
|
||||||
|
width: 55,
|
||||||
|
height: 30,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 2)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
Box<dynamic> setting = GStrorage.setting;
|
Box<dynamic> setting = GStorage.setting;
|
||||||
|
|
||||||
class CustomToast extends StatelessWidget {
|
class CustomToast extends StatelessWidget {
|
||||||
const CustomToast({super.key, required this.msg});
|
const CustomToast({super.key, required this.msg});
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
|
||||||
import 'network_img_layer.dart';
|
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
|
||||||
|
import 'package:pilipala/utils/highlight.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class HtmlRender extends StatelessWidget {
|
class HtmlRender extends StatelessWidget {
|
||||||
@ -22,6 +24,20 @@ class HtmlRender extends StatelessWidget {
|
|||||||
data: htmlContent,
|
data: htmlContent,
|
||||||
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
|
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
|
||||||
extensions: [
|
extensions: [
|
||||||
|
TagExtension(
|
||||||
|
tagsToExtend: <String>{'pre'},
|
||||||
|
builder: (ExtensionContext extensionContext) {
|
||||||
|
final Map<String, dynamic> attributes = extensionContext.attributes;
|
||||||
|
final String lang = attributes['data-lang'] as String;
|
||||||
|
final String code = attributes['codecontent'] as String;
|
||||||
|
List<String> selectedLanguages = [lang.split('@').first];
|
||||||
|
TextSpan? result = highlightExistingText(code, selectedLanguages);
|
||||||
|
if (result == null) {
|
||||||
|
return const Center(child: Text('代码块渲染失败'));
|
||||||
|
}
|
||||||
|
return SelectableText.rich(result);
|
||||||
|
},
|
||||||
|
),
|
||||||
TagExtension(
|
TagExtension(
|
||||||
tagsToExtend: <String>{'img'},
|
tagsToExtend: <String>{'img'},
|
||||||
builder: (ExtensionContext extensionContext) {
|
builder: (ExtensionContext extensionContext) {
|
||||||
@ -44,20 +60,52 @@ class HtmlRender extends StatelessWidget {
|
|||||||
if (isMall) {
|
if (isMall) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
// bool inTable =
|
return InkWell(
|
||||||
// extensionContext.element!.previousElementSibling == null ||
|
onTap: () {
|
||||||
// extensionContext.element!.nextElementSibling == null;
|
Navigator.of(context).push(
|
||||||
// imgUrl = Utils().imageUrl(imgUrl!);
|
HeroDialogRoute<void>(
|
||||||
// return Image.network(
|
builder: (BuildContext context) =>
|
||||||
// imgUrl,
|
InteractiveviewerGallery(
|
||||||
// width: isEmote ? 22 : null,
|
sources: imgList ?? [imgUrl],
|
||||||
// height: isEmote ? 22 : null,
|
initIndex: imgList?.indexOf(imgUrl) ?? 0,
|
||||||
// );
|
itemBuilder: (
|
||||||
return NetworkImgLayer(
|
BuildContext context,
|
||||||
width: isEmote ? 22 : Get.size.width - 24,
|
int index,
|
||||||
height: isEmote ? 22 : 200,
|
bool isFocus,
|
||||||
src: imgUrl,
|
bool enablePageView,
|
||||||
|
) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () {
|
||||||
|
if (enablePageView) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Hero(
|
||||||
|
tag: imgList?[index] ?? imgUrl,
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
fadeInDuration:
|
||||||
|
const Duration(milliseconds: 0),
|
||||||
|
imageUrl: imgList?[index] ?? imgUrl,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
onPageChanged: (int pageIndex) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: CachedNetworkImage(imageUrl: imgUrl),
|
||||||
|
);
|
||||||
|
// return NetworkImgLayer(
|
||||||
|
// width: isEmote ? 22 : Get.size.width - 24,
|
||||||
|
// height: isEmote ? 22 : 200,
|
||||||
|
// src: imgUrl,
|
||||||
|
// );
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
@ -66,7 +114,7 @@ class HtmlRender extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
style: {
|
style: {
|
||||||
'html': Style(
|
'html': Style(
|
||||||
fontSize: FontSize.medium,
|
fontSize: FontSize.large,
|
||||||
lineHeight: LineHeight.percent(140),
|
lineHeight: LineHeight.percent(140),
|
||||||
),
|
),
|
||||||
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
|
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
|
||||||
@ -78,7 +126,7 @@ class HtmlRender extends StatelessWidget {
|
|||||||
margin: Margins.only(bottom: 10),
|
margin: Margins.only(bottom: 10),
|
||||||
),
|
),
|
||||||
'span': Style(
|
'span': Style(
|
||||||
fontSize: FontSize.medium,
|
fontSize: FontSize.large,
|
||||||
height: Height(1.65),
|
height: Height(1.65),
|
||||||
),
|
),
|
||||||
'div': Style(height: Height.auto()),
|
'div': Style(height: Height.auto()),
|
||||||
|
|||||||
@ -4,9 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||||||
class HttpError extends StatelessWidget {
|
class HttpError extends StatelessWidget {
|
||||||
const HttpError({
|
const HttpError({
|
||||||
required this.errMsg,
|
required this.errMsg,
|
||||||
required this.fn,
|
this.fn,
|
||||||
this.btnText,
|
this.btnText,
|
||||||
this.isShowBtn = true,
|
this.isShowBtn = true,
|
||||||
|
this.isInSliver = true,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -14,20 +15,17 @@ class HttpError extends StatelessWidget {
|
|||||||
final Function()? fn;
|
final Function()? fn;
|
||||||
final String? btnText;
|
final String? btnText;
|
||||||
final bool isShowBtn;
|
final bool isShowBtn;
|
||||||
|
final bool isInSliver;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverToBoxAdapter(
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
child: SizedBox(
|
final errorContent = SizedBox(
|
||||||
height: 400,
|
height: 400,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
SvgPicture.asset(
|
SvgPicture.asset("assets/images/error.svg", height: 200),
|
||||||
"assets/images/error.svg",
|
|
||||||
height: 200,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 30),
|
||||||
Text(
|
Text(
|
||||||
errMsg ?? '请求异常',
|
errMsg ?? '请求异常',
|
||||||
@ -37,23 +35,21 @@ class HttpError extends StatelessWidget {
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (isShowBtn)
|
if (isShowBtn)
|
||||||
FilledButton.tonal(
|
FilledButton.tonal(
|
||||||
onPressed: () {
|
onPressed: () => fn?.call(),
|
||||||
fn!();
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||||
return Theme.of(context).colorScheme.primary.withAlpha(20);
|
return primary.withAlpha(20);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(btnText ?? '点击重试', style: TextStyle(color: primary)),
|
||||||
btnText ?? '点击重试',
|
|
||||||
style:
|
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if (isInSliver) {
|
||||||
|
return SliverToBoxAdapter(child: errorContent);
|
||||||
|
} else {
|
||||||
|
return Align(alignment: Alignment.topCenter, child: errorContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import 'package:pilipala/utils/global_data_cache.dart';
|
|||||||
import '../../utils/storage.dart';
|
import '../../utils/storage.dart';
|
||||||
import '../constants.dart';
|
import '../constants.dart';
|
||||||
|
|
||||||
Box<dynamic> setting = GStrorage.setting;
|
Box<dynamic> setting = GStorage.setting;
|
||||||
|
|
||||||
class NetworkImgLayer extends StatelessWidget {
|
class NetworkImgLayer extends StatelessWidget {
|
||||||
const NetworkImgLayer({
|
const NetworkImgLayer({
|
||||||
@ -20,6 +20,7 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
// 图片质量 默认1%
|
// 图片质量 默认1%
|
||||||
this.quality,
|
this.quality,
|
||||||
this.origAspectRatio,
|
this.origAspectRatio,
|
||||||
|
this.radius,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? src;
|
final String? src;
|
||||||
@ -30,10 +31,26 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
final Duration? fadeInDuration;
|
final Duration? fadeInDuration;
|
||||||
final int? quality;
|
final int? quality;
|
||||||
final double? origAspectRatio;
|
final double? origAspectRatio;
|
||||||
|
final double? radius;
|
||||||
|
|
||||||
|
BorderRadius getBorderRadius(String? type, double? radius) {
|
||||||
|
return BorderRadius.circular(
|
||||||
|
radius ??
|
||||||
|
(type == 'avatar'
|
||||||
|
? 50
|
||||||
|
: type == 'emote'
|
||||||
|
? 0
|
||||||
|
: StyleString.imgRadius.x),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final int defaultImgQuality = GlobalDataCache().imgQuality;
|
int defaultImgQuality = 10;
|
||||||
|
try {
|
||||||
|
defaultImgQuality = GlobalDataCache.imgQuality;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
if (src == '' || src == null) {
|
if (src == '' || src == null) {
|
||||||
return placeholder(context);
|
return placeholder(context);
|
||||||
}
|
}
|
||||||
@ -68,13 +85,7 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
return src != '' && src != null
|
return src != '' && src != null
|
||||||
? ClipRRect(
|
? ClipRRect(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: getBorderRadius(type, radius),
|
||||||
type == 'avatar'
|
|
||||||
? 50
|
|
||||||
: type == 'emote'
|
|
||||||
? 0
|
|
||||||
: StyleString.imgRadius.x,
|
|
||||||
),
|
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
width: width,
|
width: width,
|
||||||
@ -103,11 +114,7 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
|
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
|
||||||
borderRadius: BorderRadius.circular(type == 'avatar'
|
borderRadius: getBorderRadius(type, radius),
|
||||||
? 50
|
|
||||||
: type == 'emote'
|
|
||||||
? 0
|
|
||||||
: StyleString.imgRadius.x),
|
|
||||||
),
|
),
|
||||||
child: type == 'bg'
|
child: type == 'bg'
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
|
|||||||
@ -60,17 +60,13 @@ class VideoCardV extends StatelessWidget {
|
|||||||
// 动态
|
// 动态
|
||||||
case 'picture':
|
case 'picture':
|
||||||
try {
|
try {
|
||||||
String dynamicType = 'picture';
|
|
||||||
String uri = videoItem.uri;
|
String uri = videoItem.uri;
|
||||||
String id = '';
|
|
||||||
if (videoItem.uri.startsWith('bilibili://article/')) {
|
if (videoItem.uri.startsWith('bilibili://article/')) {
|
||||||
// https://www.bilibili.com/read/cv27063554
|
// https://www.bilibili.com/read/cv27063554
|
||||||
dynamicType = 'read';
|
|
||||||
RegExp regex = RegExp(r'\d+');
|
RegExp regex = RegExp(r'\d+');
|
||||||
Match match = regex.firstMatch(videoItem.uri)!;
|
Match match = regex.firstMatch(videoItem.uri)!;
|
||||||
String matchedNumber = match.group(0)!;
|
String matchedNumber = match.group(0)!;
|
||||||
videoItem.param = int.parse(matchedNumber);
|
videoItem.param = int.parse(matchedNumber);
|
||||||
id = 'cv${videoItem.param}';
|
|
||||||
}
|
}
|
||||||
if (uri.startsWith('http')) {
|
if (uri.startsWith('http')) {
|
||||||
String path = Uri.parse(uri).path;
|
String path = Uri.parse(uri).path;
|
||||||
@ -88,11 +84,10 @@ class VideoCardV extends StatelessWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
Get.toNamed('/read', parameters: {
|
||||||
'url': uri,
|
|
||||||
'title': videoItem.title,
|
'title': videoItem.title,
|
||||||
'id': id,
|
'id': videoItem.param,
|
||||||
'dynamicType': dynamicType
|
'articleType': 'read'
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast(err.toString());
|
||||||
@ -287,8 +282,9 @@ class VideoStat extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
StatView(view: videoItem.stat.view),
|
if (videoItem.stat.view != null) StatView(view: videoItem.stat.view),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
if (videoItem.stat.danmu != null)
|
||||||
StatDanMu(danmu: videoItem.stat.danmu),
|
StatDanMu(danmu: videoItem.stat.danmu),
|
||||||
if (videoItem is RecVideoItemModel) ...<Widget>[
|
if (videoItem is RecVideoItemModel) ...<Widget>[
|
||||||
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
||||||
|
|||||||
@ -104,7 +104,7 @@ class Api {
|
|||||||
|
|
||||||
// 评论列表
|
// 评论列表
|
||||||
// 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/main';
|
||||||
|
|
||||||
// 楼中楼
|
// 楼中楼
|
||||||
static const String replyReplyList = '/x/v2/reply/reply';
|
static const String replyReplyList = '/x/v2/reply/reply';
|
||||||
@ -175,7 +175,7 @@ class Api {
|
|||||||
static const String delHistory = '/x/v2/history/delete';
|
static const String delHistory = '/x/v2/history/delete';
|
||||||
|
|
||||||
// 搜索历史记录
|
// 搜索历史记录
|
||||||
static const String searchHistory = '/x/web-goblin/history/search';
|
static const String searchHistory = '/x/web-interface/history/search';
|
||||||
|
|
||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
@ -301,10 +301,6 @@ class Api {
|
|||||||
static const String bangumiList =
|
static const String bangumiList =
|
||||||
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1©right=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
|
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1©right=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
|
||||||
|
|
||||||
// 我的订阅
|
|
||||||
static const String bangumiFollow =
|
|
||||||
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
|
|
||||||
|
|
||||||
// 黑名单
|
// 黑名单
|
||||||
static const String blackLst = '/x/relation/blacks';
|
static const String blackLst = '/x/relation/blacks';
|
||||||
|
|
||||||
@ -499,7 +495,7 @@ class Api {
|
|||||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||||
|
|
||||||
/// 获取字幕配置
|
/// 获取字幕配置
|
||||||
static const getSubtitleConfig = '/x/player/v2';
|
static const getSubtitleConfig = '/x/player/wbi/v2';
|
||||||
|
|
||||||
/// 我的订阅
|
/// 我的订阅
|
||||||
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
||||||
@ -555,6 +551,10 @@ class Api {
|
|||||||
static const String messageSystemAPi =
|
static const String messageSystemAPi =
|
||||||
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
|
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
|
||||||
|
|
||||||
|
/// 系统通知 个人
|
||||||
|
static const String userMessageSystemAPi =
|
||||||
|
'${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify';
|
||||||
|
|
||||||
/// 系统通知标记已读
|
/// 系统通知标记已读
|
||||||
static const String systemMarkRead =
|
static const String systemMarkRead =
|
||||||
'${HttpString.messageBaseUrl}/x/sys-msg/update_cursor';
|
'${HttpString.messageBaseUrl}/x/sys-msg/update_cursor';
|
||||||
@ -575,4 +575,55 @@ class Api {
|
|||||||
/// 我的关注 - 正在直播
|
/// 我的关注 - 正在直播
|
||||||
static const String getFollowingLive =
|
static const String getFollowingLive =
|
||||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
||||||
|
|
||||||
|
/// 稍后再看&收藏夹视频列表
|
||||||
|
static const String mediaList = '/x/v2/medialist/resource/list';
|
||||||
|
|
||||||
|
/// 用户专栏
|
||||||
|
static const String opusList = '/x/polymer/web-dynamic/v1/opus/feed/space';
|
||||||
|
|
||||||
|
///
|
||||||
|
static const String getViewInfo = '/x/article/viewinfo';
|
||||||
|
|
||||||
|
/// 直播间记录
|
||||||
|
static const String liveRoomEntry =
|
||||||
|
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
|
||||||
|
|
||||||
|
/// 用户信息
|
||||||
|
static const String accountInfo = '/x/member/web/account';
|
||||||
|
|
||||||
|
/// 更新用户信息
|
||||||
|
static const String updateAccountInfo = '/x/member/web/update';
|
||||||
|
|
||||||
|
/// 删除评论
|
||||||
|
static const String replyDel = '/x/v2/reply/del';
|
||||||
|
|
||||||
|
/// 图片上传
|
||||||
|
static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs';
|
||||||
|
|
||||||
|
/// 更新追番状态
|
||||||
|
static const String updateBangumiStatus = '/pgc/web/follow/status/update';
|
||||||
|
|
||||||
|
/// 番剧点赞投币收藏状态
|
||||||
|
static const String bangumiActionStatus = '/pgc/season/episode/community';
|
||||||
|
|
||||||
|
/// @我的
|
||||||
|
static const String messageAtAPi = '/x/msgfeed/at?';
|
||||||
|
|
||||||
|
/// 订阅
|
||||||
|
static const String confirmSub = '/x/v3/fav/season/fav';
|
||||||
|
|
||||||
|
/// 订阅状态
|
||||||
|
static const String videoRelation = '/x/web-interface/archive/relation';
|
||||||
|
|
||||||
|
/// 获取空降区间
|
||||||
|
static const String getSkipSegments =
|
||||||
|
'${HttpString.sponsorBlockBaseUrl}/api/skipSegments';
|
||||||
|
|
||||||
|
/// 视频标签
|
||||||
|
static const String videoTag = '/x/tag/archive/tags';
|
||||||
|
|
||||||
|
/// 修复标题和海报
|
||||||
|
// /api/view?id=${aid} /all/video/av${aid} /video/av${aid}/
|
||||||
|
static const String fixTitleAndPic = '${HttpString.biliplusBaseUrl}/api/view';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import '../models/bangumi/list.dart';
|
import '../models/bangumi/list.dart';
|
||||||
import 'index.dart';
|
import 'index.dart';
|
||||||
|
import 'package:html/parser.dart' as html_parser;
|
||||||
|
import 'package:html/dom.dart' as html_dom;
|
||||||
|
|
||||||
class BangumiHttp {
|
class BangumiHttp {
|
||||||
static Future bangumiList({int? page}) async {
|
static Future bangumiList({int? page}) async {
|
||||||
@ -18,8 +21,19 @@ class BangumiHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future bangumiFollow({int? mid}) async {
|
static Future getRecentBangumi({
|
||||||
var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid});
|
int? mid,
|
||||||
|
int type = 1,
|
||||||
|
int pn = 1,
|
||||||
|
int ps = 20,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(Api.getRecentBangumiApi, data: {
|
||||||
|
'vmid': mid,
|
||||||
|
'type': type,
|
||||||
|
'follow_status': 0,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
@ -33,4 +47,62 @@ class BangumiHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取追番状态
|
||||||
|
static Future bangumiStatus({required int seasonId}) async {
|
||||||
|
var res = await Request()
|
||||||
|
.get('https://www.bilibili.com/bangumi/play/ss$seasonId');
|
||||||
|
html_dom.Document document = html_parser.parse(res.data);
|
||||||
|
// 查找 id 为 __NEXT_DATA__ 的 script 元素
|
||||||
|
html_dom.Element? scriptElement =
|
||||||
|
document.querySelector('script#\\__NEXT_DATA__');
|
||||||
|
if (scriptElement != null) {
|
||||||
|
// 提取 script 元素的内容
|
||||||
|
String scriptContent = scriptElement.text;
|
||||||
|
final dynamic scriptContentJson = jsonDecode(scriptContent);
|
||||||
|
Map followState = scriptContentJson['props']['pageProps']['followState'];
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': {
|
||||||
|
'isFollowed': followState['isFollowed'],
|
||||||
|
'followStatus': followState['followStatus']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
print('Script element with id "__NEXT_DATA__" not found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新追番状态
|
||||||
|
static Future updateBangumiStatus({
|
||||||
|
required int seasonId,
|
||||||
|
required int status,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(Api.updateBangumiStatus, data: {
|
||||||
|
'season_id': seasonId,
|
||||||
|
'status': status,
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取番剧点赞投币收藏状态
|
||||||
|
static Future bangumiActionStatus({required int epId}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.bangumiActionStatus,
|
||||||
|
data: {'ep_id': epId},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ class BlackHttp {
|
|||||||
static Future removeBlack({required int fid}) async {
|
static Future removeBlack({required int fid}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.removeBlack,
|
Api.removeBlack,
|
||||||
queryParameters: {
|
data: {
|
||||||
'act': 6,
|
'act': 6,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
'fid': fid,
|
'fid': fid,
|
||||||
|
|||||||
@ -1,6 +1,15 @@
|
|||||||
|
import 'package:pilipala/models/common/invalid_video.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/models/sponsor_block/segment.dart';
|
||||||
|
|
||||||
import 'index.dart';
|
import 'index.dart';
|
||||||
|
|
||||||
class CommonHttp {
|
class CommonHttp {
|
||||||
|
static final RegExp spmPrefixExp =
|
||||||
|
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||||
static Future unReadDynamic() async {
|
static Future unReadDynamic() async {
|
||||||
var res = await Request().get(Api.getUnreadDynamic,
|
var res = await Request().get(Api.getUnreadDynamic,
|
||||||
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
|
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
|
||||||
@ -14,4 +23,95 @@ class CommonHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future querySkipSegments({required String bvid}) async {
|
||||||
|
var res = await Request().getWithoutCookie(Api.getSkipSegments, data: {
|
||||||
|
'videoID': bvid,
|
||||||
|
});
|
||||||
|
if (res.data is List && res.data.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data
|
||||||
|
.map<SegmentDataModel>((e) => SegmentDataModel.fromJson(e))
|
||||||
|
.toList(),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': 'sponsorBlock数据解析失败: $err',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future fixVideoPicAndTitle({required int aid}) async {
|
||||||
|
var res = await Request().getWithoutCookie(Api.fixTitleAndPic, data: {
|
||||||
|
'id': aid,
|
||||||
|
});
|
||||||
|
if (res != null) {
|
||||||
|
if (res.data['code'] == -404) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': null,
|
||||||
|
'msg': '没有相关信息',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': InvalidVideoModel.fromJson(res.data),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': null,
|
||||||
|
'msg': '没有相关信息',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future buvidActivate() async {
|
||||||
|
try {
|
||||||
|
// 获取 HTML 数据
|
||||||
|
var html = await Request().get(Api.dynamicSpmPrefix);
|
||||||
|
|
||||||
|
// 提取 spmPrefix
|
||||||
|
String spmPrefix = spmPrefixExp.firstMatch(html.data)?.group(1) ?? '';
|
||||||
|
|
||||||
|
// 生成随机 PNG 结束部分
|
||||||
|
Random rand = Random();
|
||||||
|
String randPngEnd = base64.encode(
|
||||||
|
List<int>.generate(32, (_) => rand.nextInt(256))
|
||||||
|
..addAll(List<int>.filled(4, 0))
|
||||||
|
..addAll([73, 69, 78, 68])
|
||||||
|
..addAll(List<int>.generate(4, (_) => rand.nextInt(256))),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构建 JSON 数据
|
||||||
|
String jsonData = json.encode({
|
||||||
|
'3064': 1,
|
||||||
|
'39c8': '$spmPrefix.fp.risk',
|
||||||
|
'3c43': {
|
||||||
|
'adca': 'Linux',
|
||||||
|
'bfe9': randPngEnd.substring(randPngEnd.length - 50),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 发送 POST 请求
|
||||||
|
await Request().post(
|
||||||
|
Api.activateBuvidApi,
|
||||||
|
data: {'payload': jsonData},
|
||||||
|
options: Options(contentType: 'application/json'),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
debugPrint('buvidActivate error: $err');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,9 @@ class HttpString {
|
|||||||
static const String passBaseUrl = 'https://passport.bilibili.com';
|
static const String passBaseUrl = 'https://passport.bilibili.com';
|
||||||
static const String messageBaseUrl = 'https://message.bilibili.com';
|
static const String messageBaseUrl = 'https://message.bilibili.com';
|
||||||
static const String bangumiBaseUrl = 'https://bili.meark.me';
|
static const String bangumiBaseUrl = 'https://bili.meark.me';
|
||||||
|
static const String sponsorBlockBaseUrl = 'https://www.bsbsb.top';
|
||||||
|
static const String biliplusBaseUrl = 'https://www.biliplus.com';
|
||||||
|
|
||||||
static const List<int> validateStatusCodes = [
|
static const List<int> validateStatusCodes = [
|
||||||
302,
|
302,
|
||||||
304,
|
304,
|
||||||
|
|||||||
@ -17,7 +17,9 @@ class DanmakaHttp {
|
|||||||
var response = await Request().get(
|
var response = await Request().get(
|
||||||
Api.webDanmaku,
|
Api.webDanmaku,
|
||||||
data: params,
|
data: params,
|
||||||
extra: {'resType': ResponseType.bytes},
|
options: Options(
|
||||||
|
responseType: ResponseType.bytes,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return DmSegMobileReply.fromBuffer(response.data);
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
}
|
}
|
||||||
@ -67,9 +69,6 @@ class DanmakaHttp {
|
|||||||
var response = await Request().post(
|
var response = await Request().post(
|
||||||
Api.shootDanmaku,
|
Api.shootDanmaku,
|
||||||
data: params,
|
data: params,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import '../models/dynamics/result.dart';
|
import '../models/dynamics/result.dart';
|
||||||
import '../models/dynamics/up.dart';
|
import '../models/dynamics/up.dart';
|
||||||
import 'index.dart';
|
import 'index.dart';
|
||||||
@ -69,7 +70,7 @@ class DynamicsHttp {
|
|||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.likeDynamic,
|
Api.likeDynamic,
|
||||||
queryParameters: {
|
data: {
|
||||||
'dynamic_id': dynamicId,
|
'dynamic_id': dynamicId,
|
||||||
'up': up,
|
'up': up,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -91,7 +92,7 @@ class DynamicsHttp {
|
|||||||
|
|
||||||
//
|
//
|
||||||
static Future dynamicDetail({
|
static Future dynamicDetail({
|
||||||
String? id,
|
required String id,
|
||||||
}) async {
|
}) async {
|
||||||
var res = await Request().get(Api.dynamicDetail, data: {
|
var res = await Request().get(Api.dynamicDetail, data: {
|
||||||
'timezone_offset': -480,
|
'timezone_offset': -480,
|
||||||
@ -175,12 +176,15 @@ class DynamicsHttp {
|
|||||||
'revs_id': {'dyn_type': 8, 'rid': oid}
|
'revs_id': {'dyn_type': 8, 'rid': oid}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var res = await Request().post(Api.dynamicCreate, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.dynamicCreate,
|
||||||
|
queryParameters: {
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
|
'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
|
||||||
'x-bili-web-req-json': {'spm_id': '333.999'},
|
'x-bili-web-req-json': {'spm_id': '333.999'},
|
||||||
}, data: {
|
},
|
||||||
|
data: {
|
||||||
'dyn_req': {
|
'dyn_req': {
|
||||||
'content': {
|
'content': {
|
||||||
'contents': [
|
'contents': [
|
||||||
@ -195,7 +199,9 @@ class DynamicsHttp {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'web_repost_src': webRepostSrc
|
'web_repost_src': webRepostSrc
|
||||||
});
|
},
|
||||||
|
options: Options(contentType: 'application/json'),
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class FavHttp {
|
|||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.editFavFolder,
|
Api.editFavFolder,
|
||||||
queryParameters: {
|
data: {
|
||||||
'title': title,
|
'title': title,
|
||||||
'intro': intro,
|
'intro': intro,
|
||||||
'media_id': mediaId,
|
'media_id': mediaId,
|
||||||
@ -43,7 +43,7 @@ class FavHttp {
|
|||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.addFavFolder,
|
Api.addFavFolder,
|
||||||
queryParameters: {
|
data: {
|
||||||
'title': title,
|
'title': title,
|
||||||
'intro': intro,
|
'intro': intro,
|
||||||
'cover': cover ?? '',
|
'cover': cover ?? '',
|
||||||
|
|||||||
@ -21,7 +21,6 @@ class HtmlHttp {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Document rootTree = parse(response.data);
|
Document rootTree = parse(response.data);
|
||||||
// log(response.data.body.toString());
|
|
||||||
Element body = rootTree.body!;
|
Element body = rootTree.body!;
|
||||||
Element appDom = body.querySelector('#app')!;
|
Element appDom = body.querySelector('#app')!;
|
||||||
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
||||||
@ -52,7 +51,6 @@ class HtmlHttp {
|
|||||||
.className
|
.className
|
||||||
.split(' ')[1]
|
.split(' ')[1]
|
||||||
.split('-')[2];
|
.split('-')[2];
|
||||||
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
|
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'avatar': avatar,
|
'avatar': avatar,
|
||||||
@ -76,20 +74,10 @@ class HtmlHttp {
|
|||||||
Element body = rootTree.body!;
|
Element body = rootTree.body!;
|
||||||
Element appDom = body.querySelector('#app')!;
|
Element appDom = body.querySelector('#app')!;
|
||||||
Element authorHeader = appDom.querySelector('.up-left')!;
|
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();
|
String uname = authorHeader.querySelector('.up-name')!.text.trim();
|
||||||
// 动态详情
|
// 动态详情
|
||||||
Element opusDetail = appDom.querySelector('.article-content')!;
|
Element opusDetail = appDom.querySelector('.article-content')!;
|
||||||
// 发布时间
|
// 发布时间
|
||||||
// String updateTime =
|
|
||||||
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
|
|
||||||
// print(updateTime);
|
|
||||||
|
|
||||||
//
|
|
||||||
String opusContent =
|
String opusContent =
|
||||||
opusDetail.querySelector('#read-article-holder')!.innerHtml;
|
opusDetail.querySelector('#read-article-holder')!.innerHtml;
|
||||||
RegExp digitRegExp = RegExp(r'\d+');
|
RegExp digitRegExp = RegExp(r'\d+');
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
// ignore_for_file: avoid_print
|
// ignore_for_file: avoid_print
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' show Random;
|
|
||||||
import 'package:cookie_jar/cookie_jar.dart';
|
import 'package:cookie_jar/cookie_jar.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||||
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import '../utils/storage.dart';
|
import '../utils/storage.dart';
|
||||||
import '../utils/utils.dart';
|
import '../utils/utils.dart';
|
||||||
import 'api.dart';
|
|
||||||
import 'constants.dart';
|
import 'constants.dart';
|
||||||
import 'interceptor.dart';
|
import 'interceptor.dart';
|
||||||
|
|
||||||
@ -22,19 +19,17 @@ 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;
|
Box setting = GStorage.setting;
|
||||||
static Box localCache = GStrorage.localCache;
|
static Box localCache = GStorage.localCache;
|
||||||
late bool enableSystemProxy;
|
late bool enableSystemProxy;
|
||||||
late String systemProxyHost;
|
late String systemProxyHost;
|
||||||
late String systemProxyPort;
|
late String systemProxyPort;
|
||||||
static final RegExp spmPrefixExp =
|
|
||||||
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
|
||||||
static String? buvid;
|
static String? buvid;
|
||||||
|
|
||||||
/// 设置cookie
|
/// 设置cookie
|
||||||
static setCookie() async {
|
static setCookie() async {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
final String cookiePath = await Utils.getCookiePath();
|
final String cookiePath = await Utils.getCookiePath();
|
||||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||||
ignoreExpires: true,
|
ignoreExpires: true,
|
||||||
@ -44,7 +39,7 @@ class Request {
|
|||||||
dio.interceptors.add(cookieManager);
|
dio.interceptors.add(cookieManager);
|
||||||
final List<Cookie> cookie = await cookieManager.cookieJar
|
final List<Cookie> cookie = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||||
final userInfo = userInfoCache.get('userInfoCache');
|
final UserInfoData? userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo != null && userInfo.mid != null) {
|
if (userInfo != null && userInfo.mid != null) {
|
||||||
final List<Cookie> cookie2 = await cookieManager.cookieJar
|
final List<Cookie> cookie2 = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||||
@ -62,11 +57,6 @@ class Request {
|
|||||||
baseUrlType = 'bangumi';
|
baseUrlType = 'bangumi';
|
||||||
}
|
}
|
||||||
setBaseUrl(type: baseUrlType);
|
setBaseUrl(type: baseUrlType);
|
||||||
try {
|
|
||||||
await buvidActivate();
|
|
||||||
} catch (e) {
|
|
||||||
log("setCookie, ${e.toString()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
final String cookieString = cookie
|
final String cookieString = cookie
|
||||||
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||||
@ -122,30 +112,6 @@ class Request {
|
|||||||
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
dio.options.headers['referer'] = 'https://www.bilibili.com/';
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future buvidActivate() async {
|
|
||||||
var html = await Request().get(Api.dynamicSpmPrefix);
|
|
||||||
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
|
||||||
Random rand = Random();
|
|
||||||
String rand_png_end = base64.encode(
|
|
||||||
List<int>.generate(32, (_) => rand.nextInt(256)) +
|
|
||||||
List<int>.filled(4, 0) +
|
|
||||||
[73, 69, 78, 68] +
|
|
||||||
List<int>.generate(4, (_) => rand.nextInt(256)));
|
|
||||||
|
|
||||||
String jsonData = json.encode({
|
|
||||||
'3064': 1,
|
|
||||||
'39c8': '${spmPrefix}.fp.risk',
|
|
||||||
'3c43': {
|
|
||||||
'adca': 'Linux',
|
|
||||||
'bfe9': rand_png_end.substring(rand_png_end.length - 50),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
await Request().post(Api.activateBuvidApi,
|
|
||||||
data: {'payload': jsonData},
|
|
||||||
options: Options(contentType: 'application/json'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* config it and create
|
* config it and create
|
||||||
*/
|
*/
|
||||||
@ -171,15 +137,6 @@ class Request {
|
|||||||
|
|
||||||
dio = Dio(options);
|
dio = Dio(options);
|
||||||
|
|
||||||
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
|
|
||||||
// ..httpClientAdapter = Http2Adapter(
|
|
||||||
// ConnectionManager(
|
|
||||||
// idleTimeout: const Duration(milliseconds: 10000),
|
|
||||||
// onClientCreate: (_, ClientSetting config) =>
|
|
||||||
// config.onBadCertificate = (_) => true,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
|
|
||||||
/// 设置代理
|
/// 设置代理
|
||||||
if (enableSystemProxy) {
|
if (enableSystemProxy) {
|
||||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
dio.httpClientAdapter = IOHttpClientAdapter(
|
||||||
@ -217,18 +174,15 @@ class Request {
|
|||||||
/*
|
/*
|
||||||
* get请求
|
* get请求
|
||||||
*/
|
*/
|
||||||
get(url, {data, options, cancelToken, extra}) async {
|
get(url, {data, Options? options, cancelToken, extra}) async {
|
||||||
Response response;
|
Response response;
|
||||||
final Options options = Options();
|
|
||||||
ResponseType resType = ResponseType.json;
|
|
||||||
if (extra != null) {
|
if (extra != null) {
|
||||||
resType = extra!['resType'] ?? ResponseType.json;
|
|
||||||
if (extra['ua'] != null) {
|
if (extra['ua'] != null) {
|
||||||
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
options ??= Options();
|
||||||
|
options.headers ??= <String, dynamic>{};
|
||||||
|
options.headers?['user-agent'] = headerUa(type: extra['ua']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.responseType = resType;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await dio.get(
|
response = await dio.get(
|
||||||
url,
|
url,
|
||||||
@ -238,32 +192,44 @@ class Request {
|
|||||||
);
|
);
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
Response errResponse = Response(
|
return Response(
|
||||||
data: {
|
data: {'message': await ApiInterceptor.dioError(e)},
|
||||||
'message': await ApiInterceptor.dioError(e)
|
|
||||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
requestOptions: RequestOptions(),
|
requestOptions: RequestOptions(),
|
||||||
);
|
);
|
||||||
return errResponse;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get请求
|
||||||
|
*/
|
||||||
|
getWithoutCookie(url, {data}) {
|
||||||
|
return get(
|
||||||
|
url,
|
||||||
|
data: data,
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'cookie': 'buvid3= ; b_nut= ; sid= ',
|
||||||
|
'user-agent': headerUa(type: 'pc'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* post请求
|
* post请求
|
||||||
*/
|
*/
|
||||||
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
||||||
// print('post-data: $data');
|
|
||||||
Response response;
|
Response response;
|
||||||
try {
|
try {
|
||||||
response = await dio.post(
|
response = await dio.post(
|
||||||
url,
|
url,
|
||||||
data: data,
|
data: data,
|
||||||
queryParameters: queryParameters,
|
queryParameters: queryParameters,
|
||||||
options: options,
|
options:
|
||||||
|
options ?? Options(contentType: Headers.formUrlEncodedContentType),
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
// print('post success: ${response.data}');
|
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
Response errResponse = Response(
|
Response errResponse = Response(
|
||||||
@ -319,7 +285,7 @@ class Request {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
headerUa =
|
headerUa =
|
||||||
'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';
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36';
|
||||||
}
|
}
|
||||||
return headerUa;
|
return headerUa;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,7 @@
|
|||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:pilipala/utils/login.dart';
|
||||||
import '../utils/storage.dart';
|
|
||||||
|
|
||||||
class ApiInterceptor extends Interceptor {
|
class ApiInterceptor extends Interceptor {
|
||||||
@override
|
@override
|
||||||
@ -19,20 +18,9 @@ class ApiInterceptor extends Interceptor {
|
|||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
try {
|
try {
|
||||||
if (response.statusCode == 302) {
|
// 在响应之后处理数据
|
||||||
final List<String> locations = response.headers['location']!;
|
if (response.data is Map && response.data['code'] == -101) {
|
||||||
if (locations.isNotEmpty) {
|
LoginUtils.loginOut();
|
||||||
if (locations.first.startsWith('https://www.mcbbs.net')) {
|
|
||||||
final Uri uri = Uri.parse(locations.first);
|
|
||||||
final String? accessKey = uri.queryParameters['access_key'];
|
|
||||||
final String? mid = uri.queryParameters['mid'];
|
|
||||||
try {
|
|
||||||
Box localCache = GStrorage.localCache;
|
|
||||||
localCache.put(LocalCacheKey.accessKey,
|
|
||||||
<String, String?>{'mid': mid, 'value': accessKey});
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
print('ApiInterceptor: $err');
|
print('ApiInterceptor: $err');
|
||||||
|
|||||||
@ -89,7 +89,9 @@ class LiveHttp {
|
|||||||
|
|
||||||
// 发送弹幕
|
// 发送弹幕
|
||||||
static Future sendDanmaku({roomId, msg}) async {
|
static Future sendDanmaku({roomId, msg}) async {
|
||||||
var res = await Request().post(Api.sendLiveMsg, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.sendLiveMsg,
|
||||||
|
data: {
|
||||||
'bubble': 0,
|
'bubble': 0,
|
||||||
'msg': msg,
|
'msg': msg,
|
||||||
'color': 16777215, // 颜色
|
'color': 16777215, // 颜色
|
||||||
@ -105,7 +107,8 @@ class LiveHttp {
|
|||||||
'roomid': roomId,
|
'roomid': roomId,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
'csrf_token': await Request.getCsrf(),
|
'csrf_token': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
@ -142,4 +145,18 @@ class LiveHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 直播历史记录
|
||||||
|
static Future liveRoomEntry({required int roomId}) async {
|
||||||
|
await Request().post(
|
||||||
|
Api.liveRoomEntry,
|
||||||
|
data: {
|
||||||
|
'room_id': roomId,
|
||||||
|
'platform': 'pc',
|
||||||
|
'csrf_token': await Request.getCsrf(),
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
'visit_id': '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,9 +71,6 @@ class LoginHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.webSmsCode,
|
Api.webSmsCode,
|
||||||
data: formData,
|
data: formData,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -106,9 +103,6 @@ class LoginHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.webSmsLogin,
|
Api.webSmsLogin,
|
||||||
data: formData,
|
data: formData,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -155,9 +149,6 @@ class LoginHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.appSmsCode,
|
Api.appSmsCode,
|
||||||
data: data,
|
data: data,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
print(res);
|
print(res);
|
||||||
}
|
}
|
||||||
@ -208,9 +199,6 @@ class LoginHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.loginInByPwdApi,
|
Api.loginInByPwdApi,
|
||||||
data: data,
|
data: data,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
print(res);
|
print(res);
|
||||||
}
|
}
|
||||||
@ -239,17 +227,27 @@ class LoginHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.loginInByWebPwd,
|
Api.loginInByWebPwd,
|
||||||
data: formData,
|
data: formData,
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
|
if (res.data['data']['status'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
return {
|
||||||
|
'status': false,
|
||||||
|
'code': 1,
|
||||||
|
'data': res.data['data'],
|
||||||
|
'msg': res.data['data']['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/models/member/article.dart';
|
||||||
import 'package:pilipala/models/member/like.dart';
|
import 'package:pilipala/models/member/like.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
import '../models/dynamics/result.dart';
|
import '../models/dynamics/result.dart';
|
||||||
import '../models/follow/result.dart';
|
import '../models/follow/result.dart';
|
||||||
@ -16,14 +21,20 @@ import 'index.dart';
|
|||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
static Future memberInfo({
|
static Future memberInfo({
|
||||||
int? mid,
|
required int mid,
|
||||||
String token = '',
|
String token = '',
|
||||||
}) async {
|
}) async {
|
||||||
|
String? wWebid;
|
||||||
|
if ((await getWWebid(mid: mid))['status']) {
|
||||||
|
wWebid = GlobalDataCache.wWebid;
|
||||||
|
}
|
||||||
|
|
||||||
Map params = await WbiSign().makSign({
|
Map params = await WbiSign().makSign({
|
||||||
'mid': mid,
|
'mid': mid,
|
||||||
'token': token,
|
'token': token,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
|
...wWebid != null ? {'w_webid': wWebid} : {},
|
||||||
});
|
});
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberInfo,
|
Api.memberInfo,
|
||||||
@ -195,13 +206,15 @@ class MemberHttp {
|
|||||||
|
|
||||||
// 设置分组
|
// 设置分组
|
||||||
static Future addUsers(int? fids, String? tagids) async {
|
static Future addUsers(int? fids, String? tagids) async {
|
||||||
var res = await Request().post(Api.addUsers, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.addUsers,
|
||||||
|
data: {
|
||||||
'fids': fids,
|
'fids': fids,
|
||||||
'tagids': tagids ?? '0',
|
'tagids': tagids ?? '0',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
}, data: {
|
},
|
||||||
'cross_domain': true
|
queryParameters: {'cross_domain': true},
|
||||||
});
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||||
} else {
|
} else {
|
||||||
@ -419,11 +432,14 @@ class MemberHttp {
|
|||||||
static Future cookieToKey() async {
|
static Future cookieToKey() async {
|
||||||
var authCodeRes = await getTVCode();
|
var authCodeRes = await getTVCode();
|
||||||
if (authCodeRes['status']) {
|
if (authCodeRes['status']) {
|
||||||
var res = await Request().post(Api.cookieToKey, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.cookieToKey,
|
||||||
|
data: {
|
||||||
'auth_code': authCodeRes['data'],
|
'auth_code': authCodeRes['data'],
|
||||||
'build': 708200,
|
'build': 708200,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
await qrcodePoll(authCodeRes['data']);
|
await qrcodePoll(authCodeRes['data']);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
@ -455,11 +471,11 @@ class MemberHttp {
|
|||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
String accessKey = res.data['data']['access_token'];
|
String accessKey = res.data['data']['access_token'];
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStorage.localCache;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
var userInfo = userInfoCache.get('userInfoCache');
|
final UserInfoData? userInfo = userInfoCache.get('userInfoCache');
|
||||||
localCache.put(
|
localCache.put(
|
||||||
LocalCacheKey.accessKey, {'mid': userInfo.mid, 'value': accessKey});
|
LocalCacheKey.accessKey, {'mid': userInfo!.mid, 'value': accessKey});
|
||||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -556,4 +572,60 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future getWWebid({required int mid}) async {
|
||||||
|
String? wWebid = GlobalDataCache.wWebid;
|
||||||
|
if (wWebid != null) {
|
||||||
|
return {'status': true, 'data': wWebid};
|
||||||
|
}
|
||||||
|
var res = await Request().get('https://space.bilibili.com/$mid/article');
|
||||||
|
String? headContent = parse(res.data).head?.outerHtml;
|
||||||
|
final regex = RegExp(
|
||||||
|
r'<script id="__RENDER_DATA__" type="application/json">(.*?)</script>');
|
||||||
|
if (headContent != null) {
|
||||||
|
final match = regex.firstMatch(headContent);
|
||||||
|
if (match != null && match.groupCount >= 1) {
|
||||||
|
final content = match.group(1);
|
||||||
|
String decodedString = Uri.decodeComponent(content!);
|
||||||
|
Map<String, dynamic> map = jsonDecode(decodedString);
|
||||||
|
GlobalDataCache.wWebid = map['access_id'];
|
||||||
|
return {'status': true, 'data': map['access_id']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': '请检查登录状态'};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {'status': false, 'data': '请检查登录状态'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户专栏
|
||||||
|
static Future getMemberArticle({
|
||||||
|
required int mid,
|
||||||
|
required int pn,
|
||||||
|
String? offset,
|
||||||
|
}) async {
|
||||||
|
String? wWebid;
|
||||||
|
if ((await getWWebid(mid: mid))['status']) {
|
||||||
|
wWebid = GlobalDataCache.wWebid;
|
||||||
|
}
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'host_mid': mid,
|
||||||
|
'page': pn,
|
||||||
|
'offset': offset,
|
||||||
|
'web_location': 333.999,
|
||||||
|
...wWebid != null ? {'w_webid': wWebid} : {},
|
||||||
|
});
|
||||||
|
var res = await Request().get(Api.opusList, data: params);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': MemberArticleDataModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'] ?? '请求异常',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/models/msg/at.dart';
|
||||||
import 'package:pilipala/models/msg/like.dart';
|
import 'package:pilipala/models/msg/like.dart';
|
||||||
import 'package:pilipala/models/msg/reply.dart';
|
import 'package:pilipala/models/msg/reply.dart';
|
||||||
import 'package:pilipala/models/msg/system.dart';
|
import 'package:pilipala/models/msg/system.dart';
|
||||||
@ -64,7 +65,7 @@ class MsgHttp {
|
|||||||
.toList(),
|
.toList(),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
print('err🔟: $err');
|
debugPrint('err: $err');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@ -158,9 +159,6 @@ class MsgHttp {
|
|||||||
'csrf_token': csrf,
|
'csrf_token': csrf,
|
||||||
'csrf': csrf,
|
'csrf': csrf,
|
||||||
},
|
},
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -282,10 +280,10 @@ class MsgHttp {
|
|||||||
'data': MessageLikeModel.fromJson(res.data['data']),
|
'data': MessageLikeModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return {'status': false, 'date': [], 'msg': err.toString()};
|
return {'status': false, 'data': [], 'msg': err.toString()};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -330,4 +328,47 @@ class MsgHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future messageSystemAccount() async {
|
||||||
|
var res = await Request().get(Api.userMessageSystemAPi, data: {
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
'page_size': 20,
|
||||||
|
'build': 0,
|
||||||
|
'mobi_app': 'web',
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']['system_notify_list']
|
||||||
|
.map<MessageSystemModel>((e) => MessageSystemModel.fromJson(e))
|
||||||
|
.toList(),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @我的
|
||||||
|
static Future messageAt() async {
|
||||||
|
var res = await Request().get(Api.messageAtAPi, data: {
|
||||||
|
'build': 0,
|
||||||
|
'mobi_app': 'web',
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': MessageAtModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {'status': false, 'data': [], 'msg': err.toString()};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
lib/http/read.dart
Normal file
122
lib/http/read.dart
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/models/read/opus.dart';
|
||||||
|
import 'package:pilipala/models/read/read.dart';
|
||||||
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
import 'index.dart';
|
||||||
|
|
||||||
|
class ReadHttp {
|
||||||
|
static List<String> extractScriptContents(String htmlContent) {
|
||||||
|
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||||
|
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||||
|
List<String> scriptContents = [];
|
||||||
|
for (Match match in matches) {
|
||||||
|
String scriptContent = match.group(1)!;
|
||||||
|
scriptContents.add(scriptContent);
|
||||||
|
}
|
||||||
|
return scriptContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析专栏 opus格式
|
||||||
|
static Future parseArticleOpus({required String id}) async {
|
||||||
|
var res = await Request().get('https://www.bilibili.com/opus/$id', extra: {
|
||||||
|
'ua': 'pc',
|
||||||
|
});
|
||||||
|
String? headContent = parse(res.data).head?.outerHtml;
|
||||||
|
var document = parse(headContent);
|
||||||
|
var linkTags = document.getElementsByTagName('link');
|
||||||
|
bool isCv = false;
|
||||||
|
String cvId = '';
|
||||||
|
for (var linkTag in linkTags) {
|
||||||
|
var attributes = linkTag.attributes;
|
||||||
|
if (attributes.containsKey('rel') &&
|
||||||
|
attributes['rel'] == 'canonical' &&
|
||||||
|
attributes.containsKey('data-vue-meta') &&
|
||||||
|
attributes['data-vue-meta'] == 'true') {
|
||||||
|
final String cvHref = linkTag.attributes['href']!;
|
||||||
|
RegExp regex = RegExp(r'cv(\d+)');
|
||||||
|
RegExpMatch? match = regex.firstMatch(cvHref);
|
||||||
|
if (match != null) {
|
||||||
|
cvId = match.group(1)!;
|
||||||
|
} else {
|
||||||
|
print('No match found.');
|
||||||
|
}
|
||||||
|
isCv = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String scriptContent =
|
||||||
|
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||||
|
int startIndex = scriptContent.indexOf('{');
|
||||||
|
int endIndex = scriptContent.lastIndexOf('};');
|
||||||
|
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||||
|
// 解析JSON字符串为Map
|
||||||
|
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': OpusDataModel.fromJson(jsonData),
|
||||||
|
'isCv': isCv,
|
||||||
|
'cvId': cvId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析专栏 cv格式
|
||||||
|
static Future parseArticleCv({required String id}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
'https://www.bilibili.com/read/cv$id',
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
|
options: Options(
|
||||||
|
headers: {
|
||||||
|
'cookie': 'opus-goback=1',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
String scriptContent =
|
||||||
|
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||||
|
int startIndex = scriptContent.indexOf('{');
|
||||||
|
int endIndex = scriptContent.lastIndexOf('};');
|
||||||
|
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||||
|
// 解析JSON字符串为Map
|
||||||
|
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': ReadDataModel.fromJson(jsonData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
static Future getViewInfo({required String id}) async {
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'id': id,
|
||||||
|
'mobi_app': 'pc',
|
||||||
|
'from': 'web',
|
||||||
|
'gaia_source': 'main_web',
|
||||||
|
'web_location': 333.976,
|
||||||
|
});
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.getViewInfo,
|
||||||
|
data: {
|
||||||
|
'id': id,
|
||||||
|
'mobi_app': 'pc',
|
||||||
|
'from': 'web',
|
||||||
|
'gaia_source': 'main_web',
|
||||||
|
'web_location': 333.976,
|
||||||
|
'w_rid': params['w_rid'],
|
||||||
|
'wts': params['wts'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
import '../models/video/reply/data.dart';
|
import '../models/video/reply/data.dart';
|
||||||
import '../models/video/reply/emote.dart';
|
import '../models/video/reply/emote.dart';
|
||||||
import 'api.dart';
|
import 'api.dart';
|
||||||
@ -6,17 +11,16 @@ import 'init.dart';
|
|||||||
class ReplyHttp {
|
class ReplyHttp {
|
||||||
static Future replyList({
|
static Future replyList({
|
||||||
required int oid,
|
required int oid,
|
||||||
required int pageNum,
|
required String nextOffset,
|
||||||
required int type,
|
required int type,
|
||||||
int? ps,
|
int? ps,
|
||||||
int sort = 1,
|
int sort = 1,
|
||||||
}) async {
|
}) async {
|
||||||
var res = await Request().get(Api.replyList, data: {
|
var res = await Request().get(Api.replyList, data: {
|
||||||
'oid': oid,
|
'oid': oid,
|
||||||
'pn': pageNum,
|
|
||||||
'type': type,
|
'type': type,
|
||||||
'sort': sort,
|
'pagination_str': jsonEncode({'offset': nextOffset}),
|
||||||
'ps': ps ?? 20
|
'mode': sort + 2,
|
||||||
});
|
});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -52,19 +56,13 @@ class ReplyHttp {
|
|||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': ReplyData.fromJson(res.data['data']),
|
'data': ReplyReplyData.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
Map errMap = {
|
|
||||||
-400: '请求错误',
|
|
||||||
-404: '无此项',
|
|
||||||
12002: '评论区已关闭',
|
|
||||||
12009: '评论主体的type不合法',
|
|
||||||
};
|
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'date': [],
|
'date': [],
|
||||||
'msg': errMap[res.data['code']] ?? '请求异常',
|
'msg': res.data['message'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +76,7 @@ class ReplyHttp {
|
|||||||
}) async {
|
}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.likeReply,
|
Api.likeReply,
|
||||||
queryParameters: {
|
data: {
|
||||||
'type': type,
|
'type': type,
|
||||||
'oid': oid,
|
'oid': oid,
|
||||||
'rpid': rpid,
|
'rpid': rpid,
|
||||||
@ -115,4 +113,65 @@ class ReplyHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future replyDel({
|
||||||
|
required int type, //replyType
|
||||||
|
required int oid,
|
||||||
|
required int rpid,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.replyDel,
|
||||||
|
queryParameters: {
|
||||||
|
'type': type, //type.index
|
||||||
|
'oid': oid,
|
||||||
|
'rpid': rpid,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '删除成功'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片上传
|
||||||
|
static Future uploadImage(
|
||||||
|
{required XFile xFile, String type = 'new_dyn'}) async {
|
||||||
|
var formData = FormData.fromMap({
|
||||||
|
'file_up': await xFileToMultipartFile(xFile),
|
||||||
|
'biz': type,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
'category': 'daily',
|
||||||
|
});
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.uploadImage,
|
||||||
|
data: formData,
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
var data = res.data['data'];
|
||||||
|
data['img_src'] = data['image_url'];
|
||||||
|
data['img_width'] = data['image_width'];
|
||||||
|
data['img_height'] = data['image_height'];
|
||||||
|
data.remove('image_url');
|
||||||
|
data.remove('image_width');
|
||||||
|
data.remove('image_height');
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': data,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'date': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<MultipartFile> xFileToMultipartFile(XFile xFile) async {
|
||||||
|
var file = File(xFile.path);
|
||||||
|
var bytes = await file.readAsBytes();
|
||||||
|
return MultipartFile.fromBytes(bytes, filename: xFile.name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import '../utils/storage.dart';
|
|||||||
import 'index.dart';
|
import 'index.dart';
|
||||||
|
|
||||||
class SearchHttp {
|
class SearchHttp {
|
||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStorage.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 is String) {
|
if (res.data is String) {
|
||||||
@ -143,7 +143,11 @@ class SearchHttp {
|
|||||||
}
|
}
|
||||||
final dynamic res =
|
final dynamic res =
|
||||||
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
|
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
return res.data['data'].first['cid'];
|
return res.data['data'].first['cid'];
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Map<String, dynamic>> bangumiInfo(
|
static Future<Map<String, dynamic>> bangumiInfo(
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'dart:convert';
|
||||||
import '../common/constants.dart';
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:pilipala/models/video/later.dart';
|
||||||
import '../models/model_hot_video_item.dart';
|
import '../models/model_hot_video_item.dart';
|
||||||
import '../models/user/fav_detail.dart';
|
import '../models/user/fav_detail.dart';
|
||||||
import '../models/user/fav_folder.dart';
|
import '../models/user/fav_folder.dart';
|
||||||
@ -148,7 +151,7 @@ class UserHttp {
|
|||||||
// 暂停switchStatus传true 否则false
|
// 暂停switchStatus传true 否则false
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.pauseHistory,
|
Api.pauseHistory,
|
||||||
queryParameters: {
|
data: {
|
||||||
'switch': switchStatus,
|
'switch': switchStatus,
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -167,7 +170,7 @@ class UserHttp {
|
|||||||
static Future clearHistory() async {
|
static Future clearHistory() async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.clearHistory,
|
Api.clearHistory,
|
||||||
queryParameters: {
|
data: {
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
@ -185,7 +188,7 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.toViewLater,
|
Api.toViewLater,
|
||||||
queryParameters: data,
|
data: data,
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'msg': 'yeah!稍后再看'};
|
return {'status': true, 'msg': 'yeah!稍后再看'};
|
||||||
@ -204,7 +207,7 @@ class UserHttp {
|
|||||||
params[aid != null ? 'aid' : 'viewed'] = aid ?? true;
|
params[aid != null ? 'aid' : 'viewed'] = aid ?? true;
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.toViewDel,
|
Api.toViewDel,
|
||||||
queryParameters: params,
|
data: params,
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'msg': 'yeah!成功移除'};
|
return {'status': true, 'msg': 'yeah!成功移除'};
|
||||||
@ -213,30 +216,11 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户凭证 失效
|
|
||||||
static Future thirdLogin() async {
|
|
||||||
var res = await Request().get(
|
|
||||||
'https://passport.bilibili.com/login/app/third',
|
|
||||||
data: {
|
|
||||||
'appkey': Constants.appKey,
|
|
||||||
'api': Constants.thirdApi,
|
|
||||||
'sign': Constants.thirdSign,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
|
|
||||||
Request().get(res.data['data']['confirm_uri']);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
SmartDialog.showNotify(msg: '获取用户凭证: $err', notifyType: NotifyType.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空稍后再看
|
// 清空稍后再看
|
||||||
static Future toViewClear() async {
|
static Future toViewClear() async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.toViewClear,
|
Api.toViewClear,
|
||||||
queryParameters: {
|
data: {
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
@ -252,7 +236,7 @@ class UserHttp {
|
|||||||
static Future delHistory(kid) async {
|
static Future delHistory(kid) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.delHistory,
|
Api.delHistory,
|
||||||
queryParameters: {
|
data: {
|
||||||
'kid': kid,
|
'kid': kid,
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -278,30 +262,6 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
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(
|
static Future searchHistory(
|
||||||
@ -401,7 +361,7 @@ class UserHttp {
|
|||||||
static Future cancelSub({required int seasonId}) async {
|
static Future cancelSub({required int seasonId}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.cancelSub,
|
Api.cancelSub,
|
||||||
queryParameters: {
|
data: {
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'season_id': seasonId,
|
'season_id': seasonId,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -418,7 +378,7 @@ class UserHttp {
|
|||||||
static Future delFavFolder({required int mediaIds}) async {
|
static Future delFavFolder({required int mediaIds}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.delFavFolder,
|
Api.delFavFolder,
|
||||||
queryParameters: {
|
data: {
|
||||||
'media_ids': mediaIds,
|
'media_ids': mediaIds,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -430,4 +390,160 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<String> extractScriptContents(String htmlContent) {
|
||||||
|
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||||
|
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||||
|
List<String> scriptContents = [];
|
||||||
|
for (Match match in matches) {
|
||||||
|
String scriptContent = match.group(1)!;
|
||||||
|
scriptContents.add(scriptContent);
|
||||||
|
}
|
||||||
|
return scriptContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 稍后再看列表
|
||||||
|
static Future getMediaList({
|
||||||
|
required int type,
|
||||||
|
required int bizId,
|
||||||
|
required int ps,
|
||||||
|
int? oid,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.mediaList,
|
||||||
|
data: {
|
||||||
|
'mobi_app': 'web',
|
||||||
|
'type': type,
|
||||||
|
'biz_id': bizId,
|
||||||
|
'oid': oid ?? '',
|
||||||
|
'otype': 2,
|
||||||
|
'ps': ps,
|
||||||
|
'direction': false,
|
||||||
|
'desc': true,
|
||||||
|
'sort_field': 1,
|
||||||
|
'tid': 0,
|
||||||
|
'with_current': false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']['media_list'] != null
|
||||||
|
? res.data['data']['media_list']
|
||||||
|
.map<MediaVideoItemModel>(
|
||||||
|
(e) => MediaVideoItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: []
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析收藏夹视频
|
||||||
|
static Future parseFavVideo({
|
||||||
|
required int mediaId,
|
||||||
|
required int oid,
|
||||||
|
required String bvid,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
'https://www.bilibili.com/list/ml$mediaId',
|
||||||
|
data: {
|
||||||
|
'oid': mediaId,
|
||||||
|
'bvid': bvid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
String scriptContent =
|
||||||
|
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||||
|
int startIndex = scriptContent.indexOf('{');
|
||||||
|
int endIndex = scriptContent.lastIndexOf('};');
|
||||||
|
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||||
|
// 解析JSON字符串为Map
|
||||||
|
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': jsonData['resourceList']
|
||||||
|
.map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future getAccountInfo() async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.accountInfo,
|
||||||
|
data: {'web_location': 333.33},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': {},
|
||||||
|
'mag': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future updateAccountInfo({
|
||||||
|
required String uname,
|
||||||
|
required String sign,
|
||||||
|
required String sex,
|
||||||
|
required String birthday,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.updateAccountInfo,
|
||||||
|
data: {
|
||||||
|
'uname': uname,
|
||||||
|
'usersign': sign,
|
||||||
|
'sex': sex,
|
||||||
|
'birthday': birthday,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
options: Options(contentType: Headers.formUrlEncodedContentType),
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'msg': '更新成功',
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析up投稿
|
||||||
|
static Future parseUpArchiveVideo({
|
||||||
|
required int mid,
|
||||||
|
required int oid,
|
||||||
|
required String bvid,
|
||||||
|
String sortField = 'pubtime',
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
'https://www.bilibili.com/list/$mid',
|
||||||
|
data: {
|
||||||
|
'oid': oid,
|
||||||
|
'bvid': bvid,
|
||||||
|
'sort_field': sortField,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
String scriptContent =
|
||||||
|
extractScriptContents(parse(res.data).body!.outerHtml)[0];
|
||||||
|
int startIndex = scriptContent.indexOf('{');
|
||||||
|
int endIndex = scriptContent.lastIndexOf('};');
|
||||||
|
String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
|
||||||
|
// 解析JSON字符串为Map
|
||||||
|
Map<String, dynamic> jsonData = json.decode(jsonContent);
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': jsonData['resourceList']
|
||||||
|
.map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
import 'package:pilipala/models/video/tags.dart';
|
||||||
import '../common/constants.dart';
|
import '../common/constants.dart';
|
||||||
import '../models/common/reply_type.dart';
|
import '../models/common/reply_type.dart';
|
||||||
import '../models/home/rcmd/result.dart';
|
import '../models/home/rcmd/result.dart';
|
||||||
@ -22,11 +26,11 @@ import 'init.dart';
|
|||||||
/// 返回{'status': bool, 'data': List}
|
/// 返回{'status': bool, 'data': List}
|
||||||
/// view层根据 status 判断渲染逻辑
|
/// view层根据 status 判断渲染逻辑
|
||||||
class VideoHttp {
|
class VideoHttp {
|
||||||
static Box localCache = GStrorage.localCache;
|
static Box localCache = GStorage.localCache;
|
||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStorage.setting;
|
||||||
static bool enableRcmdDynamic =
|
static bool enableRcmdDynamic =
|
||||||
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||||
static Box userInfoCache = GStrorage.userInfo;
|
static Box userInfoCache = GStorage.userInfo;
|
||||||
|
|
||||||
// 首页推荐视频
|
// 首页推荐视频
|
||||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||||
@ -95,6 +99,8 @@ 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' &&
|
||||||
|
i['card_goto'] != 'ad_web_s' &&
|
||||||
|
i['card_goto'] != 'ad_web' &&
|
||||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||||
(i['args'] != null &&
|
(i['args'] != null &&
|
||||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||||
@ -243,7 +249,7 @@ class VideoHttp {
|
|||||||
static Future coinVideo({required String bvid, required int multiply}) async {
|
static Future coinVideo({required String bvid, required int multiply}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.coinVideo,
|
Api.coinVideo,
|
||||||
queryParameters: {
|
data: {
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'multiply': multiply,
|
'multiply': multiply,
|
||||||
'select_like': 0,
|
'select_like': 0,
|
||||||
@ -271,7 +277,7 @@ class VideoHttp {
|
|||||||
static Future oneThree({required String bvid}) async {
|
static Future oneThree({required String bvid}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.oneThree,
|
Api.oneThree,
|
||||||
queryParameters: {
|
data: {
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
@ -287,7 +293,7 @@ class VideoHttp {
|
|||||||
static Future likeVideo({required String bvid, required bool type}) async {
|
static Future likeVideo({required String bvid, required bool type}) async {
|
||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.likeVideo,
|
Api.likeVideo,
|
||||||
queryParameters: {
|
data: {
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'like': type ? 1 : 2,
|
'like': type ? 1 : 2,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
@ -303,13 +309,16 @@ class VideoHttp {
|
|||||||
// (取消)收藏
|
// (取消)收藏
|
||||||
static Future favVideo(
|
static Future favVideo(
|
||||||
{required int aid, String? addIds, String? delIds}) async {
|
{required int aid, String? addIds, String? delIds}) async {
|
||||||
var res = await Request().post(Api.favVideo, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.favVideo,
|
||||||
|
data: {
|
||||||
'rid': aid,
|
'rid': aid,
|
||||||
'type': 2,
|
'type': 2,
|
||||||
'add_media_ids': addIds ?? '',
|
'add_media_ids': addIds ?? '',
|
||||||
'del_media_ids': delIds ?? '',
|
'del_media_ids': delIds ?? '',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
@ -343,20 +352,37 @@ class VideoHttp {
|
|||||||
required String message,
|
required String message,
|
||||||
int? root,
|
int? root,
|
||||||
int? parent,
|
int? parent,
|
||||||
|
List<Map<dynamic, dynamic>>? pictures,
|
||||||
}) async {
|
}) async {
|
||||||
if (message == '') {
|
if (message == '') {
|
||||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||||
}
|
}
|
||||||
var res = await Request().post(Api.replyAdd, queryParameters: {
|
var params = <String, dynamic>{
|
||||||
'type': type.index,
|
'plat': 1,
|
||||||
'oid': oid,
|
'oid': oid,
|
||||||
|
'type': type.index,
|
||||||
'root': root == null || root == 0 ? '' : root,
|
'root': root == null || root == 0 ? '' : root,
|
||||||
'parent': parent == null || parent == 0 ? '' : parent,
|
'parent': parent == null || parent == 0 ? '' : parent,
|
||||||
'message': message,
|
'message': message,
|
||||||
|
'at_name_to_mid': {},
|
||||||
|
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||||
|
'gaia_source': 'main_web',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
};
|
||||||
log(res.toString());
|
Map sign = await WbiSign().makSign(params);
|
||||||
|
params.remove('wts');
|
||||||
|
params.remove('w_rid');
|
||||||
|
FormData formData = FormData.fromMap({...params});
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.replyAdd,
|
||||||
|
queryParameters: {
|
||||||
|
'w_rid': sign['w_rid'],
|
||||||
|
'wts': sign['wts'],
|
||||||
|
},
|
||||||
|
data: formData,
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
|
log(res.toString());
|
||||||
return {'status': true, 'data': res.data['data']};
|
return {'status': true, 'data': res.data['data']};
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
@ -376,12 +402,15 @@ class VideoHttp {
|
|||||||
// 操作用户关系
|
// 操作用户关系
|
||||||
static Future relationMod(
|
static Future relationMod(
|
||||||
{required int mid, required int act, required int reSrc}) async {
|
{required int mid, required int act, required int reSrc}) async {
|
||||||
var res = await Request().post(Api.relationMod, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.relationMod,
|
||||||
|
data: {
|
||||||
'fid': mid,
|
'fid': mid,
|
||||||
'act': act,
|
'act': act,
|
||||||
're_src': reSrc,
|
're_src': reSrc,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
if (act == 5) {
|
if (act == 5) {
|
||||||
List<int> blackMidsList =
|
List<int> blackMidsList =
|
||||||
@ -397,7 +426,9 @@ class VideoHttp {
|
|||||||
|
|
||||||
// 视频播放进度
|
// 视频播放进度
|
||||||
static Future heartBeat({bvid, cid, progress, realtime}) async {
|
static Future heartBeat({bvid, cid, progress, realtime}) async {
|
||||||
await Request().post(Api.heartBeat, queryParameters: {
|
await Request().post(
|
||||||
|
Api.heartBeat,
|
||||||
|
data: {
|
||||||
// 'aid': aid,
|
// 'aid': aid,
|
||||||
'bvid': bvid,
|
'bvid': bvid,
|
||||||
'cid': cid,
|
'cid': cid,
|
||||||
@ -409,15 +440,19 @@ class VideoHttp {
|
|||||||
// 'type': '',
|
// 'type': '',
|
||||||
// 'sub_type': '',
|
// 'sub_type': '',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加追番
|
// 添加追番
|
||||||
static Future bangumiAdd({int? seasonId}) async {
|
static Future bangumiAdd({int? seasonId}) async {
|
||||||
var res = await Request().post(Api.bangumiAdd, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.bangumiAdd,
|
||||||
|
data: {
|
||||||
'season_id': seasonId,
|
'season_id': seasonId,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'msg': res.data['result']['toast']};
|
return {'status': true, 'msg': res.data['result']['toast']};
|
||||||
} else {
|
} else {
|
||||||
@ -427,10 +462,13 @@ class VideoHttp {
|
|||||||
|
|
||||||
// 取消追番
|
// 取消追番
|
||||||
static Future bangumiDel({int? seasonId}) async {
|
static Future bangumiDel({int? seasonId}) async {
|
||||||
var res = await Request().post(Api.bangumiDel, queryParameters: {
|
var res = await Request().post(
|
||||||
|
Api.bangumiDel,
|
||||||
|
data: {
|
||||||
'season_id': seasonId,
|
'season_id': seasonId,
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
});
|
},
|
||||||
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {'status': true, 'msg': res.data['result']['toast']};
|
return {'status': true, 'msg': res.data['result']['toast']};
|
||||||
} else {
|
} else {
|
||||||
@ -473,10 +511,11 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future getSubtitle({int? cid, String? bvid}) async {
|
static Future getSubtitle({int? cid, String? bvid, String? aid}) async {
|
||||||
var res = await Request().get(Api.getSubtitleConfig, data: {
|
var res = await Request().get(Api.getSubtitleConfig, data: {
|
||||||
'cid': cid,
|
'cid': cid,
|
||||||
'bvid': bvid,
|
if (bvid != null) 'bvid': bvid,
|
||||||
|
if (aid != null) 'aid': aid,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
@ -518,8 +557,70 @@ class VideoHttp {
|
|||||||
// 获取字幕内容
|
// 获取字幕内容
|
||||||
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
||||||
var res = await Request().get('https:$url');
|
var res = await Request().get('https:$url');
|
||||||
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
|
final String content =
|
||||||
|
await SubTitleUtils.convertToWebVTT(res.data['body']);
|
||||||
final List body = res.data['body'];
|
final List body = res.data['body'];
|
||||||
return {'content': content, 'body': body};
|
return {'content': content, 'body': body};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<Map<String, dynamic>> getSubscribeStatus(
|
||||||
|
{required dynamic bvid}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.videoRelation,
|
||||||
|
data: {
|
||||||
|
'aid': IdUtils.bv2av(bvid),
|
||||||
|
'bvid': bvid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future seasonFav({
|
||||||
|
required bool isFav,
|
||||||
|
required dynamic seasonId,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().post(
|
||||||
|
isFav ? Api.cancelSub : Api.confirmSub,
|
||||||
|
data: {
|
||||||
|
'platform': 'web',
|
||||||
|
'season_id': seasonId,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取视频标签
|
||||||
|
static Future getVideoTag({required String bvid}) async {
|
||||||
|
var res = await Request().get(Api.videoTag, data: {'bvid': bvid});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'].map<VideoTagItem>((e) {
|
||||||
|
return VideoTagItem.fromJson(e);
|
||||||
|
}).toList()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/custom_toast.dart';
|
import 'package:pilipala/common/widgets/custom_toast.dart';
|
||||||
|
import 'package:pilipala/http/common.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
import 'package:pilipala/models/common/color_type.dart';
|
import 'package:pilipala/models/common/color_type.dart';
|
||||||
import 'package:pilipala/models/common/theme_type.dart';
|
import 'package:pilipala/models/common/theme_type.dart';
|
||||||
@ -32,7 +33,7 @@ void main() async {
|
|||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
await SystemChrome.setPreferredOrientations(
|
await SystemChrome.setPreferredOrientations(
|
||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||||
await GStrorage.init();
|
await GStorage.init();
|
||||||
clearLogs();
|
clearLogs();
|
||||||
Request();
|
Request();
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
@ -60,11 +61,13 @@ void main() async {
|
|||||||
systemNavigationBarColor: Colors.transparent,
|
systemNavigationBarColor: Colors.transparent,
|
||||||
systemNavigationBarDividerColor: Colors.transparent,
|
systemNavigationBarDividerColor: Colors.transparent,
|
||||||
statusBarColor: Colors.transparent,
|
statusBarColor: Colors.transparent,
|
||||||
|
systemNavigationBarContrastEnforced: false,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
PiliSchame.init();
|
PiliSchame.init();
|
||||||
await GlobalDataCache().initialize();
|
await GlobalDataCache.initialize();
|
||||||
|
CommonHttp.buvidActivate();
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@ -72,7 +75,7 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
// 主题色
|
// 主题色
|
||||||
Color defaultColor =
|
Color defaultColor =
|
||||||
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
colorThemeTypes[setting.get(SettingBoxKey.customColor, defaultValue: 0)]
|
||||||
@ -221,13 +224,23 @@ class BuildMainApp extends StatelessWidget {
|
|||||||
elevation: 20,
|
elevation: 20,
|
||||||
);
|
);
|
||||||
|
|
||||||
return GetMaterialApp(
|
AppBarTheme appBarTheme(ColorScheme colorScheme) {
|
||||||
title: 'PiliPala',
|
return AppBarTheme(
|
||||||
theme: ThemeData(
|
backgroundColor: colorScheme.surface,
|
||||||
colorScheme: currentThemeValue == ThemeType.dark
|
foregroundColor: colorScheme.onSurface,
|
||||||
? darkColorScheme
|
elevation: 0,
|
||||||
: lightColorScheme,
|
titleSpacing: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
// titleTextStyle: TextStyle(
|
||||||
|
// fontSize: Theme.of(context).textTheme.titleLarge!.fontSize),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemeData buildThemeData(ColorScheme colorScheme) {
|
||||||
|
return ThemeData(
|
||||||
|
colorScheme: colorScheme,
|
||||||
snackBarTheme: snackBarTheme,
|
snackBarTheme: snackBarTheme,
|
||||||
|
appBarTheme: appBarTheme(colorScheme),
|
||||||
pageTransitionsTheme: const PageTransitionsTheme(
|
pageTransitionsTheme: const PageTransitionsTheme(
|
||||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||||
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
||||||
@ -235,12 +248,20 @@ class BuildMainApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetMaterialApp(
|
||||||
|
title: 'PiliPala',
|
||||||
|
theme: buildThemeData(
|
||||||
|
currentThemeValue == ThemeType.dark
|
||||||
|
? darkColorScheme
|
||||||
|
: lightColorScheme,
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
darkTheme: buildThemeData(
|
||||||
colorScheme: currentThemeValue == ThemeType.light
|
currentThemeValue == ThemeType.light
|
||||||
? lightColorScheme
|
? lightColorScheme
|
||||||
: darkColorScheme,
|
: darkColorScheme,
|
||||||
snackBarTheme: snackBarTheme,
|
|
||||||
),
|
),
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
GlobalCupertinoLocalizations.delegate,
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
|||||||
@ -144,6 +144,7 @@ class EpisodeItem {
|
|||||||
this.link,
|
this.link,
|
||||||
this.longTitle,
|
this.longTitle,
|
||||||
this.pubTime,
|
this.pubTime,
|
||||||
|
this.pubdate,
|
||||||
this.pv,
|
this.pv,
|
||||||
this.releaseDate,
|
this.releaseDate,
|
||||||
this.rights,
|
this.rights,
|
||||||
@ -155,6 +156,7 @@ class EpisodeItem {
|
|||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.title,
|
this.title,
|
||||||
this.vid,
|
this.vid,
|
||||||
|
this.stat,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? aid;
|
int? aid;
|
||||||
@ -173,6 +175,7 @@ class EpisodeItem {
|
|||||||
String? link;
|
String? link;
|
||||||
String? longTitle;
|
String? longTitle;
|
||||||
int? pubTime;
|
int? pubTime;
|
||||||
|
int? pubdate;
|
||||||
int? pv;
|
int? pv;
|
||||||
String? releaseDate;
|
String? releaseDate;
|
||||||
Map? rights;
|
Map? rights;
|
||||||
@ -184,6 +187,7 @@ class EpisodeItem {
|
|||||||
String? subtitle;
|
String? subtitle;
|
||||||
String? title;
|
String? title;
|
||||||
String? vid;
|
String? vid;
|
||||||
|
String? stat;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
@ -202,6 +206,7 @@ class EpisodeItem {
|
|||||||
link = json['link'];
|
link = json['link'];
|
||||||
longTitle = json['long_title'];
|
longTitle = json['long_title'];
|
||||||
pubTime = json['pub_time'];
|
pubTime = json['pub_time'];
|
||||||
|
pubdate = json['pub_time'];
|
||||||
pv = json['pv'];
|
pv = json['pv'];
|
||||||
releaseDate = json['release_date'];
|
releaseDate = json['release_date'];
|
||||||
rights = json['rights'];
|
rights = json['rights'];
|
||||||
@ -211,7 +216,7 @@ class EpisodeItem {
|
|||||||
skip = json['skip'];
|
skip = json['skip'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
subtitle = json['subtitle'];
|
subtitle = json['subtitle'];
|
||||||
title = json['title'];
|
title = json['long_title'];
|
||||||
vid = json['vid'];
|
vid = json['vid'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,7 @@ class BangumiListItemModel {
|
|||||||
this.title,
|
this.title,
|
||||||
this.titleIcon,
|
this.titleIcon,
|
||||||
this.progress,
|
this.progress,
|
||||||
|
this.progressIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? badge;
|
String? badge;
|
||||||
@ -66,8 +67,8 @@ class BangumiListItemModel {
|
|||||||
String? subTitle;
|
String? subTitle;
|
||||||
String? title;
|
String? title;
|
||||||
String? titleIcon;
|
String? titleIcon;
|
||||||
|
|
||||||
String? progress;
|
String? progress;
|
||||||
|
int? progressIndex;
|
||||||
|
|
||||||
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
badge = json['badge'] == '' ? null : json['badge'];
|
badge = json['badge'] == '' ? null : json['badge'];
|
||||||
@ -87,7 +88,9 @@ class BangumiListItemModel {
|
|||||||
subTitle = json['sub_title'];
|
subTitle = json['sub_title'];
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
titleIcon = json['title_icon'];
|
titleIcon = json['title_icon'];
|
||||||
|
|
||||||
progress = json['progress'];
|
progress = json['progress'];
|
||||||
|
progressIndex = int.parse(
|
||||||
|
RegExp(r'第(\d+)话').firstMatch(json['progress'] ?? '第1话')?.group(1) ??
|
||||||
|
'0');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
18
lib/models/common/comment_range_type.dart
Normal file
18
lib/models/common/comment_range_type.dart
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
enum CommentRangeType {
|
||||||
|
video,
|
||||||
|
bangumi,
|
||||||
|
// dynamic,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActionTypeExtension on CommentRangeType {
|
||||||
|
String get value => [
|
||||||
|
'video',
|
||||||
|
'bangumi',
|
||||||
|
// 'dynamic',
|
||||||
|
][index];
|
||||||
|
String get label => [
|
||||||
|
'视频',
|
||||||
|
'番剧',
|
||||||
|
// '动态',
|
||||||
|
][index];
|
||||||
|
}
|
||||||
@ -4,9 +4,12 @@ enum FullScreenGestureMode {
|
|||||||
|
|
||||||
/// 从下滑到上
|
/// 从下滑到上
|
||||||
fromBottomtoTop,
|
fromBottomtoTop,
|
||||||
|
|
||||||
|
/// 关闭手势
|
||||||
|
none,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
||||||
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
|
String get values => ['fromToptoBottom', 'fromBottomtoTop', 'none'][index];
|
||||||
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
|
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏', '关闭手势'][index];
|
||||||
}
|
}
|
||||||
|
|||||||
73
lib/models/common/invalid_video.dart
Normal file
73
lib/models/common/invalid_video.dart
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
class InvalidVideoModel {
|
||||||
|
final int? id;
|
||||||
|
final int? ver;
|
||||||
|
final int? aid;
|
||||||
|
final String? lastupdate;
|
||||||
|
final int? lastupdatets;
|
||||||
|
final String? title;
|
||||||
|
final String? description;
|
||||||
|
final String? pic;
|
||||||
|
final int? tid;
|
||||||
|
final String? typename;
|
||||||
|
final int? created;
|
||||||
|
final String? createdAt;
|
||||||
|
final String? author;
|
||||||
|
final int? mid;
|
||||||
|
final String? play;
|
||||||
|
final String? coins;
|
||||||
|
final String? review;
|
||||||
|
final String? videoReview;
|
||||||
|
final String? favorites;
|
||||||
|
final String? tag;
|
||||||
|
final List<String>? tagList;
|
||||||
|
|
||||||
|
InvalidVideoModel({
|
||||||
|
this.id,
|
||||||
|
this.ver,
|
||||||
|
this.aid,
|
||||||
|
this.lastupdate,
|
||||||
|
this.lastupdatets,
|
||||||
|
this.title,
|
||||||
|
this.description,
|
||||||
|
this.pic,
|
||||||
|
this.tid,
|
||||||
|
this.typename,
|
||||||
|
this.created,
|
||||||
|
this.createdAt,
|
||||||
|
this.author,
|
||||||
|
this.mid,
|
||||||
|
this.play,
|
||||||
|
this.coins,
|
||||||
|
this.review,
|
||||||
|
this.videoReview,
|
||||||
|
this.favorites,
|
||||||
|
this.tag,
|
||||||
|
this.tagList,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory InvalidVideoModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return InvalidVideoModel(
|
||||||
|
id: json['id'],
|
||||||
|
ver: json['ver'],
|
||||||
|
aid: json['aid'],
|
||||||
|
lastupdate: json['lastupdate'],
|
||||||
|
lastupdatets: json['lastupdatets'],
|
||||||
|
title: json['title'],
|
||||||
|
description: json['description'],
|
||||||
|
pic: json['pic'],
|
||||||
|
tid: json['tid'],
|
||||||
|
typename: json['typename'],
|
||||||
|
created: json['created'],
|
||||||
|
createdAt: json['created_at'],
|
||||||
|
author: json['author'],
|
||||||
|
mid: json['mid'],
|
||||||
|
play: json['play'],
|
||||||
|
coins: json['coins'],
|
||||||
|
review: json['review'],
|
||||||
|
videoReview: json['video_review'],
|
||||||
|
favorites: json['favorites'],
|
||||||
|
tag: json['tag'],
|
||||||
|
tagList: json['tag'].toString().split(',').toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
import '../../pages/dynamics/index.dart';
|
import '../../pages/dynamics/index.dart';
|
||||||
import '../../pages/home/index.dart';
|
import '../../pages/home/index.dart';
|
||||||
import '../../pages/media/index.dart';
|
|
||||||
import '../../pages/rank/index.dart';
|
import '../../pages/rank/index.dart';
|
||||||
|
|
||||||
List defaultNavigationBars = [
|
List defaultNavigationBars = [
|
||||||
@ -51,15 +50,15 @@ List defaultNavigationBars = [
|
|||||||
{
|
{
|
||||||
'id': 3,
|
'id': 3,
|
||||||
'icon': const Icon(
|
'icon': const Icon(
|
||||||
Icons.video_collection_outlined,
|
Icons.person_outline,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
'selectIcon': const Icon(
|
'selectIcon': const Icon(
|
||||||
Icons.video_collection,
|
Icons.person,
|
||||||
size: 21,
|
size: 21,
|
||||||
),
|
),
|
||||||
'label': "媒体库",
|
'label': "我的",
|
||||||
'count': 0,
|
'count': 0,
|
||||||
'page': const MediaPage(),
|
'page': const MinePage(),
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType {
|
|||||||
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索类型为视频、专栏及相簿时
|
// 搜索类型为视频时
|
||||||
enum ArchiveFilterType {
|
enum ArchiveFilterType {
|
||||||
totalrank,
|
totalrank,
|
||||||
click,
|
click,
|
||||||
@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType {
|
|||||||
String get description =>
|
String get description =>
|
||||||
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索类型为专栏时
|
||||||
|
enum ArticleFilterType {
|
||||||
|
// 综合排序
|
||||||
|
totalrank,
|
||||||
|
// 最新发布
|
||||||
|
pubdate,
|
||||||
|
// 最多点击
|
||||||
|
click,
|
||||||
|
// 最多喜欢
|
||||||
|
attention,
|
||||||
|
// 最多评论
|
||||||
|
scores,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ArticleFilterTypeExtension on ArticleFilterType {
|
||||||
|
String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index];
|
||||||
|
}
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class LiveFollowingItemModel {
|
|||||||
String? roomNews;
|
String? roomNews;
|
||||||
String? watchIcon;
|
String? watchIcon;
|
||||||
String? textSmall;
|
String? textSmall;
|
||||||
String? roomCover;
|
String? cover;
|
||||||
String? pic;
|
String? pic;
|
||||||
int? parentAreaId;
|
int? parentAreaId;
|
||||||
int? areaId;
|
int? areaId;
|
||||||
@ -90,7 +90,7 @@ class LiveFollowingItemModel {
|
|||||||
this.roomNews,
|
this.roomNews,
|
||||||
this.watchIcon,
|
this.watchIcon,
|
||||||
this.textSmall,
|
this.textSmall,
|
||||||
this.roomCover,
|
this.cover,
|
||||||
this.pic,
|
this.pic,
|
||||||
this.parentAreaId,
|
this.parentAreaId,
|
||||||
this.areaId,
|
this.areaId,
|
||||||
@ -108,7 +108,8 @@ class LiveFollowingItemModel {
|
|||||||
isAttention = json['is_attention'];
|
isAttention = json['is_attention'];
|
||||||
clipNum = json['clipnum'];
|
clipNum = json['clipnum'];
|
||||||
fansNum = json['fans_num'];
|
fansNum = json['fans_num'];
|
||||||
areaName = json['area_name'];
|
areaName =
|
||||||
|
json['area_name'] == '' ? json['area_name_v2'] : json['area_name'];
|
||||||
areaValue = json['area_value'];
|
areaValue = json['area_value'];
|
||||||
tags = json['tags'];
|
tags = json['tags'];
|
||||||
recentRecordIdV2 = json['recent_record_id_v2'];
|
recentRecordIdV2 = json['recent_record_id_v2'];
|
||||||
@ -118,7 +119,7 @@ class LiveFollowingItemModel {
|
|||||||
roomNews = json['room_news'];
|
roomNews = json['room_news'];
|
||||||
watchIcon = json['watch_icon'];
|
watchIcon = json['watch_icon'];
|
||||||
textSmall = json['text_small'];
|
textSmall = json['text_small'];
|
||||||
roomCover = json['room_cover'];
|
cover = json['room_cover'];
|
||||||
pic = json['room_cover'];
|
pic = json['room_cover'];
|
||||||
parentAreaId = json['parent_area_id'];
|
parentAreaId = json['parent_area_id'];
|
||||||
areaId = json['area_id'];
|
areaId = json['area_id'];
|
||||||
|
|||||||
46
lib/models/member/article.dart
Normal file
46
lib/models/member/article.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
class MemberArticleDataModel {
|
||||||
|
MemberArticleDataModel({
|
||||||
|
this.hasMore,
|
||||||
|
this.items,
|
||||||
|
this.offset,
|
||||||
|
this.updateNum,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? hasMore;
|
||||||
|
List<MemberArticleItemModel>? items;
|
||||||
|
String? offset;
|
||||||
|
int? updateNum;
|
||||||
|
|
||||||
|
MemberArticleDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
hasMore = json['has_more'];
|
||||||
|
items = json['items']
|
||||||
|
.map<MemberArticleItemModel>((e) => MemberArticleItemModel.fromJson(e))
|
||||||
|
.toList();
|
||||||
|
offset = json['offset'];
|
||||||
|
updateNum = json['update_num'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MemberArticleItemModel {
|
||||||
|
MemberArticleItemModel({
|
||||||
|
this.content,
|
||||||
|
this.cover,
|
||||||
|
this.jumpUrl,
|
||||||
|
this.opusId,
|
||||||
|
this.stat,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? content;
|
||||||
|
Map? cover;
|
||||||
|
String? jumpUrl;
|
||||||
|
String? opusId;
|
||||||
|
Map? stat;
|
||||||
|
|
||||||
|
MemberArticleItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
content = json['content'];
|
||||||
|
cover = json['cover'];
|
||||||
|
jumpUrl = json['jump_url'];
|
||||||
|
opusId = json['opus_id'];
|
||||||
|
stat = json['stat'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,6 +8,7 @@ class MemberInfoModel {
|
|||||||
this.level,
|
this.level,
|
||||||
this.isFollowed,
|
this.isFollowed,
|
||||||
this.topPhoto,
|
this.topPhoto,
|
||||||
|
this.silence,
|
||||||
this.official,
|
this.official,
|
||||||
this.vip,
|
this.vip,
|
||||||
this.liveRoom,
|
this.liveRoom,
|
||||||
@ -21,6 +22,7 @@ class MemberInfoModel {
|
|||||||
int? level;
|
int? level;
|
||||||
bool? isFollowed;
|
bool? isFollowed;
|
||||||
String? topPhoto;
|
String? topPhoto;
|
||||||
|
int? silence;
|
||||||
Map? official;
|
Map? official;
|
||||||
Vip? vip;
|
Vip? vip;
|
||||||
LiveRoom? liveRoom;
|
LiveRoom? liveRoom;
|
||||||
@ -34,6 +36,7 @@ class MemberInfoModel {
|
|||||||
level = json['level'];
|
level = json['level'];
|
||||||
isFollowed = json['is_followed'];
|
isFollowed = json['is_followed'];
|
||||||
topPhoto = json['top_photo'];
|
topPhoto = json['top_photo'];
|
||||||
|
silence = json['silence'] ?? 0;
|
||||||
official = json['official'];
|
official = json['official'];
|
||||||
vip = Vip.fromJson(json['vip']);
|
vip = Vip.fromJson(json['vip']);
|
||||||
liveRoom =
|
liveRoom =
|
||||||
|
|||||||
140
lib/models/msg/at.dart
Normal file
140
lib/models/msg/at.dart
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
class MessageAtModel {
|
||||||
|
Cursor? cursor;
|
||||||
|
List<MessageAtItems>? items;
|
||||||
|
|
||||||
|
MessageAtModel({this.cursor, this.items});
|
||||||
|
|
||||||
|
MessageAtModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
|
if (json['items'] != null) {
|
||||||
|
items = <MessageAtItems>[];
|
||||||
|
json['items'].forEach((v) {
|
||||||
|
items!.add(MessageAtItems.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cursor {
|
||||||
|
Cursor({
|
||||||
|
this.id,
|
||||||
|
this.isEnd,
|
||||||
|
this.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
bool? isEnd;
|
||||||
|
int? time;
|
||||||
|
|
||||||
|
Cursor.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
isEnd = json['isEnd'];
|
||||||
|
time = json['time'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageAtItems {
|
||||||
|
int? id;
|
||||||
|
int? atTime;
|
||||||
|
User? user;
|
||||||
|
MessageAtItem? item;
|
||||||
|
|
||||||
|
MessageAtItems({this.id, this.atTime, this.user, this.item});
|
||||||
|
|
||||||
|
MessageAtItems.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
atTime = json['at_time'];
|
||||||
|
user = json['user'] != null ? User.fromJson(json['user']) : null;
|
||||||
|
item = json['item'] != null ? MessageAtItem.fromJson(json['item']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageAtItem {
|
||||||
|
String? type;
|
||||||
|
String? business;
|
||||||
|
int? businessId;
|
||||||
|
String? title;
|
||||||
|
String? image;
|
||||||
|
String? uri;
|
||||||
|
int? subjectId;
|
||||||
|
int? rootId;
|
||||||
|
int? targetId;
|
||||||
|
int? sourceId;
|
||||||
|
String? sourceContent;
|
||||||
|
String? nativeUri;
|
||||||
|
List<User>? atDetails;
|
||||||
|
List<dynamic>? topicDetails;
|
||||||
|
bool? hideReplyButton;
|
||||||
|
|
||||||
|
MessageAtItem({
|
||||||
|
this.type,
|
||||||
|
this.business,
|
||||||
|
this.businessId,
|
||||||
|
this.title,
|
||||||
|
this.image,
|
||||||
|
this.uri,
|
||||||
|
this.subjectId,
|
||||||
|
this.rootId,
|
||||||
|
this.targetId,
|
||||||
|
this.sourceId,
|
||||||
|
this.sourceContent,
|
||||||
|
this.nativeUri,
|
||||||
|
this.atDetails,
|
||||||
|
this.topicDetails,
|
||||||
|
this.hideReplyButton,
|
||||||
|
});
|
||||||
|
|
||||||
|
MessageAtItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
business = json['business'];
|
||||||
|
businessId = json['business_id'];
|
||||||
|
title = json['title'];
|
||||||
|
image = json['image'];
|
||||||
|
uri = json['uri'];
|
||||||
|
subjectId = json['subject_id'];
|
||||||
|
rootId = json['root_id'];
|
||||||
|
targetId = json['target_id'];
|
||||||
|
sourceId = json['source_id'];
|
||||||
|
sourceContent = json['source_content'];
|
||||||
|
nativeUri = json['native_uri'];
|
||||||
|
if (json['at_details'] != null) {
|
||||||
|
atDetails = <User>[];
|
||||||
|
json['at_details'].forEach((v) {
|
||||||
|
atDetails!.add(User.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['topic_details'] != null) {
|
||||||
|
topicDetails = <dynamic>[];
|
||||||
|
json['topic_details'].forEach((v) {
|
||||||
|
topicDetails!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hideReplyButton = json['hide_reply_button'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class User {
|
||||||
|
int? mid;
|
||||||
|
int? fans;
|
||||||
|
String? nickname;
|
||||||
|
String? avatar;
|
||||||
|
String? midLink;
|
||||||
|
bool? follow;
|
||||||
|
|
||||||
|
User(
|
||||||
|
{this.mid,
|
||||||
|
this.fans,
|
||||||
|
this.nickname,
|
||||||
|
this.avatar,
|
||||||
|
this.midLink,
|
||||||
|
this.follow});
|
||||||
|
|
||||||
|
User.fromJson(Map<String, dynamic> json) {
|
||||||
|
mid = json['mid'];
|
||||||
|
fans = json['fans'];
|
||||||
|
nickname = json['nickname'];
|
||||||
|
avatar = json['avatar'];
|
||||||
|
midLink = json['mid_link'];
|
||||||
|
follow = json['follow'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -39,7 +39,7 @@ class Total {
|
|||||||
List<MessageLikeItem>? items;
|
List<MessageLikeItem>? items;
|
||||||
|
|
||||||
factory Total.fromJson(Map<String, dynamic> json) => Total(
|
factory Total.fromJson(Map<String, dynamic> json) => Total(
|
||||||
cursor: Cursor.fromJson(json['cursor']),
|
cursor: json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null,
|
||||||
items: json["items"] == null
|
items: json["items"] == null
|
||||||
? []
|
? []
|
||||||
: json["items"].map<MessageLikeItem>((e) {
|
: json["items"].map<MessageLikeItem>((e) {
|
||||||
|
|||||||
@ -14,7 +14,8 @@ class SessionDataModel {
|
|||||||
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
sessionList = json['session_list']
|
sessionList = json['session_list']
|
||||||
?.map<SessionList>((e) => SessionList.fromJson(e))
|
?.map<SessionList>((e) => SessionList.fromJson(e))
|
||||||
.toList();
|
.toList() ??
|
||||||
|
[];
|
||||||
hasMore = json['has_more'];
|
hasMore = json['has_more'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ class MessageSystemModel {
|
|||||||
int? cursor;
|
int? cursor;
|
||||||
int? type;
|
int? type;
|
||||||
String? title;
|
String? title;
|
||||||
Map? content;
|
dynamic content;
|
||||||
Source? source;
|
Source? source;
|
||||||
String? timeAt;
|
String? timeAt;
|
||||||
int? cardType;
|
int? cardType;
|
||||||
@ -45,7 +45,9 @@ class MessageSystemModel {
|
|||||||
cursor: jsons["cursor"],
|
cursor: jsons["cursor"],
|
||||||
type: jsons["type"],
|
type: jsons["type"],
|
||||||
title: jsons["title"],
|
title: jsons["title"],
|
||||||
content: json.decode(jsons["content"]),
|
content: isValidJson(jsons["content"])
|
||||||
|
? json.decode(jsons["content"])
|
||||||
|
: jsons["content"],
|
||||||
source: Source.fromJson(jsons["source"]),
|
source: Source.fromJson(jsons["source"]),
|
||||||
timeAt: jsons["time_at"],
|
timeAt: jsons["time_at"],
|
||||||
cardType: jsons["card_type"],
|
cardType: jsons["card_type"],
|
||||||
@ -75,3 +77,12 @@ class Source {
|
|||||||
logo: json["logo"],
|
logo: json["logo"],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isValidJson(String str) {
|
||||||
|
try {
|
||||||
|
json.decode(str);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
485
lib/models/read/opus.dart
Normal file
485
lib/models/read/opus.dart
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
class OpusDataModel {
|
||||||
|
OpusDataModel({
|
||||||
|
this.id,
|
||||||
|
this.detail,
|
||||||
|
this.type,
|
||||||
|
this.theme,
|
||||||
|
this.themeMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? id;
|
||||||
|
OpusDetailDataModel? detail;
|
||||||
|
int? type;
|
||||||
|
String? theme;
|
||||||
|
String? themeMode;
|
||||||
|
|
||||||
|
OpusDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
detail = json['detail'] != null
|
||||||
|
? OpusDetailDataModel.fromJson(json['detail'])
|
||||||
|
: null;
|
||||||
|
type = json['type'];
|
||||||
|
theme = json['theme'];
|
||||||
|
themeMode = json['themeMode'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpusDetailDataModel {
|
||||||
|
OpusDetailDataModel({
|
||||||
|
this.basic,
|
||||||
|
this.idStr,
|
||||||
|
this.modules,
|
||||||
|
this.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
Basic? basic;
|
||||||
|
String? idStr;
|
||||||
|
List<OpusModuleDataModel>? modules;
|
||||||
|
int? type;
|
||||||
|
|
||||||
|
OpusDetailDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
basic = json['basic'] != null ? Basic.fromJson(json['basic']) : null;
|
||||||
|
idStr = json['id_str'];
|
||||||
|
if (json['modules'] != null) {
|
||||||
|
modules = <OpusModuleDataModel>[];
|
||||||
|
json['modules'].forEach((v) {
|
||||||
|
modules!.add(OpusModuleDataModel.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
type = json['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Basic {
|
||||||
|
Basic({
|
||||||
|
this.commentIdStr,
|
||||||
|
this.commentType,
|
||||||
|
this.ridStr,
|
||||||
|
this.title,
|
||||||
|
this.uid,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? commentIdStr;
|
||||||
|
int? commentType;
|
||||||
|
String? ridStr;
|
||||||
|
String? title;
|
||||||
|
int? uid;
|
||||||
|
|
||||||
|
Basic.fromJson(Map<String, dynamic> json) {
|
||||||
|
commentIdStr = json['comment_id_str'];
|
||||||
|
commentType = json['comment_type'];
|
||||||
|
ridStr = json['rid_str'];
|
||||||
|
title = json['title'];
|
||||||
|
uid = json['uid'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpusModuleDataModel {
|
||||||
|
OpusModuleDataModel({
|
||||||
|
this.moduleTitle,
|
||||||
|
this.moduleAuthor,
|
||||||
|
this.moduleContent,
|
||||||
|
this.moduleExtend,
|
||||||
|
this.moduleBottom,
|
||||||
|
this.moduleStat,
|
||||||
|
});
|
||||||
|
|
||||||
|
ModuleTop? moduleTop;
|
||||||
|
ModuleTitle? moduleTitle;
|
||||||
|
ModuleAuthor? moduleAuthor;
|
||||||
|
ModuleContent? moduleContent;
|
||||||
|
ModuleExtend? moduleExtend;
|
||||||
|
ModuleBottom? moduleBottom;
|
||||||
|
ModuleStat? moduleStat;
|
||||||
|
|
||||||
|
OpusModuleDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
moduleTop = json['module_top'] != null
|
||||||
|
? ModuleTop.fromJson(json['module_top'])
|
||||||
|
: null;
|
||||||
|
moduleTitle = json['module_title'] != null
|
||||||
|
? ModuleTitle.fromJson(json['module_title'])
|
||||||
|
: null;
|
||||||
|
moduleAuthor = json['module_author'] != null
|
||||||
|
? ModuleAuthor.fromJson(json['module_author'])
|
||||||
|
: null;
|
||||||
|
moduleContent = json['module_content'] != null
|
||||||
|
? ModuleContent.fromJson(json['module_content'])
|
||||||
|
: null;
|
||||||
|
moduleExtend = json['module_extend'] != null
|
||||||
|
? ModuleExtend.fromJson(json['module_extend'])
|
||||||
|
: null;
|
||||||
|
moduleBottom = json['module_bottom'] != null
|
||||||
|
? ModuleBottom.fromJson(json['module_bottom'])
|
||||||
|
: null;
|
||||||
|
moduleStat = json['module_stat'] != null
|
||||||
|
? ModuleStat.fromJson(json['module_stat'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleTop {
|
||||||
|
ModuleTop({
|
||||||
|
this.type,
|
||||||
|
this.video,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? type;
|
||||||
|
Map? video;
|
||||||
|
|
||||||
|
ModuleTop.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
video = json['video'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleTitle {
|
||||||
|
ModuleTitle({
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
ModuleTitle.fromJson(Map<String, dynamic> json) {
|
||||||
|
text = json['text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleAuthor {
|
||||||
|
ModuleAuthor({
|
||||||
|
this.face,
|
||||||
|
this.mid,
|
||||||
|
this.name,
|
||||||
|
this.pubTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? face;
|
||||||
|
int? mid;
|
||||||
|
String? name;
|
||||||
|
String? pubTime;
|
||||||
|
|
||||||
|
ModuleAuthor.fromJson(Map<String, dynamic> json) {
|
||||||
|
face = json['face'];
|
||||||
|
mid = json['mid'];
|
||||||
|
name = json['name'];
|
||||||
|
pubTime = json['pub_time'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleContent {
|
||||||
|
ModuleContent({
|
||||||
|
this.paragraphs,
|
||||||
|
this.moduleType,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleParagraph>? paragraphs;
|
||||||
|
String? moduleType;
|
||||||
|
|
||||||
|
ModuleContent.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['paragraphs'] != null) {
|
||||||
|
paragraphs = <ModuleParagraph>[];
|
||||||
|
json['paragraphs'].forEach((v) {
|
||||||
|
paragraphs!.add(ModuleParagraph.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
moduleType = json['module_type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraph {
|
||||||
|
ModuleParagraph({
|
||||||
|
this.align,
|
||||||
|
this.paraType,
|
||||||
|
this.pic,
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 0 左对齐 1 居中 2 右对齐
|
||||||
|
int? align;
|
||||||
|
int? paraType;
|
||||||
|
Pics? pic;
|
||||||
|
ModuleParagraphText? text;
|
||||||
|
LinkCard? linkCard;
|
||||||
|
|
||||||
|
ModuleParagraph.fromJson(Map<String, dynamic> json) {
|
||||||
|
align = json['align'];
|
||||||
|
paraType = json['para_type'] == null && json['link_card'] != null
|
||||||
|
? 3
|
||||||
|
: json['para_type'];
|
||||||
|
pic = json['pic'] != null ? Pics.fromJson(json['pic']) : null;
|
||||||
|
text = json['text'] != null
|
||||||
|
? ModuleParagraphText.fromJson(json['text'])
|
||||||
|
: null;
|
||||||
|
linkCard =
|
||||||
|
json['link_card'] != null ? LinkCard.fromJson(json['link_card']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pics {
|
||||||
|
Pics({
|
||||||
|
this.pics,
|
||||||
|
this.style,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<Pic>? pics;
|
||||||
|
int? style;
|
||||||
|
|
||||||
|
Pics.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['pics'] != null) {
|
||||||
|
pics = <Pic>[];
|
||||||
|
json['pics'].forEach((v) {
|
||||||
|
pics!.add(Pic.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
style = json['style'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Pic {
|
||||||
|
Pic({
|
||||||
|
this.height,
|
||||||
|
this.size,
|
||||||
|
this.url,
|
||||||
|
this.width,
|
||||||
|
this.aspectRatio,
|
||||||
|
this.scale,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? height;
|
||||||
|
double? size;
|
||||||
|
String? url;
|
||||||
|
int? width;
|
||||||
|
double? aspectRatio;
|
||||||
|
double? scale;
|
||||||
|
|
||||||
|
Pic.fromJson(Map<String, dynamic> json) {
|
||||||
|
height = json['height'];
|
||||||
|
size = json['size'];
|
||||||
|
url = json['url'];
|
||||||
|
width = json['width'];
|
||||||
|
aspectRatio = json['width'] / json['height'];
|
||||||
|
scale = customDivision(json['width'], 600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LinkCard {
|
||||||
|
LinkCard({
|
||||||
|
this.cover,
|
||||||
|
this.descSecond,
|
||||||
|
this.duration,
|
||||||
|
this.jumpUrl,
|
||||||
|
this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? cover;
|
||||||
|
String? descSecond;
|
||||||
|
String? duration;
|
||||||
|
String? jumpUrl;
|
||||||
|
String? title;
|
||||||
|
|
||||||
|
LinkCard.fromJson(Map<String, dynamic> json) {
|
||||||
|
cover = json['card']['cover'];
|
||||||
|
descSecond = json['card']['desc_second'];
|
||||||
|
duration = json['card']['duration'];
|
||||||
|
jumpUrl = json['card']['jump_url'];
|
||||||
|
title = json['card']['title'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphText {
|
||||||
|
ModuleParagraphText({
|
||||||
|
this.nodes,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleParagraphTextNode>? nodes;
|
||||||
|
|
||||||
|
ModuleParagraphText.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['nodes'] != null) {
|
||||||
|
nodes = <ModuleParagraphTextNode>[];
|
||||||
|
json['nodes'].forEach((v) {
|
||||||
|
nodes!.add(ModuleParagraphTextNode.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNode {
|
||||||
|
ModuleParagraphTextNode({
|
||||||
|
this.type,
|
||||||
|
this.nodeType,
|
||||||
|
this.word,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? type;
|
||||||
|
int? nodeType;
|
||||||
|
ModuleParagraphTextNodeWord? word;
|
||||||
|
|
||||||
|
ModuleParagraphTextNode.fromJson(Map<String, dynamic> json) {
|
||||||
|
type = json['type'];
|
||||||
|
nodeType = json['node_type'];
|
||||||
|
word = json['word'] != null
|
||||||
|
? ModuleParagraphTextNodeWord.fromJson(json['word'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNodeWord {
|
||||||
|
ModuleParagraphTextNodeWord({
|
||||||
|
this.color,
|
||||||
|
this.fontSize,
|
||||||
|
this.style,
|
||||||
|
this.words,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? color;
|
||||||
|
int? fontSize;
|
||||||
|
ModuleParagraphTextNodeWordStyle? style;
|
||||||
|
String? words;
|
||||||
|
|
||||||
|
ModuleParagraphTextNodeWord.fromJson(Map<String, dynamic> json) {
|
||||||
|
color = json['color'];
|
||||||
|
fontSize = json['font_size'];
|
||||||
|
style = json['style'] != null
|
||||||
|
? ModuleParagraphTextNodeWordStyle.fromJson(json['style'])
|
||||||
|
: null;
|
||||||
|
words = json['words'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleParagraphTextNodeWordStyle {
|
||||||
|
ModuleParagraphTextNodeWordStyle({
|
||||||
|
this.bold,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? bold;
|
||||||
|
|
||||||
|
ModuleParagraphTextNodeWordStyle.fromJson(Map<String, dynamic> json) {
|
||||||
|
bold = json['bold'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleExtend {
|
||||||
|
ModuleExtend({
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleExtendItem>? items;
|
||||||
|
|
||||||
|
ModuleExtend.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['items'] != null) {
|
||||||
|
items = <ModuleExtendItem>[];
|
||||||
|
json['items'].forEach((v) {
|
||||||
|
items!.add(ModuleExtendItem.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleExtendItem {
|
||||||
|
ModuleExtendItem({
|
||||||
|
this.bizId,
|
||||||
|
this.bizType,
|
||||||
|
this.icon,
|
||||||
|
this.jumpUrl,
|
||||||
|
this.text,
|
||||||
|
});
|
||||||
|
|
||||||
|
dynamic bizId;
|
||||||
|
int? bizType;
|
||||||
|
dynamic icon;
|
||||||
|
String? jumpUrl;
|
||||||
|
String? text;
|
||||||
|
|
||||||
|
ModuleExtendItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
bizId = json['biz_id'];
|
||||||
|
bizType = json['biz_type'];
|
||||||
|
icon = json['icon'];
|
||||||
|
jumpUrl = json['jump_url'];
|
||||||
|
text = json['text'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleBottom {
|
||||||
|
ModuleBottom({
|
||||||
|
this.shareInfo,
|
||||||
|
});
|
||||||
|
|
||||||
|
ShareInfo? shareInfo;
|
||||||
|
|
||||||
|
ModuleBottom.fromJson(Map<String, dynamic> json) {
|
||||||
|
shareInfo = json['share_info'] != null
|
||||||
|
? ShareInfo.fromJson(json['share_info'])
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ShareInfo {
|
||||||
|
ShareInfo({
|
||||||
|
this.pic,
|
||||||
|
this.summary,
|
||||||
|
this.title,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? pic;
|
||||||
|
String? summary;
|
||||||
|
String? title;
|
||||||
|
|
||||||
|
ShareInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
pic = json['pic'];
|
||||||
|
summary = json['summary'];
|
||||||
|
title = json['title'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleStat {
|
||||||
|
ModuleStat({
|
||||||
|
this.coin,
|
||||||
|
this.comment,
|
||||||
|
this.favorite,
|
||||||
|
this.forward,
|
||||||
|
this.like,
|
||||||
|
});
|
||||||
|
|
||||||
|
StatItem? coin;
|
||||||
|
StatItem? comment;
|
||||||
|
StatItem? favorite;
|
||||||
|
StatItem? forward;
|
||||||
|
StatItem? like;
|
||||||
|
|
||||||
|
ModuleStat.fromJson(Map<String, dynamic> json) {
|
||||||
|
coin = json['coin'] != null ? StatItem.fromJson(json['coin']) : null;
|
||||||
|
comment =
|
||||||
|
json['comment'] != null ? StatItem.fromJson(json['comment']) : null;
|
||||||
|
favorite =
|
||||||
|
json['favorite'] != null ? StatItem.fromJson(json['favorite']) : null;
|
||||||
|
forward =
|
||||||
|
json['forward'] != null ? StatItem.fromJson(json['forward']) : null;
|
||||||
|
like = json['like'] != null ? StatItem.fromJson(json['like']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatItem {
|
||||||
|
StatItem({
|
||||||
|
this.count,
|
||||||
|
this.forbidden,
|
||||||
|
this.status,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? count;
|
||||||
|
bool? forbidden;
|
||||||
|
bool? status;
|
||||||
|
|
||||||
|
StatItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
count = json['count'];
|
||||||
|
forbidden = json['forbidden'];
|
||||||
|
status = json['status'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double customDivision(int a, int b) {
|
||||||
|
double result = a / b;
|
||||||
|
if (result < 1) {
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
286
lib/models/read/read.dart
Normal file
286
lib/models/read/read.dart
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
import 'package:pilipala/models/member/info.dart';
|
||||||
|
|
||||||
|
import 'opus.dart';
|
||||||
|
|
||||||
|
class ReadDataModel {
|
||||||
|
ReadDataModel({
|
||||||
|
this.cvid,
|
||||||
|
this.readInfo,
|
||||||
|
this.readViewInfo,
|
||||||
|
this.upInfo,
|
||||||
|
this.catalogList,
|
||||||
|
this.recommendInfoList,
|
||||||
|
this.hiddenInteraction,
|
||||||
|
this.isModern,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? cvid;
|
||||||
|
ReadInfo? readInfo;
|
||||||
|
Map? readViewInfo;
|
||||||
|
Map? upInfo;
|
||||||
|
List<dynamic>? catalogList;
|
||||||
|
List<dynamic>? recommendInfoList;
|
||||||
|
bool? hiddenInteraction;
|
||||||
|
bool? isModern;
|
||||||
|
|
||||||
|
ReadDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
cvid = json['cvid'];
|
||||||
|
readInfo =
|
||||||
|
json['readInfo'] != null ? ReadInfo.fromJson(json['readInfo']) : null;
|
||||||
|
readViewInfo = json['readViewInfo'];
|
||||||
|
upInfo = json['upInfo'];
|
||||||
|
if (json['catalogList'] != null) {
|
||||||
|
catalogList = <dynamic>[];
|
||||||
|
json['catalogList'].forEach((v) {
|
||||||
|
catalogList!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (json['recommendInfoList'] != null) {
|
||||||
|
recommendInfoList = <dynamic>[];
|
||||||
|
json['recommendInfoList'].forEach((v) {
|
||||||
|
recommendInfoList!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
hiddenInteraction = json['hiddenInteraction'];
|
||||||
|
isModern = json['isModern'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReadInfo {
|
||||||
|
ReadInfo({
|
||||||
|
this.id,
|
||||||
|
this.category,
|
||||||
|
this.title,
|
||||||
|
this.summary,
|
||||||
|
this.bannerUrl,
|
||||||
|
this.author,
|
||||||
|
this.publishTime,
|
||||||
|
this.ctime,
|
||||||
|
this.mtime,
|
||||||
|
this.stats,
|
||||||
|
this.attributes,
|
||||||
|
this.words,
|
||||||
|
this.originImageUrls,
|
||||||
|
this.content,
|
||||||
|
this.opus,
|
||||||
|
this.dynIdStr,
|
||||||
|
this.totalArtNum,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
Map? category;
|
||||||
|
String? title;
|
||||||
|
String? summary;
|
||||||
|
String? bannerUrl;
|
||||||
|
Author? author;
|
||||||
|
int? publishTime;
|
||||||
|
int? ctime;
|
||||||
|
int? mtime;
|
||||||
|
Map? stats;
|
||||||
|
int? attributes;
|
||||||
|
int? words;
|
||||||
|
List<String>? originImageUrls;
|
||||||
|
String? content;
|
||||||
|
Opus? opus;
|
||||||
|
String? dynIdStr;
|
||||||
|
int? totalArtNum;
|
||||||
|
|
||||||
|
ReadInfo.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
category = json['category'];
|
||||||
|
title = json['title'];
|
||||||
|
summary = json['summary'];
|
||||||
|
bannerUrl = json['banner_url'];
|
||||||
|
author = Author.fromJson(json['author']);
|
||||||
|
publishTime = json['publish_time'];
|
||||||
|
ctime = json['ctime'];
|
||||||
|
mtime = json['mtime'];
|
||||||
|
stats = json['stats'];
|
||||||
|
attributes = json['attributes'];
|
||||||
|
words = json['words'];
|
||||||
|
if (json['origin_image_urls'] != null) {
|
||||||
|
originImageUrls = <String>[];
|
||||||
|
json['origin_image_urls'].forEach((v) {
|
||||||
|
originImageUrls!.add(v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
content = json['content'];
|
||||||
|
opus = json['opus'] != null ? Opus.fromJson(json['opus']) : null;
|
||||||
|
dynIdStr = json['dyn_id_str'];
|
||||||
|
totalArtNum = json['total_art_num'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Author {
|
||||||
|
Author({
|
||||||
|
this.mid,
|
||||||
|
this.name,
|
||||||
|
this.face,
|
||||||
|
this.vip,
|
||||||
|
this.fans,
|
||||||
|
this.level,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
String? name;
|
||||||
|
String? face;
|
||||||
|
Vip? vip;
|
||||||
|
int? fans;
|
||||||
|
int? level;
|
||||||
|
|
||||||
|
Author.fromJson(Map<String, dynamic> json) {
|
||||||
|
mid = json['mid'];
|
||||||
|
name = json['name'];
|
||||||
|
face = json['face'];
|
||||||
|
vip = json['vip'] != null ? Vip.fromJson(json['vip']) : null;
|
||||||
|
fans = json['fans'];
|
||||||
|
level = json['level'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Opus {
|
||||||
|
// "opus_id": 976625853207150600,
|
||||||
|
// "opus_source": 2,
|
||||||
|
// "title": "真的很想骂人 但又没什么好骂的",
|
||||||
|
// "content": {
|
||||||
|
// "paragraphs": [{
|
||||||
|
// "para_type": 1,
|
||||||
|
// "text": {
|
||||||
|
// "nodes": [{
|
||||||
|
// "node_type": 1,
|
||||||
|
// "word": {
|
||||||
|
// "words": "21年玩到今年4月的号没了 ow1的时候45的号 玩了三年 后面第9赛季一个英杰5的号(虽然是偷的 但我任何违规行为都没有还是给我封了) 最近玩的号叫velleity 只和队友打天梯以及训练赛 又没了 连带着我一个一把没玩过只玩过一场训练赛的小号也没了 实在是无话可说了...",
|
||||||
|
// "font_size": 17,
|
||||||
|
// "style": {},
|
||||||
|
// "font_level": "regular"
|
||||||
|
// }
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 2,
|
||||||
|
// "pic": {
|
||||||
|
// "pics": [{
|
||||||
|
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Fba4e57459451fe74dcb70fd20bde9823316082117.jpg",
|
||||||
|
// "width": 1600,
|
||||||
|
// "height": 1000,
|
||||||
|
// "size": 588.482421875
|
||||||
|
// }],
|
||||||
|
// "style": 1
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 1,
|
||||||
|
// "text": {
|
||||||
|
// "nodes": [{
|
||||||
|
// "node_type": 1,
|
||||||
|
// "word": {
|
||||||
|
// "words": "\n",
|
||||||
|
// "font_size": 17,
|
||||||
|
// "style": {},
|
||||||
|
// "font_level": "regular"
|
||||||
|
// }
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 2,
|
||||||
|
// "pic": {
|
||||||
|
// "pics": [{
|
||||||
|
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002F0945be6b621091ddb8189482a87a36fb316082117.jpg",
|
||||||
|
// "width": 1600,
|
||||||
|
// "height": 1002,
|
||||||
|
// "size": 665.7861328125
|
||||||
|
// }],
|
||||||
|
// "style": 1
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 1,
|
||||||
|
// "text": {
|
||||||
|
// "nodes": [{
|
||||||
|
// "node_type": 1,
|
||||||
|
// "word": {
|
||||||
|
// "words": "\n",
|
||||||
|
// "font_size": 17,
|
||||||
|
// "style": {},
|
||||||
|
// "font_level": "regular"
|
||||||
|
// }
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 2,
|
||||||
|
// "pic": {
|
||||||
|
// "pics": [{
|
||||||
|
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Ffa60649f8786578a764a1e68a2c5d23f316082117.jpg",
|
||||||
|
// "width": 1600,
|
||||||
|
// "height": 999,
|
||||||
|
// "size": 332.970703125
|
||||||
|
// }],
|
||||||
|
// "style": 1
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// "para_type": 1,
|
||||||
|
// "text": {
|
||||||
|
// "nodes": [{
|
||||||
|
// "node_type": 1,
|
||||||
|
// "word": {
|
||||||
|
// "words": "\n",
|
||||||
|
// "font_size": 17,
|
||||||
|
// "style": {},
|
||||||
|
// "font_level": "regular"
|
||||||
|
// }
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// "pub_info": {
|
||||||
|
// "uid": 316082117,
|
||||||
|
// "pub_time": 1726226826
|
||||||
|
// },
|
||||||
|
// "article": {
|
||||||
|
// "category_id": 15,
|
||||||
|
// "cover": [{
|
||||||
|
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Fbanner\u002Feb10074186a62f98c18e1b5b9deb38be316082117.png",
|
||||||
|
// "width": 1071,
|
||||||
|
// "height": 315,
|
||||||
|
// "size": 225.625
|
||||||
|
// }]
|
||||||
|
// },
|
||||||
|
// "version": {
|
||||||
|
// "cvid": 38660379,
|
||||||
|
// "version_id": 101683514411343360
|
||||||
|
// }
|
||||||
|
Opus({
|
||||||
|
this.opusId,
|
||||||
|
this.opusSource,
|
||||||
|
this.title,
|
||||||
|
this.content,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? opusId;
|
||||||
|
int? opusSource;
|
||||||
|
String? title;
|
||||||
|
Content? content;
|
||||||
|
|
||||||
|
Opus.fromJson(Map<String, dynamic> json) {
|
||||||
|
opusId = json['opus_id'];
|
||||||
|
opusSource = json['opus_source'];
|
||||||
|
title = json['title'];
|
||||||
|
content =
|
||||||
|
json['content'] != null ? Content.fromJson(json['content']) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Content {
|
||||||
|
Content({
|
||||||
|
this.paragraphs,
|
||||||
|
});
|
||||||
|
|
||||||
|
List<ModuleParagraph>? paragraphs;
|
||||||
|
|
||||||
|
Content.fromJson(Map<String, dynamic> json) {
|
||||||
|
if (json['paragraphs'] != null) {
|
||||||
|
paragraphs = <ModuleParagraph>[];
|
||||||
|
json['paragraphs'].forEach((v) {
|
||||||
|
paragraphs!.add(ModuleParagraph.fromJson(v));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
lib/models/sponsor_block/action_type.dart
Normal file
26
lib/models/sponsor_block/action_type.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 片段类型枚举
|
||||||
|
enum ActionType {
|
||||||
|
skip,
|
||||||
|
mute,
|
||||||
|
full,
|
||||||
|
poi,
|
||||||
|
chapter,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ActionTypeExtension on ActionType {
|
||||||
|
String get value => [
|
||||||
|
'skip',
|
||||||
|
'mute',
|
||||||
|
'full',
|
||||||
|
'poi',
|
||||||
|
'chapter',
|
||||||
|
][index];
|
||||||
|
|
||||||
|
String get label => [
|
||||||
|
'跳过',
|
||||||
|
'静音',
|
||||||
|
'完整观看',
|
||||||
|
'亮点',
|
||||||
|
'章节切换',
|
||||||
|
][index];
|
||||||
|
}
|
||||||
43
lib/models/sponsor_block/segment.dart
Normal file
43
lib/models/sponsor_block/segment.dart
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import 'action_type.dart';
|
||||||
|
import 'segment_type.dart';
|
||||||
|
|
||||||
|
class SegmentDataModel {
|
||||||
|
final SegmentType? category;
|
||||||
|
final ActionType? actionType;
|
||||||
|
final List? segment;
|
||||||
|
final String? uuid;
|
||||||
|
final num? videoDuration;
|
||||||
|
final int? locked;
|
||||||
|
final int? votes;
|
||||||
|
final String? description;
|
||||||
|
// 是否已经跳过
|
||||||
|
bool isSkip = false;
|
||||||
|
|
||||||
|
SegmentDataModel({
|
||||||
|
this.category,
|
||||||
|
this.actionType,
|
||||||
|
this.segment,
|
||||||
|
this.uuid,
|
||||||
|
this.videoDuration,
|
||||||
|
this.locked,
|
||||||
|
this.votes,
|
||||||
|
this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SegmentDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SegmentDataModel(
|
||||||
|
category: SegmentType.values.firstWhere(
|
||||||
|
(e) => e.value == json['category'],
|
||||||
|
orElse: () => SegmentType.sponsor),
|
||||||
|
actionType: ActionType.values.firstWhere(
|
||||||
|
(e) => e.value == json['actionType'],
|
||||||
|
orElse: () => ActionType.skip),
|
||||||
|
segment: json['segment'],
|
||||||
|
uuid: json['UUID'],
|
||||||
|
videoDuration: json['videoDuration'],
|
||||||
|
locked: json['locked'],
|
||||||
|
votes: json['votes'],
|
||||||
|
description: json['description'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
lib/models/sponsor_block/segment_type.dart
Normal file
46
lib/models/sponsor_block/segment_type.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// 片段类型枚举
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
|
enum SegmentType {
|
||||||
|
sponsor,
|
||||||
|
intro,
|
||||||
|
outro,
|
||||||
|
interaction,
|
||||||
|
selfpromo,
|
||||||
|
music_offtopic,
|
||||||
|
preview,
|
||||||
|
poi_highlight,
|
||||||
|
filler,
|
||||||
|
exclusive_access,
|
||||||
|
chapter,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SegmentTypeExtension on SegmentType {
|
||||||
|
String get value => [
|
||||||
|
'sponsor',
|
||||||
|
'intro',
|
||||||
|
'outro',
|
||||||
|
'interaction',
|
||||||
|
'selfpromo',
|
||||||
|
'music_offtopic',
|
||||||
|
'preview',
|
||||||
|
'poi_highlight',
|
||||||
|
'filler',
|
||||||
|
'exclusive_access',
|
||||||
|
'chapter',
|
||||||
|
][index];
|
||||||
|
|
||||||
|
String get label => [
|
||||||
|
'赞助',
|
||||||
|
'开场介绍',
|
||||||
|
'片尾致谢',
|
||||||
|
'互动',
|
||||||
|
'自我推广',
|
||||||
|
'音乐',
|
||||||
|
'预览',
|
||||||
|
'亮点',
|
||||||
|
'无效填充',
|
||||||
|
'独家访问',
|
||||||
|
'章节',
|
||||||
|
][index];
|
||||||
|
}
|
||||||
@ -39,11 +39,11 @@ class ModelResult {
|
|||||||
ModelResult.fromJson(Map<String, dynamic> json) {
|
ModelResult.fromJson(Map<String, dynamic> json) {
|
||||||
resultType = json['result_type'];
|
resultType = json['result_type'];
|
||||||
summary = json['summary'];
|
summary = json['summary'];
|
||||||
outline = json['result_type'] == 2
|
outline = json['result_type'] == 0
|
||||||
? json['outline']
|
? <OutlineItem>[]
|
||||||
|
: json['outline']
|
||||||
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
||||||
.toList()
|
.toList();
|
||||||
: <OutlineItem>[];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
276
lib/models/video/later.dart
Normal file
276
lib/models/video/later.dart
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
class MediaVideoItemModel {
|
||||||
|
MediaVideoItemModel({
|
||||||
|
this.id,
|
||||||
|
this.aid,
|
||||||
|
this.offset,
|
||||||
|
this.index,
|
||||||
|
this.intro,
|
||||||
|
this.attr,
|
||||||
|
this.tid,
|
||||||
|
this.copyRight,
|
||||||
|
this.cntInfo,
|
||||||
|
this.cover,
|
||||||
|
this.duration,
|
||||||
|
this.pubtime,
|
||||||
|
this.likeState,
|
||||||
|
this.favState,
|
||||||
|
this.page,
|
||||||
|
this.cid,
|
||||||
|
this.pages,
|
||||||
|
this.title,
|
||||||
|
this.type,
|
||||||
|
this.upper,
|
||||||
|
this.link,
|
||||||
|
this.bvid,
|
||||||
|
this.shortLink,
|
||||||
|
this.rights,
|
||||||
|
this.elecInfo,
|
||||||
|
this.coin,
|
||||||
|
this.progressPercent,
|
||||||
|
this.badge,
|
||||||
|
this.forbidFav,
|
||||||
|
this.moreType,
|
||||||
|
this.businessOid,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
int? aid;
|
||||||
|
int? offset;
|
||||||
|
int? index;
|
||||||
|
String? intro;
|
||||||
|
int? attr;
|
||||||
|
int? tid;
|
||||||
|
int? copyRight;
|
||||||
|
Map? cntInfo;
|
||||||
|
String? cover;
|
||||||
|
int? duration;
|
||||||
|
int? pubtime;
|
||||||
|
int? likeState;
|
||||||
|
int? favState;
|
||||||
|
int? page;
|
||||||
|
int? cid;
|
||||||
|
List<Page>? pages;
|
||||||
|
String? title;
|
||||||
|
int? type;
|
||||||
|
Upper? upper;
|
||||||
|
String? link;
|
||||||
|
String? bvid;
|
||||||
|
String? shortLink;
|
||||||
|
Rights? rights;
|
||||||
|
dynamic elecInfo;
|
||||||
|
Coin? coin;
|
||||||
|
double? progressPercent;
|
||||||
|
dynamic badge;
|
||||||
|
bool? forbidFav;
|
||||||
|
int? moreType;
|
||||||
|
int? businessOid;
|
||||||
|
|
||||||
|
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
MediaVideoItemModel(
|
||||||
|
id: json["id"],
|
||||||
|
aid: json["id"],
|
||||||
|
offset: json["offset"],
|
||||||
|
index: json["index"],
|
||||||
|
intro: json["intro"],
|
||||||
|
attr: json["attr"],
|
||||||
|
tid: json["tid"],
|
||||||
|
copyRight: json["copy_right"],
|
||||||
|
cntInfo: json["cnt_info"],
|
||||||
|
cover: json["cover"],
|
||||||
|
duration: json["duration"],
|
||||||
|
pubtime: json["pubtime"],
|
||||||
|
likeState: json["like_state"],
|
||||||
|
favState: json["fav_state"],
|
||||||
|
page: json["page"],
|
||||||
|
cid: json["pages"] == null ? -1 : json["pages"].first['id'],
|
||||||
|
// json["pages"] 可能为null
|
||||||
|
pages: json["pages"] == null
|
||||||
|
? []
|
||||||
|
: List<Page>.from(json["pages"].map((x) => Page.fromJson(x))),
|
||||||
|
title: json["title"],
|
||||||
|
type: json["type"],
|
||||||
|
upper: Upper.fromJson(json["upper"]),
|
||||||
|
link: json["link"],
|
||||||
|
bvid: json["bv_id"],
|
||||||
|
shortLink: json["short_link"],
|
||||||
|
rights: Rights.fromJson(json["rights"]),
|
||||||
|
elecInfo: json["elec_info"],
|
||||||
|
coin: Coin.fromJson(json["coin"]),
|
||||||
|
progressPercent: json["progress_percent"].toDouble(),
|
||||||
|
badge: json["badge"],
|
||||||
|
forbidFav: json["forbid_fav"],
|
||||||
|
moreType: json["more_type"],
|
||||||
|
businessOid: json["business_oid"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Coin {
|
||||||
|
Coin({
|
||||||
|
this.maxNum,
|
||||||
|
this.coinNumber,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? maxNum;
|
||||||
|
int? coinNumber;
|
||||||
|
|
||||||
|
factory Coin.fromJson(Map<String, dynamic> json) => Coin(
|
||||||
|
maxNum: json["max_num"],
|
||||||
|
coinNumber: json["coin_number"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Page {
|
||||||
|
Page({
|
||||||
|
this.id,
|
||||||
|
this.title,
|
||||||
|
this.intro,
|
||||||
|
this.duration,
|
||||||
|
this.link,
|
||||||
|
this.page,
|
||||||
|
this.metas,
|
||||||
|
this.from,
|
||||||
|
this.dimension,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
String? title;
|
||||||
|
String? intro;
|
||||||
|
int? duration;
|
||||||
|
String? link;
|
||||||
|
int? page;
|
||||||
|
List<Meta>? metas;
|
||||||
|
String? from;
|
||||||
|
Dimension? dimension;
|
||||||
|
|
||||||
|
factory Page.fromJson(Map<String, dynamic> json) => Page(
|
||||||
|
id: json["id"],
|
||||||
|
title: json["title"],
|
||||||
|
intro: json["intro"],
|
||||||
|
duration: json["duration"],
|
||||||
|
link: json["link"],
|
||||||
|
page: json["page"],
|
||||||
|
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
|
||||||
|
from: json["from"],
|
||||||
|
dimension: Dimension.fromJson(json["dimension"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dimension {
|
||||||
|
Dimension({
|
||||||
|
this.width,
|
||||||
|
this.height,
|
||||||
|
this.rotate,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? width;
|
||||||
|
int? height;
|
||||||
|
int? rotate;
|
||||||
|
|
||||||
|
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||||
|
width: json["width"],
|
||||||
|
height: json["height"],
|
||||||
|
rotate: json["rotate"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta {
|
||||||
|
Meta({
|
||||||
|
this.quality,
|
||||||
|
this.size,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? quality;
|
||||||
|
int? size;
|
||||||
|
|
||||||
|
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
|
||||||
|
quality: json["quality"],
|
||||||
|
size: json["size"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rights {
|
||||||
|
Rights({
|
||||||
|
this.bp,
|
||||||
|
this.elec,
|
||||||
|
this.download,
|
||||||
|
this.movie,
|
||||||
|
this.pay,
|
||||||
|
this.ugcPay,
|
||||||
|
this.hd5,
|
||||||
|
this.noReprint,
|
||||||
|
this.autoplay,
|
||||||
|
this.noBackground,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? bp;
|
||||||
|
int? elec;
|
||||||
|
int? download;
|
||||||
|
int? movie;
|
||||||
|
int? pay;
|
||||||
|
int? ugcPay;
|
||||||
|
int? hd5;
|
||||||
|
int? noReprint;
|
||||||
|
int? autoplay;
|
||||||
|
int? noBackground;
|
||||||
|
|
||||||
|
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
|
||||||
|
bp: json["bp"],
|
||||||
|
elec: json["elec"],
|
||||||
|
download: json["download"],
|
||||||
|
movie: json["movie"],
|
||||||
|
pay: json["pay"],
|
||||||
|
ugcPay: json["ugc_pay"],
|
||||||
|
hd5: json["hd5"],
|
||||||
|
noReprint: json["no_reprint"],
|
||||||
|
autoplay: json["autoplay"],
|
||||||
|
noBackground: json["no_background"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Upper {
|
||||||
|
Upper({
|
||||||
|
this.mid,
|
||||||
|
this.name,
|
||||||
|
this.face,
|
||||||
|
this.followed,
|
||||||
|
this.fans,
|
||||||
|
this.vipType,
|
||||||
|
this.vipStatue,
|
||||||
|
this.vipDueDate,
|
||||||
|
this.vipPayType,
|
||||||
|
this.officialRole,
|
||||||
|
this.officialTitle,
|
||||||
|
this.officialDesc,
|
||||||
|
this.displayName,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
String? name;
|
||||||
|
String? face;
|
||||||
|
int? followed;
|
||||||
|
int? fans;
|
||||||
|
int? vipType;
|
||||||
|
int? vipStatue;
|
||||||
|
int? vipDueDate;
|
||||||
|
int? vipPayType;
|
||||||
|
int? officialRole;
|
||||||
|
String? officialTitle;
|
||||||
|
String? officialDesc;
|
||||||
|
String? displayName;
|
||||||
|
|
||||||
|
factory Upper.fromJson(Map<String, dynamic> json) => Upper(
|
||||||
|
mid: json["mid"],
|
||||||
|
name: json["name"],
|
||||||
|
face: json["face"],
|
||||||
|
followed: json["followed"],
|
||||||
|
fans: json["fans"],
|
||||||
|
vipType: json["vip_type"],
|
||||||
|
vipStatue: json["vip_statue"],
|
||||||
|
vipDueDate: json["vip_due_date"],
|
||||||
|
vipPayType: json["vip_pay_type"],
|
||||||
|
officialRole: json["official_role"],
|
||||||
|
officialTitle: json["official_title"],
|
||||||
|
officialDesc: json["official_desc"],
|
||||||
|
displayName: json["display_name"],
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
class ReplyContent {
|
class ReplyContent {
|
||||||
ReplyContent({
|
ReplyContent({
|
||||||
this.message,
|
this.message,
|
||||||
|
this.message2,
|
||||||
this.atNameToMid, // @的用户的mid null
|
this.atNameToMid, // @的用户的mid null
|
||||||
this.members, // 被@的用户List 如果有的话 []
|
this.members, // 被@的用户List 如果有的话 []
|
||||||
this.emote, // 表情包 如果有的话 null
|
this.emote, // 表情包 如果有的话 null
|
||||||
@ -13,6 +14,7 @@ class ReplyContent {
|
|||||||
});
|
});
|
||||||
|
|
||||||
String? message;
|
String? message;
|
||||||
|
String? message2;
|
||||||
Map? atNameToMid;
|
Map? atNameToMid;
|
||||||
List<MemberItemModel>? members;
|
List<MemberItemModel>? members;
|
||||||
Map? emote;
|
Map? emote;
|
||||||
@ -24,10 +26,17 @@ class ReplyContent {
|
|||||||
Map? topicsMeta;
|
Map? topicsMeta;
|
||||||
|
|
||||||
ReplyContent.fromJson(Map<String, dynamic> json) {
|
ReplyContent.fromJson(Map<String, dynamic> json) {
|
||||||
message = json['message']
|
message = message2 = json['message']
|
||||||
.replaceAll('>', '>')
|
.replaceAll('>', '>')
|
||||||
.replaceAll('"', '"')
|
.replaceAll('"', '"')
|
||||||
.replaceAll(''', "'");
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll(' ', ' ');
|
||||||
|
|
||||||
atNameToMid = json['at_name_to_mid'] ?? {};
|
atNameToMid = json['at_name_to_mid'] ?? {};
|
||||||
members = json['members'] != null
|
members = json['members'] != null
|
||||||
? json['members']
|
? json['members']
|
||||||
@ -39,8 +48,8 @@ class ReplyContent {
|
|||||||
pictures = json['pictures'] ?? [];
|
pictures = json['pictures'] ?? [];
|
||||||
vote = json['vote'] ?? {};
|
vote = json['vote'] ?? {};
|
||||||
richText = json['rich_text'] ?? {};
|
richText = json['rich_text'] ?? {};
|
||||||
// 不包含@ 笔记 图片的时候,文字可折叠
|
// 不包含@ 笔记的时候,文字可折叠
|
||||||
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
|
isText = atNameToMid!.isEmpty && vote!.isEmpty;
|
||||||
topicsMeta = json['topics_meta'] ?? {};
|
topicsMeta = json['topics_meta'] ?? {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,21 +6,21 @@ import 'upper.dart';
|
|||||||
|
|
||||||
class ReplyData {
|
class ReplyData {
|
||||||
ReplyData({
|
ReplyData({
|
||||||
this.page,
|
this.cursor,
|
||||||
this.config,
|
this.config,
|
||||||
this.replies,
|
this.replies,
|
||||||
this.topReplies,
|
this.topReplies,
|
||||||
this.upper,
|
this.upper,
|
||||||
});
|
});
|
||||||
|
|
||||||
ReplyPage? page;
|
ReplyCursor? cursor;
|
||||||
ReplyConfig? config;
|
ReplyConfig? config;
|
||||||
late List<ReplyItemModel>? replies;
|
late List<ReplyItemModel>? replies;
|
||||||
late List<ReplyItemModel>? topReplies;
|
late List<ReplyItemModel>? topReplies;
|
||||||
ReplyUpper? upper;
|
ReplyUpper? upper;
|
||||||
|
|
||||||
ReplyData.fromJson(Map<String, dynamic> json) {
|
ReplyData.fromJson(Map<String, dynamic> json) {
|
||||||
page = ReplyPage.fromJson(json['page']);
|
cursor = ReplyCursor.fromJson(json['cursor']);
|
||||||
config = ReplyConfig.fromJson(json['config']);
|
config = ReplyConfig.fromJson(json['config']);
|
||||||
replies = json['replies'] != null
|
replies = json['replies'] != null
|
||||||
? json['replies']
|
? json['replies']
|
||||||
@ -38,3 +38,100 @@ class ReplyData {
|
|||||||
upper = ReplyUpper.fromJson(json['upper']);
|
upper = ReplyUpper.fromJson(json['upper']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReplyCursor {
|
||||||
|
ReplyCursor({
|
||||||
|
this.isBegin,
|
||||||
|
this.prev,
|
||||||
|
this.next,
|
||||||
|
this.isEnd,
|
||||||
|
this.mode,
|
||||||
|
this.modeText,
|
||||||
|
this.allCount,
|
||||||
|
this.supportMode,
|
||||||
|
this.name,
|
||||||
|
this.paginationReply,
|
||||||
|
this.sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
bool? isBegin;
|
||||||
|
int? prev;
|
||||||
|
int? next;
|
||||||
|
bool? isEnd;
|
||||||
|
int? mode;
|
||||||
|
String? modeText;
|
||||||
|
int? allCount;
|
||||||
|
List<int>? supportMode;
|
||||||
|
String? name;
|
||||||
|
PaginationReply? paginationReply;
|
||||||
|
String? sessionId;
|
||||||
|
|
||||||
|
ReplyCursor.fromJson(Map<String, dynamic> json) {
|
||||||
|
isBegin = json['is_begin'];
|
||||||
|
prev = json['prev'];
|
||||||
|
next = json['next'];
|
||||||
|
isEnd = json['is_end'];
|
||||||
|
mode = json['mode'];
|
||||||
|
modeText = json['mode_text'];
|
||||||
|
allCount = json['all_count'] ?? 0;
|
||||||
|
supportMode = json['support_mode'].cast<int>();
|
||||||
|
name = json['name'];
|
||||||
|
paginationReply = json['pagination_reply'] != null
|
||||||
|
? PaginationReply.fromJson(json['pagination_reply'])
|
||||||
|
: null;
|
||||||
|
sessionId = json['session_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PaginationReply {
|
||||||
|
PaginationReply({
|
||||||
|
this.nextOffset,
|
||||||
|
this.prevOffset,
|
||||||
|
});
|
||||||
|
String? nextOffset;
|
||||||
|
String? prevOffset;
|
||||||
|
PaginationReply.fromJson(Map<String, dynamic> json) {
|
||||||
|
nextOffset = json['next_offset'];
|
||||||
|
prevOffset = json['prev_offset'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplyReplyData {
|
||||||
|
ReplyReplyData({
|
||||||
|
this.page,
|
||||||
|
this.config,
|
||||||
|
this.replies,
|
||||||
|
this.root,
|
||||||
|
this.topReplies,
|
||||||
|
this.upper,
|
||||||
|
});
|
||||||
|
|
||||||
|
ReplyPage? page;
|
||||||
|
ReplyConfig? config;
|
||||||
|
late List<ReplyItemModel>? replies;
|
||||||
|
ReplyItemModel? root;
|
||||||
|
late List<ReplyItemModel>? topReplies;
|
||||||
|
ReplyUpper? upper;
|
||||||
|
|
||||||
|
ReplyReplyData.fromJson(Map<String, dynamic> json) {
|
||||||
|
page = ReplyPage.fromJson(json['page']);
|
||||||
|
config = ReplyConfig.fromJson(json['config']);
|
||||||
|
replies = json['replies'] != null
|
||||||
|
? json['replies']
|
||||||
|
.map<ReplyItemModel>(
|
||||||
|
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
root = json['root'] != null
|
||||||
|
? ReplyItemModel.fromJson(json['root'], false)
|
||||||
|
: null;
|
||||||
|
topReplies = json['top_replies'] != null
|
||||||
|
? json['top_replies']
|
||||||
|
.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||||
|
item, json['upper']['mid'],
|
||||||
|
isTopStatus: true))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
upper = ReplyUpper.fromJson(json['upper']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ class ReplyItemModel {
|
|||||||
this.parent,
|
this.parent,
|
||||||
this.dialog,
|
this.dialog,
|
||||||
this.count,
|
this.count,
|
||||||
|
this.rcount,
|
||||||
this.floor,
|
this.floor,
|
||||||
this.state,
|
this.state,
|
||||||
this.fansgrade,
|
this.fansgrade,
|
||||||
@ -41,6 +42,7 @@ class ReplyItemModel {
|
|||||||
int? parent;
|
int? parent;
|
||||||
int? dialog;
|
int? dialog;
|
||||||
int? count;
|
int? count;
|
||||||
|
int? rcount;
|
||||||
int? floor;
|
int? floor;
|
||||||
int? state;
|
int? state;
|
||||||
int? fansgrade;
|
int? fansgrade;
|
||||||
@ -72,6 +74,7 @@ class ReplyItemModel {
|
|||||||
parent = json['parent'];
|
parent = json['parent'];
|
||||||
dialog = json['dialog'];
|
dialog = json['dialog'];
|
||||||
count = json['count'];
|
count = json['count'];
|
||||||
|
rcount = json['rcount'] ?? 0;
|
||||||
floor = json['floor'];
|
floor = json['floor'];
|
||||||
state = json['state'];
|
state = json['state'];
|
||||||
fansgrade = json['fansgrade'];
|
fansgrade = json['fansgrade'];
|
||||||
@ -122,6 +125,7 @@ class ReplyControl {
|
|||||||
this.upLike,
|
this.upLike,
|
||||||
this.isShow,
|
this.isShow,
|
||||||
this.entryText,
|
this.entryText,
|
||||||
|
this.entryTextNum,
|
||||||
this.titleText,
|
this.titleText,
|
||||||
this.time,
|
this.time,
|
||||||
this.location,
|
this.location,
|
||||||
@ -132,6 +136,7 @@ class ReplyControl {
|
|||||||
bool? upLike;
|
bool? upLike;
|
||||||
bool? isShow;
|
bool? isShow;
|
||||||
String? entryText;
|
String? entryText;
|
||||||
|
int? entryTextNum;
|
||||||
String? titleText;
|
String? titleText;
|
||||||
String? time;
|
String? time;
|
||||||
String? location;
|
String? location;
|
||||||
@ -152,6 +157,10 @@ class ReplyControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
entryText = json['sub_reply_entry_text'];
|
entryText = json['sub_reply_entry_text'];
|
||||||
|
// 正则匹配
|
||||||
|
entryTextNum = json['sub_reply_entry_text'] != null
|
||||||
|
? int.parse(RegExp(r"\d+").stringMatch(json['sub_reply_entry_text']!)!)
|
||||||
|
: 0;
|
||||||
titleText = json['sub_reply_title_text'];
|
titleText = json['sub_reply_title_text'];
|
||||||
time = json['time_desc'];
|
time = json['time_desc'];
|
||||||
location = json['location'] != null ? json['location'].split(':')[1] : '';
|
location = json['location'] != null ? json['location'].split(':')[1] : '';
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class ReplyMember {
|
|||||||
avatar = json['avatar'];
|
avatar = json['avatar'];
|
||||||
level = json['level_info']['current_level'];
|
level = json['level_info']['current_level'];
|
||||||
pendant = Pendant.fromJson(json['pendant']);
|
pendant = Pendant.fromJson(json['pendant']);
|
||||||
officialVerify = json['officia_verify'];
|
officialVerify = json['official_verify'];
|
||||||
vip = json['vip'];
|
vip = json['vip'];
|
||||||
fansDetail = json['fans_detail'];
|
fansDetail = json['fans_detail'];
|
||||||
userSailing = json['user_sailing'] != null
|
userSailing = json['user_sailing'] != null
|
||||||
|
|||||||
17
lib/models/video/tags.dart
Normal file
17
lib/models/video/tags.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class VideoTagItem {
|
||||||
|
String? tagName;
|
||||||
|
int? tagId;
|
||||||
|
int? tagType;
|
||||||
|
|
||||||
|
VideoTagItem({
|
||||||
|
this.tagName,
|
||||||
|
this.tagId,
|
||||||
|
this.tagType,
|
||||||
|
});
|
||||||
|
|
||||||
|
VideoTagItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
tagName = json['tag_name'];
|
||||||
|
tagId = json['tag_id'];
|
||||||
|
tagType = json['type'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -377,6 +377,7 @@ class Part {
|
|||||||
int? page;
|
int? page;
|
||||||
String? from;
|
String? from;
|
||||||
String? pagePart;
|
String? pagePart;
|
||||||
|
String? title;
|
||||||
int? duration;
|
int? duration;
|
||||||
String? vid;
|
String? vid;
|
||||||
String? weblink;
|
String? weblink;
|
||||||
@ -389,6 +390,7 @@ class Part {
|
|||||||
this.page,
|
this.page,
|
||||||
this.from,
|
this.from,
|
||||||
this.pagePart,
|
this.pagePart,
|
||||||
|
this.title,
|
||||||
this.duration,
|
this.duration,
|
||||||
this.vid,
|
this.vid,
|
||||||
this.weblink,
|
this.weblink,
|
||||||
@ -406,6 +408,7 @@ class Part {
|
|||||||
page = json["page"];
|
page = json["page"];
|
||||||
from = json["from"];
|
from = json["from"];
|
||||||
pagePart = json["part"];
|
pagePart = json["part"];
|
||||||
|
title = json["part"];
|
||||||
duration = json["duration"];
|
duration = json["duration"];
|
||||||
vid = json["vid"];
|
vid = json["vid"];
|
||||||
weblink = json["weblink"];
|
weblink = json["weblink"];
|
||||||
@ -638,6 +641,7 @@ class EpisodeItem {
|
|||||||
this.page,
|
this.page,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.cover,
|
this.cover,
|
||||||
|
this.pages,
|
||||||
});
|
});
|
||||||
int? seasonId;
|
int? seasonId;
|
||||||
int? sectionId;
|
int? sectionId;
|
||||||
@ -649,6 +653,10 @@ class EpisodeItem {
|
|||||||
Part? page;
|
Part? page;
|
||||||
String? bvid;
|
String? bvid;
|
||||||
String? cover;
|
String? cover;
|
||||||
|
int? pubdate;
|
||||||
|
int? duration;
|
||||||
|
Stat? stat;
|
||||||
|
List<Page>? pages;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
seasonId = json['season_id'];
|
seasonId = json['season_id'];
|
||||||
@ -661,6 +669,10 @@ class EpisodeItem {
|
|||||||
page = Part.fromJson(json['page']);
|
page = Part.fromJson(json['page']);
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
cover = json['arc']['pic'];
|
cover = json['arc']['pic'];
|
||||||
|
pubdate = json['arc']['pubdate'];
|
||||||
|
duration = json['arc']['duration'];
|
||||||
|
stat = Stat.fromJson(json['arc']['stat']);
|
||||||
|
pages = json['pages'].map<Page>((e) => Page.fromJson(e)).toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -703,3 +715,18 @@ class Vip {
|
|||||||
status = json['status'];
|
status = json['status'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Page {
|
||||||
|
Page({
|
||||||
|
this.cid,
|
||||||
|
this.page,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? cid;
|
||||||
|
int? page;
|
||||||
|
|
||||||
|
Page.fromJson(Map<String, dynamic> json) {
|
||||||
|
cid = json['cid'];
|
||||||
|
page = json['page'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/github/latest.dart';
|
import 'package:pilipala/models/github/latest.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_gallery/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import '../../utils/cache_manage.dart';
|
import '../../utils/cache_manage.dart';
|
||||||
@ -38,9 +39,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
TextStyle subTitleStyle =
|
TextStyle subTitleStyle =
|
||||||
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: const Text('关于')),
|
||||||
title: Text('关于', style: Theme.of(context).textTheme.titleMedium),
|
|
||||||
),
|
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -124,7 +123,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
onTap: () => _aboutController.webSiteUrl(),
|
onTap: () => _aboutController.webSiteUrl(),
|
||||||
title: const Text('访问官网'),
|
title: const Text('访问官网'),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
'https://pilipalanet.mysxl.cn',
|
'https://pilipala.life',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -168,7 +167,7 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
onTap: () => _aboutController.tgChanel(),
|
onTap: () => _aboutController.tgChanel(),
|
||||||
title: const Text('TG频道'),
|
title: const Text('TG频道'),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
'https://t.me/+lm_oOVmF0RJiODk1',
|
'https://t.me/+1DFtqS6usUM5MDNl',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -295,7 +294,7 @@ class AboutController extends GetxController {
|
|||||||
displayTime: const Duration(milliseconds: 500),
|
displayTime: const Duration(milliseconds: 500),
|
||||||
).then(
|
).then(
|
||||||
(value) => launchUrl(
|
(value) => launchUrl(
|
||||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -321,35 +320,41 @@ class AboutController extends GetxController {
|
|||||||
// tg频道
|
// tg频道
|
||||||
tgChanel() {
|
tgChanel() {
|
||||||
Clipboard.setData(
|
Clipboard.setData(
|
||||||
const ClipboardData(text: 'https://t.me/+lm_oOVmF0RJiODk1'),
|
const ClipboardData(text: 'https://t.me/+1DFtqS6usUM5MDNl'),
|
||||||
);
|
);
|
||||||
SmartDialog.showToast(
|
SmartDialog.showToast(
|
||||||
'已复制,即将在浏览器打开',
|
'已复制,即将在浏览器打开',
|
||||||
displayTime: const Duration(milliseconds: 500),
|
displayTime: const Duration(milliseconds: 500),
|
||||||
).then(
|
).then(
|
||||||
(value) => launchUrl(
|
(value) => launchUrl(
|
||||||
Uri.parse('https://t.me/+lm_oOVmF0RJiODk1'),
|
Uri.parse('https://t.me/+1DFtqS6usUM5MDNl'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
aPay() {
|
aPay() {
|
||||||
try {
|
const List<String> sources = [
|
||||||
launchUrl(
|
'assets/images/pay/wechat.png',
|
||||||
Uri.parse(
|
'assets/images/pay/alipay.jpg'
|
||||||
'alipayqr://platformapi/startapp?saId=10000007&qrcode=https://qr.alipay.com/fkx14623ddwl1ping3ddd73'),
|
];
|
||||||
mode: LaunchMode.externalApplication,
|
Navigator.of(Get.context!).push(
|
||||||
|
HeroDialogRoute<void>(
|
||||||
|
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||||
|
sources: sources,
|
||||||
|
initIndex: 0,
|
||||||
|
itemBuilder: (context, index, isFocus, enablePageView) =>
|
||||||
|
Image.asset(sources[index]),
|
||||||
|
actionType: const [ImgActionType.save],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 官网
|
// 官网
|
||||||
webSiteUrl() {
|
webSiteUrl() {
|
||||||
launchUrl(
|
launchUrl(
|
||||||
Uri.parse('https://pilipalanet.mysxl.cn'),
|
Uri.parse('https://pilipala.life'),
|
||||||
mode: LaunchMode.externalApplication,
|
mode: LaunchMode.externalApplication,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,25 +3,27 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/bangumi.dart';
|
import 'package:pilipala/http/bangumi.dart';
|
||||||
import 'package:pilipala/models/bangumi/list.dart';
|
import 'package:pilipala/models/bangumi/list.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class BangumiController extends GetxController {
|
class BangumiController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs;
|
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs;
|
||||||
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs;
|
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs;
|
||||||
|
RxInt total = 0.obs;
|
||||||
int _currentPage = 1;
|
int _currentPage = 1;
|
||||||
bool isLoadingMore = true;
|
bool isLoadingMore = true;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
var userInfo;
|
UserInfoData? userInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo != null) {
|
if (userInfo != null) {
|
||||||
mid = userInfo.mid;
|
mid = userInfo!.mid!;
|
||||||
}
|
}
|
||||||
userLogin.value = userInfo != null;
|
userLogin.value = userInfo != null;
|
||||||
}
|
}
|
||||||
@ -54,9 +56,10 @@ class BangumiController extends GetxController {
|
|||||||
if (userInfo == null) {
|
if (userInfo == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var result = await BangumiHttp.bangumiFollow(mid: userInfo.mid);
|
var result = await BangumiHttp.getRecentBangumi(mid: userInfo!.mid!);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
bangumiFollowList.value = result['data'].list;
|
bangumiFollowList.value = result['data'].list;
|
||||||
|
total.value = result['data'].total;
|
||||||
} else {}
|
} else {}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,13 @@ 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/bangumi.dart';
|
||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
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/models/user/info.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/plugin/pl_player/models/play_repeat.dart';
|
||||||
@ -23,7 +25,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
// 视频bvid
|
// 视频bvid
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid']!;
|
||||||
var seasonId = Get.parameters['seasonId'] != null
|
var seasonId = Get.parameters['seasonId'] != null
|
||||||
? int.parse(Get.parameters['seasonId']!)
|
? int.tryParse(Get.parameters['seasonId']!)
|
||||||
: null;
|
: null;
|
||||||
var epId = Get.parameters['epId'] != null
|
var epId = Get.parameters['epId'] != null
|
||||||
? int.tryParse(Get.parameters['epId']!)
|
? int.tryParse(Get.parameters['epId']!)
|
||||||
@ -47,33 +49,40 @@ class BangumiIntroController extends GetxController {
|
|||||||
RxBool hasCoin = false.obs;
|
RxBool hasCoin = false.obs;
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
RxBool hasFav = false.obs;
|
RxBool hasFav = false.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
bool userLogin = false;
|
bool userLogin = false;
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
List addMediaIdsNew = [];
|
List addMediaIdsNew = [];
|
||||||
List delMediaIdsNew = [];
|
List delMediaIdsNew = [];
|
||||||
// 关注状态 默认未关注
|
// 追番状态 1想看 2在看 3已看
|
||||||
RxMap followStatus = {}.obs;
|
RxBool isFollowed = false.obs;
|
||||||
|
RxInt followStatus = 1.obs;
|
||||||
int _tempThemeValue = -1;
|
int _tempThemeValue = -1;
|
||||||
var userInfo;
|
UserInfoData? userInfo;
|
||||||
PersistentBottomSheetController? bottomSheetController;
|
PersistentBottomSheetController? bottomSheetController;
|
||||||
|
List<Map<String, dynamic>> followStatusList = [
|
||||||
|
{'title': '标记为 「想看」', 'status': 1},
|
||||||
|
{'title': '标记为 「在看」', 'status': 2},
|
||||||
|
{'title': '标记为 「已看」', 'status': 3},
|
||||||
|
{'title': '取消追番', 'status': -1},
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
print('bangumi: ${Get.parameters.toString()}');
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
userLogin = userInfo != null;
|
userLogin = userInfo != null;
|
||||||
|
if (userLogin && seasonId != null) {
|
||||||
|
bangumiStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取番剧简介&选集
|
// 获取番剧简介&选集
|
||||||
Future queryBangumiIntro() async {
|
Future queryBangumiIntro() async {
|
||||||
if (userLogin) {
|
if (userLogin) {
|
||||||
// 获取点赞状态
|
// 获取点赞投币收藏状态
|
||||||
queryHasLikeVideo();
|
bangumiActionStatus();
|
||||||
// 获取投币状态
|
|
||||||
queryHasCoinVideo();
|
|
||||||
// 获取收藏状态
|
|
||||||
queryHasFavVideo();
|
|
||||||
}
|
}
|
||||||
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
@ -83,26 +92,15 @@ class BangumiIntroController extends GetxController {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取点赞状态
|
// 获取番剧点赞投币收藏状态
|
||||||
Future queryHasLikeVideo() async {
|
Future bangumiActionStatus() async {
|
||||||
var result = await VideoHttp.hasLikeVideo(bvid: bvid);
|
var result = await BangumiHttp.bangumiActionStatus(epId: epId!);
|
||||||
// data num 被点赞标志 0:未点赞 1:已点赞
|
|
||||||
hasLike.value = result["data"] == 1 ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取投币状态
|
|
||||||
Future queryHasCoinVideo() async {
|
|
||||||
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
|
|
||||||
hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取收藏状态
|
|
||||||
Future queryHasFavVideo() async {
|
|
||||||
var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
|
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
hasFav.value = result["data"]['favoured'];
|
hasLike.value = result['data']['like'] == 1;
|
||||||
|
hasCoin.value = result['data']['coin_number'] != 0;
|
||||||
|
hasFav.value = result['data']['favorite'] == 1;
|
||||||
} else {
|
} else {
|
||||||
hasFav.value = false;
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +108,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
Future actionLikeVideo() async {
|
Future actionLikeVideo() async {
|
||||||
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
SmartDialog.showToast(!hasLike.value ? '点赞成功 👍' : '取消赞');
|
SmartDialog.showToast(!hasLike.value ? '点赞成功' : '取消赞');
|
||||||
hasLike.value = !hasLike.value;
|
hasLike.value = !hasLike.value;
|
||||||
bangumiDetail.value.stat!['likes'] =
|
bangumiDetail.value.stat!['likes'] =
|
||||||
bangumiDetail.value.stat!['likes'] + (!hasLike.value ? 1 : -1);
|
bangumiDetail.value.stat!['likes'] + (!hasLike.value ? 1 : -1);
|
||||||
@ -147,7 +145,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
var res = await VideoHttp.coinVideo(
|
var res = await VideoHttp.coinVideo(
|
||||||
bvid: bvid, multiply: _tempThemeValue);
|
bvid: bvid, multiply: _tempThemeValue);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
SmartDialog.showToast('投币成功 👏');
|
SmartDialog.showToast('投币成功');
|
||||||
hasCoin.value = true;
|
hasCoin.value = true;
|
||||||
bangumiDetail.value.stat!['coins'] =
|
bangumiDetail.value.stat!['coins'] =
|
||||||
bangumiDetail.value.stat!['coins'] +
|
bangumiDetail.value.stat!['coins'] +
|
||||||
@ -185,9 +183,11 @@ class BangumiIntroController extends GetxController {
|
|||||||
addMediaIdsNew = [];
|
addMediaIdsNew = [];
|
||||||
delMediaIdsNew = [];
|
delMediaIdsNew = [];
|
||||||
// 重新获取收藏状态
|
// 重新获取收藏状态
|
||||||
queryHasFavVideo();
|
bangumiActionStatus();
|
||||||
SmartDialog.showToast('✅ 操作成功');
|
SmartDialog.showToast('操作成功');
|
||||||
Get.back();
|
Get.back();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,21 +239,28 @@ class BangumiIntroController extends GetxController {
|
|||||||
|
|
||||||
// 追番
|
// 追番
|
||||||
Future bangumiAdd() async {
|
Future bangumiAdd() async {
|
||||||
var result =
|
var result = await VideoHttp.bangumiAdd(
|
||||||
await VideoHttp.bangumiAdd(seasonId: bangumiDetail.value.seasonId);
|
seasonId: seasonId ?? bangumiDetail.value.seasonId);
|
||||||
|
if (result['status']) {
|
||||||
|
followStatus.value = 2;
|
||||||
|
isFollowed.value = true;
|
||||||
|
}
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消追番
|
// 取消追番
|
||||||
Future bangumiDel() async {
|
Future bangumiDel() async {
|
||||||
var result =
|
var result = await VideoHttp.bangumiDel(
|
||||||
await VideoHttp.bangumiDel(seasonId: bangumiDetail.value.seasonId);
|
seasonId: seasonId ?? bangumiDetail.value.seasonId);
|
||||||
|
if (result['status']) {
|
||||||
|
isFollowed.value = false;
|
||||||
|
}
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryVideoInFolder() async {
|
Future queryVideoInFolder() async {
|
||||||
var result = await VideoHttp.videoInFolder(
|
var result = await VideoHttp.videoInFolder(
|
||||||
mid: userInfo.mid, rid: IdUtils.bv2av(bvid));
|
mid: userInfo!.mid!, rid: IdUtils.bv2av(bvid));
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
favFolderData.value = result['data'];
|
favFolderData.value = result['data'];
|
||||||
}
|
}
|
||||||
@ -302,18 +309,48 @@ class BangumiIntroController extends GetxController {
|
|||||||
episodes: episodes,
|
episodes: episodes,
|
||||||
currentCid: videoDetailCtr.cid.value,
|
currentCid: videoDetailCtr.cid.value,
|
||||||
dataType: dataType,
|
dataType: dataType,
|
||||||
context: Get.context!,
|
|
||||||
sheetHeight: Get.size.height,
|
sheetHeight: Get.size.height,
|
||||||
isFullScreen: true,
|
isFullScreen: true,
|
||||||
changeFucCall: (item, index) {
|
changeFucCall: (item, index) {
|
||||||
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
|
changeSeasonOrbangu(item.bvid, item.cid, item.aid, item.cover);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
).buildShowContent(Get.context!),
|
).buildShowContent(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
hiddenEpisodeBottomSheet() {
|
hiddenEpisodeBottomSheet() {
|
||||||
bottomSheetController?.close();
|
bottomSheetController?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取追番状态
|
||||||
|
Future bangumiStatus() async {
|
||||||
|
var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!);
|
||||||
|
if (result['status']) {
|
||||||
|
followStatus.value = result['data']['followStatus'];
|
||||||
|
isFollowed.value = result['data']['isFollowed'];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新追番状态
|
||||||
|
Future updateBangumiStatus(int status) async {
|
||||||
|
Get.back();
|
||||||
|
if (status == -1) {
|
||||||
|
bangumiDel();
|
||||||
|
} else {
|
||||||
|
var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!);
|
||||||
|
if (result['status']) {
|
||||||
|
followStatus.value = status;
|
||||||
|
final title = followStatusList.firstWhere(
|
||||||
|
(e) => e['status'] == status,
|
||||||
|
orElse: () => {'title': '未知状态'},
|
||||||
|
)['title'];
|
||||||
|
SmartDialog.showToast('追番状态$title');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
|
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
|
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_gallery/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import '../../../common/widgets/http_error.dart';
|
import '../../../common/widgets/http_error.dart';
|
||||||
@ -115,7 +116,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
late final BangumiIntroController bangumiIntroController;
|
late final BangumiIntroController bangumiIntroController;
|
||||||
late final VideoDetailController videoDetailCtr;
|
late final VideoDetailController videoDetailCtr;
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStorage.localCache;
|
||||||
late double sheetHeight;
|
late double sheetHeight;
|
||||||
int? cid;
|
int? cid;
|
||||||
bool isProcessing = false;
|
bool isProcessing = false;
|
||||||
@ -146,17 +147,34 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
showFavBottomSheet() {
|
showFavBottomSheet() async {
|
||||||
if (bangumiIntroController.userInfo.mid == null) {
|
if (bangumiIntroController.userInfo?.mid == null) {
|
||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showModalBottomSheet(
|
final mediaQueryData = MediaQuery.of(context);
|
||||||
context: context,
|
final contentHeight = mediaQueryData.size.height - kToolbarHeight;
|
||||||
useRootNavigator: true,
|
final double initialChildSize =
|
||||||
|
(contentHeight - Get.width * 9 / 16) / contentHeight;
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: Get.context!,
|
||||||
|
useSafeArea: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return FavPanel(ctr: bangumiIntroController);
|
return DraggableScrollableSheet(
|
||||||
|
initialChildSize: initialChildSize,
|
||||||
|
minChildSize: 0,
|
||||||
|
maxChildSize: 1,
|
||||||
|
snap: true,
|
||||||
|
expand: false,
|
||||||
|
snapSizes: [initialChildSize],
|
||||||
|
builder: (BuildContext context, ScrollController scrollController) {
|
||||||
|
return FavPanel(
|
||||||
|
ctr: bangumiIntroController,
|
||||||
|
scrollController: scrollController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -188,11 +206,25 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
children: [
|
children: [
|
||||||
Stack(
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
InkWell(
|
||||||
width: 105,
|
onTap: () {
|
||||||
height: 160,
|
Navigator.of(context).push(
|
||||||
|
HeroDialogRoute<void>(
|
||||||
|
builder: (BuildContext context) =>
|
||||||
|
InteractiveviewerGallery(
|
||||||
|
sources: [widget.bangumiDetail!.cover!],
|
||||||
|
initIndex: 0,
|
||||||
|
onPageChanged: (int pageIndex) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 115,
|
||||||
|
height: 115 / 0.75,
|
||||||
src: widget.bangumiDetail!.cover!,
|
src: widget.bangumiDetail!.cover!,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
text:
|
text:
|
||||||
'评分 ${widget.bangumiDetail?.rating?['score']! ?? '暂无'}',
|
'评分 ${widget.bangumiDetail?.rating?['score']! ?? '暂无'}',
|
||||||
@ -207,51 +239,40 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => showIntroDetail(),
|
onTap: () => showIntroDetail(),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 158,
|
height: 115 / 0.75,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(6, 4, 6, 6),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.bangumiDetail!.title!,
|
widget.bangumiDetail!.title!,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
SizedBox(
|
Obx(
|
||||||
width: 34,
|
() => BangumiStatusWidget(
|
||||||
height: 34,
|
ctr: bangumiIntroController,
|
||||||
child: IconButton(
|
isFollowed:
|
||||||
style: ButtonStyle(
|
bangumiIntroController.isFollowed.value,
|
||||||
padding: MaterialStateProperty.all(
|
|
||||||
EdgeInsets.zero),
|
|
||||||
backgroundColor:
|
|
||||||
MaterialStateProperty.resolveWith(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
return t.colorScheme.primaryContainer
|
|
||||||
.withOpacity(0.7);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
bangumiIntroController.bangumiAdd(),
|
|
||||||
icon: Icon(
|
|
||||||
Icons.favorite_border_rounded,
|
|
||||||
color: t.colorScheme.primary,
|
|
||||||
size: 22,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
StatView(
|
StatView(
|
||||||
@ -265,42 +286,13 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 10),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
(widget.bangumiDetail!.areas!.isNotEmpty
|
|
||||||
? widget.bangumiDetail!.areas!.first['name']
|
|
||||||
: ''),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: t.colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
widget.bangumiDetail!.publish!['pub_time_show'],
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: t.colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
widget.bangumiDetail!.newEp!['desc'],
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: t.colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
Text(
|
||||||
'简介:${widget.bangumiDetail!.evaluate!}',
|
'简介:${widget.bangumiDetail!.evaluate!}',
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 14,
|
||||||
color: t.colorScheme.outline,
|
color: t.colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -309,6 +301,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
@ -394,3 +387,97 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 追番状态
|
||||||
|
class BangumiStatusWidget extends StatelessWidget {
|
||||||
|
final BangumiIntroController ctr;
|
||||||
|
final bool isFollowed;
|
||||||
|
|
||||||
|
const BangumiStatusWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.ctr,
|
||||||
|
required this.isFollowed,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
|
void updateFollowStatus() {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return morePanel(context, ctr);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Obx(
|
||||||
|
() => SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||||
|
return ctr.isFollowed.value
|
||||||
|
? colorScheme.primaryContainer.withOpacity(0.7)
|
||||||
|
: colorScheme.outlineVariant.withOpacity(0.7);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
onPressed:
|
||||||
|
isFollowed ? () => updateFollowStatus() : () => ctr.bangumiAdd(),
|
||||||
|
icon: Icon(
|
||||||
|
ctr.isFollowed.value
|
||||||
|
? Icons.favorite
|
||||||
|
: Icons.favorite_border_rounded,
|
||||||
|
color: ctr.isFollowed.value
|
||||||
|
? colorScheme.primary
|
||||||
|
: colorScheme.outline,
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget morePanel(BuildContext context, BangumiIntroController ctr) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
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))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...ctr.followStatusList
|
||||||
|
.map(
|
||||||
|
(e) => ListTile(
|
||||||
|
onTap: () => ctr.updateBangumiStatus(e['status']),
|
||||||
|
selected: ctr.followStatus == e['status'],
|
||||||
|
title: Text(e['title']),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
|
|||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStorage.localCache;
|
||||||
late double sheetHeight;
|
late double sheetHeight;
|
||||||
|
|
||||||
class IntroDetail extends StatelessWidget {
|
class IntroDetail extends StatelessWidget {
|
||||||
|
|||||||
@ -76,9 +76,14 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Obx(
|
||||||
'最近追番',
|
() => 0 != _bangumidController.total.value
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
? Text(
|
||||||
|
'我的追番(${_bangumidController.total.value})',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.titleMedium,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -96,7 +101,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 268,
|
height: Get.size.width / 3 / 0.75 +
|
||||||
|
MediaQuery.textScalerOf(context).scale(50.0),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _futureBuilderFutureFollow,
|
future: _futureBuilderFutureFollow,
|
||||||
builder:
|
builder:
|
||||||
@ -117,7 +123,6 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return Container(
|
return Container(
|
||||||
width: Get.size.width / 3,
|
width: Get.size.width / 3,
|
||||||
height: 254,
|
|
||||||
margin: EdgeInsets.only(
|
margin: EdgeInsets.only(
|
||||||
left: StyleString.safeSpace,
|
left: StyleString.safeSpace,
|
||||||
right: index ==
|
right: index ==
|
||||||
@ -183,8 +188,10 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
return HttpError(
|
return HttpError(
|
||||||
errMsg: data['msg'],
|
errMsg: data['msg'],
|
||||||
fn: () {
|
fn: () {
|
||||||
|
setState(() {
|
||||||
_futureBuilderFuture =
|
_futureBuilderFuture =
|
||||||
_bangumidController.queryBangumiListFeed();
|
_bangumidController.queryBangumiListFeed();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -208,8 +215,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
crossAxisSpacing: StyleString.cardSpace,
|
crossAxisSpacing: StyleString.cardSpace,
|
||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
mainAxisExtent: Get.size.width / 3 / 0.75 +
|
||||||
MediaQuery.textScalerOf(context).scale(32.0),
|
MediaQuery.textScalerOf(context).scale(42.0),
|
||||||
),
|
),
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
|
|||||||
@ -5,9 +5,9 @@ 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/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
|
||||||
import '../../../common/pages_bottom_sheet.dart';
|
import '../../../common/pages_bottom_sheet.dart';
|
||||||
import '../../../models/common/video_episode_type.dart';
|
import '../../../models/common/video_episode_type.dart';
|
||||||
import '../introduction/controller.dart';
|
import '../introduction/controller.dart';
|
||||||
@ -37,14 +37,13 @@ class BangumiPanel extends StatefulWidget {
|
|||||||
class _BangumiPanelState extends State<BangumiPanel> {
|
class _BangumiPanelState extends State<BangumiPanel> {
|
||||||
late RxInt currentIndex = (-1).obs;
|
late RxInt currentIndex = (-1).obs;
|
||||||
final ScrollController listViewScrollCtr = ScrollController();
|
final ScrollController listViewScrollCtr = ScrollController();
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
dynamic userInfo;
|
UserInfoData? userInfo;
|
||||||
// 默认未开通
|
// 默认未开通
|
||||||
int vipStatus = 0;
|
int vipStatus = 0;
|
||||||
late int cid;
|
late int cid;
|
||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
late final VideoDetailController videoDetailCtr;
|
late final VideoDetailController videoDetailCtr;
|
||||||
final ItemScrollController itemScrollController = ItemScrollController();
|
|
||||||
late PersistentBottomSheetController? _bottomSheetController;
|
late PersistentBottomSheetController? _bottomSheetController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -65,7 +64,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
/// 获取大会员状态
|
/// 获取大会员状态
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo != null) {
|
if (userInfo != null) {
|
||||||
vipStatus = userInfo.vipStatus;
|
vipStatus = userInfo!.vipStatus!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,9 +85,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
item.aid,
|
item.aid,
|
||||||
item.cover,
|
item.cover,
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
if (_bottomSheetController != null) {
|
if (_bottomSheetController != null) {
|
||||||
_bottomSheetController?.close();
|
_bottomSheetController?.close();
|
||||||
}
|
}
|
||||||
|
} catch (_) {}
|
||||||
currentIndex.value = i;
|
currentIndex.value = i;
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
}
|
}
|
||||||
@ -149,7 +150,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
changeFucCall: changeFucCall,
|
changeFucCall: changeFucCall,
|
||||||
sheetHeight: widget.sheetHeight,
|
sheetHeight: widget.sheetHeight,
|
||||||
dataType: VideoEpidoesType.bangumiEpisode,
|
dataType: VideoEpidoesType.bangumiEpisode,
|
||||||
context: context,
|
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -174,11 +174,13 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
return Container(
|
return Container(
|
||||||
width: 150,
|
width: 150,
|
||||||
margin: const EdgeInsets.only(right: 10),
|
margin: const EdgeInsets.only(right: 10),
|
||||||
child: Material(
|
clipBehavior: Clip.antiAlias,
|
||||||
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(8),
|
||||||
clipBehavior: Clip.hardEdge,
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
onTap: () => changeFucCall(page, i),
|
onTap: () => changeFucCall(page, i),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
@ -229,7 +231,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
RoutePush.bangumiPush(
|
RoutePush.bangumiPush(
|
||||||
bangumiItem.seasonId,
|
bangumiItem.seasonId,
|
||||||
null,
|
null,
|
||||||
|
progressIndex: bangumiItem.progressIndex,
|
||||||
heroTag: heroTag,
|
heroTag: heroTag,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -37,7 +38,7 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
StyleString.imgRadius,
|
StyleString.imgRadius,
|
||||||
),
|
),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 0.65,
|
aspectRatio: 0.75,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
final double maxWidth = boxConstraints.maxWidth;
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
final double maxHeight = boxConstraints.maxHeight;
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
bool _isLoadingMore = false;
|
bool _isLoadingMore = false;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -55,14 +55,9 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
titleSpacing: 0,
|
|
||||||
centerTitle: false,
|
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'黑名单管理 - ${_blackListController.total.value}',
|
'黑名单管理 ${_blackListController.total.value == 0 ? '' : '- ${_blackListController.total.value}'}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -76,14 +71,20 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<BlackListItem> list = _blackListController.blackList;
|
List<BlackListItem> list = _blackListController.blackList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => list.length == 1
|
() => list.isEmpty
|
||||||
? const SizedBox()
|
? HttpError(
|
||||||
|
errMsg: '你没有拉黑任何人哦~_~',
|
||||||
|
fn: () => {},
|
||||||
|
isInSliver: false,
|
||||||
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () {},
|
onTap: () => Get.toNamed(
|
||||||
|
'/member?mid=${list[index].mid}',
|
||||||
|
arguments: {'face': list[index].face}),
|
||||||
leading: NetworkImgLayer(
|
leading: NetworkImgLayer(
|
||||||
width: 45,
|
width: 45,
|
||||||
height: 45,
|
height: 45,
|
||||||
@ -115,13 +116,10 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return CustomScrollView(
|
return HttpError(
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: data['msg'],
|
errMsg: data['msg'],
|
||||||
fn: () => _blackListController.queryBlacklist(),
|
fn: () => _blackListController.queryBlacklist(),
|
||||||
)
|
isInSliver: false,
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
late PlDanmakuController _plDanmakuController;
|
late PlDanmakuController _plDanmakuController;
|
||||||
DanmakuController? _controller;
|
DanmakuController? _controller;
|
||||||
// bool danmuPlayStatus = true;
|
// bool danmuPlayStatus = true;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
late bool enableShowDanmaku;
|
late bool enableShowDanmaku;
|
||||||
late List blockTypes;
|
late List blockTypes;
|
||||||
late double showArea;
|
late double showArea;
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import 'package:pilipala/models/common/dynamics_type.dart';
|
|||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/models/dynamics/up.dart';
|
import 'package:pilipala/models/dynamics/up.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/route_push.dart';
|
import 'package:pilipala/utils/route_push.dart';
|
||||||
@ -50,11 +51,11 @@ class DynamicsController extends GetxController {
|
|||||||
];
|
];
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
RxInt initialValue = 0.obs;
|
RxInt initialValue = 0.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
var userInfo;
|
UserInfoData? userInfo;
|
||||||
RxBool isLoadingDynamic = false.obs;
|
RxBool isLoadingDynamic = false.obs;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -146,20 +147,26 @@ 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 jumpUrl = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
if (url.contains('opus') || url.contains('read')) {
|
String url =
|
||||||
|
jumpUrl.startsWith('//') ? jumpUrl.split('//').last : jumpUrl;
|
||||||
|
if (jumpUrl.contains('opus') || jumpUrl.contains('read')) {
|
||||||
RegExp digitRegExp = RegExp(r'\d+');
|
RegExp digitRegExp = RegExp(r'\d+');
|
||||||
Iterable<Match> matches = digitRegExp.allMatches(url);
|
Iterable<Match> matches = digitRegExp.allMatches(jumpUrl);
|
||||||
String number = matches.first.group(0)!;
|
String number = matches.first.group(0)!;
|
||||||
if (url.contains('read')) {
|
if (jumpUrl.contains('read')) {
|
||||||
number = 'cv$number';
|
Get.toNamed('/read', parameters: {
|
||||||
}
|
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
|
||||||
'url': url.startsWith('//') ? url.split('//').last : url,
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'id': number,
|
'id': number,
|
||||||
'dynamicType': url.split('//').last.split('/')[1]
|
'articleType': url.split('/')[1]
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
Get.toNamed('/opus', parameters: {
|
||||||
|
'title': title,
|
||||||
|
'id': number,
|
||||||
|
'articleType': 'opus'
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
@ -240,7 +247,7 @@ class DynamicsController extends GetxController {
|
|||||||
}
|
}
|
||||||
upData.value.upList!.insertAll(0, [
|
upData.value.upList!.insertAll(0, [
|
||||||
UpItem(face: '', uname: '全部动态', mid: -1),
|
UpItem(face: '', uname: '全部动态', mid: -1),
|
||||||
UpItem(face: userInfo.face, uname: '我', mid: userInfo.mid),
|
UpItem(face: userInfo!.face, uname: '我', mid: userInfo!.mid),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class DynamicDetailController extends GetxController {
|
|||||||
int? type;
|
int? type;
|
||||||
dynamic item;
|
dynamic item;
|
||||||
int? floor;
|
int? floor;
|
||||||
int currentPage = 0;
|
String nextOffset = "";
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
|
||||||
@ -24,8 +24,9 @@ class DynamicDetailController extends GetxController {
|
|||||||
ReplySortType _sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
RxString sortTypeLabel = ReplySortType.time.labels.obs;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStorage.setting;
|
||||||
RxInt replyReqCode = 200.obs;
|
RxInt replyReqCode = 200.obs;
|
||||||
|
bool isEnd = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -36,38 +37,42 @@ class DynamicDetailController extends GetxController {
|
|||||||
acount.value =
|
acount.value =
|
||||||
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
|
||||||
}
|
}
|
||||||
int deaultReplySortIndex =
|
int defaultReplySortIndex =
|
||||||
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
|
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
|
||||||
if (deaultReplySortIndex == 2) {
|
if (defaultReplySortIndex == 2) {
|
||||||
setting.put(SettingBoxKey.replySortType, 0);
|
setting.put(SettingBoxKey.replySortType, 0);
|
||||||
deaultReplySortIndex = 0;
|
defaultReplySortIndex = 0;
|
||||||
}
|
}
|
||||||
_sortType = ReplySortType.values[deaultReplySortIndex];
|
_sortType = ReplySortType.values[defaultReplySortIndex];
|
||||||
sortTypeTitle.value = _sortType.titles;
|
sortTypeTitle.value = _sortType.titles;
|
||||||
sortTypeLabel.value = _sortType.labels;
|
sortTypeLabel.value = _sortType.labels;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryReplyList({reqType = 'init'}) async {
|
Future queryReplyList({reqType = 'init'}) async {
|
||||||
|
if (isLoadingMore || noMore.value == '没有更多了' || isEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoadingMore = true;
|
||||||
if (reqType == 'init') {
|
if (reqType == 'init') {
|
||||||
currentPage = 0;
|
nextOffset = '';
|
||||||
|
noMore.value = '';
|
||||||
}
|
}
|
||||||
var res = await ReplyHttp.replyList(
|
var res = await ReplyHttp.replyList(
|
||||||
oid: oid!,
|
oid: oid!,
|
||||||
pageNum: currentPage + 1,
|
nextOffset: nextOffset,
|
||||||
type: type!,
|
type: type!,
|
||||||
sort: _sortType.index,
|
sort: _sortType.index,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
List<ReplyItemModel> replies = res['data'].replies;
|
List<ReplyItemModel> replies = res['data'].replies;
|
||||||
acount.value = res['data'].page.acount;
|
isEnd = res['data'].cursor.isEnd ?? false;
|
||||||
|
acount.value = res['data'].cursor.allCount;
|
||||||
|
nextOffset = res['data'].cursor.paginationReply.nextOffset ?? "";
|
||||||
if (replies.isNotEmpty) {
|
if (replies.isNotEmpty) {
|
||||||
currentPage++;
|
noMore.value = isEnd ? '没有更多了' : '加载中...';
|
||||||
noMore.value = '加载中...';
|
|
||||||
if (replies.length < 20) {
|
|
||||||
noMore.value = '没有更多了';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
noMore.value =
|
||||||
|
replyList.isEmpty && nextOffset == "" ? '还没有评论' : '没有更多了';
|
||||||
}
|
}
|
||||||
if (reqType == 'init') {
|
if (reqType == 'init') {
|
||||||
// 添加置顶回复
|
// 添加置顶回复
|
||||||
@ -113,4 +118,20 @@ class DynamicDetailController extends GetxController {
|
|||||||
var res = await HtmlHttp.reqHtml(id, 'opus');
|
var res = await HtmlHttp.reqHtml(id, 'opus');
|
||||||
oid = res['commentId'];
|
oid = res['commentId'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上拉加载
|
||||||
|
Future onLoad() async {
|
||||||
|
queryReplyList(reqType: 'onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future removeReply(int? rpid, int? frpid) async {
|
||||||
|
// 移除一楼评论
|
||||||
|
if (rpid != null) {
|
||||||
|
replyList.removeWhere((item) {
|
||||||
|
return item.rpid == rpid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO 移除二楼评论
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,8 +31,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late DynamicDetailController _dynamicDetailController;
|
late DynamicDetailController _dynamicDetailController;
|
||||||
late AnimationController fabAnimationCtr;
|
late AnimationController fabAnimationCtr;
|
||||||
Future? _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
late StreamController<bool> titleStreamC; // appBar title
|
late StreamController<bool> titleStreamC =
|
||||||
|
StreamController<bool>.broadcast(); // appBar title
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
bool _visibleTitle = false;
|
bool _visibleTitle = false;
|
||||||
String? action;
|
String? action;
|
||||||
@ -48,7 +49,6 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
super.initState();
|
super.initState();
|
||||||
// floor 1原创 2转发
|
// floor 1原创 2转发
|
||||||
init();
|
init();
|
||||||
titleStreamC = StreamController<bool>();
|
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
_visibleTitle = true;
|
_visibleTitle = true;
|
||||||
titleStreamC.add(true);
|
titleStreamC.add(true);
|
||||||
@ -111,14 +111,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
int rpid = replyItem.rpid!;
|
int rpid = replyItem.rpid!;
|
||||||
Get.to(
|
Get.to(
|
||||||
() => Scaffold(
|
() => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(title: const Text('评论详情')),
|
||||||
titleSpacing: 0,
|
|
||||||
centerTitle: false,
|
|
||||||
title: Text(
|
|
||||||
'评论详情',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
body: VideoReplyReplyPanel(
|
body: VideoReplyReplyPanel(
|
||||||
oid: oid,
|
oid: oid,
|
||||||
rpid: rpid,
|
rpid: rpid,
|
||||||
@ -140,7 +133,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 300) {
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
||||||
_dynamicDetailController.queryReplyList(reqType: 'onLoad');
|
_dynamicDetailController.onLoad();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,10 +185,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 1,
|
scrolledUnderElevation: 1,
|
||||||
centerTitle: false,
|
|
||||||
titleSpacing: 0,
|
|
||||||
title: StreamBuilder(
|
title: StreamBuilder(
|
||||||
stream: titleStreamC.stream,
|
stream: titleStreamC.stream,
|
||||||
initialData: false,
|
initialData: false,
|
||||||
@ -278,8 +268,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map data = snapshot.data as Map;
|
Map? data = snapshot.data;
|
||||||
if (snapshot.data['status']) {
|
if (data != null && snapshot.data['status']) {
|
||||||
RxList<ReplyItemModel> replyList =
|
RxList<ReplyItemModel> replyList =
|
||||||
_dynamicDetailController.replyList;
|
_dynamicDetailController.replyList;
|
||||||
// 请求成功
|
// 请求成功
|
||||||
@ -335,6 +325,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
.replies!
|
.replies!
|
||||||
.add(replyItem);
|
.add(replyItem);
|
||||||
},
|
},
|
||||||
|
onDelete:
|
||||||
|
_dynamicDetailController.removeReply,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -345,8 +337,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
} else {
|
} else {
|
||||||
// 请求错误
|
// 请求错误
|
||||||
return HttpError(
|
return HttpError(
|
||||||
errMsg: data['msg'],
|
errMsg: data?['msg'] ?? '请求异常',
|
||||||
fn: () => setState(() {}),
|
fn: () => setState(() {
|
||||||
|
_futureBuilderFuture =
|
||||||
|
_dynamicDetailController.queryReplyList();
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
310
lib/pages/dynamics/forward/index.dart
Normal file
310
lib/pages/dynamics/forward/index.dart
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
|
||||||
|
import '../widgets/rich_node_panel.dart';
|
||||||
|
|
||||||
|
class DynamicForwardPage extends StatefulWidget {
|
||||||
|
const DynamicForwardPage({
|
||||||
|
super.key,
|
||||||
|
this.item,
|
||||||
|
this.mid,
|
||||||
|
this.cb,
|
||||||
|
});
|
||||||
|
|
||||||
|
final DynamicItemModel? item;
|
||||||
|
final int? mid;
|
||||||
|
final Function()? cb;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DynamicForwardPage> createState() => _DynamicForwardPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DynamicForwardPageState extends State<DynamicForwardPage> {
|
||||||
|
final TextEditingController _inputController = TextEditingController();
|
||||||
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
RxBool isExpand = false.obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dynamicForward(String type) async {
|
||||||
|
String dynamicId = widget.item!.idStr!;
|
||||||
|
var res = await DynamicsHttp.dynamicCreate(
|
||||||
|
dynIdStr: dynamicId,
|
||||||
|
mid: widget.mid!,
|
||||||
|
rawText: type == 'quickForward' ? '' : _inputController.text,
|
||||||
|
scene: 4,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('转发成功');
|
||||||
|
widget.cb?.call();
|
||||||
|
_onClose();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onClose() async {
|
||||||
|
_focusNode.unfocus();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 120));
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Obx(
|
||||||
|
() {
|
||||||
|
final EdgeInsetsGeometry padding = EdgeInsets.fromLTRB(
|
||||||
|
isExpand.value ? 10 : 16,
|
||||||
|
10,
|
||||||
|
isExpand.value ? 12 : 12,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
return AnimatedSize(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
onEnd: () => isExpand.value ? _focusNode.requestFocus() : null,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: isExpand.value ? MainAxisSize.max : MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: isExpand.value ? _topBarExpand() : _topBar(),
|
||||||
|
),
|
||||||
|
isExpand.value ? _contentExpand() : _content(),
|
||||||
|
dynamicPreview(),
|
||||||
|
if (!isExpand.value) ..._bottomBar(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转发动态预览
|
||||||
|
Widget dynamicPreview() {
|
||||||
|
ItemModulesModel? modules = widget.item!.modules;
|
||||||
|
final String type = widget.item!.type!;
|
||||||
|
String? cover = modules?.moduleAuthor?.face;
|
||||||
|
switch (type) {
|
||||||
|
/// 图文动态
|
||||||
|
case 'DYNAMIC_TYPE_DRAW':
|
||||||
|
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
|
||||||
|
|
||||||
|
/// 投稿
|
||||||
|
case 'DYNAMIC_TYPE_AV':
|
||||||
|
cover = modules?.moduleDynamic?.major?.archive?.cover;
|
||||||
|
|
||||||
|
/// 转发的动态
|
||||||
|
case 'DYNAMIC_TYPE_FORWARD':
|
||||||
|
String forwardType = widget.item!.orig!.type!;
|
||||||
|
switch (forwardType) {
|
||||||
|
/// 图文动态
|
||||||
|
case 'DYNAMIC_TYPE_DRAW':
|
||||||
|
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
|
||||||
|
|
||||||
|
/// 投稿
|
||||||
|
case 'DYNAMIC_TYPE_AV':
|
||||||
|
cover = modules?.moduleDynamic?.major?.archive?.cover;
|
||||||
|
|
||||||
|
/// 番剧
|
||||||
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
|
cover = modules?.moduleDynamic?.major?.pgc?.cover;
|
||||||
|
// 专栏文章
|
||||||
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
|
// 番剧
|
||||||
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
|
// 纯文字动态
|
||||||
|
case 'DYNAMIC_TYPE_WORD':
|
||||||
|
// 直播
|
||||||
|
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||||
|
// 合集查看
|
||||||
|
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||||
|
cover = '';
|
||||||
|
|
||||||
|
default:
|
||||||
|
cover = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 专栏文章
|
||||||
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
|
// 番剧
|
||||||
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
|
// 纯文字动态
|
||||||
|
case 'DYNAMIC_TYPE_WORD':
|
||||||
|
// 直播
|
||||||
|
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
||||||
|
// 合集查看
|
||||||
|
case 'DYNAMIC_TYPE_UGC_SEASON':
|
||||||
|
// 番剧查看
|
||||||
|
case 'DYNAMIC_TYPE_PGC_UNION':
|
||||||
|
cover = '';
|
||||||
|
default:
|
||||||
|
cover = '';
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 98,
|
||||||
|
margin: const EdgeInsets.fromLTRB(12, 0, 12, 14),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
border: Border(
|
||||||
|
left: BorderSide(
|
||||||
|
width: 4,
|
||||||
|
color: Theme.of(context).colorScheme.primary.withOpacity(0.8)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'@${widget.item!.modules!.moduleAuthor!.name}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
src: cover ?? '',
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
radius: 6,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Text.rich(
|
||||||
|
richNode(widget.item, context),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Text(data)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未展开时的顶部
|
||||||
|
Widget _topBar() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'转发动态',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => dynamicForward('quickForward'),
|
||||||
|
child: const Text('快速转发'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开时的顶部
|
||||||
|
Widget _topBarExpand() {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _onClose,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => dynamicForward('forward'),
|
||||||
|
child: const Text('转发'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
'转发动态',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未展开时的底部
|
||||||
|
List<Widget> _bottomBar() {
|
||||||
|
return [
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: MediaQuery.of(context).padding.bottom,
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未展开时的内容区
|
||||||
|
Widget _content() {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
isExpand.value = true;
|
||||||
|
},
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 10, 10, 16),
|
||||||
|
child: Text(
|
||||||
|
'说点什么吧',
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 展开时的内容区
|
||||||
|
Widget _contentExpand() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 10, 16, 0),
|
||||||
|
child: TextField(
|
||||||
|
maxLines: null,
|
||||||
|
minLines: 3,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
controller: _inputController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: '说点什么吧',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,7 +9,6 @@ import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
|||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/no_data.dart';
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/main_stream.dart';
|
import 'package:pilipala/utils/main_stream.dart';
|
||||||
import 'package:pilipala/utils/route_push.dart';
|
import 'package:pilipala/utils/route_push.dart';
|
||||||
@ -18,7 +17,6 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
import '../mine/controller.dart';
|
import '../mine/controller.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/dynamic_panel.dart';
|
import 'widgets/dynamic_panel.dart';
|
||||||
import 'up_dynamic/route_panel.dart';
|
|
||||||
import 'widgets/up_panel.dart';
|
import 'widgets/up_panel.dart';
|
||||||
|
|
||||||
class DynamicsPage extends StatefulWidget {
|
class DynamicsPage extends StatefulWidget {
|
||||||
@ -34,7 +32,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
final MineController mineController = Get.put(MineController());
|
final MineController mineController = Get.put(MineController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
late Future _futureBuilderFutureUp;
|
late Future _futureBuilderFutureUp;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -80,8 +78,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
title: SizedBox(
|
title: SizedBox(
|
||||||
height: 34,
|
height: 34,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@ -90,8 +86,13 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Obx(() {
|
Obx(() {
|
||||||
if (_dynamicsController.mid.value != -1 &&
|
final mid = _dynamicsController.mid.value;
|
||||||
_dynamicsController.upInfo.value.uname != null) {
|
final uname = _dynamicsController.upInfo.value.uname;
|
||||||
|
|
||||||
|
if (mid == -1 || uname == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 36,
|
height: 36,
|
||||||
child: AnimatedSwitcher(
|
child: AnimatedSwitcher(
|
||||||
@ -102,20 +103,17 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
scale: animation, child: child);
|
scale: animation, child: child);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_dynamicsController.upInfo.value.uname!}的动态',
|
'$uname的动态',
|
||||||
key: ValueKey<String>(
|
key: ValueKey<String>(uname),
|
||||||
_dynamicsController.upInfo.value.uname!),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelLarge!
|
.labelLarge!
|
||||||
.fontSize,
|
.fontSize,
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
Obx(
|
Obx(
|
||||||
() => _dynamicsController.userLogin.value
|
() => _dynamicsController.userLogin.value
|
||||||
@ -206,16 +204,7 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
return Obx(
|
return Obx(
|
||||||
() => UpPanel(
|
() => UpPanel(
|
||||||
upData: _dynamicsController.upData.value,
|
upData: _dynamicsController.upData.value,
|
||||||
onClickUpCb: (data) {
|
dynamicsController: _dynamicsController,
|
||||||
// _dynamicsController.onTapUp(data);
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
PlPopupRoute(
|
|
||||||
child: OverlayPanel(
|
|
||||||
ctr: _dynamicsController, upInfo: data),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,20 +3,19 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/http/dynamics.dart';
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:status_bar_control/status_bar_control.dart';
|
import 'package:status_bar_control/status_bar_control.dart';
|
||||||
import 'rich_node_panel.dart';
|
import '../forward/index.dart';
|
||||||
|
|
||||||
class ActionPanel extends StatefulWidget {
|
class ActionPanel extends StatefulWidget {
|
||||||
const ActionPanel({
|
const ActionPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
});
|
});
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
|
||||||
final DynamicItemModel item;
|
final DynamicItemModel item;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -32,9 +31,6 @@ class _ActionPanelState extends State<ActionPanel>
|
|||||||
RxDouble height = 0.0.obs;
|
RxDouble height = 0.0.obs;
|
||||||
RxBool isExpand = false.obs;
|
RxBool isExpand = false.obs;
|
||||||
late double statusHeight;
|
late double statusHeight;
|
||||||
TextEditingController _inputController = TextEditingController();
|
|
||||||
FocusNode myFocusNode = FocusNode();
|
|
||||||
String _inputText = '';
|
|
||||||
|
|
||||||
void Function()? handleState(Future Function() action) {
|
void Function()? handleState(Future Function() action) {
|
||||||
return isProcessing
|
return isProcessing
|
||||||
@ -60,7 +56,7 @@ class _ActionPanelState extends State<ActionPanel>
|
|||||||
// 动态点赞
|
// 动态点赞
|
||||||
Future onLikeDynamic() async {
|
Future onLikeDynamic() async {
|
||||||
feedBack();
|
feedBack();
|
||||||
var item = widget.item!;
|
var item = widget.item;
|
||||||
String dynamicId = item.idStr!;
|
String dynamicId = item.idStr!;
|
||||||
// 1 已点赞 2 不喜欢 0 未操作
|
// 1 已点赞 2 不喜欢 0 未操作
|
||||||
Like like = item.modules!.moduleStat!.like!;
|
Like like = item.modules!.moduleStat!.like!;
|
||||||
@ -87,300 +83,38 @@ class _ActionPanelState extends State<ActionPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转发动态预览
|
|
||||||
Widget dynamicPreview() {
|
|
||||||
ItemModulesModel? modules = widget.item.modules;
|
|
||||||
final String type = widget.item.type!;
|
|
||||||
String? cover = modules?.moduleAuthor?.face;
|
|
||||||
switch (type) {
|
|
||||||
/// 图文动态
|
|
||||||
case 'DYNAMIC_TYPE_DRAW':
|
|
||||||
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
|
|
||||||
|
|
||||||
/// 投稿
|
|
||||||
case 'DYNAMIC_TYPE_AV':
|
|
||||||
cover = modules?.moduleDynamic?.major?.archive?.cover;
|
|
||||||
|
|
||||||
/// 转发的动态
|
|
||||||
case 'DYNAMIC_TYPE_FORWARD':
|
|
||||||
String forwardType = widget.item.orig!.type!;
|
|
||||||
switch (forwardType) {
|
|
||||||
/// 图文动态
|
|
||||||
case 'DYNAMIC_TYPE_DRAW':
|
|
||||||
cover = modules?.moduleDynamic?.major?.opus?.pics?.first.url;
|
|
||||||
|
|
||||||
/// 投稿
|
|
||||||
case 'DYNAMIC_TYPE_AV':
|
|
||||||
cover = modules?.moduleDynamic?.major?.archive?.cover;
|
|
||||||
|
|
||||||
/// 专栏文章
|
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 番剧
|
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 纯文字动态
|
|
||||||
case 'DYNAMIC_TYPE_WORD':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 直播
|
|
||||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 合集查看
|
|
||||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 番剧
|
|
||||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
|
||||||
cover = modules?.moduleDynamic?.major?.pgc?.cover;
|
|
||||||
|
|
||||||
default:
|
|
||||||
cover = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 专栏文章
|
|
||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 番剧
|
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 纯文字动态
|
|
||||||
case 'DYNAMIC_TYPE_WORD':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 直播
|
|
||||||
case 'DYNAMIC_TYPE_LIVE_RCMD':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 合集查看
|
|
||||||
case 'DYNAMIC_TYPE_UGC_SEASON':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
/// 番剧查看
|
|
||||||
case 'DYNAMIC_TYPE_PGC_UNION':
|
|
||||||
cover = '';
|
|
||||||
|
|
||||||
default:
|
|
||||||
cover = '';
|
|
||||||
}
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 95,
|
|
||||||
margin: const EdgeInsets.fromLTRB(12, 0, 12, 14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondaryContainer.withOpacity(0.4),
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
border: Border(
|
|
||||||
left: BorderSide(
|
|
||||||
width: 4,
|
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.8)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'@${widget.item.modules!.moduleAuthor!.name}',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
NetworkImgLayer(
|
|
||||||
src: cover ?? '',
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
type: 'emote',
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Expanded(
|
|
||||||
child: Text.rich(
|
|
||||||
style: const TextStyle(height: 0),
|
|
||||||
richNode(widget.item, context),
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// Text(data)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态转发
|
// 动态转发
|
||||||
void forwardHandler() async {
|
void forwardHandler() async {
|
||||||
|
final userInfo = _dynamicsController.userInfo;
|
||||||
|
if (userInfo == null) {
|
||||||
|
SmartDialog.showToast('请先登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int mid = userInfo.mid!;
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
enableDrag: false,
|
enableDrag: true,
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return Obx(
|
return DynamicForwardPage(
|
||||||
() => AnimatedContainer(
|
item: widget.item,
|
||||||
duration: Durations.medium1,
|
mid: mid,
|
||||||
onEnd: () async {
|
cb: () => setState(() {
|
||||||
if (isExpand.value) {
|
stat.forward!.count =
|
||||||
await Future.delayed(const Duration(milliseconds: 80));
|
(int.parse(stat.forward!.count ?? '0') + 1).toString();
|
||||||
myFocusNode.requestFocus();
|
}),
|
||||||
}
|
|
||||||
},
|
|
||||||
height: height.value + MediaQuery.of(context).padding.bottom,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: Durations.medium1,
|
|
||||||
height: isExpand.value ? statusHeight : 0,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.fromLTRB(
|
|
||||||
isExpand.value ? 10 : 16,
|
|
||||||
10,
|
|
||||||
isExpand.value ? 14 : 12,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
if (isExpand.value) ...[
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => togglePanelState(false),
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'转发动态',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleMedium!
|
|
||||||
.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
)
|
|
||||||
] else ...[
|
|
||||||
const Text(
|
|
||||||
'转发动态',
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
isExpand.value
|
|
||||||
? FilledButton(
|
|
||||||
onPressed: () => dynamicForward('forward'),
|
|
||||||
child: const Text('转发'),
|
|
||||||
)
|
|
||||||
: TextButton(
|
|
||||||
onPressed: () {},
|
|
||||||
child: const Text('立即转发'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!isExpand.value) ...[
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => togglePanelState(true),
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
child: Container(
|
|
||||||
width: double.infinity,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 10, 14),
|
|
||||||
child: Text(
|
|
||||||
'说点什么吧',
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
] else ...[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 10, 16, 0),
|
|
||||||
child: TextField(
|
|
||||||
maxLines: 5,
|
|
||||||
focusNode: myFocusNode,
|
|
||||||
controller: _inputController,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_inputText = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: '说点什么吧',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
dynamicPreview(),
|
|
||||||
if (!isExpand.value) ...[
|
|
||||||
const Divider(thickness: 0.1, height: 1),
|
|
||||||
ListTile(
|
|
||||||
onTap: () => Get.back(),
|
|
||||||
minLeadingWidth: 0,
|
|
||||||
dense: true,
|
|
||||||
title: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePanelState(status) {
|
|
||||||
if (!status) {
|
|
||||||
Get.back();
|
|
||||||
height.value = defaultHeight;
|
|
||||||
_inputText = '';
|
|
||||||
_inputController.clear();
|
|
||||||
} else {
|
|
||||||
height.value = Get.size.height;
|
|
||||||
}
|
|
||||||
isExpand.value = !(isExpand.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
dynamicForward(String type) async {
|
|
||||||
String dynamicId = widget.item.idStr!;
|
|
||||||
var res = await DynamicsHttp.dynamicCreate(
|
|
||||||
dynIdStr: dynamicId,
|
|
||||||
mid: _dynamicsController.userInfo.mid,
|
|
||||||
rawText: _inputText,
|
|
||||||
scene: 4,
|
|
||||||
);
|
|
||||||
if (res['status']) {
|
|
||||||
SmartDialog.showToast(type == 'forward' ? '转发成功' : '发布成功');
|
|
||||||
togglePanelState(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
myFocusNode.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var color = Theme.of(context).colorScheme.outline;
|
var color = Theme.of(context).colorScheme.outline;
|
||||||
var primary = Theme.of(context).colorScheme.primary;
|
var primary = Theme.of(context).colorScheme.primary;
|
||||||
height.value = defaultHeight;
|
height.value = defaultHeight;
|
||||||
print('height.value: ${height.value}');
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
// 内容
|
// 内容
|
||||||
import 'package:cached_network_image/cached_network_image.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/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
@ -166,31 +165,6 @@ class _ContentState extends State<Content> {
|
|||||||
builder: (BuildContext context) => InteractiveviewerGallery(
|
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||||
sources: picList,
|
sources: picList,
|
||||||
initIndex: initIndex,
|
initIndex: initIndex,
|
||||||
itemBuilder: (
|
|
||||||
BuildContext context,
|
|
||||||
int index,
|
|
||||||
bool isFocus,
|
|
||||||
bool enablePageView,
|
|
||||||
) {
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
if (enablePageView) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Center(
|
|
||||||
child: Hero(
|
|
||||||
tag: picList[index],
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
fadeInDuration: const Duration(milliseconds: 0),
|
|
||||||
imageUrl: picList[index],
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onPageChanged: (int pageIndex) {},
|
onPageChanged: (int pageIndex) {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -66,7 +66,15 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
},
|
},
|
||||||
child: LayoutBuilder(builder: (context, box) {
|
child: LayoutBuilder(builder: (context, box) {
|
||||||
double width = box.maxWidth;
|
double width = box.maxWidth;
|
||||||
return Stack(
|
return Container(
|
||||||
|
margin: floor == 1
|
||||||
|
? const EdgeInsets.only(
|
||||||
|
left: StyleString.safeSpace, right: StyleString.safeSpace)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(StyleString.imgRadius)),
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
tag: liveRcmd.roomId.toString(),
|
tag: liveRcmd.roomId.toString(),
|
||||||
@ -79,16 +87,16 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
text: watchedShow['text_large'],
|
text: watchedShow['text_large'],
|
||||||
top: 6,
|
top: 8.0,
|
||||||
right: 56,
|
right: 62.0,
|
||||||
bottom: null,
|
bottom: null,
|
||||||
left: null,
|
left: null,
|
||||||
type: 'gray',
|
type: 'gray',
|
||||||
),
|
),
|
||||||
PBadge(
|
PBadge(
|
||||||
text: liveStatus == 1 ? '直播中' : '直播结束',
|
text: liveStatus == 1 ? '直播中' : '直播结束',
|
||||||
top: 6,
|
top: 8.0,
|
||||||
right: 6,
|
right: 10.0,
|
||||||
bottom: null,
|
bottom: null,
|
||||||
left: null,
|
left: null,
|
||||||
),
|
),
|
||||||
@ -136,6 +144,7 @@ Widget liveRcmdPanel(item, context, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.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/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
@ -12,31 +11,6 @@ void onPreviewImg(currentUrl, picList, initIndex, context) {
|
|||||||
builder: (BuildContext context) => InteractiveviewerGallery(
|
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||||
sources: picList,
|
sources: picList,
|
||||||
initIndex: initIndex,
|
initIndex: initIndex,
|
||||||
itemBuilder: (
|
|
||||||
BuildContext context,
|
|
||||||
int index,
|
|
||||||
bool isFocus,
|
|
||||||
bool enablePageView,
|
|
||||||
) {
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.opaque,
|
|
||||||
onTap: () {
|
|
||||||
if (enablePageView) {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Center(
|
|
||||||
child: Hero(
|
|
||||||
tag: picList[index],
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
fadeInDuration: const Duration(milliseconds: 0),
|
|
||||||
imageUrl: picList[index],
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
onPageChanged: (int pageIndex) {},
|
onPageChanged: (int pageIndex) {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -4,17 +4,22 @@ 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/up.dart';
|
import 'package:pilipala/models/dynamics/up.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/global_data_cache.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import '../controller.dart';
|
||||||
|
import '../up_dynamic/route_panel.dart';
|
||||||
|
|
||||||
class UpPanel extends StatefulWidget {
|
class UpPanel extends StatefulWidget {
|
||||||
final FollowUpModel upData;
|
final FollowUpModel upData;
|
||||||
final Function? onClickUpCb;
|
final DynamicsController dynamicsController;
|
||||||
|
|
||||||
const UpPanel({
|
const UpPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.upData,
|
required this.upData,
|
||||||
this.onClickUpCb,
|
required this.dynamicsController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -23,12 +28,13 @@ class UpPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _UpPanelState extends State<UpPanel> {
|
class _UpPanelState extends State<UpPanel> {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
int currentMid = -1;
|
RxInt currentMid = (-1).obs;
|
||||||
late double contentWidth = 56;
|
late double contentWidth = 56;
|
||||||
List<UpItem> upList = [];
|
List<UpItem> upList = [];
|
||||||
List<LiveUserItem> liveList = [];
|
List<LiveUserItem> liveList = [];
|
||||||
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
|
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
|
||||||
late MyInfo userInfo;
|
late MyInfo userInfo;
|
||||||
|
RxBool showLiveUser = false.obs;
|
||||||
|
|
||||||
void listFormat() {
|
void listFormat() {
|
||||||
userInfo = widget.upData.myInfo!;
|
userInfo = widget.upData.myInfo!;
|
||||||
@ -37,26 +43,44 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onClickUp(data, i) {
|
void onClickUp(data, i) {
|
||||||
currentMid = data.mid;
|
currentMid.value = data.mid;
|
||||||
widget.onClickUpCb?.call(data);
|
Navigator.push(
|
||||||
// int liveLen = liveList.length;
|
context,
|
||||||
// int upLen = upList.length;
|
PlPopupRoute(
|
||||||
// double itemWidth = contentWidth + itemPadding.horizontal;
|
child: OverlayPanel(
|
||||||
// double screenWidth = MediaQuery.sizeOf(context).width;
|
ctr: widget.dynamicsController,
|
||||||
// double moveDistance = 0.0;
|
upInfo: data,
|
||||||
// if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
),
|
||||||
// } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
),
|
||||||
// moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
).then((value) => {currentMid.value = -1});
|
||||||
// } else {
|
}
|
||||||
// moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
|
||||||
// }
|
void onClickUpAni(data, i) {
|
||||||
// data.hasUpdate = false;
|
final screenWidth = MediaQuery.sizeOf(context).width;
|
||||||
// scrollController.animateTo(
|
final itemWidth = contentWidth + itemPadding.horizontal;
|
||||||
// moveDistance,
|
final liveLen = liveList.length;
|
||||||
// duration: const Duration(milliseconds: 200),
|
final upLen = upList.length;
|
||||||
// curve: Curves.linear,
|
|
||||||
// );
|
currentMid.value = data.mid;
|
||||||
// setState(() {});
|
widget.dynamicsController.onTapUp(data);
|
||||||
|
|
||||||
|
double moveDistance = 0.0;
|
||||||
|
final totalItemsWidth = itemWidth * (upLen + liveLen);
|
||||||
|
|
||||||
|
if (totalItemsWidth > screenWidth) {
|
||||||
|
if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||||
|
moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||||
|
} else {
|
||||||
|
moveDistance = totalItemsWidth + 46 - screenWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.hasUpdate = false;
|
||||||
|
scrollController.animateTo(
|
||||||
|
moveDistance,
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,21 +132,70 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
if (liveList.isNotEmpty) ...[
|
if (liveList.isNotEmpty) ...[
|
||||||
for (int i = 0; i < liveList.length; i++) ...[
|
Obx(
|
||||||
upItemBuild(liveList[i], i)
|
() => AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
transitionBuilder: (Widget child,
|
||||||
|
Animation<double> animation) {
|
||||||
|
return FadeTransition(
|
||||||
|
opacity: animation, child: child);
|
||||||
|
},
|
||||||
|
child: showLiveUser.value
|
||||||
|
? Row(
|
||||||
|
key: ValueKey<bool>(showLiveUser.value),
|
||||||
|
children: [
|
||||||
|
for (int i = 0;
|
||||||
|
i < liveList.length;
|
||||||
|
i++)
|
||||||
|
UpItemWidget(
|
||||||
|
data: liveList[i],
|
||||||
|
index: i,
|
||||||
|
currentMid: currentMid,
|
||||||
|
onClickUp: onClickUp,
|
||||||
|
onClickUpAni: onClickUpAni,
|
||||||
|
itemPadding: itemPadding,
|
||||||
|
contentWidth: contentWidth,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
VerticalDivider(
|
)
|
||||||
indent: 20,
|
: SizedBox.shrink(
|
||||||
endIndent: 40,
|
key: ValueKey<bool>(showLiveUser.value),
|
||||||
width: 26,
|
),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.colorScheme
|
),
|
||||||
.primary
|
Obx(
|
||||||
.withOpacity(0.5),
|
() => IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showLiveUser.value = !showLiveUser.value;
|
||||||
|
},
|
||||||
|
icon: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
transitionBuilder: (Widget child,
|
||||||
|
Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation, child: child);
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
!showLiveUser.value
|
||||||
|
? Icons.arrow_forward_ios_rounded
|
||||||
|
: Icons.arrow_back_ios_rounded,
|
||||||
|
key: ValueKey<bool>(showLiveUser.value),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
for (int i = 0; i < upList.length; i++) ...[
|
for (int i = 0; i < upList.length; i++) ...[
|
||||||
upItemBuild(upList[i], i)
|
UpItemWidget(
|
||||||
|
data: upList[i],
|
||||||
|
index: i,
|
||||||
|
currentMid: currentMid,
|
||||||
|
onClickUp: onClickUp,
|
||||||
|
onClickUpAni: onClickUpAni,
|
||||||
|
itemPadding: itemPadding,
|
||||||
|
contentWidth: contentWidth,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
@ -142,100 +215,6 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget upItemBuild(data, i) {
|
|
||||||
bool isCurrent = currentMid == data.mid || currentMid == -1;
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
feedBack();
|
|
||||||
if (data.type == 'up') {
|
|
||||||
EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
|
|
||||||
() {
|
|
||||||
onClickUp(data, i);
|
|
||||||
});
|
|
||||||
} else if (data.type == 'live') {
|
|
||||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
|
||||||
'title': data.title,
|
|
||||||
'uname': data.uname,
|
|
||||||
'face': data.face,
|
|
||||||
'roomid': data.roomId,
|
|
||||||
});
|
|
||||||
Get.toNamed(
|
|
||||||
'/liveRoom?roomid=${data.roomId}',
|
|
||||||
arguments: {'liveItem': liveItem},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
feedBack();
|
|
||||||
if (data.mid == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String heroTag = Utils.makeHeroTag(data.mid);
|
|
||||||
Get.toNamed('/member?mid=${data.mid}',
|
|
||||||
arguments: {'face': data.face, 'heroTag': heroTag});
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: itemPadding,
|
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: isCurrent ? 1 : 0.3,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Badge(
|
|
||||||
smallSize: 8,
|
|
||||||
label: data.type == 'live' ? const Text('Live') : null,
|
|
||||||
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
|
||||||
alignment: data.type == 'live'
|
|
||||||
? AlignmentDirectional.topCenter
|
|
||||||
: AlignmentDirectional.topEnd,
|
|
||||||
padding: const EdgeInsets.only(left: 6, right: 6),
|
|
||||||
isLabelVisible: data.type == 'live' ||
|
|
||||||
(data.type == 'up' && (data.hasUpdate ?? false)),
|
|
||||||
backgroundColor: data.type == 'live'
|
|
||||||
? Theme.of(context).colorScheme.secondaryContainer
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
child: data.face != ''
|
|
||||||
? NetworkImgLayer(
|
|
||||||
width: 50,
|
|
||||||
height: 50,
|
|
||||||
src: data.face,
|
|
||||||
type: 'avatar',
|
|
||||||
)
|
|
||||||
: const CircleAvatar(
|
|
||||||
radius: 25,
|
|
||||||
backgroundImage: AssetImage(
|
|
||||||
'assets/images/noface.jpeg',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(top: 4),
|
|
||||||
child: SizedBox(
|
|
||||||
width: contentWidth,
|
|
||||||
child: Text(
|
|
||||||
data.uname,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
softWrap: false,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: currentMid == data.mid
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
|
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||||
@ -298,3 +277,129 @@ class UpPanelSkeleton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UpItemWidget extends StatelessWidget {
|
||||||
|
final dynamic data;
|
||||||
|
final int index;
|
||||||
|
final RxInt currentMid;
|
||||||
|
final Function(dynamic, int) onClickUp;
|
||||||
|
final Function(dynamic, int) onClickUpAni;
|
||||||
|
// final Function() feedBack;
|
||||||
|
final EdgeInsets itemPadding;
|
||||||
|
final double contentWidth;
|
||||||
|
|
||||||
|
const UpItemWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.data,
|
||||||
|
required this.index,
|
||||||
|
required this.currentMid,
|
||||||
|
required this.onClickUp,
|
||||||
|
required this.onClickUpAni,
|
||||||
|
// required this.feedBack,
|
||||||
|
required this.itemPadding,
|
||||||
|
required this.contentWidth,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
feedBack();
|
||||||
|
if (data.type == 'up') {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(milliseconds: 300),
|
||||||
|
() {
|
||||||
|
if (GlobalDataCache.enableDynamicSwitch) {
|
||||||
|
onClickUp(data, index);
|
||||||
|
} else {
|
||||||
|
onClickUpAni(data, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (data.type == 'live') {
|
||||||
|
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||||
|
'title': data.title,
|
||||||
|
'uname': data.uname,
|
||||||
|
'face': data.face,
|
||||||
|
'roomid': data.roomId,
|
||||||
|
});
|
||||||
|
Get.toNamed(
|
||||||
|
'/liveRoom?roomid=${data.roomId}',
|
||||||
|
arguments: {'liveItem': liveItem},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
feedBack();
|
||||||
|
if (data.mid == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String heroTag = Utils.makeHeroTag(data.mid);
|
||||||
|
Get.toNamed('/member?mid=${data.mid}',
|
||||||
|
arguments: {'face': data.face, 'heroTag': heroTag});
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: itemPadding,
|
||||||
|
child: Obx(
|
||||||
|
() => AnimatedOpacity(
|
||||||
|
opacity: currentMid.value == data.mid || currentMid.value == -1
|
||||||
|
? 1
|
||||||
|
: 0.3,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Badge(
|
||||||
|
smallSize: 8,
|
||||||
|
label: data.type == 'live' ? const Text('Live') : null,
|
||||||
|
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||||
|
alignment: data.type == 'live'
|
||||||
|
? AlignmentDirectional.topCenter
|
||||||
|
: AlignmentDirectional.topEnd,
|
||||||
|
padding: const EdgeInsets.only(left: 6, right: 6),
|
||||||
|
isLabelVisible: data.type == 'live' ||
|
||||||
|
(data.type == 'up' && (data.hasUpdate ?? false)),
|
||||||
|
backgroundColor: data.type == 'live'
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.primary,
|
||||||
|
child: data.face != ''
|
||||||
|
? NetworkImgLayer(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
src: data.face,
|
||||||
|
type: 'avatar',
|
||||||
|
)
|
||||||
|
: const CircleAvatar(
|
||||||
|
radius: 25,
|
||||||
|
backgroundImage: AssetImage(
|
||||||
|
'assets/images/noface.jpeg',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: SizedBox(
|
||||||
|
width: contentWidth,
|
||||||
|
child: Text(
|
||||||
|
data.uname,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: currentMid.value == data.mid
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.outline,
|
||||||
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -78,7 +78,15 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
],
|
],
|
||||||
LayoutBuilder(builder: (context, box) {
|
LayoutBuilder(builder: (context, box) {
|
||||||
double width = box.maxWidth;
|
double width = box.maxWidth;
|
||||||
return Stack(
|
return Container(
|
||||||
|
margin: floor == 1
|
||||||
|
? const EdgeInsets.only(
|
||||||
|
left: StyleString.safeSpace, right: StyleString.safeSpace)
|
||||||
|
: EdgeInsets.zero,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(StyleString.imgRadius)),
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
type: floor == 1 ? 'emote' : null,
|
type: floor == 1 ? 'emote' : null,
|
||||||
@ -120,8 +128,10 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
children: [
|
children: [
|
||||||
DefaultTextStyle.merge(
|
DefaultTextStyle.merge(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize: Theme.of(context)
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.fontSize,
|
||||||
color: Colors.white),
|
color: Colors.white),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -144,6 +154,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
|
|||||||
@ -3,17 +3,18 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/fan.dart';
|
import 'package:pilipala/http/fan.dart';
|
||||||
import 'package:pilipala/models/fans/result.dart';
|
import 'package:pilipala/models/fans/result.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class FansController extends GetxController {
|
class FansController extends GetxController {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
int ps = 20;
|
int ps = 20;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
RxList<FansItemModel> fansList = <FansItemModel>[].obs;
|
RxList<FansItemModel> fansList = <FansItemModel>[].obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String name;
|
late String name;
|
||||||
var userInfo;
|
UserInfoData? userInfo;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
RxBool isOwner = false.obs;
|
RxBool isOwner = false.obs;
|
||||||
|
|
||||||
@ -23,9 +24,9 @@ class FansController extends GetxController {
|
|||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
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;
|
isOwner.value = mid == userInfo?.mid;
|
||||||
name = Get.parameters['name'] ?? userInfo.uname;
|
name = Get.parameters['name'] ?? userInfo?.uname ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryFans(type) async {
|
Future queryFans(type) async {
|
||||||
@ -49,7 +50,6 @@ class FansController extends GetxController {
|
|||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
fansList.addAll(res['data'].list);
|
fansList.addAll(res['data'].list);
|
||||||
}
|
}
|
||||||
print(total);
|
|
||||||
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
||||||
loadingText.value = '没有更多了';
|
loadingText.value = '没有更多了';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,13 +49,8 @@ class _FansPageState extends State<FansPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
centerTitle: false,
|
|
||||||
titleSpacing: 0,
|
|
||||||
title: Text(
|
title: Text(
|
||||||
_fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝',
|
_fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
@ -105,7 +100,12 @@ class _FansPageState extends State<FansPage> {
|
|||||||
} else {
|
} else {
|
||||||
return HttpError(
|
return HttpError(
|
||||||
errMsg: data['msg'],
|
errMsg: data['msg'],
|
||||||
fn: () => _fansController.queryFans('init'),
|
fn: () {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture = _fansController.queryFans('init');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
isInSliver: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class FavController extends GetxController {
|
|||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
RxList<FavFolderItemData> favFolderList = <FavFolderItemData>[].obs;
|
RxList<FavFolderItemData> favFolderList = <FavFolderItemData>[].obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.userInfo;
|
||||||
UserInfoData? userInfo;
|
UserInfoData? userInfo;
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int pageSize = 60;
|
int pageSize = 60;
|
||||||
|
|||||||
@ -40,11 +40,8 @@ class _FavPageState extends State<FavPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
|
||||||
titleSpacing: 0,
|
|
||||||
title: Obx(() => Text(
|
title: Obx(() => Text(
|
||||||
'${_favController.isOwner.value ? '我' : 'Ta'}的收藏',
|
'${_favController.isOwner.value ? '我' : 'Ta'}的收藏',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
)),
|
)),
|
||||||
actions: [
|
actions: [
|
||||||
Obx(() => !_favController.isOwner.value
|
Obx(() => !_favController.isOwner.value
|
||||||
@ -79,7 +76,21 @@ class _FavPageState extends State<FavPage> {
|
|||||||
const SizedBox(width: 14),
|
const SizedBox(width: 14),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
_favController.hasMore.value = true;
|
||||||
|
_favController.currentPage = 1;
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture = _favController.queryFavFolder(type: 'init');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: _buildBody(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildBody() {
|
||||||
|
return FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
@ -98,10 +109,7 @@ class _FavPageState extends State<FavPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return CustomScrollView(
|
return HttpError(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: data?['msg'] ?? '请求异常',
|
errMsg: data?['msg'] ?? '请求异常',
|
||||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||||
fn: () {
|
fn: () {
|
||||||
@ -109,13 +117,11 @@ class _FavPageState extends State<FavPage> {
|
|||||||
RoutePush.loginRedirectPush();
|
RoutePush.loginRedirectPush();
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_futureBuilderFuture =
|
_futureBuilderFuture = _favController.queryFavFolder();
|
||||||
_favController.queryFavFolder();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
isInSliver: false,
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -128,7 +134,6 @@ class _FavPageState extends State<FavPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/logic_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class FavItem extends StatelessWidget {
|
class FavItem extends StatelessWidget {
|
||||||
@ -96,7 +97,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
[23, 1].contains(favFolderItem.attr) ? '私密' : '公开',
|
LogicUtils.isPublic(favFolderItem.attr) ? '公开' : '私密',
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
|||||||
@ -1,29 +1,35 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/common.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/user/fav_detail.dart';
|
import 'package:pilipala/models/user/fav_detail.dart';
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/fav/index.dart';
|
import 'package:pilipala/pages/fav/index.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import 'widget/invalid_video_card.dart';
|
||||||
|
|
||||||
class FavDetailController extends GetxController {
|
class FavDetailController extends GetxController {
|
||||||
FavFolderItemData? item;
|
FavFolderItemData? item;
|
||||||
Rx<FavDetailData> favDetailData = FavDetailData().obs;
|
RxString title = ''.obs;
|
||||||
|
|
||||||
int? mediaId;
|
int? mediaId;
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxMap favInfo = {}.obs;
|
RxMap favInfo = {}.obs;
|
||||||
RxList favList = [].obs;
|
RxList<FavDetailItemData> favList = <FavDetailItemData>[].obs;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
RxInt mediaCount = 0.obs;
|
RxInt mediaCount = 0.obs;
|
||||||
late String isOwner;
|
late String isOwner;
|
||||||
|
late bool hasMore = true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
item = Get.arguments;
|
item = Get.arguments;
|
||||||
|
title.value = item!.title!;
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
mediaId = int.parse(Get.parameters['mediaId']!);
|
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||||
heroTag = Get.parameters['heroTag']!;
|
heroTag = Get.parameters['heroTag']!;
|
||||||
@ -33,7 +39,7 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
|
||||||
if (type == 'onLoad' && favList.length >= mediaCount.value) {
|
if (type == 'onLoad' && !hasMore) {
|
||||||
loadingText.value = '没有更多了';
|
loadingText.value = '没有更多了';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -45,17 +51,18 @@ class FavDetailController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
favInfo.value = res['data'].info;
|
favInfo.value = res['data'].info;
|
||||||
|
hasMore = res['data'].hasMore;
|
||||||
if (currentPage == 1 && type == 'init') {
|
if (currentPage == 1 && type == 'init') {
|
||||||
favList.value = res['data'].medias;
|
favList.value = res['data'].medias;
|
||||||
mediaCount.value = res['data'].info['media_count'];
|
mediaCount.value = res['data'].info['media_count'];
|
||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
favList.addAll(res['data'].medias);
|
favList.addAll(res['data'].medias);
|
||||||
}
|
}
|
||||||
if (favList.length >= mediaCount.value) {
|
if (!hasMore) {
|
||||||
loadingText.value = '没有更多了';
|
loadingText.value = '没有更多了';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
currentPage += 1;
|
currentPage += 1;
|
||||||
|
}
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -117,15 +124,53 @@ class FavDetailController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onEditFavFolder() async {
|
onEditFavFolder() async {
|
||||||
Get.toNamed(
|
var res = await Get.toNamed(
|
||||||
'/favEdit',
|
'/favEdit',
|
||||||
arguments: {
|
arguments: {
|
||||||
'mediaId': mediaId.toString(),
|
'mediaId': mediaId.toString(),
|
||||||
'title': item!.title,
|
'title': item!.title,
|
||||||
'intro': item!.intro,
|
'intro': item!.intro,
|
||||||
'cover': item!.cover,
|
'cover': item!.cover,
|
||||||
'privacy': item!.attr,
|
'privacy': [22, 0].contains(item!.attr) ? 0 : 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
title.value = res['title'];
|
||||||
|
print(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future toViewPlayAll() async {
|
||||||
|
final FavDetailItemData firstItem = favList.first;
|
||||||
|
final String heroTag = Utils.makeHeroTag(firstItem.bvid);
|
||||||
|
Get.toNamed(
|
||||||
|
'/video?bvid=${firstItem.bvid}&cid=${firstItem.cid}',
|
||||||
|
arguments: {
|
||||||
|
'videoItem': firstItem,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
'sourceType': 'fav',
|
||||||
|
'mediaId': favInfo['id'],
|
||||||
|
'oid': firstItem.id,
|
||||||
|
'favTitle': favInfo['title'],
|
||||||
|
'favInfo': favInfo,
|
||||||
|
'count': favInfo['media_count'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查看无效视频信息
|
||||||
|
Future toViewInvalidVideo(FavDetailItemData item) async {
|
||||||
|
SmartDialog.showLoading(msg: '加载中...');
|
||||||
|
var res = await CommonHttp.fixVideoPicAndTitle(aid: item.id!);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res['status']) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: Get.context!,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return InvalidVideoCard(videoInfo: res['data']);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
late final ScrollController _controller = ScrollController();
|
late final ScrollController _controller = ScrollController();
|
||||||
final FavDetailController _favDetailController =
|
final FavDetailController _favDetailController =
|
||||||
Get.put(FavDetailController());
|
Get.put(FavDetailController());
|
||||||
late StreamController<bool> titleStreamC; // a
|
late StreamController<bool> titleStreamC =
|
||||||
|
StreamController<bool>.broadcast(); // a
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late String mediaId;
|
late String mediaId;
|
||||||
|
|
||||||
@ -31,7 +32,6 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
mediaId = Get.parameters['mediaId']!;
|
mediaId = Get.parameters['mediaId']!;
|
||||||
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
|
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
|
||||||
titleStreamC = StreamController<bool>();
|
|
||||||
_controller.addListener(
|
_controller.addListener(
|
||||||
() {
|
() {
|
||||||
if (_controller.offset > 160) {
|
if (_controller.offset > 160) {
|
||||||
@ -80,10 +80,12 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Obx(
|
||||||
_favDetailController.item!.title!,
|
() => Text(
|
||||||
|
_favDetailController.title.value,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
'共${_favDetailController.mediaCount}条视频',
|
'共${_favDetailController.mediaCount}条视频',
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
style: Theme.of(context).textTheme.labelMedium,
|
||||||
@ -156,8 +158,9 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Obx(
|
||||||
_favDetailController.item!.title!,
|
() => Text(
|
||||||
|
_favDetailController.title.value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@ -165,6 +168,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
.fontSize,
|
.fontSize,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
_favDetailController.item!.upper!.name!,
|
_favDetailController.item!.upper!.name!,
|
||||||
@ -189,7 +193,9 @@ 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.mediaCount}条视频',
|
_favDetailController.mediaCount > 0
|
||||||
|
? '共${_favDetailController.mediaCount}条视频'
|
||||||
|
: '',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize:
|
fontSize:
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
@ -211,7 +217,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
List favList = _favDetailController.favList;
|
List favList = _favDetailController.favList;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => favList.isEmpty
|
() => favList.isEmpty
|
||||||
? const SliverToBoxAdapter(child: SizedBox())
|
? const NoData()
|
||||||
: SliverList(
|
: SliverList(
|
||||||
delegate:
|
delegate:
|
||||||
SliverChildBuilderDelegate((context, index) {
|
SliverChildBuilderDelegate((context, index) {
|
||||||
@ -220,6 +226,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
isOwner: _favDetailController.isOwner,
|
isOwner: _favDetailController.isOwner,
|
||||||
callFn: () => _favDetailController
|
callFn: () => _favDetailController
|
||||||
.onCancelFav(favList[index].id),
|
.onCancelFav(favList[index].id),
|
||||||
|
viewInvalidVideoCb: () => _favDetailController
|
||||||
|
.toViewInvalidVideo(favList[index]),
|
||||||
);
|
);
|
||||||
}, childCount: favList.length),
|
}, childCount: favList.length),
|
||||||
),
|
),
|
||||||
@ -243,23 +251,34 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: MediaQuery.of(context).padding.bottom + 60,
|
height: MediaQuery.of(context).padding.bottom + 90,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
bottom: MediaQuery.of(context).padding.bottom),
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Obx(
|
child: Obx(() {
|
||||||
() => Text(
|
final mediaCount = _favDetailController.mediaCount;
|
||||||
_favDetailController.loadingText.value,
|
final loadingText = _favDetailController.loadingText.value;
|
||||||
style: TextStyle(
|
final textColor = Theme.of(context).colorScheme.outline;
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
fontSize: 13),
|
return Text(
|
||||||
),
|
mediaCount > 0 ? loadingText : '',
|
||||||
),
|
style: TextStyle(color: textColor, fontSize: 13),
|
||||||
|
);
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
floatingActionButton: Obx(
|
||||||
|
() => _favDetailController.mediaCount > 0
|
||||||
|
? FloatingActionButton.extended(
|
||||||
|
onPressed: _favDetailController.toViewPlayAll,
|
||||||
|
label: const Text('播放全部'),
|
||||||
|
icon: const Icon(Icons.playlist_play),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
final Function? callFn;
|
final Function? callFn;
|
||||||
final int? searchType;
|
final int? searchType;
|
||||||
final String isOwner;
|
final String isOwner;
|
||||||
|
final Function? viewInvalidVideoCb;
|
||||||
|
|
||||||
const FavVideoCardH({
|
const FavVideoCardH({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -26,6 +27,7 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
this.callFn,
|
this.callFn,
|
||||||
this.searchType,
|
this.searchType,
|
||||||
required this.isOwner,
|
required this.isOwner,
|
||||||
|
this.viewInvalidVideoCb,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -36,6 +38,10 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
// int? seasonId;
|
// int? seasonId;
|
||||||
|
if (videoItem.title == '已失效视频') {
|
||||||
|
viewInvalidVideoCb?.call();
|
||||||
|
return;
|
||||||
|
}
|
||||||
String? epId;
|
String? epId;
|
||||||
if (videoItem.ogv != null &&
|
if (videoItem.ogv != null &&
|
||||||
(videoItem.ogv['type_name'] == '番剧' ||
|
(videoItem.ogv['type_name'] == '番剧' ||
|
||||||
@ -65,11 +71,17 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
epId != null ? SearchType.media_bangumi : SearchType.video,
|
epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onLongPress: () => imageSaveDialog(
|
onLongPress: () {
|
||||||
|
if (videoItem.title == '已失效视频') {
|
||||||
|
SmartDialog.showToast('视频已失效');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
imageSaveDialog(
|
||||||
context,
|
context,
|
||||||
videoItem,
|
videoItem,
|
||||||
SmartDialog.dismiss,
|
SmartDialog.dismiss,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
97
lib/pages/fav_detail/widget/invalid_video_card.dart
Normal file
97
lib/pages/fav_detail/widget/invalid_video_card.dart
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/common/invalid_video.dart';
|
||||||
|
|
||||||
|
class InvalidVideoCard extends StatelessWidget {
|
||||||
|
const InvalidVideoCard({required this.videoInfo, Key? key}) : super(key: key);
|
||||||
|
final InvalidVideoModel videoInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const TextStyle textStyle = TextStyle(fontSize: 14.0);
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(
|
||||||
|
12,
|
||||||
|
14,
|
||||||
|
12,
|
||||||
|
MediaQuery.of(context).padding.bottom + 20,
|
||||||
|
),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
double maxWidth = constraints.maxWidth;
|
||||||
|
double maxHeight = maxWidth * 9 / 16;
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
src: videoInfo.pic,
|
||||||
|
radius: 20,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
SelectableText(
|
||||||
|
videoInfo.title!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
SelectableText(videoInfo.author!, style: textStyle),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
SelectableText('创建时间:${videoInfo.createdAt}', style: textStyle),
|
||||||
|
SelectableText('更新时间:${videoInfo.lastupdate}',
|
||||||
|
style: textStyle),
|
||||||
|
SelectableText('分类:${videoInfo.typename}', style: textStyle),
|
||||||
|
SelectableText(
|
||||||
|
'投币:${videoInfo.coins} 收藏:${videoInfo.favorites}',
|
||||||
|
style: textStyle),
|
||||||
|
if (videoInfo.tagList != null &&
|
||||||
|
videoInfo.tagList!.isNotEmpty) ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
_buildTags(context, videoInfo.tagList),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTags(BuildContext context, List<String>? videoTags) {
|
||||||
|
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||||
|
return Wrap(
|
||||||
|
spacing: 6,
|
||||||
|
runSpacing: 6,
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
children: videoTags!.map((tag) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed('/searchResult', parameters: {'keyword': tag});
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: colorScheme.surfaceVariant.withOpacity(0.5),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 10),
|
||||||
|
child: Text(
|
||||||
|
tag,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -56,7 +56,7 @@ class FavEditController extends GetxController {
|
|||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
SmartDialog.showToast('编辑成功');
|
SmartDialog.showToast('编辑成功');
|
||||||
Get.back();
|
Get.back(result: {'title': title});
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,8 +19,6 @@ class _FavEditPageState extends State<FavEditPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
elevation: 0,
|
|
||||||
scrolledUnderElevation: 0,
|
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => _favEditController.type.value == 'add'
|
() => _favEditController.type.value == 'add'
|
||||||
? Text(
|
? Text(
|
||||||
@ -32,7 +30,6 @@ class _FavEditPageState extends State<FavEditPage> {
|
|||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
centerTitle: false,
|
|
||||||
actions: [
|
actions: [
|
||||||
Obx(
|
Obx(
|
||||||
() => _favEditController.privacy.value == 0
|
() => _favEditController.privacy.value == 0
|
||||||
|
|||||||
@ -47,7 +47,6 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
titleSpacing: 0,
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => _favSearchCtr.submit(),
|
onPressed: () => _favSearchCtr.submit(),
|
||||||
|
|||||||
@ -6,19 +6,20 @@ import 'package:pilipala/http/follow.dart';
|
|||||||
import 'package:pilipala/http/member.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/models/member/tags.dart';
|
||||||
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
/// 查看自己的关注时,可以查看分类
|
/// 查看自己的关注时,可以查看分类
|
||||||
/// 查看其他人的关注时,只可以看全部
|
/// 查看其他人的关注时,只可以看全部
|
||||||
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStorage.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;
|
UserInfoData? userInfo;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
RxBool isOwner = false.obs;
|
RxBool isOwner = false.obs;
|
||||||
late List<MemberTagItemModel> followTags;
|
late List<MemberTagItemModel> followTags;
|
||||||
@ -30,9 +31,9 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
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;
|
isOwner.value = mid == userInfo?.mid;
|
||||||
name = Get.parameters['name'] ?? userInfo.uname;
|
name = Get.parameters['name'] ?? userInfo?.uname ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future queryFollowings(type) async {
|
Future queryFollowings(type) async {
|
||||||
@ -68,7 +69,7 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
|
|
||||||
// 当查看当前用户的关注时,请求关注分组
|
// 当查看当前用户的关注时,请求关注分组
|
||||||
Future followUpTags() async {
|
Future followUpTags() async {
|
||||||
if (userInfo != null && mid == userInfo.mid) {
|
if (userInfo != null && mid == userInfo!.mid) {
|
||||||
var res = await MemberHttp.followUpTags();
|
var res = await MemberHttp.followUpTags();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
followTags = res['data'];
|
followTags = res['data'];
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user