Compare commits
2 Commits
v1.0.13.12
...
feature-au
| Author | SHA1 | Date | |
|---|---|---|---|
| cb3fd24cf7 | |||
| 3e8216923f |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.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.4
|
flutter-version: 3.10.6
|
||||||
channel: any
|
channel: any
|
||||||
|
|
||||||
- name: 下载项目依赖
|
- name: 下载项目依赖
|
||||||
|
|||||||
21
README.md
21
README.md
@ -3,23 +3,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>PiliPala</h1>
|
<h1>PiliPala</h1>
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
</div>
|
|
||||||
<p>使用Flutter开发的BiliBili第三方客户端</p>
|
<p>使用Flutter开发的BiliBili第三方客户端</p>
|
||||||
|
<br/>
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/510shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/510shots_so.png" width="32%" alt="home" />
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/main_screen.png" width="96%" alt="home" />
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/main_screen.png" width="96%" alt="home" />
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## 开发环境
|
## 开发环境
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.guozhigq.pilipala">
|
package="com.guozhigq.pilipala">
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
@ -63,6 +64,27 @@
|
|||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
|
<!-- ADD THIS "SERVICE" element -->
|
||||||
|
<service
|
||||||
|
android:name="com.ryanheise.audioservice.AudioService"
|
||||||
|
android:exported="true"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<!-- ADD THIS "RECEIVER" element -->
|
||||||
|
<receiver
|
||||||
|
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
|
android:exported="true"
|
||||||
|
tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<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"/>
|
||||||
@ -265,4 +287,8 @@
|
|||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
## 1.0.12
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ iOS端视频播放时没有声音
|
|
||||||
+ 超过6分钟弹幕不显示
|
|
||||||
+ 视频详情页网络异常
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@ -37,5 +37,11 @@ end
|
|||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
|
||||||
|
'$(inherited)',
|
||||||
|
'AUDIO_SESSION_MICROPHONE=0'
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,8 +5,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- audio_session (0.0.1):
|
- audio_session (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- auto_orientation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
@ -20,8 +18,13 @@ PODS:
|
|||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- gt3_flutter_plugin (0.0.8):
|
- gt3_flutter_plugin (0.0.8):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
<<<<<<< HEAD
|
||||||
|
- just_audio (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
=======
|
||||||
- GT3Captcha-iOS
|
- GT3Captcha-iOS
|
||||||
- GT3Captcha-iOS (0.15.8.3)
|
- GT3Captcha-iOS (0.15.8.3)
|
||||||
|
>>>>>>> main
|
||||||
- media_kit_libs_ios_video (1.0.4):
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_native_event_loop (1.0.0):
|
- media_kit_native_event_loop (1.0.0):
|
||||||
@ -64,12 +67,16 @@ DEPENDENCIES:
|
|||||||
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
- appscheme (from `.symlinks/plugins/appscheme/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`)
|
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
|
<<<<<<< HEAD
|
||||||
|
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||||
|
- just_audio (from `.symlinks/plugins/just_audio/ios`)
|
||||||
|
=======
|
||||||
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
|
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
|
||||||
|
>>>>>>> main
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
@ -101,8 +108,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/audio_service/ios"
|
:path: ".symlinks/plugins/audio_service/ios"
|
||||||
audio_session:
|
audio_session:
|
||||||
:path: ".symlinks/plugins/audio_session/ios"
|
:path: ".symlinks/plugins/audio_session/ios"
|
||||||
auto_orientation:
|
|
||||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -111,8 +116,15 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_volume_controller:
|
flutter_volume_controller:
|
||||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
|
<<<<<<< HEAD
|
||||||
|
image_gallery_saver:
|
||||||
|
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||||
|
just_audio:
|
||||||
|
:path: ".symlinks/plugins/just_audio/ios"
|
||||||
|
=======
|
||||||
gt3_flutter_plugin:
|
gt3_flutter_plugin:
|
||||||
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
||||||
|
>>>>>>> main
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
@ -152,33 +164,37 @@ SPEC CHECKSUMS:
|
|||||||
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
||||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
|
||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
|
<<<<<<< HEAD
|
||||||
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
|
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
|
||||||
|
=======
|
||||||
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
||||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||||
|
>>>>>>> main
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
||||||
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
||||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||||
|
|
||||||
PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b
|
PODFILE CHECKSUM: fc8a34c4ba2e14d31df90bf03cf419a764f2778c
|
||||||
|
|
||||||
COCOAPODS: 1.14.3
|
COCOAPODS: 1.12.1
|
||||||
|
|||||||
@ -157,7 +157,7 @@
|
|||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1300;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1300"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|||||||
@ -103,6 +103,10 @@
|
|||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<!-- audio service配置 -->
|
||||||
|
=======
|
||||||
|
>>>>>>> main
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>audio</string>
|
<string>audio</string>
|
||||||
|
|||||||
@ -9,11 +9,7 @@ class StyleString {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Constants {
|
class Constants {
|
||||||
// 27eb53fc9058f8c3 移动端 Android
|
static const String appKey = '27eb53fc9058f8c3';
|
||||||
// 4409e2ce8ffd12b8 TV端
|
|
||||||
static const String appKey = '4409e2ce8ffd12b8';
|
|
||||||
// 59b43e04ad6965f34319062b478f83dd TV端
|
|
||||||
static const String appSec = '59b43e04ad6965f34319062b478f83dd';
|
|
||||||
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
|
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
|
||||||
static const String thirdApi =
|
static const String thirdApi =
|
||||||
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
|
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
|
||||||
|
|||||||
@ -1,5 +1,37 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Widget pBadge(
|
||||||
|
// text,
|
||||||
|
// context,
|
||||||
|
// double? top,
|
||||||
|
// double? right,
|
||||||
|
// double? bottom,
|
||||||
|
// double? left, {
|
||||||
|
// type = 'primary',
|
||||||
|
// }) {
|
||||||
|
// Color bgColor = Theme.of(context).colorScheme.primary;
|
||||||
|
// Color color = Theme.of(context).colorScheme.onPrimary;
|
||||||
|
// if (type == 'gray') {
|
||||||
|
// bgColor = Colors.black54.withOpacity(0.4);
|
||||||
|
// color = Colors.white;
|
||||||
|
// }
|
||||||
|
// return Positioned(
|
||||||
|
// top: top,
|
||||||
|
// left: left,
|
||||||
|
// right: right,
|
||||||
|
// bottom: bottom,
|
||||||
|
// child: Container(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 6),
|
||||||
|
// decoration:
|
||||||
|
// BoxDecoration(borderRadius: BorderRadius.circular(4), color: bgColor),
|
||||||
|
// child: Text(
|
||||||
|
// text,
|
||||||
|
// style: TextStyle(fontSize: 11, color: color),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
class PBadge extends StatelessWidget {
|
class PBadge extends StatelessWidget {
|
||||||
final String? text;
|
final String? text;
|
||||||
final double? top;
|
final double? top;
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class ContentContainer extends StatelessWidget {
|
|
||||||
final Widget? contentWidget;
|
|
||||||
final Widget? bottomWidget;
|
|
||||||
final bool isScrollable;
|
|
||||||
final Clip? childClipBehavior;
|
|
||||||
|
|
||||||
const ContentContainer(
|
|
||||||
{Key? key,
|
|
||||||
this.contentWidget,
|
|
||||||
this.bottomWidget,
|
|
||||||
this.isScrollable = true,
|
|
||||||
this.childClipBehavior})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
clipBehavior: childClipBehavior ?? Clip.hardEdge,
|
|
||||||
physics: isScrollable ? null : NeverScrollableScrollPhysics(),
|
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: constraints.copyWith(
|
|
||||||
minHeight: constraints.maxHeight,
|
|
||||||
maxHeight: double.infinity,
|
|
||||||
),
|
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
if (contentWidget != null)
|
|
||||||
Expanded(
|
|
||||||
child: contentWidget!,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Spacer(),
|
|
||||||
if (bottomWidget != null) bottomWidget!,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -17,10 +17,6 @@ class VideoCardH extends StatelessWidget {
|
|||||||
final Function()? longPress;
|
final Function()? longPress;
|
||||||
final Function()? longPressEnd;
|
final Function()? longPressEnd;
|
||||||
final String source;
|
final String source;
|
||||||
final bool showOwner;
|
|
||||||
final bool showView;
|
|
||||||
final bool showDanmaku;
|
|
||||||
final bool showPubdate;
|
|
||||||
|
|
||||||
const VideoCardH({
|
const VideoCardH({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -28,10 +24,6 @@ class VideoCardH extends StatelessWidget {
|
|||||||
this.longPress,
|
this.longPress,
|
||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
this.source = 'normal',
|
this.source = 'normal',
|
||||||
this.showOwner = true,
|
|
||||||
this.showView = true,
|
|
||||||
this.showDanmaku = true,
|
|
||||||
this.showPubdate = false,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -111,14 +103,7 @@ class VideoCardH extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(
|
VideoContent(videoItem: videoItem, source: source)
|
||||||
videoItem: videoItem,
|
|
||||||
source: source,
|
|
||||||
showOwner: showOwner,
|
|
||||||
showView: showView,
|
|
||||||
showDanmaku: showDanmaku,
|
|
||||||
showPubdate: showPubdate,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -134,20 +119,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
// ignore: prefer_typing_uninitialized_variables
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
final videoItem;
|
final videoItem;
|
||||||
final String source;
|
final String source;
|
||||||
final bool showOwner;
|
const VideoContent(
|
||||||
final bool showView;
|
{super.key, required this.videoItem, this.source = 'normal'});
|
||||||
final bool showDanmaku;
|
|
||||||
final bool showPubdate;
|
|
||||||
|
|
||||||
const VideoContent({
|
|
||||||
super.key,
|
|
||||||
required this.videoItem,
|
|
||||||
this.source = 'normal',
|
|
||||||
this.showOwner = true,
|
|
||||||
this.showView = true,
|
|
||||||
this.showDanmaku = true,
|
|
||||||
this.showPubdate = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -206,40 +179,34 @@ class VideoContent extends StatelessWidget {
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
// const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
if (showPubdate)
|
|
||||||
Text(
|
|
||||||
Utils.dateFormat(videoItem.pubdate!),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11, color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
if (showOwner)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
videoItem.owner.name,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (showView) ...[
|
Text(
|
||||||
StatView(
|
videoItem.owner.name,
|
||||||
theme: 'gray',
|
style: TextStyle(
|
||||||
view: videoItem.stat.view,
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
),
|
||||||
],
|
],
|
||||||
if (showDanmaku)
|
),
|
||||||
StatDanMu(
|
Row(
|
||||||
theme: 'gray',
|
children: [
|
||||||
danmu: videoItem.stat.danmaku,
|
StatView(
|
||||||
),
|
theme: 'gray',
|
||||||
|
view: videoItem.stat.view,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
StatDanMu(
|
||||||
|
theme: 'gray',
|
||||||
|
danmu: videoItem.stat.danmaku,
|
||||||
|
),
|
||||||
|
// Text(
|
||||||
|
// Utils.dateFormat(videoItem.pubdate!),
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 11,
|
||||||
|
// color: Theme.of(context).colorScheme.outline),
|
||||||
|
// )
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
// SizedBox(
|
// SizedBox(
|
||||||
// width: 20,
|
// width: 20,
|
||||||
|
|||||||
@ -333,10 +333,8 @@ class VideoStat extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
children: [
|
children: [
|
||||||
if (videoItem.stat.view != '-')
|
TextSpan(text: '${videoItem.stat.view}观看'),
|
||||||
TextSpan(text: '${videoItem.stat.view}观看'),
|
TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
|
||||||
if (videoItem.stat.danmu != '-')
|
|
||||||
TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -215,7 +215,7 @@ class Api {
|
|||||||
// 粉丝
|
// 粉丝
|
||||||
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
|
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
|
||||||
// order_type 排序规则 最近访问传空,最常访问传 attention
|
// order_type 排序规则 最近访问传空,最常访问传 attention
|
||||||
static const String fans = '/x/relation/fans';
|
static const String fans = 'https://api.bilibili.com/x/relation/fans';
|
||||||
|
|
||||||
// 直播
|
// 直播
|
||||||
// ?page=1&page_size=30&platform=web
|
// ?page=1&page_size=30&platform=web
|
||||||
@ -312,10 +312,6 @@ class Api {
|
|||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
//发送视频弹幕
|
|
||||||
//https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md
|
|
||||||
static const String shootDanmaku = '/x/v2/dm/post';
|
|
||||||
|
|
||||||
// up主分组
|
// up主分组
|
||||||
static const String followUpTag = '/x/relation/tags';
|
static const String followUpTag = '/x/relation/tags';
|
||||||
|
|
||||||
@ -326,51 +322,6 @@ class Api {
|
|||||||
// 获取指定分组下的up
|
// 获取指定分组下的up
|
||||||
static const String followUpGroup = '/x/relation/tag';
|
static const String followUpGroup = '/x/relation/tag';
|
||||||
|
|
||||||
/// 私聊
|
|
||||||
/// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions?
|
|
||||||
/// session_type=1&
|
|
||||||
/// group_fold=1&
|
|
||||||
/// unfollow_fold=0&
|
|
||||||
/// sort_rule=2&
|
|
||||||
/// build=0&
|
|
||||||
/// mobi_app=web&
|
|
||||||
/// w_rid=8641d157fb9a9255eb2159f316ee39e2&
|
|
||||||
/// wts=1697305010
|
|
||||||
|
|
||||||
static const String sessionList =
|
|
||||||
'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions';
|
|
||||||
|
|
||||||
/// 私聊用户信息
|
|
||||||
/// uids
|
|
||||||
/// build=0&mobi_app=web
|
|
||||||
static const String sessionAccountList =
|
|
||||||
'https://api.vc.bilibili.com/account/v1/user/cards';
|
|
||||||
|
|
||||||
/// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs?
|
|
||||||
/// talker_id=400787461&
|
|
||||||
/// session_type=1&
|
|
||||||
/// size=20&
|
|
||||||
/// sender_device_id=1&
|
|
||||||
/// build=0&
|
|
||||||
/// mobi_app=web&
|
|
||||||
/// web_location=333.1296&
|
|
||||||
/// w_rid=cfe3bf58c9fe181bbf4dd6c75175e6b0&
|
|
||||||
/// wts=1697350697
|
|
||||||
|
|
||||||
static const String sessionMsg =
|
|
||||||
'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs';
|
|
||||||
|
|
||||||
/// 标记已读 POST
|
|
||||||
/// talker_id:
|
|
||||||
/// session_type: 1
|
|
||||||
/// ack_seqno: 920224140918926
|
|
||||||
/// build: 0
|
|
||||||
/// mobi_app: web
|
|
||||||
/// csrf_token:
|
|
||||||
/// csrf:
|
|
||||||
static const String updateAck =
|
|
||||||
'https://api.vc.bilibili.com/session_svr/v1/session_svr/update_ack';
|
|
||||||
|
|
||||||
// 获取某个动态详情
|
// 获取某个动态详情
|
||||||
// timezone_offset=-480
|
// timezone_offset=-480
|
||||||
// id=849312409672744983
|
// id=849312409672744983
|
||||||
@ -421,49 +372,4 @@ class Api {
|
|||||||
/// local_id
|
/// local_id
|
||||||
static const getWebKey =
|
static const getWebKey =
|
||||||
'https://passport.bilibili.com/x/passport-login/web/key';
|
'https://passport.bilibili.com/x/passport-login/web/key';
|
||||||
|
|
||||||
/// cookie转access_key
|
|
||||||
static const cookieToKey =
|
|
||||||
'https://passport.bilibili.com/x/passport-tv-login/h5/qrcode/confirm';
|
|
||||||
|
|
||||||
/// 申请二维码(TV端)
|
|
||||||
static const getTVCode =
|
|
||||||
'https://passport.snm0516.aisee.tv/x/passport-tv-login/qrcode/auth_code';
|
|
||||||
|
|
||||||
///扫码登录(TV端)
|
|
||||||
static const qrcodePoll =
|
|
||||||
'https://passport.bilibili.com/x/passport-tv-login/qrcode/poll';
|
|
||||||
|
|
||||||
/// 置顶视频
|
|
||||||
static const getTopVideoApi = '/x/space/top/arc';
|
|
||||||
|
|
||||||
/// 主页 - 最近投币的视频
|
|
||||||
/// vmid
|
|
||||||
/// gaia_source = main_web
|
|
||||||
/// web_location
|
|
||||||
/// w_rid
|
|
||||||
/// wts
|
|
||||||
static const getRecentCoinVideoApi = '/x/space/coin/video';
|
|
||||||
|
|
||||||
/// 最近点赞的视频
|
|
||||||
static const getRecentLikeVideoApi = '/x/space/like/video';
|
|
||||||
|
|
||||||
/// 最近追番
|
|
||||||
static const getRecentBangumiApi = '/x/space/bangumi/follow/list';
|
|
||||||
|
|
||||||
/// 用户专栏
|
|
||||||
static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series';
|
|
||||||
|
|
||||||
/// 获赞数 播放数
|
|
||||||
/// mid
|
|
||||||
static const getMemberViewApi = '/x/space/upstat';
|
|
||||||
|
|
||||||
/// 查询某个专栏
|
|
||||||
/// mid
|
|
||||||
/// season_id
|
|
||||||
/// sort_reverse
|
|
||||||
/// page_num
|
|
||||||
/// page_size
|
|
||||||
static const getSeasonDetailApi =
|
|
||||||
'/x/polymer/web-space/seasons_archives_list';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,72 +24,4 @@ class DanmakaHttp {
|
|||||||
);
|
);
|
||||||
return DmSegMobileReply.fromBuffer(response.data);
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
}
|
}
|
||||||
static Future shootDanmaku({
|
|
||||||
int type = 1,//弹幕类选择(1:视频弹幕 2:漫画弹幕)
|
|
||||||
required int oid,// 视频cid
|
|
||||||
required String msg,//弹幕文本(长度小于 100 字符)
|
|
||||||
int mode = 1,// 弹幕类型(1:滚动弹幕 4:底端弹幕 5:顶端弹幕 6:逆向弹幕(不能使用) 7:高级弹幕 8:代码弹幕(不能使用) 9:BAS弹幕(pool必须为2))
|
|
||||||
// String? aid,// 稿件avid
|
|
||||||
// String? bvid,// bvid与aid必须有一个
|
|
||||||
required String bvid,
|
|
||||||
int? progress,// 弹幕出现在视频内的时间(单位为毫秒,默认为0)
|
|
||||||
int? color,// 弹幕颜色(默认白色,16777215)
|
|
||||||
int? fontsize,// 弹幕字号(默认25)
|
|
||||||
int? pool,// 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0)
|
|
||||||
//int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s)
|
|
||||||
int? colorful,//60001:专属渐变彩色(需要会员)
|
|
||||||
int? checkbox_type,//是否带 UP 身份标识(0:普通;4:带有标识)
|
|
||||||
// String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要
|
|
||||||
// String? access_key,// APP 登录 Token APP 方式必要
|
|
||||||
}) async {
|
|
||||||
// 构建参数对象
|
|
||||||
// assert(aid != null || bvid != null);
|
|
||||||
// assert(csrf != null || access_key != null);
|
|
||||||
assert(msg.length < 100);
|
|
||||||
// 构建参数对象
|
|
||||||
var params = <String, dynamic>{
|
|
||||||
'type': type,
|
|
||||||
'oid': oid,
|
|
||||||
'msg': msg,
|
|
||||||
'mode': mode,
|
|
||||||
//'aid': aid,
|
|
||||||
'bvid': bvid,
|
|
||||||
'progress': progress,
|
|
||||||
'color': color,
|
|
||||||
'fontsize': fontsize,
|
|
||||||
'pool': pool,
|
|
||||||
'rnd': DateTime.now().microsecondsSinceEpoch,
|
|
||||||
'colorful': colorful,
|
|
||||||
'checkbox_type': checkbox_type,
|
|
||||||
'csrf': await Request.getCsrf(),
|
|
||||||
// 'access_key': access_key,
|
|
||||||
}..removeWhere((key, value) => value == null);
|
|
||||||
|
|
||||||
var response = await Request().post(
|
|
||||||
Api.shootDanmaku,
|
|
||||||
data: params,
|
|
||||||
options: Options(
|
|
||||||
contentType: Headers.formUrlEncodedContentType,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (response.statusCode != 200) {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': '弹幕发送失败,状态码:${response.statusCode}',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (response.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': response.data['data'],
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': "${response.data['code']}: ${response.data['message']}",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,24 +115,30 @@ class Request {
|
|||||||
idleTimeout: const Duration(milliseconds: 10000),
|
idleTimeout: const Duration(milliseconds: 10000),
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
||||||
),
|
),
|
||||||
);
|
)
|
||||||
|
|
||||||
/// 设置代理
|
/// 设置代理
|
||||||
if (enableSystemProxy) {
|
..httpClientAdapter = IOHttpClientAdapter(
|
||||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
|
||||||
createHttpClient: () {
|
createHttpClient: () {
|
||||||
final client = HttpClient();
|
final client = HttpClient();
|
||||||
// Config the client.
|
// Config the client.
|
||||||
client.findProxy = (uri) {
|
client.findProxy = (uri) {
|
||||||
// return 'PROXY host:port';
|
if (enableSystemProxy) {
|
||||||
return 'PROXY $systemProxyHost:$systemProxyPort';
|
print('🌹:$systemProxyHost');
|
||||||
|
print('🌹:$systemProxyPort');
|
||||||
|
|
||||||
|
// return 'PROXY host:port';
|
||||||
|
return 'PROXY $systemProxyHost:$systemProxyPort';
|
||||||
|
} else {
|
||||||
|
// 不设置代理
|
||||||
|
return 'DIRECT';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
client.badCertificateCallback =
|
client.badCertificateCallback =
|
||||||
(X509Certificate cert, String host, int port) => true;
|
(X509Certificate cert, String host, int port) => true;
|
||||||
return client;
|
return client;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
dio.interceptors.add(ApiInterceptor());
|
dio.interceptors.add(ApiInterceptor());
|
||||||
|
|||||||
@ -1,16 +1,9 @@
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/models/member/coin.dart';
|
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
import 'package:pilipala/models/member/seasons.dart';
|
|
||||||
import 'package:pilipala/models/member/tags.dart';
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
import 'package:pilipala/utils/wbi_sign.dart';
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
@ -222,243 +215,4 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取up置顶
|
|
||||||
static Future getTopVideo(String? vmid) async {
|
|
||||||
var res = await Request().get(Api.getTopVideoApi);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': res.data['data']
|
|
||||||
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
|
||||||
.toList()
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取uo专栏
|
|
||||||
static Future getMemberSeasons(int? mid, int? pn, int? ps) async {
|
|
||||||
var res = await Request().get(Api.getMemberSeasonsApi, data: {
|
|
||||||
'mid': mid,
|
|
||||||
'page_num': pn,
|
|
||||||
'page_size': ps,
|
|
||||||
});
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最近投币
|
|
||||||
static Future getRecentCoinVideo({required int mid}) async {
|
|
||||||
Map params = await WbiSign().makSign({
|
|
||||||
'mid': mid,
|
|
||||||
'gaia_source': 'main_web',
|
|
||||||
'web_location': 333.999,
|
|
||||||
});
|
|
||||||
var res = await Request().get(
|
|
||||||
Api.getRecentCoinVideoApi,
|
|
||||||
data: {
|
|
||||||
'vmid': mid,
|
|
||||||
'gaia_source': 'main_web',
|
|
||||||
'web_location': 333.999,
|
|
||||||
'w_rid': params['w_rid'],
|
|
||||||
'wts': params['wts'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': res.data['data']
|
|
||||||
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
|
||||||
.toList(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最近点赞
|
|
||||||
static Future getRecentLikeVideo({required int mid}) async {
|
|
||||||
Map params = await WbiSign().makSign({
|
|
||||||
'mid': mid,
|
|
||||||
'gaia_source': 'main_web',
|
|
||||||
'web_location': 333.999,
|
|
||||||
});
|
|
||||||
var res = await Request().get(
|
|
||||||
Api.getRecentLikeVideoApi,
|
|
||||||
data: {
|
|
||||||
'vmid': mid,
|
|
||||||
'gaia_source': 'main_web',
|
|
||||||
'web_location': 333.999,
|
|
||||||
'w_rid': params['w_rid'],
|
|
||||||
'wts': params['wts'],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看某个专栏
|
|
||||||
static Future getSeasonDetail({
|
|
||||||
required int mid,
|
|
||||||
required int seasonId,
|
|
||||||
bool sortReverse = false,
|
|
||||||
required int pn,
|
|
||||||
required int ps,
|
|
||||||
}) async {
|
|
||||||
var res = await Request().get(
|
|
||||||
Api.getSeasonDetailApi,
|
|
||||||
data: {
|
|
||||||
'mid': mid,
|
|
||||||
'season_id': seasonId,
|
|
||||||
'sort_reverse': sortReverse,
|
|
||||||
'page_num': pn,
|
|
||||||
'page_size': ps,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': MemberSeasonsList.fromJson(res.data['data'])
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
print(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取TV authCode
|
|
||||||
static Future getTVCode() async {
|
|
||||||
SmartDialog.showLoading();
|
|
||||||
var params = {
|
|
||||||
'appkey': Constants.appKey,
|
|
||||||
'local_id': '0',
|
|
||||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
|
||||||
};
|
|
||||||
String sign = Utils.appSign(
|
|
||||||
params,
|
|
||||||
Constants.appKey,
|
|
||||||
Constants.appSec,
|
|
||||||
);
|
|
||||||
var res = await Request()
|
|
||||||
.post(Api.getTVCode, queryParameters: {...params, 'sign': sign});
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': res.data['data']['auth_code'],
|
|
||||||
'msg': '操作成功'
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取access_key
|
|
||||||
static Future cookieToKey() async {
|
|
||||||
var authCodeRes = await getTVCode();
|
|
||||||
if (authCodeRes['status']) {
|
|
||||||
var res = await Request().post(Api.cookieToKey, queryParameters: {
|
|
||||||
'auth_code': authCodeRes['data'],
|
|
||||||
'build': 708200,
|
|
||||||
'csrf': await Request.getCsrf(),
|
|
||||||
});
|
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
|
||||||
await qrcodePoll(authCodeRes['data']);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future qrcodePoll(authCode) async {
|
|
||||||
var params = {
|
|
||||||
'appkey': Constants.appKey,
|
|
||||||
'auth_code': authCode.toString(),
|
|
||||||
'local_id': '0',
|
|
||||||
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
|
|
||||||
};
|
|
||||||
String sign = Utils.appSign(
|
|
||||||
params,
|
|
||||||
Constants.appKey,
|
|
||||||
Constants.appSec,
|
|
||||||
);
|
|
||||||
var res = await Request()
|
|
||||||
.post(Api.qrcodePoll, queryParameters: {...params, 'sign': sign});
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
String accessKey = res.data['data']['access_token'];
|
|
||||||
Box localCache = GStrorage.localCache;
|
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
|
||||||
var userInfo = userInfoCache.get('userInfoCache');
|
|
||||||
localCache.put(
|
|
||||||
LocalCacheKey.accessKey, {'mid': userInfo.mid, 'value': accessKey});
|
|
||||||
return {'status': true, 'data': [], 'msg': '操作成功'};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取up播放数、点赞数
|
|
||||||
static Future memberView({required int mid}) async {
|
|
||||||
var res = await Request().get(Api.getMemberViewApi, data: {'mid': mid});
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {'status': true, 'data': res.data['data']};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'data': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
import 'package:pilipala/http/api.dart';
|
|
||||||
import 'package:pilipala/http/init.dart';
|
|
||||||
import 'package:pilipala/models/msg/account.dart';
|
|
||||||
import 'package:pilipala/models/msg/session.dart';
|
|
||||||
import 'package:pilipala/utils/wbi_sign.dart';
|
|
||||||
|
|
||||||
class MsgHttp {
|
|
||||||
// 会话列表
|
|
||||||
static Future sessionList({int? endTs}) async {
|
|
||||||
Map<String, dynamic> params = {
|
|
||||||
'session_type': 1,
|
|
||||||
'group_fold': 1,
|
|
||||||
'unfollow_fold': 0,
|
|
||||||
'sort_rule': 2,
|
|
||||||
'build': 0,
|
|
||||||
'mobi_app': 'web',
|
|
||||||
};
|
|
||||||
if (endTs != null) {
|
|
||||||
params['end_ts'] = endTs;
|
|
||||||
}
|
|
||||||
|
|
||||||
Map signParams = await WbiSign().makSign(params);
|
|
||||||
var res = await Request().get(Api.sessionList, data: signParams);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': SessionDataModel.fromJson(res.data['data']),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future accountList(uids) async {
|
|
||||||
var res = await Request().get(Api.sessionAccountList, data: {
|
|
||||||
'uids': uids,
|
|
||||||
'build': 0,
|
|
||||||
'mobi_app': 'web',
|
|
||||||
});
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': res.data['data']
|
|
||||||
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
|
|
||||||
.toList(),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future sessionMsg({
|
|
||||||
int? talkerId,
|
|
||||||
}) async {
|
|
||||||
Map params = await WbiSign().makSign({
|
|
||||||
'talker_id': talkerId,
|
|
||||||
'session_type': 1,
|
|
||||||
'size': 20,
|
|
||||||
'sender_device_id': 1,
|
|
||||||
'build': 0,
|
|
||||||
'mobi_app': 'web',
|
|
||||||
});
|
|
||||||
var res = await Request().get(Api.sessionMsg, data: params);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': SessionMsgDataModel.fromJson(res.data['data']),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': res.data['message'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -199,7 +199,7 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户凭证 失效
|
// 获取用户凭证
|
||||||
static Future thirdLogin() async {
|
static Future thirdLogin() async {
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
'https://passport.bilibili.com/login/app/third',
|
'https://passport.bilibili.com/login/app/third',
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
|
<<<<<<< HEAD
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
=======
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
>>>>>>> main
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
@ -29,7 +33,22 @@ void main() async {
|
|||||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
|
||||||
.then((_) async {
|
.then((_) async {
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
await AudioService.init<AudioHandler>(
|
||||||
|
builder: () => MyAudioHandler(),
|
||||||
|
config: const AudioServiceConfig(
|
||||||
|
androidNotificationChannelId: 'com.guozhigq.pilipala.channel.audio',
|
||||||
|
androidNotificationChannelName: 'Music playback',
|
||||||
|
androidNotificationOngoing: true,
|
||||||
|
androidStopForegroundOnPause: true,
|
||||||
|
androidNotificationIcon: 'drawable/audio_service_icon',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
=======
|
||||||
await setupServiceLocator();
|
await setupServiceLocator();
|
||||||
|
>>>>>>> main
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
// 小白条、导航栏沉浸
|
// 小白条、导航栏沉浸
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
@ -157,3 +176,34 @@ class MyApp extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MyAudioHandler extends BaseAudioHandler
|
||||||
|
with
|
||||||
|
QueueHandler, // mix in default queue callback implementations
|
||||||
|
SeekHandler {
|
||||||
|
// mix in default seek callback implementations
|
||||||
|
|
||||||
|
// The most common callbacks:
|
||||||
|
@override
|
||||||
|
Future<void> play() async {
|
||||||
|
print('play');
|
||||||
|
// All 'play' requests from all origins route to here. Implement this
|
||||||
|
// callback to start playing audio appropriate to your app. e.g. music.
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
Future<void> pause() async {}
|
||||||
|
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
Future<void> stop() async {}
|
||||||
|
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
Future<void> seek(Duration position) async {}
|
||||||
|
|
||||||
|
///
|
||||||
|
@override
|
||||||
|
Future<void> skipToQueueItem(int i) async {}
|
||||||
|
}
|
||||||
|
|||||||
@ -118,7 +118,7 @@ class RcmdStat {
|
|||||||
|
|
||||||
RcmdStat.fromJson(Map<String, dynamic> json) {
|
RcmdStat.fromJson(Map<String, dynamic> json) {
|
||||||
view = json["cover_left_text_1"];
|
view = json["cover_left_text_1"];
|
||||||
danmu = json['cover_left_text_2'] ?? '-';
|
danmu = json['cover_left_text_2'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,89 +0,0 @@
|
|||||||
class MemberCoinsDataModel {
|
|
||||||
MemberCoinsDataModel({
|
|
||||||
this.aid,
|
|
||||||
this.bvid,
|
|
||||||
this.cid,
|
|
||||||
this.coins,
|
|
||||||
this.copyright,
|
|
||||||
this.ctime,
|
|
||||||
this.desc,
|
|
||||||
this.duration,
|
|
||||||
this.owner,
|
|
||||||
this.pic,
|
|
||||||
this.pubLocation,
|
|
||||||
this.pubdate,
|
|
||||||
this.resourceType,
|
|
||||||
this.state,
|
|
||||||
this.subtitle,
|
|
||||||
this.time,
|
|
||||||
this.title,
|
|
||||||
this.tname,
|
|
||||||
this.videos,
|
|
||||||
this.view,
|
|
||||||
this.danmaku,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? aid;
|
|
||||||
String? bvid;
|
|
||||||
int? cid;
|
|
||||||
int? coins;
|
|
||||||
int? copyright;
|
|
||||||
int? ctime;
|
|
||||||
String? desc;
|
|
||||||
int? duration;
|
|
||||||
Owner? owner;
|
|
||||||
String? pic;
|
|
||||||
String? pubLocation;
|
|
||||||
int? pubdate;
|
|
||||||
String? resourceType;
|
|
||||||
int? state;
|
|
||||||
String? subtitle;
|
|
||||||
int? time;
|
|
||||||
String? title;
|
|
||||||
String? tname;
|
|
||||||
int? videos;
|
|
||||||
int? view;
|
|
||||||
int? danmaku;
|
|
||||||
|
|
||||||
MemberCoinsDataModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
aid = json['aid'];
|
|
||||||
bvid = json['bvid'];
|
|
||||||
cid = json['cid'];
|
|
||||||
coins = json['coins'];
|
|
||||||
copyright = json['copyright'];
|
|
||||||
ctime = json['ctime'];
|
|
||||||
desc = json['desc'];
|
|
||||||
duration = json['duration'];
|
|
||||||
owner = Owner.fromJson(json['owner']);
|
|
||||||
pic = json['pic'];
|
|
||||||
pubLocation = json['pub_location'];
|
|
||||||
pubdate = json['pubdate'];
|
|
||||||
resourceType = json['resource_type'];
|
|
||||||
state = json['state'];
|
|
||||||
subtitle = json['subtitle'];
|
|
||||||
time = json['time'];
|
|
||||||
title = json['title'];
|
|
||||||
tname = json['tname'];
|
|
||||||
videos = json['videos'];
|
|
||||||
view = json['stat']['view'];
|
|
||||||
danmaku = json['stat']['danmaku'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Owner {
|
|
||||||
Owner({
|
|
||||||
this.mid,
|
|
||||||
this.name,
|
|
||||||
this.face,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? mid;
|
|
||||||
String? name;
|
|
||||||
String? face;
|
|
||||||
|
|
||||||
Owner.fromJson(Map<String, dynamic> json) {
|
|
||||||
mid = json['mid'];
|
|
||||||
name = json['name'];
|
|
||||||
face = json['face'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
class MemberSeasonsDataModel {
|
|
||||||
MemberSeasonsDataModel({
|
|
||||||
this.page,
|
|
||||||
this.seasonsList,
|
|
||||||
});
|
|
||||||
|
|
||||||
Map? page;
|
|
||||||
List<MemberSeasonsList>? seasonsList;
|
|
||||||
|
|
||||||
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
page = json['page'];
|
|
||||||
seasonsList = json['seasons_list'] != null
|
|
||||||
? json['seasons_list']
|
|
||||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
|
||||||
.toList()
|
|
||||||
: [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MemberSeasonsList {
|
|
||||||
MemberSeasonsList({
|
|
||||||
this.archives,
|
|
||||||
this.meta,
|
|
||||||
this.recentAids,
|
|
||||||
this.page,
|
|
||||||
});
|
|
||||||
|
|
||||||
List<MemberArchiveItem>? archives;
|
|
||||||
MamberMeta? meta;
|
|
||||||
List? recentAids;
|
|
||||||
Map? page;
|
|
||||||
|
|
||||||
MemberSeasonsList.fromJson(Map<String, dynamic> json) {
|
|
||||||
archives = json['archives'] != null
|
|
||||||
? json['archives']
|
|
||||||
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
|
||||||
.toList()
|
|
||||||
: [];
|
|
||||||
meta = MamberMeta.fromJson(json['meta']);
|
|
||||||
page = json['page'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MemberArchiveItem {
|
|
||||||
MemberArchiveItem({
|
|
||||||
this.aid,
|
|
||||||
this.bvid,
|
|
||||||
this.ctime,
|
|
||||||
this.duration,
|
|
||||||
this.pic,
|
|
||||||
this.cover,
|
|
||||||
this.pubdate,
|
|
||||||
this.view,
|
|
||||||
this.title,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? aid;
|
|
||||||
String? bvid;
|
|
||||||
int? ctime;
|
|
||||||
int? duration;
|
|
||||||
String? pic;
|
|
||||||
String? cover;
|
|
||||||
int? pubdate;
|
|
||||||
int? view;
|
|
||||||
String? title;
|
|
||||||
|
|
||||||
MemberArchiveItem.fromJson(Map<String, dynamic> json) {
|
|
||||||
aid = json['aid'];
|
|
||||||
bvid = json['bvid'];
|
|
||||||
ctime = json['ctime'];
|
|
||||||
duration = json['duration'];
|
|
||||||
pic = json['pic'];
|
|
||||||
cover = json['pic'];
|
|
||||||
pubdate = json['pubdate'];
|
|
||||||
view = json['stat']['view'];
|
|
||||||
title = json['title'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MamberMeta {
|
|
||||||
MamberMeta({
|
|
||||||
this.cover,
|
|
||||||
this.description,
|
|
||||||
this.mid,
|
|
||||||
this.name,
|
|
||||||
this.ptime,
|
|
||||||
this.seasonId,
|
|
||||||
this.total,
|
|
||||||
});
|
|
||||||
|
|
||||||
String? cover;
|
|
||||||
String? description;
|
|
||||||
int? mid;
|
|
||||||
String? name;
|
|
||||||
int? ptime;
|
|
||||||
int? seasonId;
|
|
||||||
int? total;
|
|
||||||
|
|
||||||
MamberMeta.fromJson(Map<String, dynamic> json) {
|
|
||||||
cover = json['cover'];
|
|
||||||
description = json['description'];
|
|
||||||
mid = json['mid'];
|
|
||||||
name = json['name'];
|
|
||||||
ptime = json['ptime'];
|
|
||||||
seasonId = json['season_id'];
|
|
||||||
total = json['total'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,80 +0,0 @@
|
|||||||
class AccountListModel {
|
|
||||||
AccountListModel({
|
|
||||||
this.mid,
|
|
||||||
this.name,
|
|
||||||
this.sex,
|
|
||||||
this.face,
|
|
||||||
this.sign,
|
|
||||||
this.rank,
|
|
||||||
this.level,
|
|
||||||
this.silence,
|
|
||||||
this.vip,
|
|
||||||
this.pendant,
|
|
||||||
this.nameplate,
|
|
||||||
this.official,
|
|
||||||
this.birthday,
|
|
||||||
this.isFakeAccount,
|
|
||||||
this.isDeleted,
|
|
||||||
this.inRegAudit,
|
|
||||||
this.faceNft,
|
|
||||||
this.faceNftNew,
|
|
||||||
this.isSeniorMember,
|
|
||||||
this.digitalId,
|
|
||||||
this.digitalType,
|
|
||||||
this.attestation,
|
|
||||||
this.expertInfo,
|
|
||||||
this.honours,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? mid;
|
|
||||||
String? name;
|
|
||||||
String? sex;
|
|
||||||
String? face;
|
|
||||||
String? sign;
|
|
||||||
int? rank;
|
|
||||||
int? level;
|
|
||||||
int? silence;
|
|
||||||
Map? vip;
|
|
||||||
Map? pendant;
|
|
||||||
Map? nameplate;
|
|
||||||
Map? official;
|
|
||||||
int? birthday;
|
|
||||||
int? isFakeAccount;
|
|
||||||
int? isDeleted;
|
|
||||||
int? inRegAudit;
|
|
||||||
int? faceNft;
|
|
||||||
int? faceNftNew;
|
|
||||||
int? isSeniorMember;
|
|
||||||
String? digitalId;
|
|
||||||
int? digitalType;
|
|
||||||
Map? attestation;
|
|
||||||
Map? expertInfo;
|
|
||||||
Map? honours;
|
|
||||||
|
|
||||||
AccountListModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
mid = json['mid'];
|
|
||||||
name = json['name'] ?? '';
|
|
||||||
sex = json['sex'];
|
|
||||||
face = json['face'];
|
|
||||||
sign = json['sign'];
|
|
||||||
rank = json['rank'];
|
|
||||||
level = json['level'];
|
|
||||||
silence = json['silence'];
|
|
||||||
vip = json['vip'];
|
|
||||||
pendant = json['pendant'];
|
|
||||||
nameplate = json['nameplate'];
|
|
||||||
official = json['official'];
|
|
||||||
birthday = json['birthday'];
|
|
||||||
isFakeAccount = json['is_fake_account'];
|
|
||||||
isDeleted = json['is_deleted'];
|
|
||||||
inRegAudit = json['in_reg_audit'];
|
|
||||||
faceNft = json['face_nft'];
|
|
||||||
faceNftNew = json['face_nft_new'];
|
|
||||||
isSeniorMember = json['is_senior_member'];
|
|
||||||
digitalId = json['digital_id'];
|
|
||||||
digitalType = json['digital_type'];
|
|
||||||
attestation = json['attestation'];
|
|
||||||
expertInfo = json['expert_info'];
|
|
||||||
honours = json['honours'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,226 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:pilipala/models/msg/account.dart';
|
|
||||||
|
|
||||||
class SessionDataModel {
|
|
||||||
SessionDataModel({
|
|
||||||
this.sessionList,
|
|
||||||
this.hasMore,
|
|
||||||
});
|
|
||||||
|
|
||||||
List? sessionList;
|
|
||||||
int? hasMore;
|
|
||||||
|
|
||||||
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
sessionList = json['session_list']
|
|
||||||
?.map<SessionList>((e) => SessionList.fromJson(e))
|
|
||||||
.toList();
|
|
||||||
hasMore = json['has_more'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionList {
|
|
||||||
SessionList({
|
|
||||||
this.talkerId,
|
|
||||||
this.sessionType,
|
|
||||||
this.atSeqno,
|
|
||||||
this.topTs,
|
|
||||||
this.groupName,
|
|
||||||
this.groupCover,
|
|
||||||
this.isFollow,
|
|
||||||
this.isDnd,
|
|
||||||
this.ackSeqno,
|
|
||||||
this.ackTs,
|
|
||||||
this.sessionTs,
|
|
||||||
this.unreadCount,
|
|
||||||
this.lastMsg,
|
|
||||||
this.groupType,
|
|
||||||
this.canFold,
|
|
||||||
this.status,
|
|
||||||
this.maxSeqno,
|
|
||||||
this.newPushMsg,
|
|
||||||
this.setting,
|
|
||||||
this.isGuardian,
|
|
||||||
this.isIntercept,
|
|
||||||
this.isTrust,
|
|
||||||
this.systemMsgType,
|
|
||||||
this.liveStatus,
|
|
||||||
this.bizMsgUnreadCount,
|
|
||||||
// this.userLabel,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? talkerId;
|
|
||||||
int? sessionType;
|
|
||||||
int? atSeqno;
|
|
||||||
int? topTs;
|
|
||||||
String? groupName;
|
|
||||||
String? groupCover;
|
|
||||||
int? isFollow;
|
|
||||||
int? isDnd;
|
|
||||||
int? ackSeqno;
|
|
||||||
int? ackTs;
|
|
||||||
int? sessionTs;
|
|
||||||
int? unreadCount;
|
|
||||||
LastMsg? lastMsg;
|
|
||||||
int? groupType;
|
|
||||||
int? canFold;
|
|
||||||
int? status;
|
|
||||||
int? maxSeqno;
|
|
||||||
int? newPushMsg;
|
|
||||||
int? setting;
|
|
||||||
int? isGuardian;
|
|
||||||
int? isIntercept;
|
|
||||||
int? isTrust;
|
|
||||||
int? systemMsgType;
|
|
||||||
int? liveStatus;
|
|
||||||
int? bizMsgUnreadCount;
|
|
||||||
// int? userLabel;
|
|
||||||
AccountListModel? accountInfo;
|
|
||||||
|
|
||||||
SessionList.fromJson(Map<String, dynamic> json) {
|
|
||||||
talkerId = json["talker_id"];
|
|
||||||
sessionType = json["session_type"];
|
|
||||||
atSeqno = json["at_seqno"];
|
|
||||||
topTs = json["top_ts"];
|
|
||||||
groupName = json["group_name"];
|
|
||||||
groupCover = json["group_cover"];
|
|
||||||
isFollow = json["is_follow"];
|
|
||||||
isDnd = json["is_dnd"];
|
|
||||||
ackSeqno = json["ack_seqno"];
|
|
||||||
ackTs = json["ack_ts"];
|
|
||||||
sessionTs = json["session_ts"];
|
|
||||||
unreadCount = json["unread_count"];
|
|
||||||
lastMsg =
|
|
||||||
json["last_msg"] != null ? LastMsg.fromJson(json["last_msg"]) : null;
|
|
||||||
groupType = json["group_type"];
|
|
||||||
canFold = json["can_fold"];
|
|
||||||
status = json["status"];
|
|
||||||
maxSeqno = json["max_seqno"];
|
|
||||||
newPushMsg = json["new_push_msg"];
|
|
||||||
setting = json["setting"];
|
|
||||||
isGuardian = json["is_guardian"];
|
|
||||||
isIntercept = json["is_intercept"];
|
|
||||||
isTrust = json["is_trust"];
|
|
||||||
systemMsgType = json["system_msg_type"];
|
|
||||||
liveStatus = json["live_status"];
|
|
||||||
bizMsgUnreadCount = json["biz_msg_unread_count"];
|
|
||||||
// userLabel = json["user_label"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LastMsg {
|
|
||||||
LastMsg({
|
|
||||||
this.senderIid,
|
|
||||||
this.receiverType,
|
|
||||||
this.receiverId,
|
|
||||||
this.msgType,
|
|
||||||
this.content,
|
|
||||||
this.msgSeqno,
|
|
||||||
this.timestamp,
|
|
||||||
this.atUids,
|
|
||||||
this.msgKey,
|
|
||||||
this.msgStatus,
|
|
||||||
this.notifyCode,
|
|
||||||
this.newFaceVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? senderIid;
|
|
||||||
int? receiverType;
|
|
||||||
int? receiverId;
|
|
||||||
int? msgType;
|
|
||||||
Map? content;
|
|
||||||
int? msgSeqno;
|
|
||||||
int? timestamp;
|
|
||||||
String? atUids;
|
|
||||||
int? msgKey;
|
|
||||||
int? msgStatus;
|
|
||||||
String? notifyCode;
|
|
||||||
int? newFaceVersion;
|
|
||||||
|
|
||||||
LastMsg.fromJson(Map<String, dynamic> json) {
|
|
||||||
senderIid = json['sender_uid'];
|
|
||||||
receiverType = json['receiver_type'];
|
|
||||||
receiverId = json['receiver_id'];
|
|
||||||
msgType = json['msg_type'];
|
|
||||||
content = jsonDecode(json['content']);
|
|
||||||
msgSeqno = json['msg_seqno'];
|
|
||||||
timestamp = json['timestamp'];
|
|
||||||
atUids = json['at_uids'];
|
|
||||||
msgKey = json['msg_key'];
|
|
||||||
msgStatus = json['msg_status'];
|
|
||||||
notifyCode = json['notify_code'];
|
|
||||||
newFaceVersion = json['new_face_version'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SessionMsgDataModel {
|
|
||||||
SessionMsgDataModel({
|
|
||||||
this.messages,
|
|
||||||
this.hasMore,
|
|
||||||
this.minSeqno,
|
|
||||||
this.maxSeqno,
|
|
||||||
this.eInfos,
|
|
||||||
});
|
|
||||||
|
|
||||||
List<MessageItem>? messages;
|
|
||||||
int? hasMore;
|
|
||||||
int? minSeqno;
|
|
||||||
int? maxSeqno;
|
|
||||||
List? eInfos;
|
|
||||||
|
|
||||||
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
messages = json['messages']
|
|
||||||
.map<MessageItem>((e) => MessageItem.fromJson(e))
|
|
||||||
.toList();
|
|
||||||
hasMore = json['has_more'];
|
|
||||||
minSeqno = json['min_seqno'];
|
|
||||||
maxSeqno = json['max_seqno'];
|
|
||||||
eInfos = json['e_infos'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MessageItem {
|
|
||||||
MessageItem({
|
|
||||||
this.senderUid,
|
|
||||||
this.receiverType,
|
|
||||||
this.receiverId,
|
|
||||||
this.msgType,
|
|
||||||
this.content,
|
|
||||||
this.msgSeqno,
|
|
||||||
this.timestamp,
|
|
||||||
this.atUids,
|
|
||||||
this.msgKey,
|
|
||||||
this.msgStatus,
|
|
||||||
this.notifyCode,
|
|
||||||
this.newFaceVersion,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? senderUid;
|
|
||||||
int? receiverType;
|
|
||||||
int? receiverId;
|
|
||||||
int? msgType;
|
|
||||||
Map? content;
|
|
||||||
int? msgSeqno;
|
|
||||||
int? timestamp;
|
|
||||||
List? atUids;
|
|
||||||
int? msgKey;
|
|
||||||
int? msgStatus;
|
|
||||||
String? notifyCode;
|
|
||||||
int? newFaceVersion;
|
|
||||||
|
|
||||||
MessageItem.fromJson(Map<String, dynamic> json) {
|
|
||||||
senderUid = json['sender_uid'];
|
|
||||||
receiverType = json['receiver_type'];
|
|
||||||
receiverId = json['receiver_id'];
|
|
||||||
// 1 文本 2 图片 18 系统提示 10 系统通知
|
|
||||||
msgType = json['msg_type'];
|
|
||||||
content = jsonDecode(json['content']);
|
|
||||||
msgSeqno = json['msg_seqno'];
|
|
||||||
timestamp = json['timestamp'];
|
|
||||||
atUids = json['at_uids'];
|
|
||||||
msgKey = json['msg_key'];
|
|
||||||
msgStatus = json['msg_status'];
|
|
||||||
notifyCode = json['notify_code'];
|
|
||||||
newFaceVersion = json['new_face_version'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
|
|
||||||
class PlayUrlModel {
|
class PlayUrlModel {
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
@ -31,6 +34,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
Divider(
|
||||||
|
thickness: 8,
|
||||||
|
height: 10,
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
Image.asset(
|
Image.asset(
|
||||||
'assets/images/logo/logo_android_2.png',
|
'assets/images/logo/logo_android_2.png',
|
||||||
width: 150,
|
width: 150,
|
||||||
@ -75,9 +83,9 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
Divider(
|
Divider(
|
||||||
thickness: 1,
|
thickness: 8,
|
||||||
height: 30,
|
height: 30,
|
||||||
color: Theme.of(context).colorScheme.outlineVariant,
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => _aboutController.githubUrl(),
|
onTap: () => _aboutController.githubUrl(),
|
||||||
@ -126,6 +134,11 @@ class _AboutPageState extends State<AboutPage> {
|
|||||||
title: const Text('赞助'),
|
title: const Text('赞助'),
|
||||||
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
|
||||||
),
|
),
|
||||||
|
Divider(
|
||||||
|
thickness: 8,
|
||||||
|
height: 30,
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
library member_archive;
|
library pl_audio_player;
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
export './view.dart';
|
||||||
|
export './controller.dart';
|
||||||
539
lib/pages/audio/view.dart
Normal file
539
lib/pages/audio/view.dart
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
// import 'package:just_audio_background/just_audio_background.dart';
|
||||||
|
|
||||||
|
class AudioPlayerPage extends StatefulWidget {
|
||||||
|
const AudioPlayerPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AudioPlayerPage> createState() => _AudioPlayerPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AudioPlayerPageState extends State<AudioPlayerPage> {
|
||||||
|
static int _nextMediaId = 0;
|
||||||
|
late AudioPlayer _player;
|
||||||
|
final _playlist = ConcatenatingAudioSource(children: [
|
||||||
|
ClippingAudioSource(
|
||||||
|
start: const Duration(seconds: 0),
|
||||||
|
end: const Duration(seconds: 90),
|
||||||
|
child: AudioSource.uri(Uri.parse(
|
||||||
|
"https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3")),
|
||||||
|
tag: MediaItem(
|
||||||
|
id: '${_nextMediaId++}',
|
||||||
|
album: "Science Friday",
|
||||||
|
title: "A Salute To Head-Scratching Science (30 seconds)",
|
||||||
|
artUri: Uri.parse(
|
||||||
|
"https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// AudioSource.uri(
|
||||||
|
// Uri.parse(
|
||||||
|
// "https://upos-sz-mirror08c.bilivideo.com/upgcxcode/05/52/1205825205/1205825205-1-16.mp4?e=ig8euxZM2rNcNbRVhwdVhwdlhWdVhwdVhoNvNC8BqJIzNbfq9rVEuxTEnE8L5F6VnEsSTx0vkX8fqJeYTj_lta53NCM=&uipk=5&nbs=1&deadline=1693821903&gen=playurlv2&os=08cbv&oi=1865700872&trid=bfc9c19f85c545dd8f4794ff97f4f57fh&mid=17340771&platform=html5&upsig=9bf98515091bb8a80e1950a03a2a0d68&uparams=e,uipk,nbs,deadline,gen,os,oi,trid,mid,platform&bvc=vod&nettype=0&f=h_0_0&bw=49663&logo=80000000"),
|
||||||
|
// headers: {
|
||||||
|
// 'user-agent':
|
||||||
|
// 'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15',
|
||||||
|
// 'referer': 'https://www.bilibili.com'
|
||||||
|
// },
|
||||||
|
// tag: MediaItem(
|
||||||
|
// id: '${_nextMediaId++}',
|
||||||
|
// album: "Science Friday",
|
||||||
|
// title: "A Salute To Head-Scratching Science",
|
||||||
|
// artUri: Uri.parse(
|
||||||
|
// "https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg"),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
AudioSource.uri(
|
||||||
|
Uri.parse("https://s3.amazonaws.com/scifri-segments/scifri201711241.mp3"),
|
||||||
|
tag: MediaItem(
|
||||||
|
id: '${_nextMediaId++}',
|
||||||
|
album: "Science Friday",
|
||||||
|
title: "From Cat Rheology To Operatic Incompetence",
|
||||||
|
artUri: Uri.parse(
|
||||||
|
"https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AudioSource.uri(
|
||||||
|
Uri.parse("asset:///audio/nature.mp3"),
|
||||||
|
tag: MediaItem(
|
||||||
|
id: '${_nextMediaId++}',
|
||||||
|
album: "Public Domain",
|
||||||
|
title: "Nature Sounds",
|
||||||
|
artUri: Uri.parse(
|
||||||
|
"https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_player = AudioPlayer();
|
||||||
|
|
||||||
|
_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_player.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _init() async {
|
||||||
|
final session = await AudioSession.instance;
|
||||||
|
await session.configure(const AudioSessionConfiguration.speech());
|
||||||
|
// Listen to errors during playback.
|
||||||
|
_player.playbackEventStream.listen((event) {},
|
||||||
|
onError: (Object e, StackTrace stackTrace) {
|
||||||
|
print('A stream error occurred: $e');
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
await _player.setAudioSource(_playlist);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
// Catch load errors: 404, invalid url ...
|
||||||
|
print("Error loading playlist: $e");
|
||||||
|
print(stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream<PositionData> get _positionDataStream =>
|
||||||
|
// Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
|
||||||
|
// _player.positionStream,
|
||||||
|
// _player.bufferedPositionStream,
|
||||||
|
// _player.durationStream,
|
||||||
|
// (position, bufferedPosition, duration) => PositionData(
|
||||||
|
// position, bufferedPosition, duration ?? Duration.zero));
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: StreamBuilder<SequenceState?>(
|
||||||
|
stream: _player.sequenceStateStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final state = snapshot.data;
|
||||||
|
if (state?.sequence.isEmpty ?? true) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
final metadata = state!.currentSource!.tag as MediaItem;
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Center(
|
||||||
|
child: Image.network(metadata.artUri.toString())),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(metadata.album!,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge),
|
||||||
|
Text(metadata.title),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ControlButtons(_player),
|
||||||
|
// StreamBuilder<PositionData>(
|
||||||
|
// stream: _positionDataStream,
|
||||||
|
// builder: (context, snapshot) {
|
||||||
|
// final positionData = snapshot.data;
|
||||||
|
// return SeekBar(
|
||||||
|
// duration: positionData?.duration ?? Duration.zero,
|
||||||
|
// position: positionData?.position ?? Duration.zero,
|
||||||
|
// bufferedPosition:
|
||||||
|
// positionData?.bufferedPosition ?? Duration.zero,
|
||||||
|
// onChangeEnd: (newPosition) {
|
||||||
|
// _player.seek(newPosition);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
StreamBuilder<LoopMode>(
|
||||||
|
stream: _player.loopModeStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final loopMode = snapshot.data ?? LoopMode.off;
|
||||||
|
const icons = [
|
||||||
|
Icon(Icons.repeat, color: Colors.grey),
|
||||||
|
Icon(Icons.repeat, color: Colors.orange),
|
||||||
|
Icon(Icons.repeat_one, color: Colors.orange),
|
||||||
|
];
|
||||||
|
const cycleModes = [
|
||||||
|
LoopMode.off,
|
||||||
|
LoopMode.all,
|
||||||
|
LoopMode.one,
|
||||||
|
];
|
||||||
|
final index = cycleModes.indexOf(loopMode);
|
||||||
|
return IconButton(
|
||||||
|
icon: icons[index],
|
||||||
|
onPressed: () {
|
||||||
|
_player.setLoopMode(cycleModes[
|
||||||
|
(cycleModes.indexOf(loopMode) + 1) %
|
||||||
|
cycleModes.length]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Playlist",
|
||||||
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StreamBuilder<bool>(
|
||||||
|
stream: _player.shuffleModeEnabledStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final shuffleModeEnabled = snapshot.data ?? false;
|
||||||
|
return IconButton(
|
||||||
|
icon: shuffleModeEnabled
|
||||||
|
? const Icon(Icons.shuffle, color: Colors.orange)
|
||||||
|
: const Icon(Icons.shuffle, color: Colors.grey),
|
||||||
|
onPressed: () async {
|
||||||
|
final enable = !shuffleModeEnabled;
|
||||||
|
if (enable) {
|
||||||
|
await _player.shuffle();
|
||||||
|
}
|
||||||
|
await _player.setShuffleModeEnabled(enable);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 240.0,
|
||||||
|
child: StreamBuilder<SequenceState?>(
|
||||||
|
stream: _player.sequenceStateStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final state = snapshot.data;
|
||||||
|
final sequence = state?.sequence ?? [];
|
||||||
|
return ReorderableListView(
|
||||||
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
|
if (oldIndex < newIndex) newIndex--;
|
||||||
|
_playlist.move(oldIndex, newIndex);
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < sequence.length; i++)
|
||||||
|
Dismissible(
|
||||||
|
key: ValueKey(sequence[i]),
|
||||||
|
background: Container(
|
||||||
|
color: Colors.redAccent,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: const Padding(
|
||||||
|
padding: EdgeInsets.only(right: 8.0),
|
||||||
|
child: Icon(Icons.delete, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onDismissed: (dismissDirection) {
|
||||||
|
_playlist.removeAt(i);
|
||||||
|
},
|
||||||
|
child: Material(
|
||||||
|
color: i == state!.currentIndex
|
||||||
|
? Colors.grey.shade300
|
||||||
|
: null,
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(sequence[i].tag.title as String),
|
||||||
|
onTap: () {
|
||||||
|
_player.seek(Duration.zero, index: i);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ControlButtons extends StatelessWidget {
|
||||||
|
final AudioPlayer player;
|
||||||
|
|
||||||
|
const ControlButtons(this.player, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.volume_up),
|
||||||
|
onPressed: () {
|
||||||
|
showSliderDialog(
|
||||||
|
context: context,
|
||||||
|
title: "Adjust volume",
|
||||||
|
divisions: 10,
|
||||||
|
min: 0.0,
|
||||||
|
max: 1.0,
|
||||||
|
stream: player.volumeStream,
|
||||||
|
onChanged: player.setVolume,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StreamBuilder<SequenceState?>(
|
||||||
|
stream: player.sequenceStateStream,
|
||||||
|
builder: (context, snapshot) => IconButton(
|
||||||
|
icon: const Icon(Icons.skip_previous),
|
||||||
|
onPressed: player.hasPrevious ? player.seekToPrevious : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StreamBuilder<PlayerState>(
|
||||||
|
stream: player.playerStateStream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final playerState = snapshot.data;
|
||||||
|
final processingState = playerState?.processingState;
|
||||||
|
final playing = playerState?.playing;
|
||||||
|
if (processingState == ProcessingState.loading ||
|
||||||
|
processingState == ProcessingState.buffering) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.all(8.0),
|
||||||
|
width: 64.0,
|
||||||
|
height: 64.0,
|
||||||
|
child: const CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
} else if (playing != true) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.play_arrow),
|
||||||
|
iconSize: 64.0,
|
||||||
|
onPressed: player.play,
|
||||||
|
);
|
||||||
|
} else if (processingState != ProcessingState.completed) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.pause),
|
||||||
|
iconSize: 64.0,
|
||||||
|
onPressed: player.pause,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.replay),
|
||||||
|
iconSize: 64.0,
|
||||||
|
onPressed: () => player.seek(Duration.zero,
|
||||||
|
index: player.effectiveIndices!.first),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StreamBuilder<SequenceState?>(
|
||||||
|
stream: player.sequenceStateStream,
|
||||||
|
builder: (context, snapshot) => IconButton(
|
||||||
|
icon: const Icon(Icons.skip_next),
|
||||||
|
onPressed: player.hasNext ? player.seekToNext : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StreamBuilder<double>(
|
||||||
|
stream: player.speedStream,
|
||||||
|
builder: (context, snapshot) => IconButton(
|
||||||
|
icon: Text("${snapshot.data?.toStringAsFixed(1)}x",
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||||
|
onPressed: () {
|
||||||
|
showSliderDialog(
|
||||||
|
context: context,
|
||||||
|
title: "Adjust speed",
|
||||||
|
divisions: 10,
|
||||||
|
min: 0.5,
|
||||||
|
max: 1.5,
|
||||||
|
stream: player.speedStream,
|
||||||
|
onChanged: player.setSpeed,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSliderDialog({
|
||||||
|
required BuildContext context,
|
||||||
|
required String title,
|
||||||
|
required int divisions,
|
||||||
|
required double min,
|
||||||
|
required double max,
|
||||||
|
String valueSuffix = '',
|
||||||
|
required Stream<double> stream,
|
||||||
|
required ValueChanged<double> onChanged,
|
||||||
|
}) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(title, textAlign: TextAlign.center),
|
||||||
|
content: StreamBuilder<double>(
|
||||||
|
stream: stream,
|
||||||
|
builder: (context, snapshot) => SizedBox(
|
||||||
|
height: 100.0,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text('${snapshot.data?.toStringAsFixed(1)}$valueSuffix',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: 'Fixed',
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24.0)),
|
||||||
|
Slider(
|
||||||
|
divisions: divisions,
|
||||||
|
min: min,
|
||||||
|
max: max,
|
||||||
|
value: snapshot.data ?? 1.0,
|
||||||
|
onChanged: onChanged,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PositionData {
|
||||||
|
final Duration position;
|
||||||
|
final Duration bufferedPosition;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
PositionData(this.position, this.bufferedPosition, this.duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeekBar extends StatefulWidget {
|
||||||
|
final Duration duration;
|
||||||
|
final Duration position;
|
||||||
|
final Duration bufferedPosition;
|
||||||
|
final ValueChanged<Duration>? onChanged;
|
||||||
|
final ValueChanged<Duration>? onChangeEnd;
|
||||||
|
|
||||||
|
const SeekBar({
|
||||||
|
Key? key,
|
||||||
|
required this.duration,
|
||||||
|
required this.position,
|
||||||
|
required this.bufferedPosition,
|
||||||
|
this.onChanged,
|
||||||
|
this.onChangeEnd,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
SeekBarState createState() => SeekBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class SeekBarState extends State<SeekBar> {
|
||||||
|
double? _dragValue;
|
||||||
|
late SliderThemeData _sliderThemeData;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
_sliderThemeData = SliderTheme.of(context).copyWith(
|
||||||
|
trackHeight: 2.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
SliderTheme(
|
||||||
|
data: _sliderThemeData.copyWith(
|
||||||
|
thumbShape: HiddenThumbComponentShape(),
|
||||||
|
activeTrackColor: Colors.blue.shade100,
|
||||||
|
inactiveTrackColor: Colors.grey.shade300,
|
||||||
|
),
|
||||||
|
child: ExcludeSemantics(
|
||||||
|
child: Slider(
|
||||||
|
min: 0.0,
|
||||||
|
max: widget.duration.inMilliseconds.toDouble(),
|
||||||
|
value: min(widget.bufferedPosition.inMilliseconds.toDouble(),
|
||||||
|
widget.duration.inMilliseconds.toDouble()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_dragValue = value;
|
||||||
|
});
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(Duration(milliseconds: value.round()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
if (widget.onChangeEnd != null) {
|
||||||
|
widget.onChangeEnd!(Duration(milliseconds: value.round()));
|
||||||
|
}
|
||||||
|
_dragValue = null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliderTheme(
|
||||||
|
data: _sliderThemeData.copyWith(
|
||||||
|
inactiveTrackColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
child: Slider(
|
||||||
|
min: 0.0,
|
||||||
|
max: widget.duration.inMilliseconds.toDouble(),
|
||||||
|
value: min(_dragValue ?? widget.position.inMilliseconds.toDouble(),
|
||||||
|
widget.duration.inMilliseconds.toDouble()),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_dragValue = value;
|
||||||
|
});
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(Duration(milliseconds: value.round()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
if (widget.onChangeEnd != null) {
|
||||||
|
widget.onChangeEnd!(Duration(milliseconds: value.round()));
|
||||||
|
}
|
||||||
|
_dragValue = null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 16.0,
|
||||||
|
bottom: 0.0,
|
||||||
|
child: Text(
|
||||||
|
RegExp(r'((^0*[1-9]\d*:)?\d{2}:\d{2})\.\d+$')
|
||||||
|
.firstMatch("$_remaining")
|
||||||
|
?.group(1) ??
|
||||||
|
'$_remaining',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration get _remaining => widget.duration - widget.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HiddenThumbComponentShape extends SliderComponentShape {
|
||||||
|
@override
|
||||||
|
Size getPreferredSize(bool isEnabled, bool isDiscrete) => Size.zero;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(
|
||||||
|
PaintingContext context,
|
||||||
|
Offset center, {
|
||||||
|
required Animation<double> activationAnimation,
|
||||||
|
required Animation<double> enableAnimation,
|
||||||
|
required bool isDiscrete,
|
||||||
|
required TextPainter labelPainter,
|
||||||
|
required RenderBox parentBox,
|
||||||
|
required SliderThemeData sliderTheme,
|
||||||
|
required TextDirection textDirection,
|
||||||
|
required double value,
|
||||||
|
required double textScaleFactor,
|
||||||
|
required Size sizeWithOverflow,
|
||||||
|
}) {}
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/pages/rcmd/view.dart';
|
import 'package:pilipala/pages/rcmd/view.dart';
|
||||||
|
|
||||||
@ -36,8 +35,6 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
scrollController = _bangumidController.scrollController;
|
scrollController = _bangumidController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
StreamController<bool> searchBarStream =
|
|
||||||
Get.find<HomeController>().searchBarStream;
|
|
||||||
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
_futureBuilderFuture = _bangumidController.queryBangumiListFeed();
|
||||||
_futureBuilderFutureFollow = _bangumidController.queryBangumiFollow();
|
_futureBuilderFutureFollow = _bangumidController.queryBangumiFollow();
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
@ -54,10 +51,8 @@ class _BangumiPageState extends State<BangumiPage>
|
|||||||
scrollController.position.userScrollDirection;
|
scrollController.position.userScrollDirection;
|
||||||
if (direction == ScrollDirection.forward) {
|
if (direction == ScrollDirection.forward) {
|
||||||
mainStream.add(true);
|
mainStream.add(true);
|
||||||
searchBarStream.add(true);
|
|
||||||
} else if (direction == ScrollDirection.reverse) {
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
mainStream.add(false);
|
mainStream.add(false);
|
||||||
searchBarStream.add(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -95,12 +95,8 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
// 根据position判断是否有已缓存弹幕。没有则请求对应段
|
// 根据position判断是否有已缓存弹幕。没有则请求对应段
|
||||||
int segIndex = (currentPosition / (6 * 60 * 1000)).ceil();
|
int segIndex = (currentPosition / (6 * 60 * 1000)).ceil();
|
||||||
segIndex = segIndex < 1 ? 1 : segIndex;
|
segIndex = segIndex < 1 ? 1 : segIndex;
|
||||||
print('🌹🌹: ${segIndex}');
|
if (ctr.dmSegList[segIndex - 1].elems.isEmpty &&
|
||||||
print('🌹🌹: ${ctr.dmSegList.length}');
|
!ctr.hasrequestSeg.contains(segIndex - 1)) {
|
||||||
print('🌹🌹: ${ctr.hasrequestSeg.contains(segIndex - 1)}');
|
|
||||||
if (segIndex - 1 >= ctr.dmSegList.length ||
|
|
||||||
(ctr.dmSegList[segIndex - 1].elems.isEmpty &&
|
|
||||||
!ctr.hasrequestSeg.contains(segIndex - 1))) {
|
|
||||||
ctr.hasrequestSeg.add(segIndex - 1);
|
ctr.hasrequestSeg.add(segIndex - 1);
|
||||||
ctr.currentSegIndex = segIndex;
|
ctr.currentSegIndex = segIndex;
|
||||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
|||||||
@ -231,15 +231,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
height: 6,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
// 富文本
|
// 富文本
|
||||||
InlineSpan richNode(item, context) {
|
InlineSpan richNode(item, context) {
|
||||||
@ -9,11 +11,13 @@ InlineSpan richNode(item, context) {
|
|||||||
TextStyle authorStyle =
|
TextStyle authorStyle =
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||||
List<InlineSpan> spanChilds = [];
|
List<InlineSpan> spanChilds = [];
|
||||||
|
String contentType = 'desc';
|
||||||
|
|
||||||
dynamic richTextNodes;
|
dynamic richTextNodes;
|
||||||
if (item.modules.moduleDynamic.desc != null) {
|
if (item.modules.moduleDynamic.desc != null) {
|
||||||
richTextNodes = item.modules.moduleDynamic.desc.richTextNodes;
|
richTextNodes = item.modules.moduleDynamic.desc.richTextNodes;
|
||||||
} else if (item.modules.moduleDynamic.major != null) {
|
} else if (item.modules.moduleDynamic.major != null) {
|
||||||
|
contentType = 'major';
|
||||||
// 动态页面 richTextNodes 层级可能与主页动态层级不同
|
// 动态页面 richTextNodes 层级可能与主页动态层级不同
|
||||||
richTextNodes =
|
richTextNodes =
|
||||||
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
|
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
|
||||||
|
|||||||
@ -56,73 +56,67 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
floating: true,
|
floating: true,
|
||||||
pinned: false,
|
pinned: false,
|
||||||
delegate: _SliverHeaderDelegate(
|
delegate: _SliverHeaderDelegate(
|
||||||
height: 124,
|
height: 90,
|
||||||
child: Column(
|
child: Container(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
height: 90,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
color: Theme.of(context).colorScheme.background,
|
||||||
children: [
|
child: Row(
|
||||||
Container(
|
children: [
|
||||||
color: Theme.of(context).colorScheme.background,
|
Expanded(
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
child: ListView(
|
||||||
child: Row(
|
scrollDirection: Axis.horizontal,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
controller: scrollController,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: [
|
const SizedBox(width: 10),
|
||||||
const Text('最新关注'),
|
if (liveList.isNotEmpty) ...[
|
||||||
GestureDetector(
|
for (int i = 0; i < liveList.length; i++) ...[
|
||||||
onTap: () {
|
upItemBuild(liveList[i], i)
|
||||||
feedBack();
|
],
|
||||||
Get.toNamed('/follow?mid=${userInfo.mid}');
|
VerticalDivider(
|
||||||
},
|
indent: 20,
|
||||||
child: Container(
|
endIndent: 40,
|
||||||
padding: const EdgeInsets.only(top: 5, bottom: 5),
|
width: 26,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
for (int i = 0; i < upList.length; i++) ...[
|
||||||
|
upItemBuild(upList[i], i)
|
||||||
|
],
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Material(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => {
|
||||||
|
feedBack(),
|
||||||
|
Get.toNamed('/follow?mid=${userInfo.mid}')
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer
|
||||||
|
.withOpacity(0.3),
|
||||||
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'查看全部',
|
'全部',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.outline),
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSecondaryContainer),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
Container(
|
)),
|
||||||
height: 90,
|
),
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: ListView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
controller: scrollController,
|
|
||||||
children: [
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
if (liveList.isNotEmpty) ...[
|
|
||||||
for (int i = 0; i < liveList.length; i++) ...[
|
|
||||||
upItemBuild(liveList[i], i)
|
|
||||||
],
|
|
||||||
VerticalDivider(
|
|
||||||
indent: 20,
|
|
||||||
endIndent: 40,
|
|
||||||
width: 26,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary
|
|
||||||
.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
for (int i = 0; i < upList.length; i++) ...[
|
|
||||||
upItemBuild(upList[i], i)
|
|
||||||
],
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/common/business_type.dart';
|
import 'package:pilipala/models/common/business_type.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/pages/history/index.dart';
|
||||||
import 'package:pilipala/pages/history_search/index.dart';
|
import 'package:pilipala/pages/history_search/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
|||||||
@ -131,6 +131,7 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
|
|||||||
onChoose: null,
|
onChoose: null,
|
||||||
onUpdateMultiple: () => null,
|
onUpdateMultiple: () => null,
|
||||||
);
|
);
|
||||||
|
;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -17,10 +15,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
RxString userFace = ''.obs;
|
RxString userFace = ''.obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
Box setting = GStrorage.setting;
|
|
||||||
late final StreamController<bool> searchBarStream =
|
|
||||||
StreamController<bool>.broadcast();
|
|
||||||
late bool hideSearchBar;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -39,8 +33,6 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
length: tabs.length,
|
length: tabs.length,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
hideSearchBar =
|
|
||||||
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onRefresh() {
|
void onRefresh() {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/pages/mine/index.dart';
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
@ -19,17 +18,11 @@ class _HomePageState extends State<HomePage>
|
|||||||
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final HomeController _homeController = Get.put(HomeController());
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
late Stream<bool> stream;
|
Stream<bool> stream = Get.find<MainController>().bottomBarStream.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
stream = _homeController.searchBarStream.stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
showUserBottonSheet() {
|
showUserBottonSheet() {
|
||||||
feedBack();
|
feedBack();
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@ -53,9 +46,7 @@ class _HomePageState extends State<HomePage>
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
CustomAppBar(
|
CustomAppBar(
|
||||||
stream: _homeController.hideSearchBar
|
stream: stream,
|
||||||
? stream
|
|
||||||
: StreamController<bool>.broadcast().stream,
|
|
||||||
ctr: _homeController,
|
ctr: _homeController,
|
||||||
callback: showUserBottonSheet,
|
callback: showUserBottonSheet,
|
||||||
),
|
),
|
||||||
@ -74,7 +65,6 @@ class _HomePageState extends State<HomePage>
|
|||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
splashBorderRadius: BorderRadius.circular(10),
|
splashBorderRadius: BorderRadius.circular(10),
|
||||||
tabAlignment: TabAlignment.center,
|
|
||||||
onTap: (value) {
|
onTap: (value) {
|
||||||
feedBack();
|
feedBack();
|
||||||
if (_homeController.initialIndex == value) {
|
if (_homeController.initialIndex == value) {
|
||||||
@ -128,7 +118,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
height: snapshot.data
|
height: snapshot.data
|
||||||
? MediaQuery.of(context).padding.top + 52
|
? MediaQuery.of(context).padding.top + 52
|
||||||
: MediaQuery.of(context).padding.top - 10,
|
: MediaQuery.of(context).padding.top,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
left: 20,
|
left: 20,
|
||||||
@ -139,13 +129,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(child: SearchPage()),
|
const Expanded(child: SearchPage()),
|
||||||
if (ctr!.userLogin.value) ...[
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 6),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => Get.toNamed('/whisper'),
|
|
||||||
icon: const Icon(Icons.notifications_none))
|
|
||||||
],
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => ctr!.userLogin.value
|
() => ctr!.userLogin.value
|
||||||
? Stack(
|
? Stack(
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
|
|||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
|
||||||
import 'package:pilipala/pages/hot/controller.dart';
|
import 'package:pilipala/pages/hot/controller.dart';
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
|
|
||||||
@ -36,8 +35,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
scrollController = _hotController.scrollController;
|
scrollController = _hotController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
StreamController<bool> searchBarStream =
|
|
||||||
Get.find<HomeController>().searchBarStream;
|
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
@ -52,10 +49,8 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
scrollController.position.userScrollDirection;
|
scrollController.position.userScrollDirection;
|
||||||
if (direction == ScrollDirection.forward) {
|
if (direction == ScrollDirection.forward) {
|
||||||
mainStream.add(true);
|
mainStream.add(true);
|
||||||
searchBarStream.add(true);
|
|
||||||
} else if (direction == ScrollDirection.reverse) {
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
mainStream.add(false);
|
mainStream.add(false);
|
||||||
searchBarStream.add(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:pilipala/common/widgets/html_render.dart';
|
|||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import 'package:pilipala/common/skeleton/video_card_v.dart';
|
|||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/pages/rcmd/index.dart';
|
import 'package:pilipala/pages/rcmd/index.dart';
|
||||||
|
|
||||||
@ -39,8 +38,6 @@ class _LivePageState extends State<LivePage>
|
|||||||
scrollController = _liveController.scrollController;
|
scrollController = _liveController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
StreamController<bool> searchBarStream =
|
|
||||||
Get.find<HomeController>().searchBarStream;
|
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
@ -55,10 +52,8 @@ class _LivePageState extends State<LivePage>
|
|||||||
scrollController.position.userScrollDirection;
|
scrollController.position.userScrollDirection;
|
||||||
if (direction == ScrollDirection.forward) {
|
if (direction == ScrollDirection.forward) {
|
||||||
mainStream.add(true);
|
mainStream.add(true);
|
||||||
searchBarStream.add(true);
|
|
||||||
} else if (direction == ScrollDirection.reverse) {
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
mainStream.add(false);
|
mainStream.add(false);
|
||||||
searchBarStream.add(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -72,7 +67,6 @@ class _LivePageState extends State<LivePage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
|
||||||
return Container(
|
return Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: const EdgeInsets.only(
|
margin: const EdgeInsets.only(
|
||||||
|
|||||||
@ -52,6 +52,7 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
|
||||||
Widget childWhenDisabled = Scaffold(
|
Widget childWhenDisabled = Scaffold(
|
||||||
primary: true,
|
primary: true,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
import 'package:floating/floating.dart';
|
import 'package:floating/floating.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/pages/liveRoom/index.dart';
|
import 'package:pilipala/pages/liveRoom/index.dart';
|
||||||
@ -42,6 +43,10 @@ class _BottomControlState extends State<BottomControl> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
const textStyle = TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 12,
|
||||||
|
);
|
||||||
return AppBar(
|
return AppBar(
|
||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
|
|||||||
@ -55,7 +55,6 @@ class MainController extends GetxController {
|
|||||||
StreamController<bool>.broadcast();
|
StreamController<bool>.broadcast();
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
DateTime? _lastPressedAt;
|
DateTime? _lastPressedAt;
|
||||||
late bool hideTabBar;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -63,7 +62,6 @@ class MainController extends GetxController {
|
|||||||
if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
|
if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
|
||||||
Utils.checkUpdata();
|
Utils.checkUpdata();
|
||||||
}
|
}
|
||||||
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> onBackPressed(BuildContext context) {
|
Future<bool> onBackPressed(BuildContext context) {
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -115,8 +113,8 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
MediaQuery.of(context).size.width * 9 / 16;
|
MediaQuery.of(context).size.width * 9 / 16;
|
||||||
localCache.put('sheetHeight', sheetHeight);
|
localCache.put('sheetHeight', sheetHeight);
|
||||||
localCache.put('statusBarHeight', statusBarHeight);
|
localCache.put('statusBarHeight', statusBarHeight);
|
||||||
return PopScope(
|
return WillPopScope(
|
||||||
onPopInvoked: (bool status) => _mainController.onBackPressed(context),
|
onWillPop: () => _mainController.onBackPressed(context),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: FadeTransition(
|
body: FadeTransition(
|
||||||
@ -144,9 +142,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: StreamBuilder(
|
bottomNavigationBar: StreamBuilder(
|
||||||
stream: _mainController.hideTabBar
|
stream: _mainController.bottomBarStream.stream,
|
||||||
? _mainController.bottomBarStream.stream
|
|
||||||
: StreamController<bool>.broadcast().stream,
|
|
||||||
initialData: true,
|
initialData: true,
|
||||||
builder: (context, AsyncSnapshot snapshot) {
|
builder: (context, AsyncSnapshot snapshot) {
|
||||||
return AnimatedSlide(
|
return AnimatedSlide(
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
|
||||||
|
|
||||||
class MemberArchiveController extends GetxController {
|
class ArchiveController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
ArchiveController(this.mid);
|
||||||
late int mid;
|
int? mid;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
RxMap<String, String> currentOrder = <String, String>{}.obs;
|
RxMap<String, String> currentOrder = <String, String>{}.obs;
|
||||||
@ -14,27 +12,20 @@ class MemberArchiveController extends GetxController {
|
|||||||
{'type': 'click', 'label': '最多播放'},
|
{'type': 'click', 'label': '最多播放'},
|
||||||
{'type': 'stow', 'label': '最多收藏'},
|
{'type': 'stow', 'label': '最多收藏'},
|
||||||
];
|
];
|
||||||
RxList<VListItemModel> archivesList = <VListItemModel>[].obs;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
mid = int.parse(Get.parameters['mid']!);
|
mid ??= int.parse(Get.parameters['mid']!);
|
||||||
|
print('🐶🐶: $mid');
|
||||||
currentOrder.value = orderList.first;
|
currentOrder.value = orderList.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户投稿
|
// 获取用户投稿
|
||||||
Future getMemberArchive(type) async {
|
Future getMemberArchive() async {
|
||||||
if (type == 'onRefresh') {
|
|
||||||
pn = 1;
|
|
||||||
}
|
|
||||||
var res = await MemberHttp.memberArchive(
|
var res = await MemberHttp.memberArchive(
|
||||||
mid: mid,
|
mid: mid, pn: pn, order: currentOrder['type']!);
|
||||||
pn: pn,
|
|
||||||
order: currentOrder['type']!,
|
|
||||||
);
|
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
archivesList.addAll(res['data'].list.vlist);
|
|
||||||
count = res['data'].page['count'];
|
count = res['data'].page['count'];
|
||||||
pn += 1;
|
pn += 1;
|
||||||
}
|
}
|
||||||
@ -43,16 +34,11 @@ class MemberArchiveController extends GetxController {
|
|||||||
|
|
||||||
toggleSort() async {
|
toggleSort() async {
|
||||||
pn = 1;
|
pn = 1;
|
||||||
int index = orderList.indexOf(currentOrder);
|
int index = orderList.indexOf(currentOrder.value);
|
||||||
if (index == orderList.length - 1) {
|
if (index == orderList.length - 1) {
|
||||||
currentOrder.value = orderList.first;
|
currentOrder.value = orderList.first;
|
||||||
} else {
|
} else {
|
||||||
currentOrder.value = orderList[index + 1];
|
currentOrder.value = orderList[index + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 上拉加载
|
|
||||||
Future onLoad() async {
|
|
||||||
getMemberArchive('onLoad');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
4
lib/pages/member/archive/index.dart
Normal file
4
lib/pages/member/archive/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library archive_panel;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export 'index.dart';
|
||||||
240
lib/pages/member/archive/view.dart
Normal file
240
lib/pages/member/archive/view.dart
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loading_more_list/loading_more_list.dart';
|
||||||
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
|
import 'package:pilipala/pages/member/archive/index.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||||
|
|
||||||
|
class ArchivePanel extends StatefulWidget {
|
||||||
|
final int? mid;
|
||||||
|
const ArchivePanel({super.key, this.mid});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ArchivePanel> createState() => _ArchivePanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArchivePanelState extends State<ArchivePanel>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
DateTime lastRefreshTime = DateTime.now();
|
||||||
|
late final LoadMoreListSource source;
|
||||||
|
late final ArchiveController _archiveController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
print('🐶🐶: ${widget.mid}');
|
||||||
|
_archiveController = Get.put(ArchiveController(widget.mid),
|
||||||
|
tag: Utils.makeHeroTag(widget.mid));
|
||||||
|
source = LoadMoreListSource(_archiveController);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return PullToRefreshNotification(
|
||||||
|
onRefresh: () async {
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
maxDragOffset: 50,
|
||||||
|
child: GlowNotificationWidget(
|
||||||
|
Column(
|
||||||
|
children: <Widget>[
|
||||||
|
// 下拉刷新指示器
|
||||||
|
// PullToRefreshContainer(
|
||||||
|
// (PullToRefreshScrollNotificationInfo? info) {
|
||||||
|
// return PullToRefreshHeader(info, lastRefreshTime);
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(left: 14, top: 8, bottom: 8, right: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text('排序方式'),
|
||||||
|
SizedBox(
|
||||||
|
height: 35,
|
||||||
|
width: 85,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
// _archiveController.order = 'click';
|
||||||
|
// _archiveController.pn = 1;
|
||||||
|
_archiveController.toggleSort();
|
||||||
|
source.refresh(true);
|
||||||
|
// LoadMoreListSource().loadData();
|
||||||
|
},
|
||||||
|
child: Obx(
|
||||||
|
() => AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
transitionBuilder:
|
||||||
|
(Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation, child: child);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
_archiveController.currentOrder['label']!,
|
||||||
|
key: ValueKey<String>(
|
||||||
|
_archiveController.currentOrder['label']!),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: LoadingMoreList<VListItemModel>(
|
||||||
|
ListConfig<VListItemModel>(
|
||||||
|
sourceList: source,
|
||||||
|
itemBuilder:
|
||||||
|
(BuildContext c, VListItemModel item, int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
VideoCardH(videoItem: item)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return VideoCardH(videoItem: item);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indicatorBuilder: _buildIndicator,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
showGlowLeading: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||||
|
TextStyle style =
|
||||||
|
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||||
|
Widget? widget;
|
||||||
|
switch (status) {
|
||||||
|
case IndicatorStatus.none:
|
||||||
|
widget = Container(height: 0.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.loadingMoreBusying:
|
||||||
|
widget = Text('加载中...', style: style);
|
||||||
|
widget = _setbackground(false, widget, height: 60.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.fullScreenBusying:
|
||||||
|
widget = Text('加载中...', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.error:
|
||||||
|
|
||||||
|
/// TODO 异常逻辑
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(false, widget);
|
||||||
|
|
||||||
|
widget = GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: widget,
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.fullScreenError:
|
||||||
|
|
||||||
|
/// TODO 异常逻辑
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
widget = GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: widget,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.noMoreLoad:
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(false, widget, height: 60.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.empty:
|
||||||
|
widget = Text('用户没有投稿', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||||
|
widget = Padding(
|
||||||
|
padding: height == double.infinity
|
||||||
|
? EdgeInsets.zero
|
||||||
|
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: height,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: widget,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getIndicator(BuildContext context) {
|
||||||
|
final TargetPlatform platform = Theme.of(context).platform;
|
||||||
|
return platform == TargetPlatform.iOS
|
||||||
|
? const CupertinoActivityIndicator(
|
||||||
|
animating: true,
|
||||||
|
radius: 16.0,
|
||||||
|
)
|
||||||
|
: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||||
|
late ArchiveController ctr;
|
||||||
|
LoadMoreListSource(this.ctr);
|
||||||
|
bool forceRefresh = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
bool isSuccess = false;
|
||||||
|
var res = await ctr.getMemberArchive();
|
||||||
|
if (res['status']) {
|
||||||
|
if (ctr.pn == 2) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
addAll(res['data'].list.vlist);
|
||||||
|
}
|
||||||
|
if (length < res['data'].page['count']) {
|
||||||
|
isSuccess = true;
|
||||||
|
} else {
|
||||||
|
isSuccess = false;
|
||||||
|
}
|
||||||
|
return isSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> refresh([bool clearBeforeRequest = false]) async {
|
||||||
|
// _hasMore = true;
|
||||||
|
// pageindex = 1;
|
||||||
|
// //force to refresh list when you don't want clear list before request
|
||||||
|
// //for the case, if your list already has 20 items.
|
||||||
|
forceRefresh = !clearBeforeRequest;
|
||||||
|
var result = await super.refresh(clearBeforeRequest);
|
||||||
|
|
||||||
|
forceRefresh = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,6 @@ import 'package:pilipala/http/member.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/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/models/member/coin.dart';
|
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
@ -14,17 +13,16 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
class MemberController extends GetxController {
|
class MemberController extends GetxController {
|
||||||
late int mid;
|
late int mid;
|
||||||
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
|
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
|
||||||
late Map userStat;
|
Map? userStat;
|
||||||
RxString face = ''.obs;
|
RxString face = ''.obs;
|
||||||
String? heroTag;
|
String? heroTag;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
late int ownerMid;
|
late int ownerMid;
|
||||||
// 投稿列表
|
// 投稿列表
|
||||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||||
dynamic userInfo;
|
var userInfo;
|
||||||
RxInt attribute = (-1).obs;
|
RxInt attribute = (-1).obs;
|
||||||
RxString attributeText = '关注'.obs;
|
RxString attributeText = '关注'.obs;
|
||||||
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -40,7 +38,6 @@ class MemberController extends GetxController {
|
|||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
Future<Map<String, dynamic>> getInfo() async {
|
Future<Map<String, dynamic>> getInfo() async {
|
||||||
await getMemberStat();
|
await getMemberStat();
|
||||||
await getMemberView();
|
|
||||||
var res = await MemberHttp.memberInfo(mid: mid);
|
var res = await MemberHttp.memberInfo(mid: mid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
memberInfo.value = res['data'];
|
memberInfo.value = res['data'];
|
||||||
@ -58,14 +55,13 @@ class MemberController extends GetxController {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户播放数 获赞数
|
// Future getMemberCardInfo() async {
|
||||||
Future<Map<String, dynamic>> getMemberView() async {
|
// var res = await MemberHttp.memberCardInfo(mid: mid);
|
||||||
var res = await MemberHttp.memberView(mid: mid);
|
// if (res['status']) {
|
||||||
if (res['status']) {
|
// print(userStat);
|
||||||
userStat.addAll(res['data']);
|
// }
|
||||||
}
|
// return res;
|
||||||
return res;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// 关注/取关up
|
// 关注/取关up
|
||||||
Future actionRelationMod() async {
|
Future actionRelationMod() async {
|
||||||
@ -177,35 +173,4 @@ class MemberController extends GetxController {
|
|||||||
void shareUser() {
|
void shareUser() {
|
||||||
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
|
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求专栏
|
|
||||||
Future getMemberSeasons() async {
|
|
||||||
if (userInfo == null) return;
|
|
||||||
var res = await MemberHttp.getMemberSeasons(mid, 1, 10);
|
|
||||||
if (!res['status']) {
|
|
||||||
SmartDialog.showToast("用户专栏请求异常:${res['msg']}");
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 请求投币视频
|
|
||||||
Future getRecentCoinVideo() async {
|
|
||||||
if (userInfo == null) return;
|
|
||||||
var res = await MemberHttp.getRecentCoinVideo(mid: mid);
|
|
||||||
recentCoinsList.value = res['data'];
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳转查看动态
|
|
||||||
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
|
||||||
|
|
||||||
// 跳转查看投稿
|
|
||||||
void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid');
|
|
||||||
|
|
||||||
// 跳转查看专栏
|
|
||||||
void pushSeasonsPage() {}
|
|
||||||
// 跳转查看最近投币
|
|
||||||
void pushRecentCoinsPage() async {
|
|
||||||
if (recentCoinsList.isNotEmpty) {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
31
lib/pages/member/dynamic/controller.dart
Normal file
31
lib/pages/member/dynamic/controller.dart
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
|
||||||
|
class MemberDynamicPanelController extends GetxController {
|
||||||
|
MemberDynamicPanelController(this.mid);
|
||||||
|
int? mid;
|
||||||
|
String offset = '';
|
||||||
|
int count = 0;
|
||||||
|
bool hasMore = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
mid ??= int.parse(Get.parameters['mid']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future getMemberDynamic() async {
|
||||||
|
if (!hasMore) {
|
||||||
|
return {'status': false};
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.memberDynamic(
|
||||||
|
offset: offset,
|
||||||
|
mid: mid,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
offset = res['data'].offset;
|
||||||
|
hasMore = res['data'].hasMore;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
library member_like;
|
library dynamic_panel;
|
||||||
|
|
||||||
export './controller.dart';
|
export './controller.dart';
|
||||||
export './view.dart';
|
export './view.dart';
|
||||||
152
lib/pages/member/dynamic/view.dart
Normal file
152
lib/pages/member/dynamic/view.dart
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:loading_more_list/loading_more_list.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MemberDynamicPanel extends StatefulWidget {
|
||||||
|
final int? mid;
|
||||||
|
const MemberDynamicPanel({super.key, this.mid});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
DateTime lastRefreshTime = DateTime.now();
|
||||||
|
late final LoadMoreListSource source;
|
||||||
|
late final MemberDynamicPanelController _dynamicController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_dynamicController = Get.put(MemberDynamicPanelController(widget.mid),
|
||||||
|
tag: Utils.makeHeroTag(widget.mid));
|
||||||
|
source = LoadMoreListSource(_dynamicController);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return LoadingMoreList<DynamicItemModel>(
|
||||||
|
ListConfig<DynamicItemModel>(
|
||||||
|
sourceList: source,
|
||||||
|
itemBuilder: (BuildContext c, DynamicItemModel item, int index) {
|
||||||
|
return DynamicPanel(item: item);
|
||||||
|
},
|
||||||
|
indicatorBuilder: _buildIndicator,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||||
|
TextStyle style =
|
||||||
|
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||||
|
Widget? widget;
|
||||||
|
switch (status) {
|
||||||
|
case IndicatorStatus.none:
|
||||||
|
widget = Container(height: 0.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.loadingMoreBusying:
|
||||||
|
widget = Text('加载中...', style: style);
|
||||||
|
widget = _setbackground(false, widget, height: 60.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.fullScreenBusying:
|
||||||
|
widget = Text('加载中...', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.error:
|
||||||
|
|
||||||
|
/// TODO 异常逻辑
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(false, widget);
|
||||||
|
|
||||||
|
widget = GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: widget,
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.fullScreenError:
|
||||||
|
|
||||||
|
/// TODO 异常逻辑
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
widget = GestureDetector(
|
||||||
|
onTap: () {},
|
||||||
|
child: widget,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.noMoreLoad:
|
||||||
|
widget = Text('没有更多了', style: style);
|
||||||
|
widget = _setbackground(false, widget, height: 60.0);
|
||||||
|
break;
|
||||||
|
case IndicatorStatus.empty:
|
||||||
|
widget = Text('用户没有投稿', style: style);
|
||||||
|
widget = _setbackground(true, widget);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||||
|
widget = Padding(
|
||||||
|
padding: height == double.infinity
|
||||||
|
? EdgeInsets.zero
|
||||||
|
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: height,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: widget,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget getIndicator(BuildContext context) {
|
||||||
|
final TargetPlatform platform = Theme.of(context).platform;
|
||||||
|
return platform == TargetPlatform.iOS
|
||||||
|
? const CupertinoActivityIndicator(
|
||||||
|
animating: true,
|
||||||
|
radius: 16.0,
|
||||||
|
)
|
||||||
|
: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.0,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||||
|
late MemberDynamicPanelController ctr;
|
||||||
|
LoadMoreListSource(this.ctr);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
|
bool isSuccess = false;
|
||||||
|
var res = await ctr.getMemberDynamic();
|
||||||
|
if (res['status']) {
|
||||||
|
addAll(res['data'].items);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (res['data'].hasMore) {
|
||||||
|
isSuccess = true;
|
||||||
|
} else {
|
||||||
|
isSuccess = false;
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
return isSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,16 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/member/archive/view.dart';
|
||||||
|
import 'package:pilipala/pages/member/dynamic/index.dart';
|
||||||
import 'package:pilipala/pages/member/index.dart';
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
import 'widgets/conis.dart';
|
|
||||||
import 'widgets/profile.dart';
|
import 'widgets/profile.dart';
|
||||||
import 'widgets/seasons.dart';
|
|
||||||
|
|
||||||
class MemberPage extends StatefulWidget {
|
class MemberPage extends StatefulWidget {
|
||||||
const MemberPage({super.key});
|
const MemberPage({super.key});
|
||||||
@ -23,10 +23,9 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
late MemberController _memberController;
|
late MemberController _memberController;
|
||||||
late Future _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late Future _memberSeasonsFuture;
|
|
||||||
late Future _memberCoinsFuture;
|
|
||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
|
late TabController _tabController;
|
||||||
final StreamController<bool> appbarStream = StreamController<bool>();
|
final StreamController<bool> appbarStream = StreamController<bool>();
|
||||||
late int mid;
|
late int mid;
|
||||||
|
|
||||||
@ -36,13 +35,12 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
mid = int.parse(Get.parameters['mid']!);
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
|
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
|
||||||
_memberController = Get.put(MemberController(), tag: heroTag);
|
_memberController = Get.put(MemberController(), tag: heroTag);
|
||||||
|
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||||
_futureBuilderFuture = _memberController.getInfo();
|
_futureBuilderFuture = _memberController.getInfo();
|
||||||
_memberSeasonsFuture = _memberController.getMemberSeasons();
|
|
||||||
_memberCoinsFuture = _memberController.getRecentCoinVideo();
|
|
||||||
_extendNestCtr.addListener(
|
_extendNestCtr.addListener(
|
||||||
() {
|
() {
|
||||||
double offset = _extendNestCtr.position.pixels;
|
double offset = _extendNestCtr.position.pixels;
|
||||||
if (offset > 100) {
|
if (offset > 230) {
|
||||||
appbarStream.add(true);
|
appbarStream.add(true);
|
||||||
} else {
|
} else {
|
||||||
appbarStream.add(false);
|
appbarStream.add(false);
|
||||||
@ -61,222 +59,183 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
primary: true,
|
primary: true,
|
||||||
body: Column(
|
body: ExtendedNestedScrollView(
|
||||||
children: [
|
controller: _extendNestCtr,
|
||||||
AppBar(
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||||
title: StreamBuilder(
|
return <Widget>[
|
||||||
stream: appbarStream.stream,
|
SliverAppBar(
|
||||||
initialData: false,
|
pinned: false,
|
||||||
builder: (context, AsyncSnapshot snapshot) {
|
primary: true,
|
||||||
return AnimatedOpacity(
|
elevation: 0,
|
||||||
opacity: snapshot.data ? 1 : 0,
|
scrolledUnderElevation: 1,
|
||||||
curve: Curves.easeOut,
|
forceElevated: innerBoxIsScrolled,
|
||||||
duration: const Duration(milliseconds: 500),
|
expandedHeight: 290,
|
||||||
child: Row(
|
titleSpacing: 0,
|
||||||
children: [
|
title: StreamBuilder(
|
||||||
Row(
|
stream: appbarStream.stream,
|
||||||
children: [
|
initialData: false,
|
||||||
Obx(
|
builder: (context, AsyncSnapshot snapshot) {
|
||||||
() => NetworkImgLayer(
|
return AnimatedOpacity(
|
||||||
width: 35,
|
opacity: snapshot.data ? 1 : 0,
|
||||||
height: 35,
|
curve: Curves.easeOut,
|
||||||
type: 'avatar',
|
duration: const Duration(milliseconds: 500),
|
||||||
src: _memberController.face.value,
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Obx(
|
||||||
|
() => NetworkImgLayer(
|
||||||
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
type: 'avatar',
|
||||||
|
src: _memberController.face.value,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 10),
|
||||||
const SizedBox(width: 10),
|
Obx(
|
||||||
Obx(
|
() => Text(
|
||||||
() => Text(
|
_memberController.memberInfo.value.name ?? '',
|
||||||
_memberController.memberInfo.value.name ?? '',
|
style: TextStyle(
|
||||||
style: TextStyle(
|
color: Theme.of(context)
|
||||||
color: Theme.of(context)
|
.colorScheme
|
||||||
.colorScheme
|
.onBackground,
|
||||||
.onBackground,
|
fontSize: 14),
|
||||||
fontSize: 14),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.more_vert),
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
if (_memberController.ownerMid !=
|
||||||
|
_memberController.mid) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => _memberController.blockUser(),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.block, size: 19),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(_memberController.attribute.value != 128
|
||||||
|
? '加入黑名单'
|
||||||
|
: '移除黑名单'),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => Get.toNamed(
|
|
||||||
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
|
||||||
icon: const Icon(Icons.search_outlined),
|
|
||||||
),
|
|
||||||
PopupMenuButton(
|
|
||||||
icon: const Icon(Icons.more_vert),
|
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
|
||||||
if (_memberController.ownerMid != _memberController.mid) ...[
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () => _memberController.blockUser(),
|
onTap: () => _memberController.shareUser(),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.block, size: 19),
|
const Icon(Icons.share_outlined, size: 19),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(_memberController.attribute.value != 128
|
Text(_memberController.ownerMid !=
|
||||||
? '加入黑名单'
|
_memberController.mid
|
||||||
: '移除黑名单'),
|
? '分享UP主'
|
||||||
|
: '分享我的主页'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
PopupMenuItem(
|
|
||||||
onTap: () => _memberController.shareUser(),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.share_outlined, size: 19),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Text(_memberController.ownerMid != _memberController.mid
|
|
||||||
? '分享UP主'
|
|
||||||
: '分享我的主页'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _extendNestCtr,
|
|
||||||
child: Padding(
|
|
||||||
padding: EdgeInsets.only(
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
|
||||||
),
|
),
|
||||||
child: Column(
|
const SizedBox(width: 4),
|
||||||
|
],
|
||||||
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
|
background: Stack(
|
||||||
children: [
|
children: [
|
||||||
profileWidget(),
|
|
||||||
|
|
||||||
/// 动态链接
|
|
||||||
ListTile(
|
|
||||||
onTap: _memberController.pushDynamicsPage,
|
|
||||||
title: const Text('Ta的动态'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 视频
|
|
||||||
ListTile(
|
|
||||||
onTap: _memberController.pushArchivesPage,
|
|
||||||
title: const Text('Ta的投稿'),
|
|
||||||
trailing:
|
|
||||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 专栏
|
|
||||||
ListTile(
|
|
||||||
onTap: () {},
|
|
||||||
title: const Text('Ta的专栏'),
|
|
||||||
),
|
|
||||||
MediaQuery.removePadding(
|
|
||||||
removeTop: true,
|
|
||||||
removeBottom: true,
|
|
||||||
context: context,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: StyleString.safeSpace,
|
|
||||||
right: StyleString.safeSpace,
|
|
||||||
),
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _memberSeasonsFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState ==
|
|
||||||
ConnectionState.done) {
|
|
||||||
if (snapshot.data == null) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
if (snapshot.data['status']) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
if (data['data'].seasonsList.isEmpty) {
|
|
||||||
return commenWidget('用户没有设置专栏');
|
|
||||||
} else {
|
|
||||||
return MemberSeasonsPanel(data: data['data']);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 收藏
|
|
||||||
|
|
||||||
/// 追番
|
|
||||||
/// 最近投币
|
|
||||||
Obx(
|
Obx(
|
||||||
() => _memberController.recentCoinsList.isNotEmpty
|
() => _memberController.face.value != ''
|
||||||
? ListTile(
|
? Positioned.fill(
|
||||||
onTap: () {},
|
bottom: 10,
|
||||||
title: const Text('最近投币的视频'),
|
child: Container(
|
||||||
// trailing: const Icon(Icons.arrow_forward_outlined,
|
decoration: BoxDecoration(
|
||||||
// size: 19),
|
image: DecorationImage(
|
||||||
|
fit: BoxFit.fitWidth,
|
||||||
|
image: NetworkImage(
|
||||||
|
_memberController.face.value),
|
||||||
|
alignment: Alignment.topCenter,
|
||||||
|
isAntiAlias: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
foregroundDecoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.background
|
||||||
|
.withOpacity(0.44),
|
||||||
|
Theme.of(context).colorScheme.background,
|
||||||
|
],
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
stops: const [0.0, 0.46],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
MediaQuery.removePadding(
|
Positioned(
|
||||||
removeTop: true,
|
left: 0,
|
||||||
removeBottom: true,
|
right: 0,
|
||||||
context: context,
|
bottom: 0,
|
||||||
child: Padding(
|
height: 20,
|
||||||
padding: const EdgeInsets.only(
|
child: Container(
|
||||||
left: StyleString.safeSpace,
|
color: Theme.of(context).colorScheme.background,
|
||||||
right: StyleString.safeSpace,
|
|
||||||
),
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _memberCoinsFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState ==
|
|
||||||
ConnectionState.done) {
|
|
||||||
if (snapshot.data == null) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
if (snapshot.data['status']) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
return MemberCoinsPanel(data: data['data']);
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 最近点赞
|
profileWidget(),
|
||||||
// ListTile(
|
|
||||||
// onTap: () {},
|
|
||||||
// title: const Text('最近点赞的视频'),
|
|
||||||
// trailing:
|
|
||||||
// const Icon(Icons.arrow_forward_outlined, size: 19),
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
];
|
||||||
],
|
},
|
||||||
|
pinnedHeaderSliverHeightBuilder: () {
|
||||||
|
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||||
|
},
|
||||||
|
onlyOneScrollInBody: true,
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 50,
|
||||||
|
child: TabBar(controller: _tabController, tabs: const [
|
||||||
|
Tab(text: '主页'),
|
||||||
|
Tab(text: '动态'),
|
||||||
|
Tab(text: '投稿'),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: TabBarView(
|
||||||
|
controller: _tabController,
|
||||||
|
children: [
|
||||||
|
const Text('主页'),
|
||||||
|
MemberDynamicPanel(mid: mid),
|
||||||
|
ArchivePanel(mid: mid),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget profileWidget() {
|
Widget profileWidget() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
|
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
@ -291,7 +250,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
profile(_memberController),
|
profile(_memberController),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 14),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
@ -301,7 +260,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium!
|
.bodyLarge!
|
||||||
.copyWith(fontWeight: FontWeight.bold),
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
@ -373,11 +332,29 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 4),
|
||||||
if (_memberController.memberInfo.value.sign != '')
|
if (_memberController.memberInfo.value.sign != '')
|
||||||
SelectableText(
|
SelectableText(
|
||||||
_memberController.memberInfo.value.sign!,
|
_memberController.memberInfo.value.sign!,
|
||||||
),
|
maxLines: _memberController
|
||||||
|
.memberInfo.value.official!['title'] !=
|
||||||
|
''
|
||||||
|
? 1
|
||||||
|
: 2,
|
||||||
|
style: const TextStyle(
|
||||||
|
overflow: TextOverflow.ellipsis),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
content: SelectableText(_memberController
|
||||||
|
.memberInfo.value.sign!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -394,22 +371,4 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget commenWidget(msg) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 20,
|
|
||||||
bottom: 30,
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
msg,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/models/member/coin.dart';
|
|
||||||
import 'package:pilipala/pages/member_coin/widgets/item.dart';
|
|
||||||
|
|
||||||
class MemberCoinsPanel extends StatelessWidget {
|
|
||||||
final List<MemberCoinsDataModel>? data;
|
|
||||||
const MemberCoinsPanel({super.key, this.data});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, boxConstraints) {
|
|
||||||
return GridView.builder(
|
|
||||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2, // Use a fixed count for GridView
|
|
||||||
crossAxisSpacing: StyleString.safeSpace,
|
|
||||||
mainAxisSpacing: StyleString.safeSpace,
|
|
||||||
childAspectRatio: 0.94,
|
|
||||||
),
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: data!.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
return MemberCoinsItem(coinItem: data![i]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -11,7 +11,7 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
return Builder(
|
return Builder(
|
||||||
builder: ((context) {
|
builder: ((context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
|
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Hero(
|
Hero(
|
||||||
@ -78,8 +78,7 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
const EdgeInsets.only(top: 10, left: 10, right: 10),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
@ -137,14 +136,8 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
const Text('-',
|
||||||
!loadingStatus
|
style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
? Utils.numFormat(
|
|
||||||
ctr.userStat!['likes'],
|
|
||||||
)
|
|
||||||
: '-',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold)),
|
|
||||||
Text(
|
Text(
|
||||||
'获赞',
|
'获赞',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
|
||||||
import 'package:pilipala/models/member/seasons.dart';
|
|
||||||
import 'package:pilipala/pages/member_seasons/widgets/item.dart';
|
|
||||||
|
|
||||||
class MemberSeasonsPanel extends StatelessWidget {
|
|
||||||
final MemberSeasonsDataModel? data;
|
|
||||||
const MemberSeasonsPanel({super.key, this.data});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemCount: data!.seasonsList!.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
MemberSeasonsList item = data!.seasonsList![index];
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 12, right: 4),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 12, left: 4),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
item.meta!.name!,
|
|
||||||
maxLines: 1,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall!,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
PBadge(
|
|
||||||
stack: 'relative',
|
|
||||||
size: 'small',
|
|
||||||
text: item.meta!.total.toString(),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
SizedBox(
|
|
||||||
width: 35,
|
|
||||||
height: 35,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () => Get.toNamed(
|
|
||||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.arrow_forward,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LayoutBuilder(
|
|
||||||
builder: (context, boxConstraints) {
|
|
||||||
return GridView.builder(
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2, // Use a fixed count for GridView
|
|
||||||
crossAxisSpacing: StyleString.safeSpace,
|
|
||||||
mainAxisSpacing: StyleString.safeSpace,
|
|
||||||
childAspectRatio: 0.94,
|
|
||||||
),
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: item.archives!.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
return MemberSeasonsItem(seasonItem: item.archives![i]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,120 +0,0 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
|
||||||
import 'controller.dart';
|
|
||||||
|
|
||||||
class MemberArchivePage extends StatefulWidget {
|
|
||||||
const MemberArchivePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MemberArchivePage> createState() => _MemberArchivePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MemberArchivePageState extends State<MemberArchivePage> {
|
|
||||||
final MemberArchiveController _memberArchivesController =
|
|
||||||
Get.put(MemberArchiveController());
|
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
late ScrollController scrollController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_futureBuilderFuture =
|
|
||||||
_memberArchivesController.getMemberArchive('onRefresh');
|
|
||||||
scrollController = _memberArchivesController.scrollController;
|
|
||||||
scrollController.addListener(
|
|
||||||
() {
|
|
||||||
if (scrollController.position.pixels >=
|
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle(
|
|
||||||
'member_archives', const Duration(milliseconds: 500), () {
|
|
||||||
_memberArchivesController.onLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('他的投稿'),
|
|
||||||
// actions: [
|
|
||||||
// Obx(
|
|
||||||
// () => PopupMenuButton<String>(
|
|
||||||
// padding: EdgeInsets.zero,
|
|
||||||
// tooltip: '投稿排序',
|
|
||||||
// icon: Icon(
|
|
||||||
// Icons.more_vert_outlined,
|
|
||||||
// color: Theme.of(context).colorScheme.outline,
|
|
||||||
// ),
|
|
||||||
// position: PopupMenuPosition.under,
|
|
||||||
// onSelected: (String type) {},
|
|
||||||
// itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
|
||||||
// for (var i in _memberArchivesController.orderList) ...[
|
|
||||||
// PopupMenuItem<String>(
|
|
||||||
// onTap: () {},
|
|
||||||
// value: _memberArchivesController.currentOrder['label'],
|
|
||||||
// child: Row(
|
|
||||||
// mainAxisSize: MainAxisSize.min,
|
|
||||||
// children: [
|
|
||||||
// Text(i['label']!),
|
|
||||||
// if (_memberArchivesController.currentOrder['label'] ==
|
|
||||||
// i['label']) ...[
|
|
||||||
// const SizedBox(width: 10),
|
|
||||||
// const Icon(Icons.done, size: 20),
|
|
||||||
// ],
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ]
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
),
|
|
||||||
body: CustomScrollView(
|
|
||||||
controller: _memberArchivesController.scrollController,
|
|
||||||
slivers: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.data != null) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
List list = _memberArchivesController.archivesList;
|
|
||||||
if (data['status']) {
|
|
||||||
return Obx(
|
|
||||||
() => list.isNotEmpty
|
|
||||||
? SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) {
|
|
||||||
return VideoCardH(
|
|
||||||
videoItem: list[index],
|
|
||||||
showOwner: false,
|
|
||||||
showPubdate: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
childCount: list.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SliverToBoxAdapter(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class MemberCoinController extends GetxController {}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
library member_coin;
|
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class MemberCoinPage extends StatefulWidget {
|
|
||||||
const MemberCoinPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MemberCoinPage> createState() => _MemberCoinPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MemberCoinPageState extends State<MemberCoinPage> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
|
||||||
import 'package:pilipala/http/search.dart';
|
|
||||||
import 'package:pilipala/models/member/coin.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
class MemberCoinsItem extends StatelessWidget {
|
|
||||||
final MemberCoinsDataModel coinItem;
|
|
||||||
|
|
||||||
const MemberCoinsItem({
|
|
||||||
Key? key,
|
|
||||||
required this.coinItem,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String heroTag = Utils.makeHeroTag(coinItem.aid);
|
|
||||||
return Card(
|
|
||||||
elevation: 0,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
int cid =
|
|
||||||
await SearchHttp.ab2c(aid: coinItem.aid, bvid: coinItem.bvid);
|
|
||||||
Get.toNamed('/video?bvid=${coinItem.bvid}&cid=$cid',
|
|
||||||
arguments: {'videoItem': coinItem, 'heroTag': heroTag});
|
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: StyleString.aspectRatio,
|
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
NetworkImgLayer(
|
|
||||||
src: coinItem.pic,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
if (coinItem.duration != null)
|
|
||||||
PBadge(
|
|
||||||
bottom: 6,
|
|
||||||
right: 6,
|
|
||||||
type: 'gray',
|
|
||||||
text: Utils.timeFormat(coinItem.duration),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
coinItem.title!,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatView(
|
|
||||||
view: coinItem.view,
|
|
||||||
theme: 'gray',
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
Utils.CustomStamp_str(
|
|
||||||
timestamp: coinItem.pubdate, date: 'MM-DD'),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/http/member.dart';
|
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
|
||||||
|
|
||||||
class MemberDynamicsController extends GetxController {
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
late int mid;
|
|
||||||
String offset = '';
|
|
||||||
int count = 0;
|
|
||||||
bool hasMore = true;
|
|
||||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
mid = int.parse(Get.parameters['mid']!);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future getMemberDynamic(type) async {
|
|
||||||
if (type == 'onRefresh') {
|
|
||||||
offset = '';
|
|
||||||
dynamicsList.clear();
|
|
||||||
}
|
|
||||||
var res = await MemberHttp.memberDynamic(
|
|
||||||
offset: offset,
|
|
||||||
mid: mid,
|
|
||||||
);
|
|
||||||
if (res['status']) {
|
|
||||||
dynamicsList.addAll(res['data'].items);
|
|
||||||
offset = res['data'].offset;
|
|
||||||
hasMore = res['data'].hasMore;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上拉加载
|
|
||||||
Future onLoad() async {
|
|
||||||
getMemberDynamic('onLoad');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
library member_dynamics;
|
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
|
||||||
@ -1,86 +0,0 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/pages/member_dynamics/index.dart';
|
|
||||||
|
|
||||||
import '../dynamics/widgets/dynamic_panel.dart';
|
|
||||||
|
|
||||||
class MemberDynamicsPage extends StatefulWidget {
|
|
||||||
const MemberDynamicsPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MemberDynamicsPage> createState() => _MemberDynamicsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
|
||||||
final MemberDynamicsController _memberDynamicController =
|
|
||||||
Get.put(MemberDynamicsController());
|
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
late ScrollController scrollController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_futureBuilderFuture =
|
|
||||||
_memberDynamicController.getMemberDynamic('onRefresh');
|
|
||||||
scrollController = _memberDynamicController.scrollController;
|
|
||||||
scrollController.addListener(
|
|
||||||
() {
|
|
||||||
if (scrollController.position.pixels >=
|
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle(
|
|
||||||
'member_dynamics', const Duration(milliseconds: 500), () {
|
|
||||||
_memberDynamicController.onLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_memberDynamicController.scrollController.removeListener(() {});
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('他的动态'),
|
|
||||||
),
|
|
||||||
body: CustomScrollView(
|
|
||||||
controller: _memberDynamicController.scrollController,
|
|
||||||
slivers: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
List list = _memberDynamicController.dynamicsList;
|
|
||||||
if (data['status']) {
|
|
||||||
return Obx(
|
|
||||||
() => list.isNotEmpty
|
|
||||||
? SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate(
|
|
||||||
(context, index) {
|
|
||||||
return DynamicPanel(item: list[index]);
|
|
||||||
},
|
|
||||||
childCount: list.length,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SliverToBoxAdapter(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SliverToBoxAdapter();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
|
|
||||||
class MemberLikeController extends GetxController {}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class MemberLikePage extends StatefulWidget {
|
|
||||||
const MemberLikePage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MemberLikePage> createState() => _MemberLikePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MemberLikePageState extends State<MemberLikePage> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/http/member.dart';
|
|
||||||
import 'package:pilipala/models/member/seasons.dart';
|
|
||||||
|
|
||||||
class MemberSeasonsController extends GetxController {
|
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
late int mid;
|
|
||||||
late int seasonId;
|
|
||||||
int pn = 1;
|
|
||||||
int ps = 30;
|
|
||||||
int count = 0;
|
|
||||||
RxList<MemberArchiveItem> seasonsList = <MemberArchiveItem>[].obs;
|
|
||||||
late Map page;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
mid = int.parse(Get.parameters['mid']!);
|
|
||||||
seasonId = int.parse(Get.parameters['seasonId']!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取专栏详情
|
|
||||||
Future getSeasonDetail(type) async {
|
|
||||||
if (type == 'onRefresh') {
|
|
||||||
pn = 1;
|
|
||||||
}
|
|
||||||
var res = await MemberHttp.getSeasonDetail(
|
|
||||||
mid: mid,
|
|
||||||
seasonId: seasonId,
|
|
||||||
pn: pn,
|
|
||||||
ps: ps,
|
|
||||||
sortReverse: false,
|
|
||||||
);
|
|
||||||
if (res['status']) {
|
|
||||||
seasonsList.addAll(res['data'].archives);
|
|
||||||
page = res['data'].page;
|
|
||||||
pn += 1;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上拉加载
|
|
||||||
Future onLoad() async {
|
|
||||||
getSeasonDetail('onLoad');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
library member_seasons;
|
|
||||||
|
|
||||||
export 'controller.dart';
|
|
||||||
export 'view.dart';
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'controller.dart';
|
|
||||||
import 'widgets/item.dart';
|
|
||||||
|
|
||||||
class MemberSeasonsPage extends StatefulWidget {
|
|
||||||
const MemberSeasonsPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MemberSeasonsPage> createState() => _MemberSeasonsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
|
|
||||||
final MemberSeasonsController _memberSeasonsController =
|
|
||||||
Get.put(MemberSeasonsController());
|
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
late ScrollController scrollController;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_futureBuilderFuture =
|
|
||||||
_memberSeasonsController.getSeasonDetail('onRefresh');
|
|
||||||
scrollController = _memberSeasonsController.scrollController;
|
|
||||||
scrollController.addListener(
|
|
||||||
() {
|
|
||||||
if (scrollController.position.pixels >=
|
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle(
|
|
||||||
'member_archives', const Duration(milliseconds: 500), () {
|
|
||||||
_memberSeasonsController.onLoad();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('他的专栏'),
|
|
||||||
),
|
|
||||||
body: Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
left: StyleString.safeSpace,
|
|
||||||
right: StyleString.safeSpace,
|
|
||||||
),
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _memberSeasonsController.scrollController,
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.data != null) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
List list = _memberSeasonsController.seasonsList;
|
|
||||||
if (data['status']) {
|
|
||||||
return Obx(
|
|
||||||
() => list.isNotEmpty
|
|
||||||
? LayoutBuilder(
|
|
||||||
builder: (context, boxConstraints) {
|
|
||||||
return GridView.builder(
|
|
||||||
gridDelegate:
|
|
||||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: 2,
|
|
||||||
crossAxisSpacing: StyleString.safeSpace,
|
|
||||||
mainAxisSpacing: StyleString.safeSpace,
|
|
||||||
childAspectRatio: 0.94,
|
|
||||||
),
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: _memberSeasonsController
|
|
||||||
.seasonsList.length,
|
|
||||||
itemBuilder: (context, i) {
|
|
||||||
return MemberSeasonsItem(
|
|
||||||
seasonItem: _memberSeasonsController
|
|
||||||
.seasonsList[i],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
|
||||||
import 'package:pilipala/http/search.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
class MemberSeasonsItem extends StatelessWidget {
|
|
||||||
final dynamic seasonItem;
|
|
||||||
|
|
||||||
const MemberSeasonsItem({
|
|
||||||
Key? key,
|
|
||||||
required this.seasonItem,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
String heroTag = Utils.makeHeroTag(seasonItem.aid);
|
|
||||||
return Card(
|
|
||||||
elevation: 0,
|
|
||||||
clipBehavior: Clip.hardEdge,
|
|
||||||
margin: EdgeInsets.zero,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
int cid =
|
|
||||||
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
|
|
||||||
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
|
|
||||||
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
|
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: StyleString.aspectRatio,
|
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: seasonItem.pic,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (seasonItem.duration != null)
|
|
||||||
PBadge(
|
|
||||||
bottom: 6,
|
|
||||||
right: 6,
|
|
||||||
type: 'gray',
|
|
||||||
text: Utils.timeFormat(seasonItem.duration),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
seasonItem.title,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatView(
|
|
||||||
view: seasonItem.view,
|
|
||||||
theme: 'gray',
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
Text(
|
|
||||||
Utils.CustomStamp_str(
|
|
||||||
timestamp: seasonItem.pubdate, date: 'MM-DD'),
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -159,6 +159,7 @@ class _ImagePreviewState extends State<ImagePreview>
|
|||||||
_previewController.onChange(index),
|
_previewController.onChange(index),
|
||||||
canScrollPage: (GestureDetails? gestureDetails) =>
|
canScrollPage: (GestureDetails? gestureDetails) =>
|
||||||
gestureDetails!.totalScale! <= 1.0,
|
gestureDetails!.totalScale! <= 1.0,
|
||||||
|
preloadPagesCount: 2,
|
||||||
itemCount: widget.imgList!.length,
|
itemCount: widget.imgList!.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return ExtendedImage.network(
|
return ExtendedImage.network(
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_v.dart';
|
import 'package:pilipala/common/widgets/video_card_v.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
|
||||||
import 'package:pilipala/pages/main/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
@ -38,8 +37,6 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
ScrollController scrollController = _rcmdController.scrollController;
|
ScrollController scrollController = _rcmdController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
Get.find<MainController>().bottomBarStream;
|
Get.find<MainController>().bottomBarStream;
|
||||||
StreamController<bool> searchBarStream =
|
|
||||||
Get.find<HomeController>().searchBarStream;
|
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
@ -50,14 +47,13 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
_rcmdController.onLoad();
|
_rcmdController.onLoad();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final ScrollDirection direction =
|
final ScrollDirection direction =
|
||||||
scrollController.position.userScrollDirection;
|
scrollController.position.userScrollDirection;
|
||||||
if (direction == ScrollDirection.forward) {
|
if (direction == ScrollDirection.forward) {
|
||||||
mainStream.add(true);
|
mainStream.add(true);
|
||||||
searchBarStream.add(true);
|
|
||||||
} else if (direction == ScrollDirection.reverse) {
|
} else if (direction == ScrollDirection.reverse) {
|
||||||
mainStream.add(false);
|
mainStream.add(false);
|
||||||
searchBarStream.add(false);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -86,7 +82,6 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
controller: _rcmdController.scrollController,
|
controller: _rcmdController.scrollController,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding:
|
padding:
|
||||||
@ -157,10 +152,11 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
// crossAxisCount = 1;
|
// crossAxisCount = 1;
|
||||||
// }
|
// }
|
||||||
int crossAxisCount = ctr.crossAxisCount.value;
|
int crossAxisCount = ctr.crossAxisCount.value;
|
||||||
double mainAxisExtent = (Get.size.width /
|
double mainAxisExtent =
|
||||||
crossAxisCount /
|
(Get.size.width / crossAxisCount / StyleString.aspectRatio) +
|
||||||
StyleString.aspectRatio) +
|
(crossAxisCount == 1
|
||||||
(crossAxisCount == 1 ? 68 : MediaQuery.textScalerOf(context).scale(86));
|
? 68
|
||||||
|
: 86 * MediaQuery.of(context).textScaleFactor);
|
||||||
return SliverGrid(
|
return SliverGrid(
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
// 行间距
|
// 行间距
|
||||||
@ -195,8 +191,8 @@ class _RcmdPageState extends State<RcmdPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadingMore extends StatelessWidget {
|
class LoadingMore extends StatelessWidget {
|
||||||
final dynamic ctr;
|
dynamic ctr;
|
||||||
const LoadingMore({super.key, this.ctr});
|
LoadingMore({super.key, this.ctr});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -207,13 +203,12 @@ class LoadingMore extends StatelessWidget {
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (ctr != null) {
|
if (ctr != null) {
|
||||||
ctr!.isLoadingMore = true;
|
|
||||||
ctr!.onLoad();
|
ctr!.onLoad();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'点击加载更多 👇',
|
'加载更多 👇',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.outline, fontSize: 13),
|
color: Theme.of(context).colorScheme.outline, fontSize: 13),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -160,25 +160,25 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _searchSuggest() {
|
Widget _searchSuggest() {
|
||||||
SSearchController ssCtr = _searchController;
|
SSearchController _ssCtr = _searchController;
|
||||||
return Obx(
|
return Obx(
|
||||||
() => ssCtr.searchSuggestList.isNotEmpty &&
|
() => _ssCtr.searchSuggestList.isNotEmpty &&
|
||||||
ssCtr.searchSuggestList.first.term != null &&
|
_ssCtr.searchSuggestList.first.term != null &&
|
||||||
ssCtr.controller.value.text != ''
|
_ssCtr.controller.value.text != ''
|
||||||
? ListView.builder(
|
? ListView.builder(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: ssCtr.searchSuggestList.length,
|
itemCount: _ssCtr.searchSuggestList.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
customBorder: RoundedRectangleBorder(
|
customBorder: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
onTap: () => ssCtr
|
onTap: () => _ssCtr
|
||||||
.onClickKeyword(ssCtr.searchSuggestList[index].term!),
|
.onClickKeyword(_ssCtr.searchSuggestList[index].term!),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
|
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
|
||||||
child: ssCtr.searchSuggestList[index].textRich,
|
child: _ssCtr.searchSuggestList[index].textRich,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -235,7 +235,6 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
return Obx(
|
return Obx(
|
||||||
() => HotKeyword(
|
() => HotKeyword(
|
||||||
width: width,
|
width: width,
|
||||||
// ignore: invalid_use_of_protected_member
|
|
||||||
hotSearchList: _searchController.hotSearchList.value,
|
hotSearchList: _searchController.hotSearchList.value,
|
||||||
onClick: (keyword) async {
|
onClick: (keyword) async {
|
||||||
_searchController.searchFocusNode.unfocus();
|
_searchController.searchFocusNode.unfocus();
|
||||||
|
|||||||
@ -83,7 +83,6 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
return SearchVideoPanel(
|
return SearchVideoPanel(
|
||||||
ctr: _searchPanelController,
|
ctr: _searchPanelController,
|
||||||
// ignore: invalid_use_of_protected_member
|
|
||||||
list: list.value,
|
list: list.value,
|
||||||
);
|
);
|
||||||
case SearchType.media_bangumi:
|
case SearchType.media_bangumi:
|
||||||
|
|||||||
@ -82,7 +82,6 @@ class _SearchResultPageState extends State<SearchResultPage>
|
|||||||
labelStyle: const TextStyle(fontSize: 13),
|
labelStyle: const TextStyle(fontSize: 13),
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
unselectedLabelColor: Theme.of(context).colorScheme.outline,
|
||||||
tabAlignment: TabAlignment.start,
|
|
||||||
onTap: (index) {
|
onTap: (index) {
|
||||||
if (index == _searchResultController!.tabIndex) {
|
if (index == _searchResultController!.tabIndex) {
|
||||||
Get.find<SearchPanelController>(
|
Get.find<SearchPanelController>(
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||||
import 'package:pilipala/models/common/reply_sort_type.dart';
|
import 'package:pilipala/models/common/reply_sort_type.dart';
|
||||||
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
|
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'widgets/switch_item.dart';
|
import 'widgets/switch_item.dart';
|
||||||
@ -170,12 +169,6 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
setKey: SettingBoxKey.enableSaveLastData,
|
setKey: SettingBoxKey.enableSaveLastData,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
),
|
),
|
||||||
const SetSwitchItem(
|
|
||||||
title: '启用ai总结',
|
|
||||||
subTitle: '视频详情页开启ai总结',
|
|
||||||
setKey: SettingBoxKey.enableAi,
|
|
||||||
defaultVal: true,
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
title: Text('评论展示', style: titleStyle),
|
title: Text('评论展示', style: titleStyle),
|
||||||
@ -183,21 +176,23 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
'当前优先展示「${ReplySortType.values[defaultReplySort].titles}」',
|
'当前优先展示「${ReplySortType.values[defaultReplySort].titles}」',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultReplySort,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '评论展示', value: defaultReplySort, values: ReplySortType.values.map((e) {
|
defaultReplySort = item;
|
||||||
return {'title': e.titles, 'value': e.index};
|
setting.put(SettingBoxKey.replySortType, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultReplySort = result;
|
|
||||||
setting.put(SettingBoxKey.replySortType, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in ReplySortType.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.index,
|
||||||
|
child: Text(i.titles),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -206,21 +201,23 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
'当前优先展示「${DynamicsType.values[defaultDynamicType].labels}」',
|
'当前优先展示「${DynamicsType.values[defaultDynamicType].labels}」',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultDynamicType,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '动态展示', value: defaultDynamicType, values: DynamicsType.values.map((e) {
|
defaultDynamicType = item;
|
||||||
return {'title': e.labels, 'value': e.index};
|
setting.put(SettingBoxKey.defaultDynamicType, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultDynamicType = result;
|
|
||||||
setting.put(SettingBoxKey.defaultDynamicType, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in DynamicsType.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.index,
|
||||||
|
child: Text(i.labels),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
enableFeedback: true,
|
enableFeedback: true,
|
||||||
@ -228,7 +225,6 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
title: Text('设置代理', style: titleStyle),
|
title: Text('设置代理', style: titleStyle),
|
||||||
subtitle: Text('设置代理 host:port', style: subTitleStyle),
|
subtitle: Text('设置代理 host:port', style: subTitleStyle),
|
||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
|
|||||||
@ -44,10 +44,12 @@ class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
|
|||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: SingleChildScrollView(
|
||||||
child: Text(
|
child: Center(
|
||||||
'当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}',
|
child: Text(
|
||||||
style: TextStyle(fontSize: 14 * currentSize),
|
'当前字体大小:${currentSize == 1.0 ? '默认' : currentSize}',
|
||||||
|
style: TextStyle(fontSize: 14 * currentSize),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/setting/widgets/switch_item.dart';
|
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
|
||||||
import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
|
import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
@ -15,11 +13,9 @@ class PlaySpeedPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||||
Box videoStorage = GStrorage.video;
|
Box videoStorage = GStrorage.video;
|
||||||
Box settingStorage = GStrorage.setting;
|
|
||||||
late double playSpeedDefault;
|
late double playSpeedDefault;
|
||||||
late double longPressSpeedDefault;
|
late double longPressSpeedDefault;
|
||||||
late List customSpeedsList;
|
late List customSpeedsList;
|
||||||
late bool enableAutoLongPressSpeed;
|
|
||||||
List<Map<dynamic, dynamic>> sheetMenu = [
|
List<Map<dynamic, dynamic>> sheetMenu = [
|
||||||
{
|
{
|
||||||
'id': 1,
|
'id': 1,
|
||||||
@ -28,7 +24,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
Icons.speed,
|
Icons.speed,
|
||||||
size: 21,
|
size: 21,
|
||||||
),
|
),
|
||||||
'show': true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 2,
|
'id': 2,
|
||||||
@ -37,7 +32,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
Icons.speed_sharp,
|
Icons.speed_sharp,
|
||||||
size: 21,
|
size: 21,
|
||||||
),
|
),
|
||||||
'show': true,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': -1,
|
'id': -1,
|
||||||
@ -46,7 +40,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
Icons.delete_outline,
|
Icons.delete_outline,
|
||||||
size: 21,
|
size: 21,
|
||||||
),
|
),
|
||||||
'show': true,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -62,15 +55,6 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
// 自定义倍速
|
// 自定义倍速
|
||||||
customSpeedsList =
|
customSpeedsList =
|
||||||
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
|
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
|
||||||
enableAutoLongPressSpeed = settingStorage
|
|
||||||
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
|
|
||||||
if (enableAutoLongPressSpeed) {
|
|
||||||
Map newItem = sheetMenu[1];
|
|
||||||
newItem['show'] = false;
|
|
||||||
setState(() {
|
|
||||||
sheetMenu[1] = newItem;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加自定义倍速
|
// 添加自定义倍速
|
||||||
@ -136,21 +120,19 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
//重要
|
//重要
|
||||||
itemCount: sheetMenu.length,
|
itemCount: sheetMenu.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return sheetMenu[index]['show']
|
return ListTile(
|
||||||
? ListTile(
|
onTap: () {
|
||||||
onTap: () {
|
Navigator.pop(context);
|
||||||
Navigator.pop(context);
|
menuAction(type, i, sheetMenu[index]['id']);
|
||||||
menuAction(type, i, sheetMenu[index]['id']);
|
},
|
||||||
},
|
minLeadingWidth: 0,
|
||||||
minLeadingWidth: 0,
|
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||||
iconColor: Theme.of(context).colorScheme.onSurface,
|
leading: sheetMenu[index]['leading'],
|
||||||
leading: sheetMenu[index]['leading'],
|
title: Text(
|
||||||
title: Text(
|
sheetMenu[index]['title'],
|
||||||
sheetMenu[index]['title'],
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
),
|
||||||
),
|
);
|
||||||
)
|
|
||||||
: const SizedBox();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -228,27 +210,11 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
|||||||
title: const Text('默认倍速'),
|
title: const Text('默认倍速'),
|
||||||
subtitle: Text(playSpeedDefault.toString()),
|
subtitle: Text(playSpeedDefault.toString()),
|
||||||
),
|
),
|
||||||
SetSwitchItem(
|
ListTile(
|
||||||
title: '动态长按倍速',
|
dense: false,
|
||||||
subTitle: '根据默认倍速长按时自动双倍',
|
title: const Text('默认长按倍速'),
|
||||||
setKey: SettingBoxKey.enableAutoLongPressSpeed,
|
subtitle: Text(longPressSpeedDefault.toString()),
|
||||||
defaultVal: enableAutoLongPressSpeed,
|
|
||||||
callFn: (val) {
|
|
||||||
Map newItem = sheetMenu[1];
|
|
||||||
val ? newItem['show'] = false : newItem['show'] = true;
|
|
||||||
setState(() {
|
|
||||||
sheetMenu[1] = newItem;
|
|
||||||
enableAutoLongPressSpeed = val;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
!enableAutoLongPressSpeed
|
|
||||||
? ListTile(
|
|
||||||
dense: false,
|
|
||||||
title: const Text('默认长按倍速'),
|
|
||||||
subtitle: Text(longPressSpeedDefault.toString()),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 14,
|
left: 14,
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
|
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/services/service_locator.dart';
|
import 'package:pilipala/services/service_locator.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -69,7 +68,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
dense: false,
|
dense: false,
|
||||||
onTap: () => Get.toNamed('/playSpeedSet'),
|
onTap: () => Get.toNamed('/playSpeedSet'),
|
||||||
title: Text('倍速设置', style: titleStyle),
|
title: Text('倍速设置', style: titleStyle),
|
||||||
subtitle: Text('设置视频播放速度', style: subTitleStyle),
|
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
|
||||||
),
|
),
|
||||||
const SetSwitchItem(
|
const SetSwitchItem(
|
||||||
title: '开启1080P',
|
title: '开启1080P',
|
||||||
@ -97,7 +96,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
),
|
),
|
||||||
const SetSwitchItem(
|
const SetSwitchItem(
|
||||||
title: '自动PiP播放',
|
title: '自动PiP播放',
|
||||||
subTitle: '进入后台时画中画播放',
|
subTitle: 'app切换至后台时画中画播放',
|
||||||
setKey: SettingBoxKey.autoPiP,
|
setKey: SettingBoxKey.autoPiP,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
),
|
),
|
||||||
@ -150,21 +149,23 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
'当前画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
'当前画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultVideoQa,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '默认画质', value: defaultVideoQa, values: VideoQuality.values.reversed.map((e) {
|
defaultVideoQa = item;
|
||||||
return {'title': e.description, 'value': e.code};
|
setting.put(SettingBoxKey.defaultVideoQa, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultVideoQa = result;
|
|
||||||
setting.put(SettingBoxKey.defaultVideoQa, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in VideoQuality.values.reversed) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.code,
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -173,21 +174,23 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
'当前音质${AudioQualityCode.fromCode(defaultAudioQa)!.description!}',
|
'当前音质${AudioQualityCode.fromCode(defaultAudioQa)!.description!}',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultAudioQa,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '默认音质', value: defaultAudioQa, values: AudioQuality.values.reversed.map((e) {
|
defaultAudioQa = item;
|
||||||
return {'title': e.description, 'value': e.code};
|
setting.put(SettingBoxKey.defaultAudioQa, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultAudioQa = result;
|
|
||||||
setting.put(SettingBoxKey.defaultAudioQa, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in AudioQuality.values.reversed) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.code,
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -196,21 +199,23 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
'当前解码格式${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!}',
|
'当前解码格式${VideoDecodeFormatsCode.fromCode(defaultDecode)!.description!}',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
String? result = await showDialog(
|
initialValue: defaultDecode,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<String>(title: '默认解码格式', value: defaultDecode, values: VideoDecodeFormats.values.map((e) {
|
defaultDecode = item;
|
||||||
return {'title': e.description, 'value': e.code};
|
setting.put(SettingBoxKey.defaultDecode, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultDecode = result;
|
|
||||||
setting.put(SettingBoxKey.defaultDecode, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in VideoDecodeFormats.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.code,
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -219,21 +224,23 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
'当前全屏方式:${FullScreenModeCode.fromCode(defaultFullScreenMode)!.description}',
|
'当前全屏方式:${FullScreenModeCode.fromCode(defaultFullScreenMode)!.description}',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultFullScreenMode,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '默认全屏方式', value: defaultFullScreenMode, values: FullScreenMode.values.map((e) {
|
defaultFullScreenMode = item;
|
||||||
return {'title': e.description, 'value': e.code};
|
setting.put(SettingBoxKey.fullScreenMode, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultFullScreenMode = result;
|
|
||||||
setting.put(SettingBoxKey.fullScreenMode, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in FullScreenMode.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.code,
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -242,21 +249,23 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
'当前展示方式:${BtmProgresBehaviorCode.fromCode(defaultBtmProgressBehavior)!.description}',
|
'当前展示方式:${BtmProgresBehaviorCode.fromCode(defaultBtmProgressBehavior)!.description}',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
onTap: () async {
|
trailing: PopupMenuButton(
|
||||||
int? result = await showDialog(
|
initialValue: defaultBtmProgressBehavior,
|
||||||
context: context,
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
builder: (context) {
|
onSelected: (item) {
|
||||||
return SelectDialog<int>(title: '底部进度条展示', value: defaultBtmProgressBehavior, values: BtmProgresBehavior.values.map((e) {
|
defaultBtmProgressBehavior = item;
|
||||||
return {'title': e.description, 'value': e.code};
|
setting.put(SettingBoxKey.btmProgressBehavior, item);
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultBtmProgressBehavior = result;
|
|
||||||
setting.put(SettingBoxKey.btmProgressBehavior, result);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
},
|
||||||
},
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in BtmProgresBehavior.values) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i.code,
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ 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/member.dart';
|
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class PrivacySetting extends StatefulWidget {
|
class PrivacySetting extends StatefulWidget {
|
||||||
@ -54,16 +53,6 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
|||||||
title: Text('黑名单管理', style: titleStyle),
|
title: Text('黑名单管理', style: titleStyle),
|
||||||
subtitle: Text('已拉黑用户', style: subTitleStyle),
|
subtitle: Text('已拉黑用户', style: subTitleStyle),
|
||||||
),
|
),
|
||||||
ListTile(
|
|
||||||
onTap: () {
|
|
||||||
if (!userLogin) {
|
|
||||||
SmartDialog.showToast('请先登录');
|
|
||||||
}
|
|
||||||
MemberHttp.cookieToKey();
|
|
||||||
},
|
|
||||||
dense: false,
|
|
||||||
title: Text('刷新access_key', style: titleStyle),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,8 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/common/theme_type.dart';
|
import 'package:pilipala/models/common/theme_type.dart';
|
||||||
import 'package:pilipala/pages/setting/pages/color_select.dart';
|
|
||||||
import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
|
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
@ -20,8 +18,6 @@ class StyleSetting extends StatefulWidget {
|
|||||||
|
|
||||||
class _StyleSettingState extends State<StyleSetting> {
|
class _StyleSettingState extends State<StyleSetting> {
|
||||||
final SettingController settingController = Get.put(SettingController());
|
final SettingController settingController = Get.put(SettingController());
|
||||||
final ColorSelectController colorSelectController = Get.put(ColorSelectController());
|
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
late int picQuality;
|
late int picQuality;
|
||||||
late ThemeType _tempThemeValue;
|
late ThemeType _tempThemeValue;
|
||||||
@ -60,7 +56,6 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
title: const Text('震动反馈'),
|
title: const Text('震动反馈'),
|
||||||
subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle),
|
subtitle: Text('请确定手机设置中已开启震动反馈', style: subTitleStyle),
|
||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
@ -88,42 +83,37 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
setKey: SettingBoxKey.enableMYBar,
|
setKey: SettingBoxKey.enableMYBar,
|
||||||
defaultVal: true,
|
defaultVal: true,
|
||||||
),
|
),
|
||||||
const SetSwitchItem(
|
// SetSwitchItem(
|
||||||
title: '首页顶栏收起',
|
// title: '首页单列',
|
||||||
subTitle: '首页列表滑动时,收起顶栏',
|
// subTitle: '每行展示一个内容卡片',
|
||||||
setKey: SettingBoxKey.hideSearchBar,
|
// setKey: SettingBoxKey.enableSingleRow,
|
||||||
defaultVal: true,
|
// defaultVal: false,
|
||||||
needReboot: true,
|
// callFn: (val) => {SmartDialog.showToast('下次启动时生效')},
|
||||||
),
|
// ),
|
||||||
const SetSwitchItem(
|
|
||||||
title: '首页底栏收起',
|
|
||||||
subTitle: '首页列表滑动时,收起底栏',
|
|
||||||
setKey: SettingBoxKey.hideTabBar,
|
|
||||||
defaultVal: true,
|
|
||||||
needReboot: true,
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () async {
|
|
||||||
int? result = await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) {
|
|
||||||
return SelectDialog<int>(title: '自定义列数', value: defaultCustomRows, values: [1, 2, 3, 4, 5].map((e) {
|
|
||||||
return {'title': '$e 列', 'value': e};
|
|
||||||
}).toList());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (result != null) {
|
|
||||||
defaultCustomRows = result;
|
|
||||||
setting.put(SettingBoxKey.customRows, result);
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dense: false,
|
dense: false,
|
||||||
title: Text('自定义列数', style: titleStyle),
|
title: Text('自定义列数', style: titleStyle),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'当前列数 $defaultCustomRows 列',
|
'当前列数',
|
||||||
style: subTitleStyle,
|
style: subTitleStyle,
|
||||||
),
|
),
|
||||||
|
trailing: PopupMenuButton(
|
||||||
|
initialValue: defaultCustomRows,
|
||||||
|
icon: const Icon(Icons.more_vert_outlined, size: 22),
|
||||||
|
onSelected: (item) {
|
||||||
|
defaultCustomRows = item;
|
||||||
|
setting.put(SettingBoxKey.customRows, item);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
for (var i in [1, 2, 3, 4, 5]) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
value: i,
|
||||||
|
child: Text(i.toString()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
@ -179,58 +169,88 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
},
|
},
|
||||||
title: Text('图片质量', style: titleStyle),
|
title: Text('图片质量', style: titleStyle),
|
||||||
subtitle: Text('选择合适的图片清晰度,上限100%', style: subTitleStyle),
|
subtitle: Text('选择合适的图片清晰度,上限100%', style: subTitleStyle),
|
||||||
trailing: Padding(
|
trailing: Obx(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
() => Text(
|
||||||
child: Obx(
|
'${settingController.picQuality.value}%',
|
||||||
() => Text(
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
'${settingController.picQuality.value}%',
|
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
ThemeType? result = await showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return SelectDialog<ThemeType>(title: '主题模式', value: _tempThemeValue, values: ThemeType.values.map((e) {
|
return AlertDialog(
|
||||||
return {'title': e.description, 'value': e};
|
title: const Text('主题模式'),
|
||||||
}).toList());
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||||
|
content: StatefulBuilder(
|
||||||
|
builder: (context, StateSetter setState) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
for (var i in ThemeType.values) ...[
|
||||||
|
RadioListTile(
|
||||||
|
value: i,
|
||||||
|
title: Text(i.description, style: titleStyle),
|
||||||
|
groupValue: _tempThemeValue,
|
||||||
|
onChanged: (ThemeType? value) {
|
||||||
|
setState(() {
|
||||||
|
_tempThemeValue = i;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline),
|
||||||
|
)),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
settingController.themeType.value = _tempThemeValue;
|
||||||
|
setting.put(
|
||||||
|
SettingBoxKey.themeMode, _tempThemeValue.code);
|
||||||
|
Get.forceAppUpdate();
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确定'))
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (result != null) {
|
|
||||||
_tempThemeValue = result;
|
|
||||||
settingController.themeType.value = result;
|
|
||||||
setting.put(
|
|
||||||
SettingBoxKey.themeMode, result.code);
|
|
||||||
Get.forceAppUpdate();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
title: Text('主题模式', style: titleStyle),
|
title: Text('主题模式', style: titleStyle),
|
||||||
subtitle: Obx(() => Text(
|
subtitle: Obx(() => Text(
|
||||||
'当前模式:${settingController.themeType.value.description}',
|
'当前模式:${settingController.themeType.value.description}',
|
||||||
style: subTitleStyle)),
|
style: subTitleStyle)),
|
||||||
|
trailing: const Icon(Icons.arrow_right_alt_outlined),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () => Get.toNamed('/colorSetting'),
|
onTap: () => Get.toNamed('/colorSetting'),
|
||||||
title: Text('应用主题', style: titleStyle),
|
title: Text('应用主题', style: titleStyle),
|
||||||
subtitle: Obx(() => Text(
|
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
|
||||||
'当前主题:${colorSelectController.type.value == 0 ? '动态取色': '指定颜色'}',
|
|
||||||
style: subTitleStyle)),
|
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () => Get.toNamed('/fontSizeSetting'),
|
onTap: () => Get.toNamed('/fontSizeSetting'),
|
||||||
title: Text('字体大小', style: titleStyle),
|
title: Text('字体大小', style: titleStyle),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
|
||||||
),
|
),
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
onTap: () => Get.toNamed('/displayModeSetting'),
|
onTap: () => Get.toNamed('/displayModeSetting'),
|
||||||
title: Text('屏幕帧率', style: titleStyle),
|
title: Text('屏幕帧率', style: titleStyle),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:pilipala/models/common/theme_type.dart';
|
|
||||||
|
|
||||||
class SelectDialog<T> extends StatefulWidget {
|
|
||||||
final T value;
|
|
||||||
final String title;
|
|
||||||
final List<dynamic> values;
|
|
||||||
const SelectDialog({super.key, required this.value, required this.values, required this.title});
|
|
||||||
|
|
||||||
@override
|
|
||||||
_SelectDialogState<T> createState() => _SelectDialogState<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
|
||||||
|
|
||||||
late T _tempValue;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_tempValue = widget.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
|
|
||||||
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(widget.title),
|
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
|
||||||
content: StatefulBuilder(
|
|
||||||
builder: (context, StateSetter setState) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
for (var i in widget.values) ...[
|
|
||||||
RadioListTile(
|
|
||||||
value: i['value'],
|
|
||||||
title: Text(i['title'], style: titleStyle),
|
|
||||||
groupValue: _tempValue,
|
|
||||||
onChanged: (value) {
|
|
||||||
print(value);
|
|
||||||
setState(() {
|
|
||||||
_tempValue = value as T;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
child: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.outline),
|
|
||||||
)),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Navigator.pop(context, _tempValue),
|
|
||||||
child: const Text('确定'))
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -10,7 +9,6 @@ class SetSwitchItem extends StatefulWidget {
|
|||||||
final String? setKey;
|
final String? setKey;
|
||||||
final bool? defaultVal;
|
final bool? defaultVal;
|
||||||
final Function? callFn;
|
final Function? callFn;
|
||||||
final bool? needReboot;
|
|
||||||
|
|
||||||
const SetSwitchItem({
|
const SetSwitchItem({
|
||||||
this.title,
|
this.title,
|
||||||
@ -18,7 +16,6 @@ class SetSwitchItem extends StatefulWidget {
|
|||||||
this.setKey,
|
this.setKey,
|
||||||
this.defaultVal,
|
this.defaultVal,
|
||||||
this.callFn,
|
this.callFn,
|
||||||
this.needReboot,
|
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -46,9 +43,6 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
if (widget.callFn != null) {
|
if (widget.callFn != null) {
|
||||||
widget.callFn!.call(val);
|
widget.callFn!.call(val);
|
||||||
}
|
}
|
||||||
if (widget.needReboot != null && widget.needReboot!) {
|
|
||||||
SmartDialog.showToast('重启生效');
|
|
||||||
}
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +61,6 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
|
|||||||
? Text(widget.subTitle!, style: subTitleStyle)
|
? Text(widget.subTitle!, style: subTitleStyle)
|
||||||
: null,
|
: null,
|
||||||
trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
|
|
||||||
scale: 0.8,
|
scale: 0.8,
|
||||||
child: Switch(
|
child: Switch(
|
||||||
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
|
|||||||
@ -76,7 +76,6 @@ class VideoIntroController extends GetxController {
|
|||||||
if (Get.arguments.containsKey('videoItem')) {
|
if (Get.arguments.containsKey('videoItem')) {
|
||||||
preRender = true;
|
preRender = true;
|
||||||
var args = Get.arguments['videoItem'];
|
var args = Get.arguments['videoItem'];
|
||||||
var keys = Get.arguments.keys.toList();
|
|
||||||
videoItem!['pic'] = args.pic;
|
videoItem!['pic'] = args.pic;
|
||||||
if (args.title is String) {
|
if (args.title is String) {
|
||||||
videoItem!['title'] = args.title;
|
videoItem!['title'] = args.title;
|
||||||
@ -87,9 +86,11 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
videoItem!['title'] = str;
|
videoItem!['title'] = str;
|
||||||
}
|
}
|
||||||
videoItem!['stat'] = keys.contains('stat') && args.stat;
|
if (args.stat != null) {
|
||||||
videoItem!['pubdate'] = keys.contains('pubdate') && args.pubdate;
|
videoItem!['stat'] = args.stat;
|
||||||
videoItem!['owner'] = keys.contains('owner') && args.owner;
|
}
|
||||||
|
videoItem!['pubdate'] = args.pubdate;
|
||||||
|
videoItem!['owner'] = args.owner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
userLogin = userInfo != null;
|
userLogin = userInfo != null;
|
||||||
@ -568,7 +569,13 @@ class VideoIntroController extends GetxController {
|
|||||||
upMid: videoDetail.value.owner!.mid!,
|
upMid: videoDetail.value.owner!.mid!,
|
||||||
);
|
);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
modelResult = res['data'].modelResult;
|
if (res['data'].modelResult.resultType == 0) {
|
||||||
|
SmartDialog.showToast('该视频不支持ai总结');
|
||||||
|
}
|
||||||
|
if (res['data'].modelResult.resultType == 2 ||
|
||||||
|
res['data'].modelResult.resultType == 1) {
|
||||||
|
modelResult = res['data'].modelResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import 'package:pilipala/common/widgets/stat/view.dart';
|
|||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||||
|
import 'package:pilipala/services/service_locator.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 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -133,7 +134,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
late final dynamic followStatus;
|
late final dynamic followStatus;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String memberHeroTag;
|
late String memberHeroTag;
|
||||||
late bool enableAi;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -150,7 +150,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
? '-'
|
? '-'
|
||||||
: Utils.numFormat(videoIntroController.userStat['follower']);
|
: Utils.numFormat(videoIntroController.userStat['follower']);
|
||||||
followStatus = videoIntroController.followStatus;
|
followStatus = videoIntroController.followStatus;
|
||||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -248,7 +247,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
||||||
sliver: SliverToBoxAdapter(
|
sliver: SliverToBoxAdapter(
|
||||||
child: !loadingStatus
|
child: !loadingStatus || videoItem.isNotEmpty
|
||||||
? Column(
|
? Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -278,7 +277,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
children: [
|
children: [
|
||||||
StatView(
|
StatView(
|
||||||
theme: 'gray',
|
theme: 'gray',
|
||||||
view: !loadingStatus
|
view: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.view
|
? widget.videoDetail!.stat!.view
|
||||||
: videoItem['stat'].view,
|
: videoItem['stat'].view,
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
@ -286,7 +285,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
StatDanMu(
|
StatDanMu(
|
||||||
theme: 'gray',
|
theme: 'gray',
|
||||||
danmu: !loadingStatus
|
danmu: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.danmaku
|
? widget.videoDetail!.stat!.danmaku
|
||||||
: videoItem['stat'].danmaku,
|
: videoItem['stat'].danmaku,
|
||||||
size: 'medium',
|
size: 'medium',
|
||||||
@ -294,7 +293,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
Utils.dateFormat(
|
Utils.dateFormat(
|
||||||
!loadingStatus
|
!widget.loadingStatus
|
||||||
? widget.videoDetail!.pubdate
|
? widget.videoDetail!.pubdate
|
||||||
: videoItem['pubdate'],
|
: videoItem['pubdate'],
|
||||||
formatType: 'detail'),
|
formatType: 'detail'),
|
||||||
@ -318,22 +317,23 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (enableAi)
|
Positioned(
|
||||||
Positioned(
|
right: 10,
|
||||||
right: 10,
|
top: 6,
|
||||||
top: 6,
|
child: GestureDetector(
|
||||||
child: GestureDetector(
|
onTap: () async {
|
||||||
onTap: () async {
|
var res = await videoIntroController.aiConclusion();
|
||||||
var res =
|
if (res['status']) {
|
||||||
await videoIntroController.aiConclusion();
|
if (res['data'].modelResult.resultType == 2 ||
|
||||||
if (res['status']) {
|
res['data'].modelResult.resultType == 1) {
|
||||||
showAiBottomSheet();
|
showAiBottomSheet();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
child:
|
},
|
||||||
Image.asset('assets/images/ai.png', height: 22),
|
child:
|
||||||
),
|
Image.asset('assets/images/ai.png', height: 22),
|
||||||
)
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// 点赞收藏转发 布局样式1
|
// 点赞收藏转发 布局样式1
|
||||||
|
|||||||
@ -27,7 +27,6 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
late VideoDetailController _videoDetailController;
|
late VideoDetailController _videoDetailController;
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -51,12 +50,6 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
@ -87,92 +80,73 @@ class _PagesPanelState extends State<PagesPanel> {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showBottomSheet(
|
showBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (_) => Container(
|
||||||
return StatefulBuilder(builder:
|
height: widget.sheetHeight,
|
||||||
(BuildContext context, StateSetter setState) {
|
color: Theme.of(context).colorScheme.background,
|
||||||
WidgetsBinding.instance
|
child: Column(
|
||||||
.addPostFrameCallback((_) async {
|
children: [
|
||||||
await Future.delayed(
|
Container(
|
||||||
const Duration(milliseconds: 200));
|
height: 45,
|
||||||
_scrollController.jumpTo(currentIndex * 56);
|
padding:
|
||||||
});
|
const EdgeInsets.only(left: 14, right: 14),
|
||||||
return Container(
|
child: Row(
|
||||||
height: widget.sheetHeight,
|
mainAxisAlignment:
|
||||||
color: Theme.of(context).colorScheme.background,
|
MainAxisAlignment.spaceBetween,
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Container(
|
'合集(${episodes.length})',
|
||||||
height: 45,
|
style:
|
||||||
padding: const EdgeInsets.only(
|
Theme.of(context).textTheme.titleMedium,
|
||||||
left: 14, right: 14),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'合集(${episodes.length})',
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.titleMedium,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
Divider(
|
icon: const Icon(Icons.close),
|
||||||
height: 1,
|
onPressed: () => Navigator.pop(context),
|
||||||
color: Theme.of(context)
|
),
|
||||||
.dividerColor
|
],
|
||||||
.withOpacity(0.1),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Divider(
|
||||||
child: Material(
|
height: 1,
|
||||||
child: ListView.builder(
|
color: Theme.of(context)
|
||||||
controller: _scrollController,
|
.dividerColor
|
||||||
itemCount: episodes.length,
|
.withOpacity(0.1),
|
||||||
itemBuilder: (context, index) {
|
),
|
||||||
return ListTile(
|
Expanded(
|
||||||
onTap: () {
|
child: Material(
|
||||||
changeFucCall(
|
child: ListView.builder(
|
||||||
episodes[index], index);
|
itemCount: episodes.length,
|
||||||
Get.back();
|
itemBuilder: (context, index) {
|
||||||
},
|
return InkWell(
|
||||||
dense: false,
|
onTap: () {
|
||||||
leading: index == currentIndex
|
changeFucCall(episodes[index], index);
|
||||||
? Image.asset(
|
Get.back();
|
||||||
'assets/images/live.gif',
|
},
|
||||||
color: Theme.of(context)
|
child: Padding(
|
||||||
.colorScheme
|
padding: const EdgeInsets.only(
|
||||||
.primary,
|
top: 10,
|
||||||
height: 12,
|
bottom: 10,
|
||||||
)
|
left: 15,
|
||||||
: null,
|
right: 15),
|
||||||
title: Text(
|
child: Text(
|
||||||
episodes[index].pagePart!,
|
episodes[index].pagePart!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
|
||||||
color: index == currentIndex
|
color: index == currentIndex
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary
|
.primary
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurface,
|
.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
});
|
),
|
||||||
},
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -28,7 +28,6 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
late VideoDetailController _videoDetailController;
|
late VideoDetailController _videoDetailController;
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -74,12 +73,6 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Builder(builder: (context) {
|
return Builder(builder: (context) {
|
||||||
@ -97,82 +90,63 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => showBottomSheet(
|
onTap: () => showBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (_) => Container(
|
||||||
return StatefulBuilder(
|
height: widget.sheetHeight,
|
||||||
builder: (BuildContext context, StateSetter setState) {
|
color: Theme.of(context).colorScheme.background,
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
child: Column(
|
||||||
await Future.delayed(const Duration(milliseconds: 200));
|
children: [
|
||||||
_scrollController.jumpTo(currentIndex * 56);
|
Container(
|
||||||
});
|
height: 45,
|
||||||
return Container(
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
height: widget.sheetHeight,
|
child: Row(
|
||||||
color: Theme.of(context).colorScheme.background,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
Text(
|
||||||
Container(
|
'合集(${episodes.length})',
|
||||||
height: 45,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
padding: const EdgeInsets.only(left: 14, right: 14),
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'合集(${episodes.length})',
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
onPressed: () => Navigator.pop(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
Divider(
|
icon: const Icon(Icons.close),
|
||||||
height: 1,
|
onPressed: () => Navigator.pop(context),
|
||||||
color:
|
),
|
||||||
Theme.of(context).dividerColor.withOpacity(0.1),
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: Material(
|
Divider(
|
||||||
child: ListView.builder(
|
height: 1,
|
||||||
controller: _scrollController,
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
itemCount: episodes.length,
|
),
|
||||||
itemBuilder: (context, index) {
|
Expanded(
|
||||||
return ListTile(
|
child: Material(
|
||||||
onTap: () =>
|
child: ListView.builder(
|
||||||
changeFucCall(episodes[index], index),
|
itemCount: episodes.length,
|
||||||
dense: false,
|
itemBuilder: (context, index) {
|
||||||
leading: index == currentIndex
|
return InkWell(
|
||||||
? Image.asset(
|
onTap: () =>
|
||||||
'assets/images/live.gif',
|
changeFucCall(episodes[index], index),
|
||||||
color: Theme.of(context)
|
child: Padding(
|
||||||
.colorScheme
|
padding: const EdgeInsets.only(
|
||||||
.primary,
|
top: 10, bottom: 10, left: 15, right: 15),
|
||||||
height: 12,
|
child: Text(
|
||||||
)
|
episodes[index].title!,
|
||||||
: null,
|
style: TextStyle(
|
||||||
title: Text(
|
|
||||||
episodes[index].title!,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
color: index == currentIndex
|
color: index == currentIndex
|
||||||
? Theme.of(context)
|
? Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary
|
.primary
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSurface,
|
.onSurface),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
});
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
||||||
|
|||||||
@ -218,10 +218,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState lifecycleState) {
|
void didChangeAppLifecycleState(AppLifecycleState lifecycleState) {
|
||||||
var routePath = Get.currentRoute;
|
if (lifecycleState == AppLifecycleState.inactive && autoPiP) {
|
||||||
if (lifecycleState == AppLifecycleState.inactive &&
|
|
||||||
autoPiP &&
|
|
||||||
routePath.startsWith('/video')) {
|
|
||||||
floating.enable(
|
floating.enable(
|
||||||
aspectRatio: Rational(
|
aspectRatio: Rational(
|
||||||
videoDetailController.data.dash!.video!.first.width!,
|
videoDetailController.data.dash!.video!.first.width!,
|
||||||
@ -357,6 +354,28 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 105,
|
||||||
|
bottom: 10,
|
||||||
|
child: TextButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.resolveWith((states) {
|
||||||
|
return Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primaryContainer;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
Get.toNamed('/audioPlayer'),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.multitrack_audio_outlined,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
label: const Text('音频'),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 12,
|
right: 12,
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
@ -375,7 +394,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
Icons.play_circle_outline,
|
Icons.play_circle_outline,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
label: const Text('Play'),
|
label: const Text('视频'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
@ -16,7 +15,6 @@ import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
|||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/http/danmaku.dart';
|
|
||||||
|
|
||||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
final PlPlayerController? controller;
|
final PlPlayerController? controller;
|
||||||
@ -92,41 +90,41 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
child: Material(
|
child: Material(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
// ListTile(
|
ListTile(
|
||||||
// onTap: () {},
|
onTap: () {},
|
||||||
// dense: true,
|
dense: true,
|
||||||
// enabled: false,
|
enabled: false,
|
||||||
// leading:
|
leading:
|
||||||
// const Icon(Icons.network_cell_outlined, size: 20),
|
const Icon(Icons.network_cell_outlined, size: 20),
|
||||||
// title: Text('省流模式', style: titleStyle),
|
title: Text('省流模式', style: titleStyle),
|
||||||
// subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle),
|
subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle),
|
||||||
// trailing: Transform.scale(
|
trailing: Transform.scale(
|
||||||
// scale: 0.75,
|
scale: 0.75,
|
||||||
// child: Switch(
|
child: Switch(
|
||||||
// thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
|
||||||
// (Set<MaterialState> states) {
|
(Set<MaterialState> states) {
|
||||||
// if (states.isNotEmpty &&
|
if (states.isNotEmpty &&
|
||||||
// states.first == MaterialState.selected) {
|
states.first == MaterialState.selected) {
|
||||||
// return const Icon(Icons.done);
|
return const Icon(Icons.done);
|
||||||
// }
|
}
|
||||||
// return null; // All other states will use the default thumbIcon.
|
return null; // All other states will use the default thumbIcon.
|
||||||
// }),
|
}),
|
||||||
// value: false,
|
value: false,
|
||||||
// onChanged: (value) => {},
|
onChanged: (value) => {},
|
||||||
// ),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Obx(
|
||||||
|
// () => ListTile(
|
||||||
|
// onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||||
|
// dense: true,
|
||||||
|
// leading: const Icon(Icons.speed_outlined, size: 20),
|
||||||
|
// title: Text('播放速度', style: titleStyle),
|
||||||
|
// subtitle: Text(
|
||||||
|
// '当前倍速 x${widget.controller!.playbackSpeed}',
|
||||||
|
// style: subTitleStyle),
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
ListTile(
|
|
||||||
onTap: () async {
|
|
||||||
var res = await UserHttp.toViewLater(
|
|
||||||
bvid: widget.videoDetailCtr!.bvid);
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
dense: true,
|
|
||||||
leading: const Icon(Icons.watch_later_outlined, size: 20),
|
|
||||||
title: Text('添加至「稍后再看」', style: titleStyle),
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => {Get.back(), showSetVideoQa()},
|
onTap: () => {Get.back(), showSetVideoQa()},
|
||||||
dense: true,
|
dense: true,
|
||||||
@ -181,85 +179,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 发送弹幕
|
|
||||||
void showShootDanmakuSheet() {
|
|
||||||
final TextEditingController textController = TextEditingController();
|
|
||||||
bool isSending = false; // 追踪是否正在发送
|
|
||||||
showDialog(
|
|
||||||
context: Get.context!,
|
|
||||||
builder: (context) {
|
|
||||||
// TODO: 支持更多类型和颜色的弹幕
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('发送弹幕(测试)'),
|
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
|
||||||
return TextField(
|
|
||||||
controller: textController,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => Get.back(),
|
|
||||||
child: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
StatefulBuilder(builder: (context, StateSetter setState) {
|
|
||||||
return TextButton(
|
|
||||||
onPressed: isSending
|
|
||||||
? null
|
|
||||||
: () async {
|
|
||||||
String msg = textController.text;
|
|
||||||
if (msg.isEmpty) {
|
|
||||||
SmartDialog.showToast('弹幕内容不能为空');
|
|
||||||
return;
|
|
||||||
} else if (msg.length > 100) {
|
|
||||||
SmartDialog.showToast('弹幕内容不能超过100个字符');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
isSending = true; // 开始发送,更新状态
|
|
||||||
});
|
|
||||||
//修改按钮文字
|
|
||||||
// SmartDialog.showToast('弹幕发送中,\n$msg');
|
|
||||||
dynamic res = await DanmakaHttp.shootDanmaku(
|
|
||||||
oid: widget.videoDetailCtr!.cid!.value,
|
|
||||||
msg: textController.text,
|
|
||||||
bvid: widget.videoDetailCtr!.bvid!,
|
|
||||||
progress:
|
|
||||||
widget.controller!.position.value.inMilliseconds,
|
|
||||||
type: 1,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
isSending = false; // 发送结束,更新状态
|
|
||||||
});
|
|
||||||
if (res['status']) {
|
|
||||||
SmartDialog.showToast('发送成功');
|
|
||||||
// 发送成功,自动预览该弹幕,避免重新请求
|
|
||||||
// TODO: 暂停状态下预览弹幕仍会移动与计时,可考虑添加到dmSegList或其他方式实现
|
|
||||||
widget.controller!.danmakuController!.addItems([
|
|
||||||
DanmakuItem(
|
|
||||||
msg,
|
|
||||||
color: Colors.white,
|
|
||||||
time: widget
|
|
||||||
.controller!.position.value.inMilliseconds,
|
|
||||||
type: DanmakuItemType.scroll,
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
Get.back();
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Text(isSending ? '发送中...' : '发送'),
|
|
||||||
);
|
|
||||||
})
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 选择倍速
|
/// 选择倍速
|
||||||
void showSetSpeedSheet() {
|
void showSetSpeedSheet() {
|
||||||
double currentSpeed = widget.controller!.playbackSpeed;
|
double currentSpeed = widget.controller!.playbackSpeed;
|
||||||
@ -906,20 +825,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
// ),
|
// ),
|
||||||
// fuc: () => _.screenshot(),
|
// fuc: () => _.screenshot(),
|
||||||
// ),
|
// ),
|
||||||
SizedBox(
|
|
||||||
width: 56,
|
|
||||||
height: 34,
|
|
||||||
child: TextButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
onPressed: () => showShootDanmakuSheet(),
|
|
||||||
child: const Text(
|
|
||||||
'发弹幕',
|
|
||||||
style: textStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 34,
|
width: 34,
|
||||||
height: 34,
|
height: 34,
|
||||||
@ -994,8 +899,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
SizedBox(width: buttonSpace),
|
SizedBox(width: buttonSpace),
|
||||||
ComBtn(
|
ComBtn(
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.more_vert_outlined,
|
FontAwesomeIcons.sliders,
|
||||||
size: 18,
|
size: 15,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
fuc: () => showSettingSheet(),
|
fuc: () => showSettingSheet(),
|
||||||
|
|||||||
@ -6,7 +6,6 @@ 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/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
import 'package:pilipala/http/member.dart';
|
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
@ -103,6 +102,7 @@ class WebviewController extends GetxController {
|
|||||||
try {
|
try {
|
||||||
await SetCookie.onSet();
|
await SetCookie.onSet();
|
||||||
var result = await UserHttp.userInfo();
|
var result = await UserHttp.userInfo();
|
||||||
|
UserHttp.thirdLogin();
|
||||||
if (result['status'] && result['data'].isLogin) {
|
if (result['status'] && result['data'].isLogin) {
|
||||||
SmartDialog.showToast('登录成功');
|
SmartDialog.showToast('登录成功');
|
||||||
try {
|
try {
|
||||||
@ -115,7 +115,6 @@ class WebviewController extends GetxController {
|
|||||||
MediaController mediaCtr = Get.find<MediaController>();
|
MediaController mediaCtr = Get.find<MediaController>();
|
||||||
mediaCtr.mid = result['data'].mid;
|
mediaCtr.mid = result['data'].mid;
|
||||||
await LoginUtils.refreshLoginStatus(true);
|
await LoginUtils.refreshLoginStatus(true);
|
||||||
MemberHttp.cookieToKey();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.show(builder: (context) {
|
SmartDialog.show(builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
|||||||
@ -1,65 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/http/msg.dart';
|
|
||||||
import 'package:pilipala/models/msg/account.dart';
|
|
||||||
import 'package:pilipala/models/msg/session.dart';
|
|
||||||
|
|
||||||
class WhisperController extends GetxController {
|
|
||||||
RxList<SessionList> sessionList = <SessionList>[].obs;
|
|
||||||
RxList<AccountListModel> accountList = <AccountListModel>[].obs;
|
|
||||||
bool isLoading = false;
|
|
||||||
|
|
||||||
Future querySessionList(String? type) async {
|
|
||||||
if (isLoading) return;
|
|
||||||
var res = await MsgHttp.sessionList(
|
|
||||||
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
|
|
||||||
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) {
|
|
||||||
await queryAccountList(res['data'].sessionList);
|
|
||||||
// 将 accountList 转换为 Map 结构
|
|
||||||
Map<int, dynamic> accountMap = {};
|
|
||||||
for (var j in accountList) {
|
|
||||||
accountMap[j.mid!] = j;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 遍历 sessionList,通过 mid 查找并赋值 accountInfo
|
|
||||||
for (var i in res['data'].sessionList) {
|
|
||||||
var accountInfo = accountMap[i.talkerId];
|
|
||||||
if (accountInfo != null) {
|
|
||||||
i.accountInfo = accountInfo;
|
|
||||||
}
|
|
||||||
if (i.talkerId == 844424930131966) {
|
|
||||||
i.accountInfo = AccountListModel(
|
|
||||||
name: 'UP主小助手',
|
|
||||||
face:
|
|
||||||
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (res['status'] && res['data'].sessionList != null) {
|
|
||||||
if (type == 'onLoad') {
|
|
||||||
sessionList.addAll(res['data'].sessionList);
|
|
||||||
} else {
|
|
||||||
sessionList.value = res['data'].sessionList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isLoading = false;
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future queryAccountList(sessionList) async {
|
|
||||||
List midsList = sessionList.map((e) => e.talkerId!).toList();
|
|
||||||
var res = await MsgHttp.accountList(midsList.join(','));
|
|
||||||
if (res['status']) {
|
|
||||||
accountList.value = res['data'];
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future onLoad() async {
|
|
||||||
querySessionList('onLoad');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future onRefresh() async {
|
|
||||||
querySessionList('onRefresh');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
library whisper;
|
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
|
||||||
@ -1,230 +0,0 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
import 'controller.dart';
|
|
||||||
|
|
||||||
class WhisperPage extends StatefulWidget {
|
|
||||||
const WhisperPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<WhisperPage> createState() => _WhisperPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WhisperPageState extends State<WhisperPage> {
|
|
||||||
late final WhisperController _whisperController =
|
|
||||||
Get.put(WhisperController());
|
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_futureBuilderFuture = _whisperController.querySessionList('init');
|
|
||||||
_scrollController.addListener(_scrollListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future _scrollListener() async {
|
|
||||||
if (_scrollController.position.pixels >=
|
|
||||||
_scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle('my-throttler', const Duration(milliseconds: 800),
|
|
||||||
() async {
|
|
||||||
await _whisperController.onLoad();
|
|
||||||
_whisperController.isLoading = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('消息'),
|
|
||||||
),
|
|
||||||
body: Column(
|
|
||||||
children: [
|
|
||||||
// LayoutBuilder(
|
|
||||||
// builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
// // 在这里根据父级容器的约束条件构建小部件树
|
|
||||||
// return Padding(
|
|
||||||
// padding: const EdgeInsets.only(left: 20, right: 20),
|
|
||||||
// child: SizedBox(
|
|
||||||
// height: constraints.maxWidth / 5,
|
|
||||||
// child: GridView.count(
|
|
||||||
// primary: false,
|
|
||||||
// crossAxisCount: 4,
|
|
||||||
// padding: const EdgeInsets.all(0),
|
|
||||||
// childAspectRatio: 1.25,
|
|
||||||
// children: [
|
|
||||||
// Column(
|
|
||||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
// children: [
|
|
||||||
// SizedBox(
|
|
||||||
// width: 36,
|
|
||||||
// height: 36,
|
|
||||||
// child: IconButton(
|
|
||||||
// style: ButtonStyle(
|
|
||||||
// padding:
|
|
||||||
// MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
// backgroundColor:
|
|
||||||
// MaterialStateProperty.resolveWith((states) {
|
|
||||||
// return Theme.of(context)
|
|
||||||
// .colorScheme
|
|
||||||
// .primary
|
|
||||||
// .withOpacity(0.1);
|
|
||||||
// }),
|
|
||||||
// ),
|
|
||||||
// onPressed: () {},
|
|
||||||
// icon: Icon(
|
|
||||||
// Icons.message_outlined,
|
|
||||||
// size: 18,
|
|
||||||
// color: Theme.of(context).colorScheme.primary,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// const SizedBox(height: 6),
|
|
||||||
// const Text('回复我的', style: TextStyle(fontSize: 13))
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
Expanded(
|
|
||||||
child: RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
|
||||||
await _whisperController.onRefresh();
|
|
||||||
},
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map data = snapshot.data as Map;
|
|
||||||
if (data['status']) {
|
|
||||||
List sessionList = _whisperController.sessionList;
|
|
||||||
return Obx(
|
|
||||||
() => sessionList.isEmpty
|
|
||||||
? const SizedBox()
|
|
||||||
: ListView.separated(
|
|
||||||
itemCount: sessionList.length,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics:
|
|
||||||
const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (_, int i) {
|
|
||||||
return ListTile(
|
|
||||||
onTap: () => Get.toNamed(
|
|
||||||
'/whisperDetail',
|
|
||||||
parameters: {
|
|
||||||
'talkerId': sessionList[i]
|
|
||||||
.talkerId
|
|
||||||
.toString(),
|
|
||||||
'name': sessionList[i]
|
|
||||||
.accountInfo
|
|
||||||
.name,
|
|
||||||
'face': sessionList[i]
|
|
||||||
.accountInfo
|
|
||||||
.face,
|
|
||||||
'mid': sessionList[i]
|
|
||||||
.accountInfo
|
|
||||||
.mid
|
|
||||||
.toString(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
leading: Badge(
|
|
||||||
isLabelVisible: false,
|
|
||||||
backgroundColor: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primary,
|
|
||||||
label: Text(sessionList[i]
|
|
||||||
.unreadCount
|
|
||||||
.toString()),
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
width: 45,
|
|
||||||
height: 45,
|
|
||||||
type: 'avatar',
|
|
||||||
src: sessionList[i]
|
|
||||||
.accountInfo
|
|
||||||
.face,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Text(
|
|
||||||
sessionList[i].accountInfo.name),
|
|
||||||
subtitle: Text(
|
|
||||||
sessionList[i]
|
|
||||||
.lastMsg
|
|
||||||
.content['text'] ??
|
|
||||||
sessionList[i]
|
|
||||||
.lastMsg
|
|
||||||
.content['content'] ??
|
|
||||||
sessionList[i]
|
|
||||||
.lastMsg
|
|
||||||
.content['title'] ??
|
|
||||||
sessionList[i]
|
|
||||||
.lastMsg
|
|
||||||
.content[
|
|
||||||
'reply_content'] ??
|
|
||||||
'',
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelMedium!
|
|
||||||
.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline)),
|
|
||||||
trailing: Text(
|
|
||||||
Utils.dateFormat(sessionList[i]
|
|
||||||
.lastMsg
|
|
||||||
.timestamp),
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall!
|
|
||||||
.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder:
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
return Divider(
|
|
||||||
indent: 72,
|
|
||||||
endIndent: 20,
|
|
||||||
height: 6,
|
|
||||||
color: Colors.grey.withOpacity(0.1),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 请求错误
|
|
||||||
return SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/http/msg.dart';
|
|
||||||
import 'package:pilipala/models/msg/session.dart';
|
|
||||||
|
|
||||||
class WhisperDetailController extends GetxController {
|
|
||||||
late int talkerId;
|
|
||||||
late String name;
|
|
||||||
late String face;
|
|
||||||
late String mid;
|
|
||||||
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void onInit() {
|
|
||||||
super.onInit();
|
|
||||||
talkerId = int.parse(Get.parameters['talkerId']!);
|
|
||||||
name = Get.parameters['name']!;
|
|
||||||
face = Get.parameters['face']!;
|
|
||||||
mid = Get.parameters['mid']!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future querySessionMsg() async {
|
|
||||||
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
|
|
||||||
if (res['status']) {
|
|
||||||
messageList.value = res['data'].messages;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
library whisper_detail;
|
|
||||||
|
|
||||||
export './controller.dart';
|
|
||||||
export './view.dart';
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user