Compare commits
90 Commits
v1.0.8.091
...
desktop-ma
| Author | SHA1 | Date | |
|---|---|---|---|
| 41e9cfcbbb | |||
| 45bd4fc6d5 | |||
| 789d95e728 | |||
| 86c87dc1d5 | |||
| 3d6c270070 | |||
| f214c45448 | |||
| 5f26e19c62 | |||
| 960104929f | |||
| f25dab2eb8 | |||
| 15947e45da | |||
| 6c983cf849 | |||
| 4d5f3eb14a | |||
| 94aef39f7b | |||
| 424bdd9fff | |||
| c77c8e683d | |||
| ad1ced51f2 | |||
| c0c1a3a59a | |||
| 690b168a45 | |||
| 7f7919d585 | |||
| 76974bd874 | |||
| f8173b0b5f | |||
| 7ecfbac786 | |||
| c794eb465c | |||
| 7adbf76362 | |||
| 353287e053 | |||
| 856d699fd7 | |||
| 15914e5961 | |||
| 0e5b1633be | |||
| 24f22f8afa | |||
| 7c38340fc6 | |||
| 79f661e5da | |||
| 3ba90d6c85 | |||
| 85e86f1d61 | |||
| ec58d060bf | |||
| 7576f39010 | |||
| 77f47b8242 | |||
| 4b3e791370 | |||
| f25f5c28d9 | |||
| 7ca367869b | |||
| 7222ca4425 | |||
| 692d596818 | |||
| 47e3cf46e4 | |||
| 6b2229dddc | |||
| 1e202979d3 | |||
| 82ad1662aa | |||
| 7feda8d187 | |||
| d83b4bc59e | |||
| 1d1d4f8c7d | |||
| 1061ffca3d | |||
| 52ee5b36be | |||
| 87807466ff | |||
| 227cfb637e | |||
| 2c4ee083ef | |||
| 2ef3a8cd25 | |||
| 3a19b089c5 | |||
| 21e6d1aa52 | |||
| 10965fae73 | |||
| 8f987e8352 | |||
| 4e147b6f18 | |||
| 6d982bdba2 | |||
| 3edce0c4ec | |||
| 2eb7b388e1 | |||
| 2fd23aa20d | |||
| 2ecd1d3dab | |||
| 26d8ab5b43 | |||
| 8fa59f8f58 | |||
| 6ea4626288 | |||
| 329f158155 | |||
| d6b6df3eed | |||
| 3f50aab12d | |||
| 227da31857 | |||
| 7ad6b25abe | |||
| f79e4765c2 | |||
| e8671dee6b | |||
| 97268c36dc | |||
| d1272efad4 | |||
| ba815bccda | |||
| 75fb81b959 | |||
| a48d15ee73 | |||
| 620d7214df | |||
| 7458c33173 | |||
| 97fa047c60 | |||
| 9f4b928257 | |||
| afcc5a9a02 | |||
| 7181db66bd | |||
| 820a1e9162 | |||
| 566f75f760 | |||
| 4c49f466db | |||
| 8f97431665 | |||
| f543be562a |
4
change_log/1.0.10.1016.md
Normal file
4
change_log/1.0.10.1016.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
## 1.0.10
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 长按倍速抬起后未恢复默认倍速
|
||||||
28
change_log/1.0.9.1015.md
Normal file
28
change_log/1.0.9.1015.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
## 1.0.9
|
||||||
|
|
||||||
|
|
||||||
|
### 新功能
|
||||||
|
+ 自定义倍速、默认倍速
|
||||||
|
+ 历史记录搜索
|
||||||
|
+ 收藏夹搜索
|
||||||
|
+ 历史记录多选删除
|
||||||
|
+ 视频循环播放
|
||||||
|
+ 免登录看1080P
|
||||||
|
+ 评论区视频链接跳转
|
||||||
|
+ up主分组
|
||||||
|
+ up主投稿搜索
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 搜索视频标题乱码
|
||||||
|
+ 屏幕帧率
|
||||||
|
+ 动态页面渲染
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 优化
|
||||||
|
+ 快进手势
|
||||||
|
+ 视频简介链接匹配
|
||||||
|
+ 视频全屏时安全区域
|
||||||
|
|
||||||
|
更多更新日志可在Github上查看
|
||||||
|
问题反馈、功能建议请查看「关于」页面。
|
||||||
@ -1,6 +1,8 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- appscheme (1.0.4):
|
- appscheme (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- auto_orientation (0.0.1):
|
||||||
|
- Flutter
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
@ -12,8 +14,6 @@ PODS:
|
|||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- image_gallery_saver (2.0.2):
|
|
||||||
- Flutter
|
|
||||||
- media_kit_libs_ios_video (1.0.4):
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
- Flutter
|
- Flutter
|
||||||
- media_kit_native_event_loop (1.0.0):
|
- media_kit_native_event_loop (1.0.0):
|
||||||
@ -28,6 +28,8 @@ PODS:
|
|||||||
- permission_handler_apple (9.1.1):
|
- permission_handler_apple (9.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
|
- saver_gallery (0.0.1):
|
||||||
|
- Flutter
|
||||||
- screen_brightness_ios (0.1.0):
|
- screen_brightness_ios (0.1.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
@ -52,17 +54,18 @@ PODS:
|
|||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- appscheme (from `.symlinks/plugins/appscheme/ios`)
|
- appscheme (from `.symlinks/plugins/appscheme/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`)
|
||||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
|
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
@ -82,6 +85,8 @@ SPEC REPOS:
|
|||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
appscheme:
|
appscheme:
|
||||||
:path: ".symlinks/plugins/appscheme/ios"
|
:path: ".symlinks/plugins/appscheme/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:
|
||||||
@ -90,8 +95,6 @@ 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"
|
||||||
image_gallery_saver:
|
|
||||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
@ -104,6 +107,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
|
saver_gallery:
|
||||||
|
:path: ".symlinks/plugins/saver_gallery/ios"
|
||||||
screen_brightness_ios:
|
screen_brightness_ios:
|
||||||
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
@ -127,12 +132,12 @@ EXTERNAL SOURCES:
|
|||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
|
||||||
|
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
|
||||||
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
|
||||||
@ -140,6 +145,7 @@ SPEC CHECKSUMS:
|
|||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
|
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:pilipala/common/constants.dart';
|
|||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
import 'package:pilipala/http/dynamics.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
@ -27,6 +28,11 @@ class VideoCardV extends StatelessWidget {
|
|||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
bool isStringNumeric(String str) {
|
||||||
|
RegExp numericRegex = RegExp(r'^\d+$');
|
||||||
|
return numericRegex.hasMatch(str);
|
||||||
|
}
|
||||||
|
|
||||||
void onPushDetail(heroTag) async {
|
void onPushDetail(heroTag) async {
|
||||||
String goto = videoItem.goto;
|
String goto = videoItem.goto;
|
||||||
switch (goto) {
|
switch (goto) {
|
||||||
@ -64,11 +70,35 @@ class VideoCardV extends StatelessWidget {
|
|||||||
break;
|
break;
|
||||||
// 动态
|
// 动态
|
||||||
case 'picture':
|
case 'picture':
|
||||||
|
String dynamicType = 'picture';
|
||||||
|
String uri = videoItem.uri;
|
||||||
|
if (videoItem.uri.contains('bilibili://article/')) {
|
||||||
|
dynamicType = 'article';
|
||||||
|
RegExp regex = RegExp(r'\d+');
|
||||||
|
Match match = regex.firstMatch(videoItem.uri)!;
|
||||||
|
String matchedNumber = match.group(0)!;
|
||||||
|
videoItem.param = 'cv' + matchedNumber;
|
||||||
|
}
|
||||||
|
if (uri.startsWith('http')) {
|
||||||
|
String path = Uri.parse(uri).path;
|
||||||
|
if (isStringNumeric(path.split('/')[1])) {
|
||||||
|
// 请求接口
|
||||||
|
var res = await DynamicsHttp.dynamicDetail(id: path.split('/')[1]);
|
||||||
|
if (res['status']) {
|
||||||
|
Get.toNamed('/dynamicDetail', arguments: {
|
||||||
|
'item': res['data'],
|
||||||
|
'floor': 1,
|
||||||
|
'action': 'detail'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
'url': videoItem.uri,
|
'url': uri,
|
||||||
'title': videoItem.title,
|
'title': videoItem.title,
|
||||||
'id': videoItem.param.toString(),
|
'id': videoItem.param.toString(),
|
||||||
'dynamicType': 'picture'
|
'dynamicType': dynamicType
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -287,23 +317,18 @@ class VideoStat extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return RichText(
|
||||||
|
maxLines: 1,
|
||||||
|
text: TextSpan(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
Text(
|
TextSpan(text: '${videoItem.stat.view}观看'),
|
||||||
'${videoItem.stat.view}观看',
|
TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'),
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
' • ${videoItem.stat.danmu}弹幕',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -129,12 +129,14 @@ class Api {
|
|||||||
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
static const String userFavFolder = '/x/v3/fav/folder/created/list';
|
||||||
|
|
||||||
/// 收藏夹 详情
|
/// 收藏夹 详情
|
||||||
/// media_id int 收藏夹id
|
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
|
||||||
/// pn int 当前页
|
/// pn int 当前页
|
||||||
/// ps int pageSize
|
/// ps int pageSize
|
||||||
/// keyword String 搜索词
|
/// keyword String 搜索词
|
||||||
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
|
||||||
/// tid int 分区id
|
/// tid int 分区id
|
||||||
|
/// platform web
|
||||||
|
/// type 0 当前收藏夹 1 全部收藏夹
|
||||||
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
// https://api.bilibili.com/x/v3/fav/resource/list?media_id=76614671&pn=1&ps=20&keyword=&order=mtime&type=0&tid=0
|
||||||
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
|
||||||
|
|
||||||
@ -170,6 +172,9 @@ class Api {
|
|||||||
// 删除某条历史记录
|
// 删除某条历史记录
|
||||||
static const String delHistory = '/x/v2/history/delete';
|
static const String delHistory = '/x/v2/history/delete';
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static const String searchHistory = '/x/web-goblin/history/search';
|
||||||
|
|
||||||
// 热搜
|
// 热搜
|
||||||
static const String hotSearchList =
|
static const String hotSearchList =
|
||||||
'https://s.search.bilibili.com/main/hotword';
|
'https://s.search.bilibili.com/main/hotword';
|
||||||
@ -245,6 +250,9 @@ class Api {
|
|||||||
// wts=1689767832
|
// wts=1689767832
|
||||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||||
|
|
||||||
|
// 用户动态搜索
|
||||||
|
static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||||
|
|
||||||
// 用户动态
|
// 用户动态
|
||||||
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
||||||
|
|
||||||
@ -303,4 +311,20 @@ class Api {
|
|||||||
static const String onlineTotal = '/x/player/online/total';
|
static const String onlineTotal = '/x/player/online/total';
|
||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
// up主分组
|
||||||
|
static const String followUpTag = '/x/relation/tags';
|
||||||
|
|
||||||
|
// 设置Up主分组
|
||||||
|
// 0 添加至默认分组 否则使用,分割tagid
|
||||||
|
static const String addUsers = '/x/relation/tags/addUsers';
|
||||||
|
|
||||||
|
// 获取指定分组下的up
|
||||||
|
static const String followUpGroup = '/x/relation/tag';
|
||||||
|
|
||||||
|
// 获取某个动态详情
|
||||||
|
// timezone_offset=-480
|
||||||
|
// id=849312409672744983
|
||||||
|
// features=itemOpusStyle
|
||||||
|
static const String dynamicDetail = '/x/polymer/web-dynamic/v1/detail';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ class DynamicsHttp {
|
|||||||
'data': DynamicsDataModel.fromJson(res.data['data']),
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
print(err);
|
||||||
return {
|
return {
|
||||||
'status': false,
|
'status': false,
|
||||||
'data': [],
|
'data': [],
|
||||||
@ -85,4 +86,35 @@ class DynamicsHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
static Future dynamicDetail({
|
||||||
|
String? id,
|
||||||
|
}) async {
|
||||||
|
var res = await Request().get(Api.dynamicDetail, data: {
|
||||||
|
'timezone_offset': -480,
|
||||||
|
'id': id,
|
||||||
|
'features': 'itemOpusStyle',
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicItemModel.fromJson(res.data['data']['item']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': err.toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,15 +9,29 @@ class HtmlHttp {
|
|||||||
"https://www.bilibili.com/opus/$id",
|
"https://www.bilibili.com/opus/$id",
|
||||||
extra: {'ua': 'pc'},
|
extra: {'ua': 'pc'},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (response.data.contains('Redirecting to')) {
|
||||||
|
RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)');
|
||||||
|
Match match = regex.firstMatch(response.data)!;
|
||||||
|
String matchedString = match.group(0)!;
|
||||||
|
response = await Request().get(
|
||||||
|
'https:$matchedString' + '/',
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
Document rootTree = parse(response.data);
|
Document rootTree = parse(response.data);
|
||||||
|
// log(response.data.body.toString());
|
||||||
Element body = rootTree.body!;
|
Element body = rootTree.body!;
|
||||||
Element appDom = body.querySelector('#app')!;
|
Element appDom = body.querySelector('#app')!;
|
||||||
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
|
||||||
// 头像
|
// 头像
|
||||||
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
|
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
|
||||||
avatar = 'https:${avatar.split('@')[0]}';
|
avatar = 'https:${avatar.split('@')[0]}';
|
||||||
String uname =
|
String uname = authorHeader
|
||||||
authorHeader.querySelector('.fixed-author-header__author__name')!.text;
|
.querySelector('.fixed-author-header__author__name')!
|
||||||
|
.text;
|
||||||
|
|
||||||
// 动态详情
|
// 动态详情
|
||||||
Element opusDetail = appDom.querySelector('.opus-detail')!;
|
Element opusDetail = appDom.querySelector('.opus-detail')!;
|
||||||
// 发布时间
|
// 发布时间
|
||||||
@ -26,6 +40,9 @@ class HtmlHttp {
|
|||||||
//
|
//
|
||||||
String opusContent =
|
String opusContent =
|
||||||
opusDetail.querySelector('.opus-module-content')!.innerHtml;
|
opusDetail.querySelector('.opus-module-content')!.innerHtml;
|
||||||
|
String test = opusDetail
|
||||||
|
.querySelector('.horizontal-scroll-album__pic__img')!
|
||||||
|
.innerHtml;
|
||||||
String commentId = opusDetail
|
String commentId = opusDetail
|
||||||
.querySelector('.bili-comment-container')!
|
.querySelector('.bili-comment-container')!
|
||||||
.className
|
.className
|
||||||
@ -37,9 +54,12 @@ class HtmlHttp {
|
|||||||
'avatar': avatar,
|
'avatar': avatar,
|
||||||
'uname': uname,
|
'uname': uname,
|
||||||
'updateTime': updateTime,
|
'updateTime': updateTime,
|
||||||
'content': opusContent,
|
'content': test + opusContent,
|
||||||
'commentId': int.parse(commentId)
|
'commentId': int.parse(commentId)
|
||||||
};
|
};
|
||||||
|
} catch (err) {
|
||||||
|
print('err: $err');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// read
|
// read
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
import 'package:pilipala/utils/wbi_sign.dart';
|
import 'package:pilipala/utils/wbi_sign.dart';
|
||||||
|
|
||||||
class MemberHttp {
|
class MemberHttp {
|
||||||
@ -18,6 +20,7 @@ class MemberHttp {
|
|||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberInfo,
|
Api.memberInfo,
|
||||||
data: params,
|
data: params,
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -65,7 +68,7 @@ class MemberHttp {
|
|||||||
int ps = 30,
|
int ps = 30,
|
||||||
int tid = 0,
|
int tid = 0,
|
||||||
int? pn,
|
int? pn,
|
||||||
String keyword = '',
|
String? keyword,
|
||||||
String order = 'pubdate',
|
String order = 'pubdate',
|
||||||
bool orderAvoided = true,
|
bool orderAvoided = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -74,7 +77,7 @@ class MemberHttp {
|
|||||||
'ps': ps,
|
'ps': ps,
|
||||||
'tid': tid,
|
'tid': tid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'keyword': keyword,
|
'keyword': keyword ?? '',
|
||||||
'order': order,
|
'order': order,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
@ -83,6 +86,7 @@ class MemberHttp {
|
|||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.memberArchive,
|
Api.memberArchive,
|
||||||
data: params,
|
data: params,
|
||||||
|
extra: {'ua': 'pc'},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
@ -119,4 +123,96 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索用户动态
|
||||||
|
static Future memberDynamicSearch({int? pn, int? ps, int? mid}) async {
|
||||||
|
var res = await Request().get(Api.memberDynamic, data: {
|
||||||
|
'keyword': '海拔',
|
||||||
|
'mid': mid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
'platform': 'web'
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询分组
|
||||||
|
static Future followUpTags() async {
|
||||||
|
var res = await Request().get(Api.followUpTag);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']
|
||||||
|
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置分组
|
||||||
|
static Future addUsers(int? fids, String? tagids) async {
|
||||||
|
var res = await Request().post(Api.addUsers, queryParameters: {
|
||||||
|
'fids': fids,
|
||||||
|
'tagids': tagids ?? '0',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
}, data: {
|
||||||
|
'cross_domain': true
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': [], 'msg': '操作成功'};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取某分组下的up
|
||||||
|
static Future followUpGroup(
|
||||||
|
int? mid,
|
||||||
|
int? tagid,
|
||||||
|
int? pn,
|
||||||
|
int? ps,
|
||||||
|
) async {
|
||||||
|
var res = await Request().get(Api.followUpGroup, data: {
|
||||||
|
'mid': mid,
|
||||||
|
'tagid': tagid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
// FollowItemModel
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data']
|
||||||
|
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,7 @@ class ReplyHttp {
|
|||||||
Map errMap = {
|
Map errMap = {
|
||||||
-400: '请求错误',
|
-400: '请求错误',
|
||||||
-404: '无此项',
|
-404: '无此项',
|
||||||
12002: '当前页面评论功能已关闭"',
|
12002: '当前页面评论功能已关闭',
|
||||||
12009: '评论主体的type不合法',
|
12009: '评论主体的type不合法',
|
||||||
12061: 'UP主已关闭评论区',
|
12061: 'UP主已关闭评论区',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/search/hot.dart';
|
import 'package:pilipala/models/search/hot.dart';
|
||||||
import 'package:pilipala/models/search/result.dart';
|
import 'package:pilipala/models/search/result.dart';
|
||||||
import 'package:pilipala/models/search/suggest.dart';
|
import 'package:pilipala/models/search/suggest.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class SearchHttp {
|
class SearchHttp {
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
static Future hotSearchList() async {
|
static Future hotSearchList() async {
|
||||||
var res = await Request().get(Api.hotSearchList);
|
var res = await Request().get(Api.hotSearchList);
|
||||||
if (res.data is String) {
|
if (res.data is String) {
|
||||||
@ -78,6 +81,12 @@ class SearchHttp {
|
|||||||
try {
|
try {
|
||||||
switch (searchType) {
|
switch (searchType) {
|
||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
|
for (var i in res.data['data']['result']) {
|
||||||
|
// 屏蔽推广和拉黑用户
|
||||||
|
i['available'] = !blackMidsList.contains(i['mid']);
|
||||||
|
}
|
||||||
data = SearchVideoModel.fromJson(res.data['data']);
|
data = SearchVideoModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
case SearchType.live_room:
|
case SearchType.live_room:
|
||||||
@ -89,9 +98,9 @@ class SearchHttp {
|
|||||||
case SearchType.media_bangumi:
|
case SearchType.media_bangumi:
|
||||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
// case SearchType.article:
|
case SearchType.article:
|
||||||
// data = SearchArticleModel.fromJson(res.data['data']);
|
data = SearchArticleModel.fromJson(res.data['data']);
|
||||||
// break;
|
break;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
|
|||||||
@ -71,14 +71,15 @@ class UserHttp {
|
|||||||
required int pn,
|
required int pn,
|
||||||
required int ps,
|
required int ps,
|
||||||
String keyword = '',
|
String keyword = '',
|
||||||
String order = 'mtime'}) async {
|
String order = 'mtime',
|
||||||
|
int type = 0}) async {
|
||||||
var res = await Request().get(Api.userFavFolderDetail, data: {
|
var res = await Request().get(Api.userFavFolderDetail, data: {
|
||||||
'media_id': mediaId,
|
'media_id': mediaId,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'ps': ps,
|
'ps': ps,
|
||||||
'keyword': keyword,
|
'keyword': keyword,
|
||||||
'order': order,
|
'order': order,
|
||||||
'type': 0,
|
'type': type,
|
||||||
'tid': 0,
|
'tid': 0,
|
||||||
'platform': 'web'
|
'platform': 'web'
|
||||||
});
|
});
|
||||||
@ -238,7 +239,7 @@ class UserHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.delHistory,
|
Api.delHistory,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'kid': 'archive_$kid',
|
'kid': kid,
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
@ -274,4 +275,22 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static Future searchHistory(
|
||||||
|
{required int pn, required String keyword}) async {
|
||||||
|
var res = await Request().get(
|
||||||
|
Api.searchHistory,
|
||||||
|
data: {
|
||||||
|
'pn': pn,
|
||||||
|
'keyword': keyword,
|
||||||
|
'business': 'all',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ class VideoHttp {
|
|||||||
static Box setting = GStrorage.setting;
|
static Box setting = GStrorage.setting;
|
||||||
static bool enableRcmdDynamic =
|
static bool enableRcmdDynamic =
|
||||||
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
|
||||||
|
static Box userInfoCache = GStrorage.userInfo;
|
||||||
|
|
||||||
// 首页推荐视频
|
// 首页推荐视频
|
||||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||||
@ -133,6 +134,11 @@ class VideoHttp {
|
|||||||
// 'platform': '',
|
// 'platform': '',
|
||||||
// 'high_quality': ''
|
// 'high_quality': ''
|
||||||
};
|
};
|
||||||
|
// 免登录查看1080p
|
||||||
|
if (userInfoCache.get('userInfoCache') == null &&
|
||||||
|
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
|
||||||
|
data['try_look'] = 1;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
var res = await Request().get(Api.videoUrl, data: data);
|
var res = await Request().get(Api.videoUrl, data: data);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -61,6 +64,23 @@ class MyApp extends StatelessWidget {
|
|||||||
double textScale =
|
double textScale =
|
||||||
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
setting.get(SettingBoxKey.defaultTextScale, defaultValue: 1.0);
|
||||||
|
|
||||||
|
// 强制设置高帧率
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
try {
|
||||||
|
late List modes;
|
||||||
|
FlutterDisplayMode.supported.then((value) {
|
||||||
|
modes = value;
|
||||||
|
var storageDisplay = setting.get(SettingBoxKey.displayMode);
|
||||||
|
DisplayMode f = DisplayMode.auto;
|
||||||
|
if (storageDisplay != null) {
|
||||||
|
f = modes.firstWhere((e) => e.toString() == storageDisplay);
|
||||||
|
}
|
||||||
|
DisplayMode preferred = modes.toList().firstWhere((el) => el == f);
|
||||||
|
FlutterDisplayMode.setPreferredMode(preferred);
|
||||||
|
});
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||||
ColorScheme? lightColorScheme;
|
ColorScheme? lightColorScheme;
|
||||||
|
|||||||
@ -17,7 +17,7 @@ enum SearchType {
|
|||||||
// 用户:bili_user
|
// 用户:bili_user
|
||||||
bili_user,
|
bili_user,
|
||||||
// 专栏:article
|
// 专栏:article
|
||||||
// article,
|
article,
|
||||||
// 相簿:photo
|
// 相簿:photo
|
||||||
// photo
|
// photo
|
||||||
}
|
}
|
||||||
|
|||||||
@ -244,7 +244,9 @@ class Vote {
|
|||||||
choiceCnt = json['choice_cnt'];
|
choiceCnt = json['choice_cnt'];
|
||||||
share = json['share'];
|
share = json['share'];
|
||||||
defaultShare = json['default_share'];
|
defaultShare = json['default_share'];
|
||||||
endTime = json['end_time'];
|
endTime = json['end_time'] is int
|
||||||
|
? json['end_time']
|
||||||
|
: int.parse(json['end_time']);
|
||||||
joinNum = json['join_num'];
|
joinNum = json['join_num'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
|
|||||||
@ -8,7 +8,7 @@ class FollowDataModel {
|
|||||||
List<FollowItemModel>? list;
|
List<FollowItemModel>? list;
|
||||||
|
|
||||||
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
total = json['total'];
|
total = json['total'] ?? 0;
|
||||||
list = json['list']
|
list = json['list']
|
||||||
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
@ -19,7 +19,7 @@ class FollowItemModel {
|
|||||||
FollowItemModel({
|
FollowItemModel({
|
||||||
this.mid,
|
this.mid,
|
||||||
this.attribute,
|
this.attribute,
|
||||||
this.mtime,
|
// this.mtime,
|
||||||
this.tag,
|
this.tag,
|
||||||
this.special,
|
this.special,
|
||||||
this.uname,
|
this.uname,
|
||||||
@ -30,7 +30,7 @@ class FollowItemModel {
|
|||||||
|
|
||||||
int? mid;
|
int? mid;
|
||||||
int? attribute;
|
int? attribute;
|
||||||
int? mtime;
|
// int? mtime;
|
||||||
List? tag;
|
List? tag;
|
||||||
int? special;
|
int? special;
|
||||||
String? uname;
|
String? uname;
|
||||||
@ -41,7 +41,7 @@ class FollowItemModel {
|
|||||||
FollowItemModel.fromJson(Map<String, dynamic> json) {
|
FollowItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
attribute = json['attribute'];
|
attribute = json['attribute'];
|
||||||
mtime = json['mtime'];
|
// mtime = json['mtime'];
|
||||||
tag = json['tag'];
|
tag = json['tag'];
|
||||||
special = json['special'];
|
special = json['special'];
|
||||||
uname = json['uname'];
|
uname = json['uname'];
|
||||||
|
|||||||
23
lib/models/member/tags.dart
Normal file
23
lib/models/member/tags.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
class MemberTagItemModel {
|
||||||
|
MemberTagItemModel({
|
||||||
|
this.count,
|
||||||
|
this.name,
|
||||||
|
this.tagid,
|
||||||
|
this.tip,
|
||||||
|
this.checked,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? count;
|
||||||
|
String? name;
|
||||||
|
int? tagid;
|
||||||
|
String? tip;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
|
MemberTagItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
count = json['count'];
|
||||||
|
name = json['name'];
|
||||||
|
tagid = json['tagid'];
|
||||||
|
tip = json['tip'];
|
||||||
|
checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@ class SearchVideoModel {
|
|||||||
List<SearchVideoItemModel>? list;
|
List<SearchVideoItemModel>? list;
|
||||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||||
list = json['result']
|
list = json['result']
|
||||||
|
.where((e) => e['available'] == true)
|
||||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -17,7 +18,7 @@ class SearchVideoItemModel {
|
|||||||
this.id,
|
this.id,
|
||||||
this.cid,
|
this.cid,
|
||||||
// this.author,
|
// this.author,
|
||||||
// this.mid,
|
this.mid,
|
||||||
// this.typeid,
|
// this.typeid,
|
||||||
// this.typename,
|
// this.typename,
|
||||||
this.arcurl,
|
this.arcurl,
|
||||||
@ -47,7 +48,7 @@ class SearchVideoItemModel {
|
|||||||
int? id;
|
int? id;
|
||||||
int? cid;
|
int? cid;
|
||||||
// String? author;
|
// String? author;
|
||||||
// String? mid;
|
int? mid;
|
||||||
// String? typeid;
|
// String? typeid;
|
||||||
// String? typename;
|
// String? typename;
|
||||||
String? arcurl;
|
String? arcurl;
|
||||||
@ -80,6 +81,7 @@ class SearchVideoItemModel {
|
|||||||
arcurl = json['arcurl'];
|
arcurl = json['arcurl'];
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
mid = json['mid'];
|
||||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
@ -397,6 +399,7 @@ class SearchArticleItemModel {
|
|||||||
this.pubTime,
|
this.pubTime,
|
||||||
this.like,
|
this.like,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.subTitle,
|
||||||
this.rankOffset,
|
this.rankOffset,
|
||||||
this.mid,
|
this.mid,
|
||||||
this.imageUrls,
|
this.imageUrls,
|
||||||
@ -414,6 +417,7 @@ class SearchArticleItemModel {
|
|||||||
int? pubTime;
|
int? pubTime;
|
||||||
int? like;
|
int? like;
|
||||||
List? title;
|
List? title;
|
||||||
|
String? subTitle;
|
||||||
int? rankOffset;
|
int? rankOffset;
|
||||||
int? mid;
|
int? mid;
|
||||||
List? imageUrls;
|
List? imageUrls;
|
||||||
@ -431,6 +435,7 @@ class SearchArticleItemModel {
|
|||||||
pubTime = json['pub_time'];
|
pubTime = json['pub_time'];
|
||||||
like = json['like'];
|
like = json['like'];
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
|
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
rankOffset = json['rank_offset'];
|
rankOffset = json['rank_offset'];
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
imageUrls = json['image_urls'];
|
imageUrls = json['image_urls'];
|
||||||
|
|||||||
@ -3,17 +3,23 @@ class HistoryData {
|
|||||||
this.cursor,
|
this.cursor,
|
||||||
this.tab,
|
this.tab,
|
||||||
this.list,
|
this.list,
|
||||||
|
this.page,
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor? cursor;
|
Cursor? cursor;
|
||||||
List<HisTabItem>? tab;
|
List<HisTabItem>? tab;
|
||||||
List<HisListItem>? list;
|
List<HisListItem>? list;
|
||||||
|
Map? page;
|
||||||
|
|
||||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||||
cursor = Cursor.fromJson(json['cursor']);
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
tab = json['tab'] != null
|
||||||
list =
|
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
: [];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
page = json['page'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ class HisListItem {
|
|||||||
this.kid,
|
this.kid,
|
||||||
this.tagName,
|
this.tagName,
|
||||||
this.liveStatus,
|
this.liveStatus,
|
||||||
|
this.checked,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? title;
|
String? title;
|
||||||
@ -105,6 +112,7 @@ class HisListItem {
|
|||||||
int? kid;
|
int? kid;
|
||||||
String? tagName;
|
String? tagName;
|
||||||
int? liveStatus;
|
int? liveStatus;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
@ -131,6 +139,7 @@ class HisListItem {
|
|||||||
kid = json['kid'];
|
kid = json['kid'];
|
||||||
tagName = json['tag_name'];
|
tagName = json['tag_name'];
|
||||||
liveStatus = json['live_status'];
|
liveStatus = json['live_status'];
|
||||||
|
checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -21,7 +22,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
? int.parse(Get.parameters['seasonId']!)
|
? int.parse(Get.parameters['seasonId']!)
|
||||||
: null;
|
: null;
|
||||||
var epId = Get.parameters['epId'] != null
|
var epId = Get.parameters['epId'] != null
|
||||||
? int.parse(Get.parameters['epId']!)
|
? int.tryParse(Get.parameters['epId']!)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// 是否预渲染 骨架屏
|
// 是否预渲染 骨架屏
|
||||||
@ -257,7 +258,7 @@ class BangumiIntroController extends GetxController {
|
|||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
@ -292,4 +293,31 @@ class BangumiIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
if (bangumiDetail.value.episodes != null) {
|
||||||
|
episodes = bangumiDetail.value.episodes!;
|
||||||
|
}
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
int currentIndex =
|
||||||
|
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
// 列表循环
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
if (nextIndex == episodes.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextIndex <= episodes.length - 1 &&
|
||||||
|
platRepeat == PlayRepeat.listOrder) {}
|
||||||
|
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
|
int aid = episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,10 +34,12 @@ class BangumiIntroPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final BangumiIntroController bangumiIntroController =
|
late BangumiIntroController bangumiIntroController;
|
||||||
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
late VideoDetailController videoDetailCtr;
|
||||||
BangumiInfoModel? bangumiDetail;
|
BangumiInfoModel? bangumiDetail;
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
|
late int cid;
|
||||||
|
late String heroTag;
|
||||||
|
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
@ -46,10 +48,19 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
heroTag = Get.arguments['heroTag'];
|
||||||
|
cid = widget.cid!;
|
||||||
|
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||||
|
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
bangumiIntroController.bangumiDetail.listen((value) {
|
bangumiIntroController.bangumiDetail.listen((value) {
|
||||||
bangumiDetail = value;
|
bangumiDetail = value;
|
||||||
});
|
});
|
||||||
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
print('🐶🐶$p0');
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -61,9 +72,11 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
// 请求成功
|
// 请求成功
|
||||||
|
|
||||||
return BangumiInfo(
|
return BangumiInfo(
|
||||||
loadingStatus: false,
|
loadingStatus: false,
|
||||||
bangumiDetail: bangumiDetail,
|
bangumiDetail: bangumiDetail,
|
||||||
|
cid: cid,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 请求错误
|
// 请求错误
|
||||||
@ -77,7 +90,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
return BangumiInfo(
|
return BangumiInfo(
|
||||||
loadingStatus: true,
|
loadingStatus: true,
|
||||||
bangumiDetail: bangumiDetail,
|
bangumiDetail: bangumiDetail,
|
||||||
cid: widget.cid,
|
cid: cid,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -118,6 +131,12 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
bangumiItem = bangumiIntroController.bangumiItem;
|
bangumiItem = bangumiIntroController.bangumiItem;
|
||||||
sheetHeight = localCache.get('sheetHeight');
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
cid = widget.cid!;
|
cid = widget.cid!;
|
||||||
|
print('cid: $cid');
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
print('cid: $cid');
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class BangumiPanel extends StatefulWidget {
|
class BangumiPanel extends StatefulWidget {
|
||||||
@ -30,16 +32,28 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
|||||||
dynamic userInfo;
|
dynamic userInfo;
|
||||||
// 默认未开通
|
// 默认未开通
|
||||||
int vipStatus = 0;
|
int vipStatus = 0;
|
||||||
|
late int cid;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
late final VideoDetailController videoDetailCtr;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
cid = widget.cid!;
|
||||||
|
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
|
||||||
scrollToIndex();
|
scrollToIndex();
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (userInfo != null) {
|
if (userInfo != null) {
|
||||||
vipStatus = userInfo.vipStatus;
|
vipStatus = userInfo.vipStatus;
|
||||||
}
|
}
|
||||||
|
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
|
||||||
|
videoDetailCtr.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
|
||||||
|
scrollToIndex();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'黑名单管理 - ${_blackListController.blackList.length}',
|
'黑名单管理 - ${_blackListController.total.value}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -138,6 +138,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
class BlackListController extends GetxController {
|
class BlackListController extends GetxController {
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int pageSize = 50;
|
int pageSize = 50;
|
||||||
|
RxInt total = 0.obs;
|
||||||
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||||
|
|
||||||
Future queryBlacklist({type = 'init'}) async {
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
@ -148,6 +149,7 @@ class BlackListController extends GetxController {
|
|||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
blackList.value = result['data'].list;
|
blackList.value = result['data'].list;
|
||||||
|
total.value = result['data'].total;
|
||||||
} else {
|
} else {
|
||||||
blackList.addAll(result['data'].list);
|
blackList.addAll(result['data'].list);
|
||||||
}
|
}
|
||||||
@ -161,6 +163,7 @@ class BlackListController extends GetxController {
|
|||||||
var result = await BlackHttp.removeBlack(fid: mid);
|
var result = await BlackHttp.removeBlack(fid: mid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
blackList.removeWhere((e) => e.mid == mid);
|
blackList.removeWhere((e) => e.mid == mid);
|
||||||
|
total.value = total.value - 1;
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,6 +86,9 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
_controller!.onResume();
|
_controller!.onResume();
|
||||||
danmuPlayStatus = true;
|
danmuPlayStatus = true;
|
||||||
}
|
}
|
||||||
|
if (!playerController.isOpenDanmu.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
PlDanmakuController ctr = _plDanmakuController;
|
PlDanmakuController ctr = _plDanmakuController;
|
||||||
int currentPosition = position.inMilliseconds;
|
int currentPosition = position.inMilliseconds;
|
||||||
blockTypes = playerController.blockTypes;
|
blockTypes = playerController.blockTypes;
|
||||||
@ -98,9 +101,6 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
ctr.queryDanmaku();
|
ctr.queryDanmaku();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!playerController.isOpenDanmu.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 超出分段数返回
|
// 超出分段数返回
|
||||||
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
|
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -177,7 +177,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
return AnimatedOpacity(
|
return AnimatedOpacity(
|
||||||
opacity: snapshot.data ? 1 : 0,
|
opacity: snapshot.data ? 1 : 0,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: author(_dynamicDetailController!.item, context),
|
child: AuthorPanel(item: _dynamicDetailController.item),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
|
||||||
/// TODO 点击跳转
|
/// TODO 点击跳转
|
||||||
Widget addWidget(item, context, type, {floor = 1}) {
|
Widget addWidget(item, context, type, {floor = 1}) {
|
||||||
@ -19,8 +22,27 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
: Theme.of(context).colorScheme.background;
|
: Theme.of(context).colorScheme.background;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ADDITIONAL_TYPE_UGC':
|
case 'ADDITIONAL_TYPE_UGC':
|
||||||
|
// 转发的投稿
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {},
|
onTap: () async {
|
||||||
|
String text = dynamicProperty[type].jumpUrl;
|
||||||
|
RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
|
||||||
|
Iterable<Match> matches = bvRegex.allMatches(text);
|
||||||
|
if (matches.isNotEmpty) {
|
||||||
|
Match match = matches.first;
|
||||||
|
String bvid = match.group(0)!;
|
||||||
|
String cover = dynamicProperty[type].cover;
|
||||||
|
try {
|
||||||
|
int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: {'pic': cover, 'heroTag': bvid});
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("No match found.");
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||||
@ -61,6 +83,7 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
);
|
);
|
||||||
case 'ADDITIONAL_TYPE_RESERVE':
|
case 'ADDITIONAL_TYPE_RESERVE':
|
||||||
return dynamicProperty[type].state != -1
|
return dynamicProperty[type].state != -1
|
||||||
|
? dynamicProperty[type].title != null
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@ -88,9 +111,15 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
.labelMedium!
|
.labelMedium!
|
||||||
.fontSize),
|
.fontSize),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(text: dynamicProperty[type].desc1['text']),
|
if (dynamicProperty[type].desc1 != null)
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
dynamicProperty[type].desc1['text']),
|
||||||
const TextSpan(text: ' '),
|
const TextSpan(text: ' '),
|
||||||
TextSpan(text: dynamicProperty[type].desc2['text']),
|
if (dynamicProperty[type].desc2 != null)
|
||||||
|
TextSpan(
|
||||||
|
text:
|
||||||
|
dynamicProperty[type].desc2['text']),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -100,62 +129,65 @@ Widget addWidget(item, context, type, {floor = 1}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
: const SizedBox()
|
||||||
: const SizedBox();
|
: const SizedBox();
|
||||||
case 'ADDITIONAL_TYPE_GOODS':
|
case 'ADDITIONAL_TYPE_GOODS':
|
||||||
return Padding(
|
// 商品
|
||||||
padding: const EdgeInsets.only(top: 6),
|
return const SizedBox();
|
||||||
child: InkWell(
|
// return Padding(
|
||||||
onTap: () {},
|
// padding: const EdgeInsets.only(top: 6),
|
||||||
child: Container(
|
// child: InkWell(
|
||||||
padding:
|
// onTap: () {},
|
||||||
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
// child: Container(
|
||||||
decoration: BoxDecoration(
|
// padding:
|
||||||
color: bgColor,
|
// const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
// decoration: BoxDecoration(
|
||||||
),
|
// color: bgColor,
|
||||||
child: Row(
|
// borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
children: [
|
// ),
|
||||||
NetworkImgLayer(
|
// child: Row(
|
||||||
width: 75,
|
// children: [
|
||||||
height: 75,
|
// NetworkImgLayer(
|
||||||
src: dynamicProperty[type].items.first.cover,
|
// width: 75,
|
||||||
),
|
// height: 75,
|
||||||
const SizedBox(width: 10),
|
// src: dynamicProperty[type].items.first.cover,
|
||||||
Expanded(
|
// ),
|
||||||
child: Column(
|
// const SizedBox(width: 10),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// Expanded(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
// child: Column(
|
||||||
children: [
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
// mainAxisAlignment: MainAxisAlignment.start,
|
||||||
dynamicProperty[type].items.first.name,
|
// children: [
|
||||||
maxLines: 1,
|
// Text(
|
||||||
overflow: TextOverflow.ellipsis,
|
// dynamicProperty[type].items.first.name,
|
||||||
),
|
// maxLines: 1,
|
||||||
Text(
|
// overflow: TextOverflow.ellipsis,
|
||||||
dynamicProperty[type].items.first.brief,
|
// ),
|
||||||
maxLines: 1,
|
// Text(
|
||||||
style: TextStyle(
|
// dynamicProperty[type].items.first.brief,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
// maxLines: 1,
|
||||||
fontSize: Theme.of(context)
|
// style: TextStyle(
|
||||||
.textTheme
|
// color: Theme.of(context).colorScheme.outline,
|
||||||
.labelMedium!
|
// fontSize: Theme.of(context)
|
||||||
.fontSize,
|
// .textTheme
|
||||||
),
|
// .labelMedium!
|
||||||
),
|
// .fontSize,
|
||||||
const SizedBox(height: 2),
|
// ),
|
||||||
Text(
|
// ),
|
||||||
dynamicProperty[type].items.first.price,
|
// const SizedBox(height: 2),
|
||||||
style: TextStyle(
|
// Text(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
// dynamicProperty[type].items.first.price,
|
||||||
),
|
// style: TextStyle(
|
||||||
),
|
// color: Theme.of(context).colorScheme.primary,
|
||||||
],
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ],
|
||||||
],
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ],
|
||||||
));
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),);
|
||||||
case 'ADDITIONAL_TYPE_MATCH':
|
case 'ADDITIONAL_TYPE_MATCH':
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
case 'ADDITIONAL_TYPE_COMMON':
|
case 'ADDITIONAL_TYPE_COMMON':
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget author(item, context) {
|
class AuthorPanel extends StatelessWidget {
|
||||||
|
final dynamic item;
|
||||||
|
const AuthorPanel({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
|
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
@ -60,6 +67,93 @@ Widget author(item, context) {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (item.type == 'DYNAMIC_TYPE_AV')
|
||||||
|
SizedBox(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) {
|
||||||
|
return MorePanel(item: item);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.more_vert_outlined, size: 18),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MorePanel extends StatelessWidget {
|
||||||
|
final dynamic item;
|
||||||
|
const MorePanel({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
// clipBehavior: Clip.hardEdge,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: Container(
|
||||||
|
height: 35,
|
||||||
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
width: 32,
|
||||||
|
height: 3,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(3))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
String bvid = item.modules.moduleDynamic.major.archive.bvid;
|
||||||
|
var res = await UserHttp.toViewLater(bvid: bvid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
Get.back();
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast('出错了:${err.toString()}');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
// dense: true,
|
||||||
|
leading: const Icon(Icons.watch_later_outlined, size: 19),
|
||||||
|
title: Text(
|
||||||
|
'稍后再看',
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(thickness: 0.1, height: 1),
|
||||||
|
ListTile(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
dense: true,
|
||||||
|
title: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,28 +1,165 @@
|
|||||||
// 内容
|
// 内容
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
|
|
||||||
import 'rich_node_panel.dart';
|
import 'rich_node_panel.dart';
|
||||||
|
|
||||||
Widget content(item, context, source) {
|
// ignore: must_be_immutable
|
||||||
|
class Content extends StatefulWidget {
|
||||||
|
dynamic item;
|
||||||
|
String? source;
|
||||||
|
Content({
|
||||||
|
super.key,
|
||||||
|
this.item,
|
||||||
|
this.source,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Content> createState() => _ContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContentState extends State<Content> {
|
||||||
|
late bool hasPics;
|
||||||
|
List<OpusPicsModel> pics = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
hasPics = widget.item.modules.moduleDynamic.major != null &&
|
||||||
|
widget.item.modules.moduleDynamic.major.opus != null &&
|
||||||
|
widget.item.modules.moduleDynamic.major.opus.pics.isNotEmpty;
|
||||||
|
if (hasPics) {
|
||||||
|
pics = widget.item.modules.moduleDynamic.major.opus.pics;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineSpan picsNodes() {
|
||||||
|
List<InlineSpan> spanChilds = [];
|
||||||
|
int len = pics.length;
|
||||||
|
List<String> picList = [];
|
||||||
|
|
||||||
|
if (len == 1) {
|
||||||
|
OpusPicsModel pictureItem = pics.first;
|
||||||
|
picList.add(pictureItem.url!);
|
||||||
|
spanChilds.add(const TextSpan(text: '\n'));
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 4),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pictureItem.url,
|
||||||
|
width: box.maxWidth / 2,
|
||||||
|
height: box.maxWidth *
|
||||||
|
0.5 *
|
||||||
|
(pictureItem.height != null && pictureItem.width != null
|
||||||
|
? pictureItem.height! / pictureItem.width!
|
||||||
|
: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (len > 1) {
|
||||||
|
List<Widget> list = [];
|
||||||
|
for (var i = 0; i < len; i++) {
|
||||||
|
picList.add(pics[i].url!);
|
||||||
|
list.add(
|
||||||
|
LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
useSafeArea: false,
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return ImagePreview(initialPage: i, imgList: picList);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: pics[i].url,
|
||||||
|
width: box.maxWidth,
|
||||||
|
height: box.maxWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
spanChilds.add(
|
||||||
|
WidgetSpan(
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, BoxConstraints box) {
|
||||||
|
double maxWidth = box.maxWidth;
|
||||||
|
double crossCount = len < 3 ? 2 : 3;
|
||||||
|
double height = maxWidth /
|
||||||
|
crossCount *
|
||||||
|
(len % crossCount == 0
|
||||||
|
? len ~/ crossCount
|
||||||
|
: len ~/ crossCount + 1) +
|
||||||
|
6;
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
height: height,
|
||||||
|
child: GridView.count(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
crossAxisCount: crossCount.toInt(),
|
||||||
|
mainAxisSpacing: 4.0,
|
||||||
|
crossAxisSpacing: 4.0,
|
||||||
|
childAspectRatio: 1,
|
||||||
|
children: list,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return TextSpan(
|
||||||
|
children: spanChilds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
TextStyle authorStyle =
|
TextStyle authorStyle =
|
||||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (item.modules.moduleDynamic.topic != null) ...[
|
if (widget.item.modules.moduleDynamic.topic != null) ...[
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
child: Text(
|
child: Text(
|
||||||
'#${item.modules.moduleDynamic.topic.name}',
|
'#${widget.item.modules.moduleDynamic.topic.name}',
|
||||||
style: authorStyle,
|
style: authorStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
// 禁用SelectableRegion的触摸交互功能
|
// 禁用SelectableRegion的触摸交互功能
|
||||||
ignoring: source == 'detail' ? false : true,
|
ignoring: widget.source == 'detail' ? false : true,
|
||||||
child: SelectableRegion(
|
child: SelectableRegion(
|
||||||
magnifierConfiguration: const TextMagnifierConfiguration(),
|
magnifierConfiguration: const TextMagnifierConfiguration(),
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
@ -30,13 +167,17 @@ Widget content(item, context, source) {
|
|||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
/// fix 默认20px高度
|
/// fix 默认20px高度
|
||||||
style: const TextStyle(height: 0),
|
style: const TextStyle(height: 0),
|
||||||
richNode(item, context),
|
richNode(widget.item, context),
|
||||||
maxLines: source == 'detail' ? 999 : 3,
|
maxLines: widget.source == 'detail' ? 999 : 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (hasPics) ...[
|
||||||
|
Text.rich(picsNodes()),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -39,11 +39,11 @@ class DynamicPanel extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
child: author(item, context),
|
child: AuthorPanel(item: item),
|
||||||
),
|
),
|
||||||
if (item!.modules!.moduleDynamic!.desc != null ||
|
if (item!.modules!.moduleDynamic!.desc != null ||
|
||||||
item!.modules!.moduleDynamic!.major != null)
|
item!.modules!.moduleDynamic!.major != null)
|
||||||
content(item, context, source),
|
Content(item: item, source: source),
|
||||||
forWard(item, context, _dynamicsController, source),
|
forWard(item, context, _dynamicsController, source),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
if (source == null) ActionPanel(item: item),
|
if (source == null) ActionPanel(item: item),
|
||||||
|
|||||||
@ -27,8 +27,9 @@ InlineSpan richNode(item, context) {
|
|||||||
} else {
|
} else {
|
||||||
for (var i in richTextNodes) {
|
for (var i in richTextNodes) {
|
||||||
/// fix 渲染专栏时内容会重复
|
/// fix 渲染专栏时内容会重复
|
||||||
if (item.modules.moduleDynamic.major.opus.title == null &&
|
// if (item.modules.moduleDynamic.major.opus.title == null &&
|
||||||
i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
// i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
||||||
|
if (i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
|
||||||
spanChilds.add(
|
spanChilds.add(
|
||||||
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
|
||||||
}
|
}
|
||||||
@ -109,6 +110,7 @@ InlineSpan richNode(item, context) {
|
|||||||
alignment: PlaceholderAlignment.middle,
|
alignment: PlaceholderAlignment.middle,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
try {
|
||||||
String dynamicId = item.basic['comment_id_str'];
|
String dynamicId = item.basic['comment_id_str'];
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
@ -119,6 +121,7 @@ InlineSpan richNode(item, context) {
|
|||||||
'pageTitle': '投票'
|
'pageTitle': '投票'
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
} catch (_) {}
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'投票:${i.text}',
|
'投票:${i.text}',
|
||||||
@ -193,107 +196,107 @@ InlineSpan richNode(item, context) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contentType == 'major' &&
|
// if (contentType == 'major' &&
|
||||||
item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
|
// item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
|
||||||
// 图片可能跟其他widget重复渲染
|
// // 图片可能跟其他widget重复渲染
|
||||||
List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
|
// List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
|
||||||
int len = pics.length;
|
// int len = pics.length;
|
||||||
List<String> picList = [];
|
// List<String> picList = [];
|
||||||
|
|
||||||
if (len == 1) {
|
// if (len == 1) {
|
||||||
OpusPicsModel pictureItem = pics.first;
|
// OpusPicsModel pictureItem = pics.first;
|
||||||
picList.add(pictureItem.url!);
|
// picList.add(pictureItem.url!);
|
||||||
spanChilds.add(const TextSpan(text: '\n'));
|
// spanChilds.add(const TextSpan(text: '\n'));
|
||||||
spanChilds.add(
|
// spanChilds.add(
|
||||||
WidgetSpan(
|
// WidgetSpan(
|
||||||
child: LayoutBuilder(
|
// child: LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
// builder: (context, BoxConstraints box) {
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
showDialog(
|
// showDialog(
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
return ImagePreview(initialPage: 0, imgList: picList);
|
// return ImagePreview(initialPage: 0, imgList: picList);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
child: Padding(
|
// child: Padding(
|
||||||
padding: const EdgeInsets.only(top: 4),
|
// padding: const EdgeInsets.only(top: 4),
|
||||||
child: NetworkImgLayer(
|
// child: NetworkImgLayer(
|
||||||
src: pictureItem.url,
|
// src: pictureItem.url,
|
||||||
width: box.maxWidth / 2,
|
// width: box.maxWidth / 2,
|
||||||
height: box.maxWidth *
|
// height: box.maxWidth *
|
||||||
0.5 *
|
// 0.5 *
|
||||||
(pictureItem.height != null &&
|
// (pictureItem.height != null &&
|
||||||
pictureItem.width != null
|
// pictureItem.width != null
|
||||||
? pictureItem.height! / pictureItem.width!
|
// ? pictureItem.height! / pictureItem.width!
|
||||||
: 1),
|
// : 1),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
if (len > 1) {
|
// if (len > 1) {
|
||||||
List<Widget> list = [];
|
// List<Widget> list = [];
|
||||||
for (var i = 0; i < len; i++) {
|
// for (var i = 0; i < len; i++) {
|
||||||
picList.add(pics[i].url!);
|
// picList.add(pics[i].url!);
|
||||||
list.add(
|
// list.add(
|
||||||
LayoutBuilder(
|
// LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
// builder: (context, BoxConstraints box) {
|
||||||
return GestureDetector(
|
// return GestureDetector(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
showDialog(
|
// showDialog(
|
||||||
useSafeArea: false,
|
// useSafeArea: false,
|
||||||
context: context,
|
// context: context,
|
||||||
builder: (context) {
|
// builder: (context) {
|
||||||
return ImagePreview(initialPage: i, imgList: picList);
|
// return ImagePreview(initialPage: i, imgList: picList);
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
child: NetworkImgLayer(
|
// child: NetworkImgLayer(
|
||||||
src: pics[i].url,
|
// src: pics[i].url,
|
||||||
width: box.maxWidth,
|
// width: box.maxWidth,
|
||||||
height: box.maxWidth,
|
// height: box.maxWidth,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
spanChilds.add(
|
// spanChilds.add(
|
||||||
WidgetSpan(
|
// WidgetSpan(
|
||||||
child: LayoutBuilder(
|
// child: LayoutBuilder(
|
||||||
builder: (context, BoxConstraints box) {
|
// builder: (context, BoxConstraints box) {
|
||||||
double maxWidth = box.maxWidth;
|
// double maxWidth = box.maxWidth;
|
||||||
double crossCount = len < 3 ? 2 : 3;
|
// double crossCount = len < 3 ? 2 : 3;
|
||||||
double height = maxWidth /
|
// double height = maxWidth /
|
||||||
crossCount *
|
// crossCount *
|
||||||
(len % crossCount == 0
|
// (len % crossCount == 0
|
||||||
? len ~/ crossCount
|
// ? len ~/ crossCount
|
||||||
: len ~/ crossCount + 1) +
|
// : len ~/ crossCount + 1) +
|
||||||
6;
|
// 6;
|
||||||
return Container(
|
// return Container(
|
||||||
padding: const EdgeInsets.only(top: 6),
|
// padding: const EdgeInsets.only(top: 6),
|
||||||
height: height,
|
// height: height,
|
||||||
child: GridView.count(
|
// child: GridView.count(
|
||||||
padding: EdgeInsets.zero,
|
// padding: EdgeInsets.zero,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
// physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisCount: crossCount.toInt(),
|
// crossAxisCount: crossCount.toInt(),
|
||||||
mainAxisSpacing: 4.0,
|
// mainAxisSpacing: 4.0,
|
||||||
crossAxisSpacing: 4.0,
|
// crossAxisSpacing: 4.0,
|
||||||
childAspectRatio: 1,
|
// childAspectRatio: 1,
|
||||||
children: list,
|
// children: list,
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
// spanChilds.add(
|
// spanChilds.add(
|
||||||
// WidgetSpan(
|
// WidgetSpan(
|
||||||
// child: NetworkImgLayer(
|
// child: NetworkImgLayer(
|
||||||
@ -304,7 +307,7 @@ InlineSpan richNode(item, context) {
|
|||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
// );
|
// );
|
||||||
}
|
// }
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
children: spanChilds,
|
children: spanChilds,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -91,7 +91,10 @@ class _UpPanelState extends State<UpPanel> {
|
|||||||
),
|
),
|
||||||
Material(
|
Material(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => {feedBack(), Get.toNamed('/follow')},
|
onTap: () => {
|
||||||
|
feedBack(),
|
||||||
|
Get.toNamed('/follow?mid=${userInfo.mid}')
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 100,
|
height: 100,
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
|
|||||||
@ -44,6 +44,14 @@ class _FavPageState extends State<FavPage> {
|
|||||||
'我的收藏',
|
'我的收藏',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: FutureBuilder(
|
body: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
|
|||||||
@ -92,13 +92,18 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
// IconButton(
|
// IconButton(
|
||||||
// onPressed: () {},
|
// onPressed: () {},
|
||||||
// icon: const Icon(Icons.more_vert),
|
// icon: const Icon(Icons.more_vert),
|
||||||
// ),
|
// ),
|
||||||
// const SizedBox(width: 4)
|
const SizedBox(width: 6),
|
||||||
// ],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Container(
|
background: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
75
lib/pages/fav_search/controller.dart
Normal file
75
lib/pages/fav_search/controller.dart
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/models/user/fav_detail.dart';
|
||||||
|
|
||||||
|
class FavSearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs; // 搜索词
|
||||||
|
String hintText = '请输入已收藏视频名称'; // 默认
|
||||||
|
RxBool loadingStatus = false.obs; // 加载状态
|
||||||
|
RxString loadingText = '加载中...'.obs; // 加载提示
|
||||||
|
bool hasMore = false;
|
||||||
|
late int searchType;
|
||||||
|
late int mediaId;
|
||||||
|
|
||||||
|
int currentPage = 1; // 当前页
|
||||||
|
int count = 0; // 总数
|
||||||
|
RxList<FavDetailItemData> favList = <FavDetailItemData>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
searchType = int.parse(Get.parameters['searchType']!);
|
||||||
|
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = true;
|
||||||
|
currentPage = 1;
|
||||||
|
searchFav();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索收藏夹视频
|
||||||
|
Future searchFav({type = 'init'}) async {
|
||||||
|
var res = await await UserHttp.userFavFolderDetail(
|
||||||
|
pn: currentPage,
|
||||||
|
ps: 20,
|
||||||
|
mediaId: mediaId,
|
||||||
|
keyword: searchKeyWord.value,
|
||||||
|
type: searchType,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (currentPage == 1 && type == 'init') {
|
||||||
|
favList.value = res['data'].medias;
|
||||||
|
} else if (type == 'onLoad') {
|
||||||
|
favList.addAll(res['data'].medias);
|
||||||
|
}
|
||||||
|
hasMore = res['data'].hasMore;
|
||||||
|
}
|
||||||
|
currentPage += 1;
|
||||||
|
loadingStatus.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
if (!hasMore) return;
|
||||||
|
searchFav(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/fav_search/index.dart
Normal file
4
lib/pages/fav_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library fav_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
116
lib/pages/fav_search/view.dart
Normal file
116
lib/pages/fav_search/view.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/pages/favDetail/widget/fav_video_card.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class FavSearchPage extends StatefulWidget {
|
||||||
|
final int? sourceType;
|
||||||
|
final int? mediaId;
|
||||||
|
const FavSearchPage({super.key, this.sourceType, this.mediaId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FavSearchPage> createState() => _FavSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FavSearchPageState extends State<FavSearchPage> {
|
||||||
|
final FavSearchController _favSearchCtr = Get.put(FavSearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
scrollController = _favSearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('fav', const Duration(seconds: 1), () {
|
||||||
|
_favSearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _favSearchCtr.submit(),
|
||||||
|
icon: const Icon(Icons.search_outlined, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _favSearchCtr.searchFocusNode,
|
||||||
|
controller: _favSearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _favSearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _favSearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _favSearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _favSearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => _favSearchCtr.loadingStatus.value && _favSearchCtr.favList.isEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _favSearchCtr.favList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: _favSearchCtr.favList.length + 1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == _favSearchCtr.favList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FavVideoCardH(
|
||||||
|
videoItem: _favSearchCtr.favList[index],
|
||||||
|
callFn: () => null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,28 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/follow.dart';
|
import 'package:pilipala/http/follow.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class FollowController extends GetxController {
|
/// 查看自己的关注时,可以查看分类
|
||||||
|
/// 查看其他人的关注时,只可以看全部
|
||||||
|
class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
int ps = 20;
|
int ps = 20;
|
||||||
int total = 0;
|
int total = 0;
|
||||||
RxList<FollowItemModel> followList = [FollowItemModel()].obs;
|
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
|
||||||
late int mid;
|
late int mid;
|
||||||
late String name;
|
late String name;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
RxString loadingText = '加载中...'.obs;
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
RxBool isOwner = false.obs;
|
||||||
|
late List<MemberTagItemModel> followTags;
|
||||||
|
late TabController tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -23,6 +31,7 @@ class FollowController extends GetxController {
|
|||||||
mid = Get.parameters['mid'] != null
|
mid = Get.parameters['mid'] != null
|
||||||
? int.parse(Get.parameters['mid']!)
|
? int.parse(Get.parameters['mid']!)
|
||||||
: userInfo.mid;
|
: userInfo.mid;
|
||||||
|
isOwner.value = mid == userInfo.mid;
|
||||||
name = Get.parameters['name'] ?? userInfo.uname;
|
name = Get.parameters['name'] ?? userInfo.uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,4 +65,20 @@ class FollowController extends GetxController {
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当查看当前用户的关注时,请求关注分组
|
||||||
|
Future followUpTags() async {
|
||||||
|
if (userInfo != null && mid == userInfo.mid) {
|
||||||
|
var res = await MemberHttp.followUpTags();
|
||||||
|
if (res['status']) {
|
||||||
|
followTags = res['data'];
|
||||||
|
tabController = TabController(
|
||||||
|
initialIndex: 0,
|
||||||
|
length: res['data'].length,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
|
||||||
import 'package:pilipala/common/widgets/no_data.dart';
|
|
||||||
import 'package:pilipala/models/follow/result.dart';
|
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/follow_item.dart';
|
import 'widgets/follow_list.dart';
|
||||||
|
import 'widgets/owner_follow_list.dart';
|
||||||
|
|
||||||
class FollowPage extends StatefulWidget {
|
class FollowPage extends StatefulWidget {
|
||||||
const FollowPage({super.key});
|
const FollowPage({super.key});
|
||||||
@ -19,30 +15,12 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
late String mid;
|
late String mid;
|
||||||
late FollowController _followController;
|
late FollowController _followController;
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mid = Get.parameters['mid']!;
|
mid = Get.parameters['mid']!;
|
||||||
_followController = Get.put(FollowController(), tag: mid);
|
_followController = Get.put(FollowController(), tag: mid);
|
||||||
_futureBuilderFuture = _followController.queryFollowings('init');
|
|
||||||
scrollController.addListener(
|
|
||||||
() async {
|
|
||||||
if (scrollController.position.pixels >=
|
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
|
||||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
|
||||||
_followController.queryFollowings('onLoad');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
scrollController.removeListener(() {});
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -54,73 +32,57 @@ class _FollowPageState extends State<FollowPage> {
|
|||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Text(
|
title: Text(
|
||||||
'${_followController.name}的关注',
|
_followController.isOwner.value
|
||||||
|
? '我的关注'
|
||||||
|
: '${_followController.name}的关注',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: Obx(
|
||||||
onRefresh: () async =>
|
() => !_followController.isOwner.value
|
||||||
await _followController.queryFollowings('init'),
|
? FollowList(ctr: _followController)
|
||||||
child: FutureBuilder(
|
: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _followController.followUpTags(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
var data = snapshot.data;
|
var data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
List<FollowItemModel> list = _followController.followList;
|
return Column(
|
||||||
return Obx(
|
children: [
|
||||||
() => list.isNotEmpty
|
TabBar(
|
||||||
? ListView.builder(
|
controller: _followController.tabController,
|
||||||
controller: scrollController,
|
isScrollable: true,
|
||||||
itemCount: list.length + 1,
|
tabs: [
|
||||||
itemBuilder: (BuildContext context, int index) {
|
for (var i in data['data']) ...[
|
||||||
if (index == list.length) {
|
Tab(text: i.name),
|
||||||
return Container(
|
]
|
||||||
height:
|
]),
|
||||||
MediaQuery.of(context).padding.bottom +
|
Expanded(
|
||||||
60,
|
child: TabBarView(
|
||||||
padding: EdgeInsets.only(
|
controller: _followController.tabController,
|
||||||
bottom: MediaQuery.of(context)
|
children: [
|
||||||
.padding
|
for (var i = 0;
|
||||||
.bottom),
|
i < _followController.tabController.length;
|
||||||
child: Center(
|
i++) ...[
|
||||||
child: Obx(
|
OwnerFollowList(
|
||||||
() => Text(
|
ctr: _followController,
|
||||||
_followController.loadingText.value,
|
tagItem: _followController.followTags[i],
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.outline,
|
|
||||||
fontSize: 13),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return followItem(item: list[index]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
: const CustomScrollView(
|
]
|
||||||
slivers: [NoData()],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
HttpError(
|
|
||||||
errMsg: data['msg'],
|
|
||||||
fn: () => _followController.queryFollowings('init'),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,16 @@
|
|||||||
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/follow/result.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget followItem({item}) {
|
class FollowItem extends StatelessWidget {
|
||||||
|
final FollowItemModel item;
|
||||||
|
const FollowItem({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(item!.mid);
|
String heroTag = Utils.makeHeroTag(item!.mid);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -22,13 +28,13 @@ Widget followItem({item}) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
item.uname,
|
item.uname!,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
item.sign,
|
item.sign!,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -36,3 +42,4 @@ Widget followItem({item}) {
|
|||||||
trailing: const SizedBox(width: 6),
|
trailing: const SizedBox(width: 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
111
lib/pages/follow/widgets/follow_list.dart
Normal file
111
lib/pages/follow/widgets/follow_list.dart
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
|
|
||||||
|
import 'follow_item.dart';
|
||||||
|
|
||||||
|
class FollowList extends StatefulWidget {
|
||||||
|
final FollowController ctr;
|
||||||
|
const FollowList({
|
||||||
|
super.key,
|
||||||
|
required this.ctr,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FollowList> createState() => _FollowListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FollowListState extends State<FollowList> {
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = widget.ctr.queryFollowings('init');
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
widget.ctr.queryFollowings('onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async => await widget.ctr.queryFollowings('init'),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
List<FollowItemModel> list = widget.ctr.followList;
|
||||||
|
return Obx(
|
||||||
|
() => list.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: list.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == list.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
widget.ctr.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FollowItem(item: list[index]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(slivers: [NoData()]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => widget.ctr.queryFollowings('init'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
128
lib/pages/follow/widgets/owner_follow_list.dart
Normal file
128
lib/pages/follow/widgets/owner_follow_list.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/follow/result.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
|
import 'follow_item.dart';
|
||||||
|
|
||||||
|
class OwnerFollowList extends StatefulWidget {
|
||||||
|
final FollowController ctr;
|
||||||
|
final MemberTagItemModel? tagItem;
|
||||||
|
const OwnerFollowList({super.key, required this.ctr, this.tagItem});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<OwnerFollowList> createState() => _OwnerFollowListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OwnerFollowListState extends State<OwnerFollowList>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
late int mid;
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
int pn = 1;
|
||||||
|
int ps = 20;
|
||||||
|
late MemberTagItemModel tagItem;
|
||||||
|
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
mid = widget.ctr.mid;
|
||||||
|
tagItem = widget.tagItem!;
|
||||||
|
_futureBuilderFuture = followUpGroup('init');
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
followUpGroup('onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分组下up
|
||||||
|
Future followUpGroup(type) async {
|
||||||
|
if (type == 'init') {
|
||||||
|
pn = 1;
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.followUpGroup(mid, tagItem.tagid, pn, ps);
|
||||||
|
if (res['status']) {
|
||||||
|
if (res['data'].isNotEmpty) {
|
||||||
|
if (type == 'init') {
|
||||||
|
followList.value = res['data'];
|
||||||
|
} else {
|
||||||
|
followList.addAll(res['data']);
|
||||||
|
}
|
||||||
|
pn += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () async => await followUpGroup('init'),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
var data = snapshot.data;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => followList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount: followList.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == followList.length) {
|
||||||
|
return Container(
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).padding.bottom + 60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).padding.bottom),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return FollowItem(item: followList[index]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(slivers: [NoData()]),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => widget.ctr.queryFollowings('init'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
|
|
||||||
class HistoryController extends GetxController {
|
class HistoryController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
RxBool isLoadingMore = false.obs;
|
RxBool isLoadingMore = false.obs;
|
||||||
RxBool pauseStatus = false.obs;
|
RxBool pauseStatus = false.obs;
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
RxBool isLoading = false.obs;
|
RxBool isLoading = false.obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
RxInt checkedCount = 0.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -123,8 +125,15 @@ class HistoryController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除某条历史记录
|
// 删除某条历史记录
|
||||||
Future delHistory(kid) async {
|
Future delHistory(kid, business) async {
|
||||||
var res = await UserHttp.delHistory(kid);
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
historyList.removeWhere((e) => e.kid == kid);
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
@ -133,12 +142,61 @@ class HistoryController extends GetxController {
|
|||||||
|
|
||||||
// 删除已看历史记录
|
// 删除已看历史记录
|
||||||
Future onDelHistory() async {
|
Future onDelHistory() async {
|
||||||
|
/// TODO 优化
|
||||||
List<HisListItem> result =
|
List<HisListItem> result =
|
||||||
historyList.where((e) => e.progress == -1).toList();
|
historyList.where((e) => e.progress == -1).toList();
|
||||||
for (HisListItem i in result) {
|
for (HisListItem i in result) {
|
||||||
await UserHttp.delHistory(i.kid);
|
String resKid = 'archive_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
historyList.removeWhere((e) => e.kid == i.kid);
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
}
|
}
|
||||||
SmartDialog.showToast('操作完成');
|
SmartDialog.showToast('操作完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除选中的记录
|
||||||
|
Future onDelCheckedHistory() async {
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认删除所选历史记录吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
/// TODO 优化
|
||||||
|
await SmartDialog.dismiss();
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.checked!).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String str = 'archive';
|
||||||
|
try {
|
||||||
|
str = i.history!.business!;
|
||||||
|
} catch (_) {}
|
||||||
|
String resKid = '${str}_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
checkedCount.value = 0;
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
enableMultiple.value = false;
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,23 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
_historyController.enableMultiple.listen((p0) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
onChoose(index) {
|
||||||
|
_historyController.historyList[index].checked =
|
||||||
|
!_historyController.historyList[index].checked!;
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.where((item) => item.checked!).length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新多选状态
|
||||||
|
onUpdateMultiple() {
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,14 +65,24 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBarWidget(
|
||||||
|
visible: _historyController.enableMultiple.value,
|
||||||
|
child1: AppBar(
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(Icons.arrow_back_outlined),
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'观看记录',
|
'观看记录',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed('/historySearch'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (String type) {
|
onSelected: (String type) {
|
||||||
// 处理菜单项选择的逻辑
|
// 处理菜单项选择的逻辑
|
||||||
@ -69,6 +96,10 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
case 'del':
|
case 'del':
|
||||||
_historyController.onDelHistory();
|
_historyController.onDelHistory();
|
||||||
break;
|
break;
|
||||||
|
case 'multiple':
|
||||||
|
_historyController.enableMultiple.value = true;
|
||||||
|
setState(() {});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -89,11 +120,58 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
value: 'del',
|
value: 'del',
|
||||||
child: Text('删除已看记录'),
|
child: Text('删除已看记录'),
|
||||||
),
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
value: 'multiple',
|
||||||
|
child: Text('多选删除'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child2: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_historyController.enableMultiple.value = false;
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value = 0;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close_outlined),
|
||||||
|
),
|
||||||
|
title: Obx(
|
||||||
|
() => Text(
|
||||||
|
'已选择${_historyController.checkedCount.value}项',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = true;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
},
|
||||||
|
child: const Text('全选'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _historyController.onDelCheckedHistory(),
|
||||||
|
child: Text(
|
||||||
|
'删除',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _historyController.onRefresh();
|
await _historyController.onRefresh();
|
||||||
@ -120,6 +198,8 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
videoItem:
|
videoItem:
|
||||||
_historyController.historyList[index],
|
_historyController.historyList[index],
|
||||||
ctr: _historyController,
|
ctr: _historyController,
|
||||||
|
onChoose: () => onChoose(index),
|
||||||
|
onUpdateMultiple: () => onUpdateMultiple(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount:
|
childCount:
|
||||||
@ -155,6 +235,36 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// bottomNavigationBar: BottomAppBar(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const AppBarWidget({
|
||||||
|
required this.child1,
|
||||||
|
required this.child2,
|
||||||
|
required this.visible,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final PreferredSizeWidget child1;
|
||||||
|
final PreferredSizeWidget child2;
|
||||||
|
final bool visible;
|
||||||
|
@override
|
||||||
|
Size get preferredSize => child1.preferredSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: !visible ? child1 : child2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,13 +12,23 @@ 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/index.dart';
|
||||||
|
import 'package:pilipala/pages/history_search/index.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class HistoryItem extends StatelessWidget {
|
class HistoryItem extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic ctr;
|
||||||
const HistoryItem({super.key, required this.videoItem, this.ctr});
|
final Function? onChoose;
|
||||||
|
final Function? onUpdateMultiple;
|
||||||
|
const HistoryItem({
|
||||||
|
super.key,
|
||||||
|
required this.videoItem,
|
||||||
|
this.ctr,
|
||||||
|
this.onChoose,
|
||||||
|
this.onUpdateMultiple,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -27,6 +37,11 @@ class HistoryItem extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(aid);
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
if (ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (videoItem.history.business.contains('article')) {
|
if (videoItem.history.business.contains('article')) {
|
||||||
int cid = videoItem.history.cid ??
|
int cid = videoItem.history.cid ??
|
||||||
// videoItem.history.oid ??
|
// videoItem.history.oid ??
|
||||||
@ -117,6 +132,17 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
if (ctr is HistorySearchController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
ctr!.enableMultiple.value = true;
|
||||||
|
onChoose!();
|
||||||
|
onUpdateMultiple!();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@ -131,6 +157,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
@ -163,7 +191,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// 右上角
|
// 右上角
|
||||||
if (BusinessType.showBadge.showBadge
|
if (BusinessType.showBadge.showBadge
|
||||||
.contains(videoItem.history.business) ||
|
.contains(
|
||||||
|
videoItem.history.business) ||
|
||||||
videoItem.history.business ==
|
videoItem.history.business ==
|
||||||
BusinessType.live.type)
|
BusinessType.live.type)
|
||||||
PBadge(
|
PBadge(
|
||||||
@ -178,6 +207,60 @@ class HistoryItem extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Obx(
|
||||||
|
() => Positioned.fill(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: ctr!.enableMultiple.value ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
ctr!.enableMultiple.value &&
|
||||||
|
videoItem.checked
|
||||||
|
? 0.6
|
||||||
|
: 0),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: videoItem.checked ? 1 : 0,
|
||||||
|
duration:
|
||||||
|
const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
EdgeInsets.zero),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.resolveWith(
|
||||||
|
(states) {
|
||||||
|
return Colors.white
|
||||||
|
.withOpacity(0.8);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.done_all_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -193,7 +276,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic ctr;
|
||||||
const VideoContent({super.key, required this.videoItem, this.ctr});
|
const VideoContent({super.key, required this.videoItem, this.ctr});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -214,7 +297,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
maxLines: videoItem.videos > 1 ? 1 : 2,
|
maxLines: videoItem.videos > 1 ? 1 : 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
if (videoItem.showTitle != null)
|
if (videoItem.showTitle != null) ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
videoItem.showTitle,
|
videoItem.showTitle,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
@ -222,16 +306,19 @@ class VideoContent extends StatelessWidget {
|
|||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
fontWeight: FontWeight.w400,
|
fontWeight: FontWeight.w400,
|
||||||
color: Theme.of(context).colorScheme.outline),
|
color: Theme.of(context).colorScheme.outline),
|
||||||
maxLines: 2,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
],
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
if (videoItem.authorName != '')
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
videoItem.authorName,
|
videoItem.authorName,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -247,10 +334,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline),
|
color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
if (videoItem.badge != '番剧' &&
|
|
||||||
!videoItem.tagName.contains('动画') &&
|
|
||||||
videoItem.history.business != 'live' &&
|
|
||||||
!videoItem.history.business.contains('article'))
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
@ -267,6 +350,10 @@ class VideoContent extends StatelessWidget {
|
|||||||
onSelected: (String type) {},
|
onSelected: (String type) {},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<PopupMenuEntry<String>>[
|
||||||
|
if (videoItem.badge != '番剧' &&
|
||||||
|
!videoItem.tagName.contains('动画') &&
|
||||||
|
videoItem.history.business != 'live' &&
|
||||||
|
!videoItem.history.business.contains('article'))
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var res = await UserHttp.toViewLater(
|
var res = await UserHttp.toViewLater(
|
||||||
@ -284,7 +371,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () => ctr!.delHistory(videoItem.kid),
|
onTap: () => ctr!.delHistory(
|
||||||
|
videoItem.kid, videoItem.history.business),
|
||||||
value: 'pause',
|
value: 'pause',
|
||||||
height: 35,
|
height: 35,
|
||||||
child: const Row(
|
child: const Row(
|
||||||
|
|||||||
91
lib/pages/history_search/controller.dart
Normal file
91
lib/pages/history_search/controller.dart
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/models/user/history.dart';
|
||||||
|
|
||||||
|
class HistorySearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int pn = 1;
|
||||||
|
int count = 0;
|
||||||
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
pn = 1;
|
||||||
|
searchHistories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchHistories({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await UserHttp.searchHistory(
|
||||||
|
pn: pn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' && pn == 1) {
|
||||||
|
historyList.value = res['data'].list;
|
||||||
|
} else {
|
||||||
|
historyList.addAll(res['data'].list);
|
||||||
|
}
|
||||||
|
count = res['data'].page['total'];
|
||||||
|
if (historyList.length == count) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
pn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
searchHistories(type: 'onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future delHistory(kid, business) async {
|
||||||
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
|
if (res['status']) {
|
||||||
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/history_search/index.dart
Normal file
4
lib/pages/history_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library history_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
174
lib/pages/history_search/view.dart
Normal file
174
lib/pages/history_search/view.dart
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/pages/history/widgets/item.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class HistorySearchPage extends StatefulWidget {
|
||||||
|
const HistorySearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistorySearchPage> createState() => _HistorySearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||||
|
final HistorySearchController _historySearchCtr =
|
||||||
|
Get.put(HistorySearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _historySearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_historySearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _historySearchCtr.submit(),
|
||||||
|
icon: const Icon(Icons.search_outlined, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _historySearchCtr.searchFocusNode,
|
||||||
|
controller: _historySearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _historySearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _historySearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _historySearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _historySearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _historySearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [const SizedBox()]
|
||||||
|
: [
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _historySearchCtr.searchHistories(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _historySearchCtr.historyList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_historySearchCtr.historyList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_historySearchCtr
|
||||||
|
.historyList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_historySearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return HistoryItem(
|
||||||
|
videoItem: _historySearchCtr
|
||||||
|
.historyList[index],
|
||||||
|
ctr: _historySearchCtr,
|
||||||
|
onChoose: null,
|
||||||
|
onUpdateMultiple: () => null,
|
||||||
|
);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _historySearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? const SizedBox(child: Text('加载中...'))
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -139,7 +139,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Get.toNamed('/webview', parameters: {
|
Get.toNamed('/webview', parameters: {
|
||||||
'url': 'https:$url',
|
'url': url.startsWith('http') ? url : 'https:$url',
|
||||||
'type': 'url',
|
'type': 'url',
|
||||||
'pageTitle': title,
|
'pageTitle': title,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,7 +20,7 @@ class LiveController extends GetxController {
|
|||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
crossAxisCount.value =
|
crossAxisCount.value =
|
||||||
setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2;
|
setting.get(SettingBoxKey.customRows, defaultValue: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
|
|||||||
@ -120,16 +120,19 @@ class LiveContent extends StatelessWidget {
|
|||||||
if (crossAxisCount == 1) const SizedBox(height: 4),
|
if (crossAxisCount == 1) const SizedBox(height: 4),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Expanded(
|
||||||
|
child: Text(
|
||||||
liveItem.uname,
|
liveItem.uname,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
fontSize:
|
||||||
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
if (crossAxisCount == 1) ...[
|
if (crossAxisCount == 1) ...[
|
||||||
Text(
|
Text(
|
||||||
' • ${liveItem!.areaName!}',
|
' • ${liveItem!.areaName!}',
|
||||||
@ -169,7 +172,7 @@ class VideoStat extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 50,
|
height: 50,
|
||||||
padding: const EdgeInsets.only(top: 22, left: 10, right: 10),
|
padding: const EdgeInsets.only(top: 26, left: 10, right: 10),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
@ -181,19 +184,18 @@ class VideoStat extends StatelessWidget {
|
|||||||
tileMode: TileMode.mirror,
|
tileMode: TileMode.mirror,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: RichText(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.justify,
|
||||||
|
softWrap: false,
|
||||||
|
text: TextSpan(
|
||||||
|
style: const TextStyle(fontSize: 11, color: Colors.white),
|
||||||
children: [
|
children: [
|
||||||
Text(
|
TextSpan(text: liveItem!.areaName!),
|
||||||
liveItem!.areaName!,
|
TextSpan(text: liveItem!.watchedShow!['text_small']),
|
||||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
liveItem!.watchedShow!['text_small'],
|
|
||||||
style: const TextStyle(fontSize: 11, color: Colors.white),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,7 +39,8 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(toolbarHeight: 30),
|
appBar: AppBar(toolbarHeight: 30),
|
||||||
body: Column(
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: null,
|
leading: null,
|
||||||
@ -79,6 +80,7 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
: const SizedBox())
|
: const SizedBox())
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
// const SizedBox(height: 10),
|
// const SizedBox(height: 10),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 170 * MediaQuery.of(context).textScaleFactor,
|
height: 200 * MediaQuery.of(context).textScaleFactor,
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
|||||||
@ -2,23 +2,43 @@ import 'package:get/get.dart';
|
|||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.dart';
|
||||||
|
|
||||||
class ArchiveController extends GetxController {
|
class ArchiveController extends GetxController {
|
||||||
|
ArchiveController(this.mid);
|
||||||
int? mid;
|
int? mid;
|
||||||
int pn = 1;
|
int pn = 1;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
RxMap<String, String> currentOrder = <String, String>{}.obs;
|
||||||
|
List<Map<String, String>> orderList = [
|
||||||
|
{'type': 'pubdate', 'label': '最新发布'},
|
||||||
|
{'type': 'click', 'label': '最多播放'},
|
||||||
|
{'type': 'stow', 'label': '最多收藏'},
|
||||||
|
];
|
||||||
|
|
||||||
@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户投稿
|
// 获取用户投稿
|
||||||
Future getMemberArchive() async {
|
Future getMemberArchive() async {
|
||||||
var res = await MemberHttp.memberArchive(mid: mid, pn: pn);
|
var res = await MemberHttp.memberArchive(
|
||||||
|
mid: mid, pn: pn, order: currentOrder['type']!);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
count = res['data'].page['count'];
|
count = res['data'].page['count'];
|
||||||
pn += 1;
|
pn += 1;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSort() async {
|
||||||
|
pn = 1;
|
||||||
|
int index = orderList.indexOf(currentOrder.value);
|
||||||
|
if (index == orderList.length - 1) {
|
||||||
|
currentOrder.value = orderList.first;
|
||||||
|
} else {
|
||||||
|
currentOrder.value = orderList[index + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,10 +5,12 @@ import 'package:loading_more_list/loading_more_list.dart';
|
|||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/pages/member/archive/index.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';
|
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||||
|
|
||||||
class ArchivePanel extends StatefulWidget {
|
class ArchivePanel extends StatefulWidget {
|
||||||
const ArchivePanel({super.key});
|
final int? mid;
|
||||||
|
const ArchivePanel({super.key, this.mid});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ArchivePanel> createState() => _ArchivePanelState();
|
State<ArchivePanel> createState() => _ArchivePanelState();
|
||||||
@ -17,11 +19,21 @@ class ArchivePanel extends StatefulWidget {
|
|||||||
class _ArchivePanelState extends State<ArchivePanel>
|
class _ArchivePanelState extends State<ArchivePanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
DateTime lastRefreshTime = DateTime.now();
|
DateTime lastRefreshTime = DateTime.now();
|
||||||
late final LoadMoreListSource source = LoadMoreListSource();
|
late final LoadMoreListSource source;
|
||||||
|
late final ArchiveController _archiveController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
@ -40,14 +52,63 @@ class _ArchivePanelState extends State<ArchivePanel>
|
|||||||
// return PullToRefreshHeader(info, lastRefreshTime);
|
// return PullToRefreshHeader(info, lastRefreshTime);
|
||||||
// },
|
// },
|
||||||
// ),
|
// ),
|
||||||
const SizedBox(height: 4),
|
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(
|
Expanded(
|
||||||
child: LoadingMoreList<VListItemModel>(
|
child: LoadingMoreList<VListItemModel>(
|
||||||
ListConfig<VListItemModel>(
|
ListConfig<VListItemModel>(
|
||||||
sourceList: source,
|
sourceList: source,
|
||||||
itemBuilder:
|
itemBuilder:
|
||||||
(BuildContext c, VListItemModel item, int index) {
|
(BuildContext c, VListItemModel item, int index) {
|
||||||
|
if (index == 0) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
VideoCardH(videoItem: item)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return VideoCardH(videoItem: item);
|
return VideoCardH(videoItem: item);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
indicatorBuilder: _buildIndicator,
|
indicatorBuilder: _buildIndicator,
|
||||||
),
|
),
|
||||||
@ -142,14 +203,18 @@ class _ArchivePanelState extends State<ArchivePanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||||
final ArchiveController _archiveController =
|
late ArchiveController ctr;
|
||||||
Get.put(ArchiveController(), tag: Get.arguments['heroTag']);
|
LoadMoreListSource(this.ctr);
|
||||||
|
bool forceRefresh = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
bool isSuccess = false;
|
bool isSuccess = false;
|
||||||
var res = await _archiveController.getMemberArchive();
|
var res = await ctr.getMemberArchive();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
|
if (ctr.pn == 2) {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
addAll(res['data'].list.vlist);
|
addAll(res['data'].list.vlist);
|
||||||
}
|
}
|
||||||
if (length < res['data'].page['count']) {
|
if (length < res['data'].page['count']) {
|
||||||
@ -159,4 +224,17 @@ class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
|||||||
}
|
}
|
||||||
return isSuccess;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.dart';
|
||||||
|
|
||||||
class MemberDynamicPanelController extends GetxController {
|
class MemberDynamicPanelController extends GetxController {
|
||||||
|
MemberDynamicPanelController(this.mid);
|
||||||
int? mid;
|
int? mid;
|
||||||
String offset = '';
|
String offset = '';
|
||||||
int count = 0;
|
int count = 0;
|
||||||
@ -9,7 +10,7 @@ class MemberDynamicPanelController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
mid = int.parse(Get.parameters['mid']!);
|
mid ??= int.parse(Get.parameters['mid']!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future getMemberDynamic() async {
|
Future getMemberDynamic() async {
|
||||||
|
|||||||
@ -4,11 +4,13 @@ import 'package:get/get.dart';
|
|||||||
import 'package:loading_more_list/loading_more_list.dart';
|
import 'package:loading_more_list/loading_more_list.dart';
|
||||||
import 'package:pilipala/models/dynamics/result.dart';
|
import 'package:pilipala/models/dynamics/result.dart';
|
||||||
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
|
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
|
|
||||||
class MemberDynamicPanel extends StatefulWidget {
|
class MemberDynamicPanel extends StatefulWidget {
|
||||||
const MemberDynamicPanel({super.key});
|
final int? mid;
|
||||||
|
const MemberDynamicPanel({super.key, this.mid});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
|
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
|
||||||
@ -17,11 +19,20 @@ class MemberDynamicPanel extends StatefulWidget {
|
|||||||
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
DateTime lastRefreshTime = DateTime.now();
|
DateTime lastRefreshTime = DateTime.now();
|
||||||
late final LoadMoreListSource source = LoadMoreListSource();
|
late final LoadMoreListSource source;
|
||||||
|
late final MemberDynamicPanelController _dynamicController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_dynamicController = Get.put(MemberDynamicPanelController(widget.mid),
|
||||||
|
tag: Utils.makeHeroTag(widget.mid));
|
||||||
|
source = LoadMoreListSource(_dynamicController);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
@ -118,13 +129,13 @@ class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||||
final _dynamicController =
|
late MemberDynamicPanelController ctr;
|
||||||
Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']);
|
LoadMoreListSource(this.ctr);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||||
bool isSuccess = false;
|
bool isSuccess = false;
|
||||||
var res = await _dynamicController.getMemberDynamic();
|
var res = await ctr.getMemberDynamic();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
addAll(res['data'].items);
|
addAll(res['data'].items);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,19 +27,20 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
final StreamController<bool> appbarStream = StreamController<bool>();
|
final StreamController<bool> appbarStream = StreamController<bool>();
|
||||||
|
late int mid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
heroTag =
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
Get.arguments['heroTag'] ?? Utils.makeHeroTag(Get.parameters['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);
|
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||||
_futureBuilderFuture = _memberController.getInfo();
|
_futureBuilderFuture = _memberController.getInfo();
|
||||||
_extendNestCtr.addListener(
|
_extendNestCtr.addListener(
|
||||||
() {
|
() {
|
||||||
double offset = _extendNestCtr.position.pixels;
|
double offset = _extendNestCtr.position.pixels;
|
||||||
if (offset > 250) {
|
if (offset > 230) {
|
||||||
appbarStream.add(true);
|
appbarStream.add(true);
|
||||||
} else {
|
} else {
|
||||||
appbarStream.add(false);
|
appbarStream.add(false);
|
||||||
@ -68,7 +69,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 1,
|
scrolledUnderElevation: 1,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
expandedHeight: 320,
|
expandedHeight: 290,
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
title: StreamBuilder(
|
title: StreamBuilder(
|
||||||
stream: appbarStream.stream,
|
stream: appbarStream.stream,
|
||||||
@ -109,6 +110,11 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
@ -190,159 +196,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
profileWidget(),
|
||||||
padding: const EdgeInsets.only(left: 18, right: 18),
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState ==
|
|
||||||
ConnectionState.done) {
|
|
||||||
Map data = snapshot.data!;
|
|
||||||
if (data['status']) {
|
|
||||||
return Obx(
|
|
||||||
() => Stack(
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
profile(_memberController),
|
|
||||||
const SizedBox(height: 14),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
_memberController
|
|
||||||
.memberInfo.value.name!,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyLarge!
|
|
||||||
.copyWith(
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold),
|
|
||||||
)),
|
|
||||||
const SizedBox(width: 2),
|
|
||||||
if (_memberController
|
|
||||||
.memberInfo.value.sex ==
|
|
||||||
'女')
|
|
||||||
const Icon(
|
|
||||||
FontAwesomeIcons.venus,
|
|
||||||
size: 14,
|
|
||||||
color: Colors.pink,
|
|
||||||
),
|
|
||||||
if (_memberController
|
|
||||||
.memberInfo.value.sex ==
|
|
||||||
'男')
|
|
||||||
const Icon(
|
|
||||||
FontAwesomeIcons.mars,
|
|
||||||
size: 14,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
Image.asset(
|
|
||||||
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
|
||||||
height: 11,
|
|
||||||
),
|
|
||||||
const SizedBox(width: 6),
|
|
||||||
if (_memberController.memberInfo
|
|
||||||
.value.vip!.status ==
|
|
||||||
1 &&
|
|
||||||
_memberController.memberInfo
|
|
||||||
.value.vip!.label![
|
|
||||||
'img_label_uri_hans'] !=
|
|
||||||
'') ...[
|
|
||||||
Image.network(
|
|
||||||
_memberController.memberInfo
|
|
||||||
.value.vip!.label![
|
|
||||||
'img_label_uri_hans'],
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
] else if (_memberController
|
|
||||||
.memberInfo
|
|
||||||
.value
|
|
||||||
.vip!
|
|
||||||
.status ==
|
|
||||||
1 &&
|
|
||||||
_memberController.memberInfo
|
|
||||||
.value.vip!.label![
|
|
||||||
'img_label_uri_hans_static'] !=
|
|
||||||
'') ...[
|
|
||||||
Image.network(
|
|
||||||
_memberController.memberInfo
|
|
||||||
.value.vip!.label![
|
|
||||||
'img_label_uri_hans_static'],
|
|
||||||
height: 20,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (_memberController.memberInfo.value
|
|
||||||
.official!['title'] !=
|
|
||||||
'') ...[
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
Text.rich(
|
|
||||||
maxLines: 2,
|
|
||||||
TextSpan(
|
|
||||||
text: _memberController
|
|
||||||
.memberInfo
|
|
||||||
.value
|
|
||||||
.official!['role'] ==
|
|
||||||
1
|
|
||||||
? '个人认证:'
|
|
||||||
: '企业认证:',
|
|
||||||
style: TextStyle(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.primaryColor,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: _memberController
|
|
||||||
.memberInfo
|
|
||||||
.value
|
|
||||||
.official!['title'],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
softWrap: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (_memberController
|
|
||||||
.memberInfo.value.sign !=
|
|
||||||
'')
|
|
||||||
SelectableRegion(
|
|
||||||
magnifierConfiguration:
|
|
||||||
const TextMagnifierConfiguration(),
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
selectionControls:
|
|
||||||
MaterialTextSelectionControls(),
|
|
||||||
child: Text(
|
|
||||||
_memberController
|
|
||||||
.memberInfo.value.sign!,
|
|
||||||
textAlign: TextAlign.left,
|
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return profile(_memberController,
|
|
||||||
loadingStatus: true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -367,10 +221,10 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TabBarView(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: const [
|
children: [
|
||||||
Text('主页'),
|
const Text('主页'),
|
||||||
MemberDynamicPanel(),
|
MemberDynamicPanel(mid: mid),
|
||||||
ArchivePanel(),
|
ArchivePanel(mid: mid),
|
||||||
],
|
],
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
@ -378,4 +232,143 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget profileWidget() {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data!;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
profile(_memberController),
|
||||||
|
const SizedBox(height: 14),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
_memberController.memberInfo.value.name!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyLarge!
|
||||||
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
)),
|
||||||
|
const SizedBox(width: 2),
|
||||||
|
if (_memberController.memberInfo.value.sex == '女')
|
||||||
|
const Icon(
|
||||||
|
FontAwesomeIcons.venus,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.pink,
|
||||||
|
),
|
||||||
|
if (_memberController.memberInfo.value.sex == '男')
|
||||||
|
const Icon(
|
||||||
|
FontAwesomeIcons.mars,
|
||||||
|
size: 14,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
||||||
|
height: 11,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
if (_memberController
|
||||||
|
.memberInfo.value.vip!.status ==
|
||||||
|
1 &&
|
||||||
|
_memberController.memberInfo.value.vip!
|
||||||
|
.label!['img_label_uri_hans'] !=
|
||||||
|
'') ...[
|
||||||
|
Image.network(
|
||||||
|
_memberController.memberInfo.value.vip!
|
||||||
|
.label!['img_label_uri_hans'],
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
] else if (_memberController
|
||||||
|
.memberInfo.value.vip!.status ==
|
||||||
|
1 &&
|
||||||
|
_memberController.memberInfo.value.vip!
|
||||||
|
.label!['img_label_uri_hans_static'] !=
|
||||||
|
'') ...[
|
||||||
|
Image.network(
|
||||||
|
_memberController.memberInfo.value.vip!
|
||||||
|
.label!['img_label_uri_hans_static'],
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (_memberController
|
||||||
|
.memberInfo.value.official!['title'] !=
|
||||||
|
'') ...[
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text.rich(
|
||||||
|
maxLines: 2,
|
||||||
|
TextSpan(
|
||||||
|
text: _memberController
|
||||||
|
.memberInfo.value.official!['role'] ==
|
||||||
|
1
|
||||||
|
? '个人认证:'
|
||||||
|
: '企业认证:',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: _memberController
|
||||||
|
.memberInfo.value.official!['title'],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
softWrap: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
if (_memberController.memberInfo.value.sign != '')
|
||||||
|
SelectableText(
|
||||||
|
_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!),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return profile(_memberController, loadingStatus: true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
90
lib/pages/member_search/controller.dart
Normal file
90
lib/pages/member_search/controller.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
|
|
||||||
|
class MemberSearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int archivePn = 1;
|
||||||
|
int archiveCount = 0;
|
||||||
|
RxList<VListItemModel> archiveList = <VListItemModel>[].obs;
|
||||||
|
int dynamic_pn = 1;
|
||||||
|
RxList<VListItemModel> dynamicList = <VListItemModel>[].obs;
|
||||||
|
|
||||||
|
int ps = 30;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
|
uname.value = Get.parameters['uname']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
archivePn = 1;
|
||||||
|
searchArchives();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchArchives({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.memberArchive(
|
||||||
|
mid: mid,
|
||||||
|
pn: archivePn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
order: 'pubdate',
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' || archivePn == 1) {
|
||||||
|
archiveList.value = res['data'].list.vlist;
|
||||||
|
} else {
|
||||||
|
archiveList.addAll(res['data'].list.vlist);
|
||||||
|
}
|
||||||
|
archiveCount = res['data'].page['count'];
|
||||||
|
if (archiveList.length == archiveCount) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
archivePn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
}
|
||||||
|
// loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索动态
|
||||||
|
Future searchDynamic() async {}
|
||||||
|
|
||||||
|
//
|
||||||
|
onLoad() {
|
||||||
|
searchArchives(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/member_search/index.dart
Normal file
4
lib/pages/member_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library member_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
195
lib/pages/member_search/view.dart
Normal file
195
lib/pages/member_search/view.dart
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MemberSearchPage extends StatefulWidget {
|
||||||
|
const MemberSearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemberSearchPage> createState() => _MemberSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberSearchPageState extends State<MemberSearchPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final MemberSearchController _memberSearchCtr =
|
||||||
|
Get.put(MemberSearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _memberSearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_memberSearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// _tabController = TabController(length: 2, vsync: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// _tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _memberSearchCtr.submit(),
|
||||||
|
icon: const Icon(CupertinoIcons.search, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _memberSearchCtr.searchFocusNode,
|
||||||
|
controller: _memberSearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _memberSearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _memberSearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _memberSearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _memberSearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _memberSearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
// TabBar(
|
||||||
|
// controller: _tabController,
|
||||||
|
// tabs: const [
|
||||||
|
// Tab(text: "视频"),
|
||||||
|
// Tab(text: "动态"),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
// TabBarView(
|
||||||
|
// controller: _tabController,
|
||||||
|
// children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: _memberSearchCtr.searchArchives(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _memberSearchCtr.archiveList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_memberSearchCtr.archiveList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_memberSearchCtr
|
||||||
|
.archiveList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_memberSearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return VideoCardH(
|
||||||
|
videoItem: _memberSearchCtr
|
||||||
|
.archiveList[index]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _memberSearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
@ -111,4 +112,20 @@ class MineController extends GetxController {
|
|||||||
}
|
}
|
||||||
Get.forceAppUpdate();
|
Get.forceAppUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushFollow() {
|
||||||
|
if (!userLogin.value) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.toNamed('/follow?mid=${userInfo.value.mid}');
|
||||||
|
}
|
||||||
|
|
||||||
|
pushFans() {
|
||||||
|
if (!userLogin.value) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Get.toNamed('/fan?mid=${userInfo.value.mid}');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -264,7 +264,7 @@ class _MinePageState extends State<MinePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => Get.toNamed('/follow'),
|
onTap: () => _mineController.pushFollow(),
|
||||||
borderRadius: StyleString.mdRadius,
|
borderRadius: StyleString.mdRadius,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@ -294,7 +294,7 @@ class _MinePageState extends State<MinePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => Get.toNamed('/fan'),
|
onTap: () => _mineController.pushFans(),
|
||||||
borderRadius: StyleString.mdRadius,
|
borderRadius: StyleString.mdRadius,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|||||||
@ -14,12 +14,13 @@ class RcmdController extends GetxController {
|
|||||||
Box recVideo = GStrorage.recVideo;
|
Box recVideo = GStrorage.recVideo;
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
RxInt crossAxisCount = 2.obs;
|
RxInt crossAxisCount = 2.obs;
|
||||||
|
late bool enableSaveLastData;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
crossAxisCount.value =
|
crossAxisCount.value =
|
||||||
setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2;
|
setting.get(SettingBoxKey.customRows, defaultValue: 2);
|
||||||
if (recVideo.get('cacheList') != null &&
|
if (recVideo.get('cacheList') != null &&
|
||||||
recVideo.get('cacheList').isNotEmpty) {
|
recVideo.get('cacheList').isNotEmpty) {
|
||||||
List<RecVideoItemAppModel> list = [];
|
List<RecVideoItemAppModel> list = [];
|
||||||
@ -28,6 +29,8 @@ class RcmdController extends GetxController {
|
|||||||
}
|
}
|
||||||
videoList.value = list;
|
videoList.value = list;
|
||||||
}
|
}
|
||||||
|
enableSaveLastData =
|
||||||
|
setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
@ -49,7 +52,11 @@ class RcmdController extends GetxController {
|
|||||||
videoList.value = res['data'];
|
videoList.value = res['data'];
|
||||||
}
|
}
|
||||||
} else if (type == 'onRefresh') {
|
} else if (type == 'onRefresh') {
|
||||||
|
if (enableSaveLastData) {
|
||||||
|
videoList.insertAll(0, res['data']);
|
||||||
|
} else {
|
||||||
videoList.value = res['data'];
|
videoList.value = res['data'];
|
||||||
|
}
|
||||||
} else if (type == 'onLoad') {
|
} else if (type == 'onLoad') {
|
||||||
videoList.addAll(res['data']);
|
videoList.addAll(res['data']);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,8 +91,8 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
return searchUserPanel(context, ctr, list);
|
return searchUserPanel(context, ctr, list);
|
||||||
case SearchType.live_room:
|
case SearchType.live_room:
|
||||||
return searchLivePanel(context, ctr, list);
|
return searchLivePanel(context, ctr, list);
|
||||||
// case SearchType.article:
|
case SearchType.article:
|
||||||
// return searchArticlePanel(context, ctr, list);
|
return searchArticlePanel(context, ctr, list);
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -12,7 +13,14 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
|||||||
itemCount: list.length,
|
itemCount: list.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
|
'url': 'www.bilibili.com/read/cv${list[index].id}',
|
||||||
|
'title': list[index].subTitle,
|
||||||
|
'id': 'cv${list[index].id}',
|
||||||
|
'dynamicType': 'read'
|
||||||
|
});
|
||||||
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(
|
padding: const EdgeInsets.fromLTRB(
|
||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
|
|||||||
@ -79,6 +79,12 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
|||||||
setKey: SettingBoxKey.enableWordRe,
|
setKey: SettingBoxKey.enableWordRe,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
),
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: '首页推荐刷新',
|
||||||
|
subTitle: '下拉刷新时保留上次内容',
|
||||||
|
setKey: SettingBoxKey.enableSaveLastData,
|
||||||
|
defaultVal: false,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
dense: false,
|
dense: false,
|
||||||
title: Text('评论展示', style: titleStyle),
|
title: Text('评论展示', style: titleStyle),
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class SetDiaplayMode extends StatefulWidget {
|
class SetDiaplayMode extends StatefulWidget {
|
||||||
const SetDiaplayMode({super.key});
|
const SetDiaplayMode({super.key});
|
||||||
@ -14,6 +16,7 @@ class _SetDiaplayModeState extends State<SetDiaplayMode> {
|
|||||||
List<DisplayMode> modes = <DisplayMode>[];
|
List<DisplayMode> modes = <DisplayMode>[];
|
||||||
DisplayMode? active;
|
DisplayMode? active;
|
||||||
DisplayMode? preferred;
|
DisplayMode? preferred;
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
final ValueNotifier<int> page = ValueNotifier<int>(0);
|
final ValueNotifier<int> page = ValueNotifier<int>(0);
|
||||||
late final PageController controller = PageController()
|
late final PageController controller = PageController()
|
||||||
@ -29,24 +32,36 @@ class _SetDiaplayModeState extends State<SetDiaplayMode> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取所有的mode
|
||||||
Future<void> fetchAll() async {
|
Future<void> fetchAll() async {
|
||||||
preferred = await FlutterDisplayMode.preferred;
|
preferred = await FlutterDisplayMode.preferred;
|
||||||
active = await FlutterDisplayMode.active;
|
active = await FlutterDisplayMode.active;
|
||||||
// GStorage().setDisplayModeType(preferred!);
|
await setting.put(SettingBoxKey.displayMode, preferred.toString());
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化mode/手动设置
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
modes = await FlutterDisplayMode.supported;
|
modes = await FlutterDisplayMode.supported;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
}
|
}
|
||||||
// var res = await GStorage().getDisplayModeType();
|
var res = await getDisplayModeType(modes);
|
||||||
// preferred = modes.toList().firstWhere((el) => el == res);
|
|
||||||
|
preferred = modes.toList().firstWhere((el) => el == res);
|
||||||
FlutterDisplayMode.setPreferredMode(preferred!);
|
FlutterDisplayMode.setPreferredMode(preferred!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<DisplayMode> getDisplayModeType(modes) async {
|
||||||
|
var value = setting.get(SettingBoxKey.displayMode);
|
||||||
|
DisplayMode f = DisplayMode.auto;
|
||||||
|
if (value != null) {
|
||||||
|
f = modes.firstWhere((e) => e.toString() == value);
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
304
lib/pages/setting/pages/play_speed_set.dart
Normal file
304
lib/pages/setting/pages/play_speed_set.dart
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
class PlaySpeedPage extends StatefulWidget {
|
||||||
|
const PlaySpeedPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PlaySpeedPage> createState() => _PlaySpeedPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||||
|
Box videoStorage = GStrorage.video;
|
||||||
|
late double playSpeedDefault;
|
||||||
|
late double longPressSpeedDefault;
|
||||||
|
late List customSpeedsList;
|
||||||
|
List<Map<dynamic, dynamic>> sheetMenu = [
|
||||||
|
{
|
||||||
|
'id': 1,
|
||||||
|
'title': '设置为默认倍速',
|
||||||
|
'leading': const Icon(
|
||||||
|
Icons.speed,
|
||||||
|
size: 21,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 2,
|
||||||
|
'title': '设置为默认长按倍速',
|
||||||
|
'leading': const Icon(
|
||||||
|
Icons.speed_sharp,
|
||||||
|
size: 21,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': -1,
|
||||||
|
'title': '删除该项',
|
||||||
|
'leading': const Icon(
|
||||||
|
Icons.delete_outline,
|
||||||
|
size: 21,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// 默认倍速
|
||||||
|
playSpeedDefault =
|
||||||
|
videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
|
||||||
|
// 默认长按倍速
|
||||||
|
longPressSpeedDefault =
|
||||||
|
videoStorage.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0);
|
||||||
|
// 自定义倍速
|
||||||
|
customSpeedsList =
|
||||||
|
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加自定义倍速
|
||||||
|
void onAddSpeed() {
|
||||||
|
double customSpeed = 1.0;
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('添加倍速'),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// const Text('输入你想要的视频倍速,例如:1.0'),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: '自定义倍速',
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (e) {
|
||||||
|
customSpeed = double.parse(e);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: const Text('取消'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
customSpeedsList.add(customSpeed);
|
||||||
|
await videoStorage.put(
|
||||||
|
VideoBoxKey.customSpeedsList, customSpeedsList);
|
||||||
|
setState(() {});
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
child: const Text('确认添加'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设定倍速弹窗
|
||||||
|
void showBottomSheet(type, i) {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(top: 10),
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const ClampingScrollPhysics(),
|
||||||
|
//重要
|
||||||
|
itemCount: sheetMenu.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
menuAction(type, i, sheetMenu[index]['id']);
|
||||||
|
},
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onSurface,
|
||||||
|
leading: sheetMenu[index]['leading'],
|
||||||
|
title: Text(
|
||||||
|
sheetMenu[index]['title'],
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
void menuAction(type, index, id) async {
|
||||||
|
double chooseSpeed = 1.0;
|
||||||
|
if (type == 'system' && id == -1) {
|
||||||
|
SmartDialog.showToast('系统预设倍速不支持删除');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 获取当前选中的倍速值
|
||||||
|
if (type == 'system') {
|
||||||
|
chooseSpeed = PlaySpeed.values[index].value;
|
||||||
|
} else {
|
||||||
|
chooseSpeed = customSpeedsList[index];
|
||||||
|
}
|
||||||
|
// 设置
|
||||||
|
if (id == 1) {
|
||||||
|
// 设置默认倍速
|
||||||
|
playSpeedDefault = chooseSpeed;
|
||||||
|
videoStorage.put(VideoBoxKey.playSpeedDefault, playSpeedDefault);
|
||||||
|
} else if (id == 2) {
|
||||||
|
// 设置默认长按倍速
|
||||||
|
longPressSpeedDefault = chooseSpeed;
|
||||||
|
videoStorage.put(
|
||||||
|
VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault);
|
||||||
|
} else if (id == -1) {
|
||||||
|
if (customSpeedsList[index] == playSpeedDefault) {
|
||||||
|
playSpeedDefault = 1.0;
|
||||||
|
videoStorage.put(VideoBoxKey.playSpeedDefault, playSpeedDefault);
|
||||||
|
}
|
||||||
|
if (customSpeedsList[index] == longPressSpeedDefault) {
|
||||||
|
longPressSpeedDefault = 2.0;
|
||||||
|
videoStorage.put(
|
||||||
|
VideoBoxKey.longPressSpeedDefault, longPressSpeedDefault);
|
||||||
|
}
|
||||||
|
customSpeedsList.removeAt(index);
|
||||||
|
await videoStorage.put(VideoBoxKey.customSpeedsList, customSpeedsList);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
SmartDialog.showToast('操作成功');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
titleSpacing: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
title: Text(
|
||||||
|
'倍速设置',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.only(left: 14, right: 14, top: 6, bottom: 0),
|
||||||
|
child: Text(
|
||||||
|
'点击下方按钮设置默认(长按)倍速',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: false,
|
||||||
|
title: const Text('默认倍速'),
|
||||||
|
subtitle: Text(playSpeedDefault.toString()),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
dense: false,
|
||||||
|
title: const Text('默认长按倍速'),
|
||||||
|
subtitle: Text(longPressSpeedDefault.toString()),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
bottom: 10,
|
||||||
|
top: 20,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'系统预设倍速',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 18,
|
||||||
|
right: 18,
|
||||||
|
bottom: 30,
|
||||||
|
),
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 2,
|
||||||
|
children: [
|
||||||
|
for (var i in PlaySpeed.values) ...[
|
||||||
|
FilledButton.tonal(
|
||||||
|
onPressed: () => showBottomSheet('system', i.index),
|
||||||
|
child: Text(i.description),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'自定义倍速',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => onAddSpeed(),
|
||||||
|
child: const Text('添加'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 18,
|
||||||
|
right: 18,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 40,
|
||||||
|
),
|
||||||
|
child: customSpeedsList.isNotEmpty
|
||||||
|
? Wrap(
|
||||||
|
alignment: WrapAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 2,
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < customSpeedsList.length; i++) ...[
|
||||||
|
FilledButton.tonal(
|
||||||
|
onPressed: () => showBottomSheet('custom', i),
|
||||||
|
child: Text(customSpeedsList[i].toString()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox(
|
||||||
|
height: 80,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'未添加',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
@ -54,6 +55,18 @@ class _PlaySettingState extends State<PlaySetting> {
|
|||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
ListTile(
|
||||||
|
dense: false,
|
||||||
|
onTap: () => Get.toNamed('/playSpeedSet'),
|
||||||
|
title: Text('倍速设置', style: titleStyle),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
|
||||||
|
),
|
||||||
|
const SetSwitchItem(
|
||||||
|
title: '开启1080P',
|
||||||
|
subTitle: '免登录查看1080P视频',
|
||||||
|
setKey: SettingBoxKey.p1080,
|
||||||
|
defaultVal: true,
|
||||||
|
),
|
||||||
const SetSwitchItem(
|
const SetSwitchItem(
|
||||||
title: '自动播放',
|
title: '自动播放',
|
||||||
subTitle: '进入详情页自动播放',
|
subTitle: '进入详情页自动播放',
|
||||||
|
|||||||
@ -22,12 +22,14 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
late int picQuality;
|
late int picQuality;
|
||||||
late ThemeType _tempThemeValue;
|
late ThemeType _tempThemeValue;
|
||||||
|
late dynamic defaultCustomRows;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||||
_tempThemeValue = settingController.themeType.value;
|
_tempThemeValue = settingController.themeType.value;
|
||||||
|
defaultCustomRows = setting.get(SettingBoxKey.customRows, defaultValue: 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -76,12 +78,37 @@ class _StyleSettingState extends State<StyleSetting> {
|
|||||||
setKey: SettingBoxKey.iosTransition,
|
setKey: SettingBoxKey.iosTransition,
|
||||||
defaultVal: false,
|
defaultVal: false,
|
||||||
),
|
),
|
||||||
SetSwitchItem(
|
// SetSwitchItem(
|
||||||
title: '首页单列',
|
// title: '首页单列',
|
||||||
subTitle: '每行展示一个内容卡片',
|
// subTitle: '每行展示一个内容卡片',
|
||||||
setKey: SettingBoxKey.enableSingleRow,
|
// setKey: SettingBoxKey.enableSingleRow,
|
||||||
defaultVal: false,
|
// defaultVal: false,
|
||||||
callFn: (val) => {SmartDialog.showToast('下次启动时生效')},
|
// callFn: (val) => {SmartDialog.showToast('下次启动时生效')},
|
||||||
|
// ),
|
||||||
|
ListTile(
|
||||||
|
dense: false,
|
||||||
|
title: Text('自定义列数', style: titleStyle),
|
||||||
|
subtitle: Text(
|
||||||
|
'当前列数',
|
||||||
|
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,
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class VideoDetailController extends GetxController
|
|||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
/// 路由传参
|
/// 路由传参
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid']!;
|
||||||
int cid = int.parse(Get.parameters['cid']!);
|
RxInt cid = int.parse(Get.parameters['cid']!).obs;
|
||||||
RxInt danmakuCid = 0.obs;
|
RxInt danmakuCid = 0.obs;
|
||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
// 视频详情
|
// 视频详情
|
||||||
@ -109,7 +109,7 @@ class VideoDetailController extends GetxController
|
|||||||
localCache.get(LocalCacheKey.historyPause) == true) {
|
localCache.get(LocalCacheKey.historyPause) == true) {
|
||||||
enableHeart = false;
|
enableHeart = false;
|
||||||
}
|
}
|
||||||
danmakuCid.value = cid;
|
danmakuCid.value = cid.value;
|
||||||
|
|
||||||
///
|
///
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
@ -215,10 +215,8 @@ class VideoDetailController extends GetxController
|
|||||||
direction: (firstVideo.width! - firstVideo.height!) > 0
|
direction: (firstVideo.width! - firstVideo.height!) > 0
|
||||||
? 'horizontal'
|
? 'horizontal'
|
||||||
: 'vertical',
|
: 'vertical',
|
||||||
// 默认1倍速
|
|
||||||
speed: 1.0,
|
|
||||||
bvid: bvid,
|
bvid: bvid,
|
||||||
cid: cid,
|
cid: cid.value,
|
||||||
enableHeart: enableHeart,
|
enableHeart: enableHeart,
|
||||||
isFirstTime: isFirstTime,
|
isFirstTime: isFirstTime,
|
||||||
autoplay: autoplay,
|
autoplay: autoplay,
|
||||||
@ -230,7 +228,7 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
// 视频链接
|
// 视频链接
|
||||||
Future queryVideoUrl() async {
|
Future queryVideoUrl() async {
|
||||||
var result = await VideoHttp.videoUrl(cid: cid, bvid: bvid);
|
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
data = result['data'];
|
data = result['data'];
|
||||||
|
|
||||||
|
|||||||
@ -11,11 +11,14 @@ import 'package:pilipala/models/user/fav_folder.dart';
|
|||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
import 'widgets/group_panel.dart';
|
||||||
|
|
||||||
class VideoIntroController extends GetxController {
|
class VideoIntroController extends GetxController {
|
||||||
// 视频bvid
|
// 视频bvid
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid']!;
|
||||||
@ -58,6 +61,7 @@ class VideoIntroController extends GetxController {
|
|||||||
RxString total = '1'.obs;
|
RxString total = '1'.obs;
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
bool isPaused = false;
|
bool isPaused = false;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -102,9 +106,10 @@ class VideoIntroController extends GetxController {
|
|||||||
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
||||||
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
||||||
}
|
}
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
// Get.find<VideoDetailController>(tag: heroTag).tabs.value = [
|
||||||
.tabs
|
// '简介',
|
||||||
.value = ['简介', '评论 ${result['data']!.stat!.reply}'];
|
// '评论 ${result['data']!.stat!.reply}'
|
||||||
|
// ];
|
||||||
// 获取到粉丝数再返回
|
// 获取到粉丝数再返回
|
||||||
await queryUserStat();
|
await queryUserStat();
|
||||||
}
|
}
|
||||||
@ -425,6 +430,20 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
followStatus['attribute'] = actionStatus;
|
followStatus['attribute'] = actionStatus;
|
||||||
followStatus.refresh();
|
followStatus.refresh();
|
||||||
|
if (actionStatus == 2) {
|
||||||
|
if (context.mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: const Text('关注成功'),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
action: SnackBarAction(
|
||||||
|
label: '设置分组',
|
||||||
|
onPressed: setFollowGroup,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
},
|
},
|
||||||
@ -440,16 +459,16 @@ class VideoIntroController extends GetxController {
|
|||||||
Future changeSeasonOrbangu(bvid, cid, aid) async {
|
Future changeSeasonOrbangu(bvid, cid, aid) async {
|
||||||
// 重新获取视频资源
|
// 重新获取视频资源
|
||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
/// 未渲染回复组件时可能异常
|
/// 未渲染回复组件时可能异常
|
||||||
VideoReplyController videoReplyCtr =
|
VideoReplyController videoReplyCtr =
|
||||||
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoReplyController>(tag: heroTag);
|
||||||
videoReplyCtr.aid = aid;
|
videoReplyCtr.aid = aid;
|
||||||
videoReplyCtr.queryReplyList(type: 'init');
|
videoReplyCtr.queryReplyList(type: 'init');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -486,4 +505,60 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
// if (videoDetail.value.ugcSeason != null) {
|
||||||
|
// UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
||||||
|
// List<SectionItem> sections = ugcSeason.sections!;
|
||||||
|
// for (int i = 0; i < sections.length; i++) {
|
||||||
|
// List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||||
|
// for (int j = 0; j < episodesList.length; j++) {
|
||||||
|
// if (episodesList[j].cid == lastPlayCid.value) {
|
||||||
|
// episodes = episodesList;
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
if (videoDetail.value.ugcSeason != null) {
|
||||||
|
UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
||||||
|
List<SectionItem> sections = ugcSeason.sections!;
|
||||||
|
episodes = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < sections.length; i++) {
|
||||||
|
List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||||
|
episodes.addAll(episodesList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int currentIndex = episodes.indexWhere((e) => e.cid == lastPlayCid.value);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
|
||||||
|
// 列表循环
|
||||||
|
if (nextIndex >= episodes.length) {
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
if (platRepeat == PlayRepeat.listOrder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
|
int aid = episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置关注分组
|
||||||
|
void setFollowGroup() {
|
||||||
|
Get.bottomSheet(
|
||||||
|
GroupPanel(mid: videoDetail.value.owner!.mid!),
|
||||||
|
isScrollControlled: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -241,9 +241,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => showIntroDetail(),
|
onTap: () => showIntroDetail(),
|
||||||
child: Row(
|
child: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.only(bottom: 6),
|
||||||
Expanded(
|
|
||||||
child: Text(
|
child: Text(
|
||||||
!loadingStatus
|
!loadingStatus
|
||||||
? widget.videoDetail!.title
|
? widget.videoDetail!.title
|
||||||
@ -252,36 +251,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
const SizedBox(width: 20),
|
|
||||||
Opacity(
|
|
||||||
opacity: loadingStatus ? 0 : 1,
|
|
||||||
child: SizedBox(
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
child: IconButton(
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding:
|
|
||||||
MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
backgroundColor:
|
|
||||||
MaterialStateProperty.resolveWith((states) {
|
|
||||||
return t.highlightColor.withOpacity(0.2);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
onPressed: showIntroDetail,
|
|
||||||
icon: Icon(
|
|
||||||
Icons.more_horiz,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => showIntroDetail(),
|
onTap: () => showIntroDetail(),
|
||||||
@ -330,17 +303,17 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 7),
|
const SizedBox(height: 7),
|
||||||
// 点赞收藏转发 布局样式1
|
// 点赞收藏转发 布局样式1
|
||||||
SingleChildScrollView(
|
// SingleChildScrollView(
|
||||||
padding: const EdgeInsets.only(top: 7, bottom: 7),
|
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||||
scrollDirection: Axis.horizontal,
|
// scrollDirection: Axis.horizontal,
|
||||||
child: actionRow(
|
// child: actionRow(
|
||||||
context,
|
// context,
|
||||||
videoIntroController,
|
// videoIntroController,
|
||||||
videoDetailCtr,
|
// videoDetailCtr,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
// 点赞收藏转发 布局样式2
|
// 点赞收藏转发 布局样式2
|
||||||
// actionGrid(context, videoIntroController),
|
actionGrid(context, videoIntroController),
|
||||||
// 合集
|
// 合集
|
||||||
if (!loadingStatus &&
|
if (!loadingStatus &&
|
||||||
widget.videoDetail!.ugcSeason != null) ...[
|
widget.videoDetail!.ugcSeason != null) ...[
|
||||||
@ -458,7 +431,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.only(top: 6, bottom: 10),
|
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||||
height: constraints.maxWidth / 5 * 0.8,
|
height: constraints.maxWidth / 5 * 0.8,
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
primary: false,
|
primary: false,
|
||||||
@ -477,12 +450,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
? widget.videoDetail!.stat!.like!.toString()
|
? widget.videoDetail!.stat!.like!.toString()
|
||||||
: '-'),
|
: '-'),
|
||||||
),
|
),
|
||||||
ActionItem(
|
// ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.clock),
|
// icon: const Icon(FontAwesomeIcons.clock),
|
||||||
onTap: () => videoIntroController.actionShareVideo(),
|
// onTap: () => videoIntroController.actionShareVideo(),
|
||||||
selectStatus: false,
|
// selectStatus: false,
|
||||||
loadingStatus: loadingStatus,
|
// loadingStatus: loadingStatus,
|
||||||
text: '稍后再看'),
|
// text: '稍后再看'),
|
||||||
Obx(
|
Obx(
|
||||||
() => ActionItem(
|
() => ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
@ -498,22 +471,28 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
() => ActionItem(
|
() => ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.star),
|
icon: const Icon(FontAwesomeIcons.star),
|
||||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||||
// onTap: () => videoIntroController.actionFavVideo(),
|
|
||||||
onTap: () => showFavBottomSheet(),
|
onTap: () => showFavBottomSheet(),
|
||||||
|
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
loadingStatus: loadingStatus,
|
loadingStatus: loadingStatus,
|
||||||
text: !loadingStatus
|
text: !loadingStatus
|
||||||
? widget.videoDetail!.stat!.favorite!.toString()
|
? widget.videoDetail!.stat!.favorite!.toString()
|
||||||
: '-'),
|
: '-'),
|
||||||
),
|
),
|
||||||
|
ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.comment),
|
||||||
|
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: loadingStatus,
|
||||||
|
text: !loadingStatus
|
||||||
|
? widget.videoDetail!.stat!.reply!.toString()
|
||||||
|
: '评论'),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||||
onTap: () => videoIntroController.actionShareVideo(),
|
onTap: () => videoIntroController.actionShareVideo(),
|
||||||
selectStatus: false,
|
selectStatus: false,
|
||||||
loadingStatus: loadingStatus,
|
loadingStatus: loadingStatus,
|
||||||
text: !loadingStatus
|
text: '分享'),
|
||||||
? widget.videoDetail!.stat!.share!.toString()
|
|
||||||
: '-'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ class ActionItem extends StatelessWidget {
|
|||||||
final Icon? icon;
|
final Icon? icon;
|
||||||
final Icon? selectIcon;
|
final Icon? selectIcon;
|
||||||
final Function? onTap;
|
final Function? onTap;
|
||||||
|
final Function? onLongPress;
|
||||||
final bool? loadingStatus;
|
final bool? loadingStatus;
|
||||||
final String? text;
|
final String? text;
|
||||||
final bool selectStatus;
|
final bool selectStatus;
|
||||||
@ -15,6 +16,7 @@ class ActionItem extends StatelessWidget {
|
|||||||
this.icon,
|
this.icon,
|
||||||
this.selectIcon,
|
this.selectIcon,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.onLongPress,
|
||||||
this.loadingStatus,
|
this.loadingStatus,
|
||||||
this.text,
|
this.text,
|
||||||
this.selectStatus = false,
|
this.selectStatus = false,
|
||||||
@ -27,6 +29,9 @@ class ActionItem extends StatelessWidget {
|
|||||||
feedBack(),
|
feedBack(),
|
||||||
onTap!(),
|
onTap!(),
|
||||||
},
|
},
|
||||||
|
onLongPress: () => {
|
||||||
|
if (onLongPress != null) {onLongPress!()}
|
||||||
|
},
|
||||||
borderRadius: StyleString.mdRadius,
|
borderRadius: StyleString.mdRadius,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|||||||
156
lib/pages/video/detail/introduction/widgets/group_panel.dart
Normal file
156
lib/pages/video/detail/introduction/widgets/group_panel.dart
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/member/tags.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
class GroupPanel extends StatefulWidget {
|
||||||
|
final int? mid;
|
||||||
|
const GroupPanel({super.key, this.mid});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GroupPanel> createState() => _GroupPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GroupPanelState extends State<GroupPanel> {
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
late double sheetHeight;
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
late List<MemberTagItemModel> tagsList;
|
||||||
|
bool showDefault = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
|
_futureBuilderFuture = MemberHttp.followUpTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSave() async {
|
||||||
|
feedBack();
|
||||||
|
// 是否有选中的 有选中的带id,没选使用默认0
|
||||||
|
bool anyHasChecked = tagsList.any((e) => e.checked == true);
|
||||||
|
late String tagids;
|
||||||
|
if (anyHasChecked) {
|
||||||
|
List checkedList = tagsList.where((e) => e.checked == true).toList();
|
||||||
|
List<int> tagidList = checkedList.map<int>((e) => e.tagid).toList();
|
||||||
|
tagids = tagidList.join(',');
|
||||||
|
} else {
|
||||||
|
tagids = '0';
|
||||||
|
}
|
||||||
|
// 保存
|
||||||
|
var res = await MemberHttp.addUsers(widget.mid, tagids);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
if (res['status']) {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
height: sheetHeight,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AppBar(
|
||||||
|
centerTitle: false,
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(Icons.close_outlined)),
|
||||||
|
title:
|
||||||
|
Text('设置关注分组', style: Theme.of(context).textTheme.titleMedium),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
tagsList = data['data'];
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: data['data'].length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: () {
|
||||||
|
data['data'][index].checked =
|
||||||
|
!data['data'][index].checked;
|
||||||
|
showDefault =
|
||||||
|
!data['data'].any((e) => e.checked == true);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
leading: const Icon(Icons.group_outlined),
|
||||||
|
minLeadingWidth: 0,
|
||||||
|
title: Text(data['data'][index].name),
|
||||||
|
subtitle: data['data'][index].tip != ''
|
||||||
|
? Text(data['data'][index].tip)
|
||||||
|
: null,
|
||||||
|
trailing: Transform.scale(
|
||||||
|
scale: 0.9,
|
||||||
|
child: Checkbox(
|
||||||
|
value: data['data'][index].checked,
|
||||||
|
onChanged: (bool? checkValue) {
|
||||||
|
data['data'][index].checked = checkValue;
|
||||||
|
showDefault = !data['data']
|
||||||
|
.any((e) => e.checked == true);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return const Text('请求中');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).disabledColor.withOpacity(0.08),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
top: 12,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 12,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => onSave(),
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.primary, // 设置按钮背景色
|
||||||
|
),
|
||||||
|
child: Text(showDefault ? '保存至默认分组' : '保存'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
@ -129,7 +130,50 @@ class IntroDetail extends StatelessWidget {
|
|||||||
final currentDesc = descV2[index];
|
final currentDesc = descV2[index];
|
||||||
switch (currentDesc.type) {
|
switch (currentDesc.type) {
|
||||||
case 1:
|
case 1:
|
||||||
return TextSpan(text: currentDesc.rawText);
|
List<InlineSpan> spanChildren = [];
|
||||||
|
RegExp urlRegExp = RegExp(r'https?://\S+\b');
|
||||||
|
Iterable<Match> matches = urlRegExp.allMatches(currentDesc.rawText);
|
||||||
|
|
||||||
|
int previousEndIndex = 0;
|
||||||
|
for (Match match in matches) {
|
||||||
|
if (match.start > previousEndIndex) {
|
||||||
|
spanChildren.add(TextSpan(
|
||||||
|
text: currentDesc.rawText
|
||||||
|
.substring(previousEndIndex, match.start)));
|
||||||
|
}
|
||||||
|
spanChildren.add(
|
||||||
|
TextSpan(
|
||||||
|
text: match.group(0),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
// 处理点击事件
|
||||||
|
try {
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': match.group(0)!,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': match.group(0)!,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
previousEndIndex = match.end;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousEndIndex < currentDesc.rawText.length) {
|
||||||
|
spanChildren.add(TextSpan(
|
||||||
|
text: currentDesc.rawText.substring(previousEndIndex)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TextSpan result = TextSpan(children: spanChildren);
|
||||||
|
return result;
|
||||||
case 2:
|
case 2:
|
||||||
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
|
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
|
||||||
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
|
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
|
|
||||||
class PagesPanel extends StatefulWidget {
|
class PagesPanel extends StatefulWidget {
|
||||||
final List<Part> pages;
|
final List<Part> pages;
|
||||||
@ -22,13 +23,23 @@ class PagesPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _PagesPanelState extends State<PagesPanel> {
|
class _PagesPanelState extends State<PagesPanel> {
|
||||||
late List<Part> episodes;
|
late List<Part> episodes;
|
||||||
|
late int cid;
|
||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
late VideoDetailController _videoDetailController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
cid = widget.cid!;
|
||||||
episodes = widget.pages;
|
episodes = widget.pages;
|
||||||
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
currentIndex = episodes.indexWhere((e) => e.cid == cid);
|
||||||
|
_videoDetailController.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
currentIndex = episodes.indexWhere((e) => e.cid == cid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
|
||||||
class SeasonPanel extends StatefulWidget {
|
class SeasonPanel extends StatefulWidget {
|
||||||
@ -23,11 +24,16 @@ class SeasonPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _SeasonPanelState extends State<SeasonPanel> {
|
class _SeasonPanelState extends State<SeasonPanel> {
|
||||||
late List<EpisodeItem> episodes;
|
late List<EpisodeItem> episodes;
|
||||||
|
late int cid;
|
||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
late VideoDetailController _videoDetailController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
cid = widget.cid!;
|
||||||
|
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
|
||||||
/// 根据 cid 找到对应集,找到对应 episodes
|
/// 根据 cid 找到对应集,找到对应 episodes
|
||||||
/// 有多个episodes时,只显示其中一个
|
/// 有多个episodes时,只显示其中一个
|
||||||
@ -36,7 +42,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
for (int i = 0; i < sections.length; i++) {
|
for (int i = 0; i < sections.length; i++) {
|
||||||
List<EpisodeItem> episodesList = sections[i].episodes!;
|
List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||||
for (int j = 0; j < episodesList.length; j++) {
|
for (int j = 0; j < episodesList.length; j++) {
|
||||||
if (episodesList[j].cid == widget.cid) {
|
if (episodesList[j].cid == cid) {
|
||||||
episodes = episodesList;
|
episodes = episodesList;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -47,7 +53,12 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
// episodes = widget.ugcSeason.sections!
|
// episodes = widget.ugcSeason.sections!
|
||||||
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
|
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
|
||||||
// .episodes!;
|
// .episodes!;
|
||||||
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
currentIndex = episodes.indexWhere((e) => e.cid == cid);
|
||||||
|
_videoDetailController.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
currentIndex = episodes.indexWhere((e) => e.cid == cid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
@ -57,7 +68,9 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
item.aid,
|
item.aid,
|
||||||
);
|
);
|
||||||
currentIndex = i;
|
currentIndex = i;
|
||||||
|
setState(() {});
|
||||||
Get.back();
|
Get.back();
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -96,8 +96,8 @@ class VideoReplyController extends GetxController {
|
|||||||
} else {
|
} else {
|
||||||
replyList.addAll(replies);
|
replyList.addAll(replies);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
count.value = res['data'].page.count;
|
count.value = res['data'].page.count;
|
||||||
|
}
|
||||||
isLoadingMore = false;
|
isLoadingMore = false;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -195,7 +195,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map data = snapshot.data as Map;
|
var data = snapshot.data;
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
// 请求成功
|
// 请求成功
|
||||||
return Obx(
|
return Obx(
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/pages/preview/index.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
import 'package:pilipala/pages/video/detail/replyNew/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/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
@ -667,10 +668,11 @@ InlineSpan buildContent(
|
|||||||
// 匹配 jumpUrl
|
// 匹配 jumpUrl
|
||||||
String matchUrl = matchMember;
|
String matchUrl = matchMember;
|
||||||
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
|
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
|
||||||
List urlKeys = content.jumpUrl.keys.toList();
|
List urlKeys = content.jumpUrl.keys.toList().reversed.toList();
|
||||||
matchUrl = matchMember.splitMapJoin(
|
matchUrl = matchMember.splitMapJoin(
|
||||||
/// RegExp.escape() 转义特殊字符
|
/// RegExp.escape() 转义特殊字符
|
||||||
RegExp(RegExp.escape(urlKeys.join("|"))),
|
RegExp(urlKeys.map((key) => key).join("|")),
|
||||||
|
// RegExp(RegExp.escape(urlKeys.join("|"))),
|
||||||
onMatch: (Match match) {
|
onMatch: (Match match) {
|
||||||
String matchStr = match[0]!;
|
String matchStr = match[0]!;
|
||||||
String appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
String appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
|
||||||
@ -688,6 +690,17 @@ InlineSpan buildContent(
|
|||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
if (appUrlSchema == '') {
|
if (appUrlSchema == '') {
|
||||||
|
String str = Uri.parse(matchStr).pathSegments[0];
|
||||||
|
Map matchRes = IdUtils.matchAvorBv(input: str);
|
||||||
|
List matchKeys = matchRes.keys.toList();
|
||||||
|
if (matchKeys.isNotEmpty) {
|
||||||
|
if (matchKeys.first == 'BV') {
|
||||||
|
Get.toNamed(
|
||||||
|
'/searchResult',
|
||||||
|
parameters: {'keyword': matchRes['BV']},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -696,6 +709,7 @@ InlineSpan buildContent(
|
|||||||
'pageTitle': ''
|
'pageTitle': ''
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (appUrlSchema.startsWith('bilibili://search') &&
|
if (appUrlSchema.startsWith('bilibili://search') &&
|
||||||
enableWordRe) {
|
enableWordRe) {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import 'package:pilipala/pages/video/detail/controller.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/related/index.dart';
|
import 'package:pilipala/pages/video/detail/related/index.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/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'widgets/app_bar.dart';
|
import 'widgets/app_bar.dart';
|
||||||
@ -41,6 +42,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late StreamController<double> appbarStream;
|
late StreamController<double> appbarStream;
|
||||||
late VideoIntroController videoIntroController;
|
late VideoIntroController videoIntroController;
|
||||||
|
late BangumiIntroController bangumiIntroController;
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
|
|
||||||
PlayerStatus playerStatus = PlayerStatus.playing;
|
PlayerStatus playerStatus = PlayerStatus.playing;
|
||||||
@ -61,6 +63,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
heroTag = Get.arguments['heroTag'];
|
heroTag = Get.arguments['heroTag'];
|
||||||
videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
|
videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
|
||||||
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
||||||
|
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||||
statusBarHeight = localCache.get('statusBarHeight');
|
statusBarHeight = localCache.get('statusBarHeight');
|
||||||
autoExitFullcreen =
|
autoExitFullcreen =
|
||||||
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
||||||
@ -98,6 +101,23 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
if (autoExitFullcreen) {
|
if (autoExitFullcreen) {
|
||||||
plPlayerController!.triggerFullScreen(status: false);
|
plPlayerController!.triggerFullScreen(status: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 顺序播放 列表循环
|
||||||
|
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
|
||||||
|
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
|
||||||
|
if (videoDetailController.videoType == SearchType.video) {
|
||||||
|
videoIntroController.nextPlay();
|
||||||
|
}
|
||||||
|
if (videoDetailController.videoType == SearchType.media_bangumi) {
|
||||||
|
bangumiIntroController.nextPlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 单个循环
|
||||||
|
if (plPlayerController!.playRepeat == PlayRepeat.singleCycle) {
|
||||||
|
plPlayerController!.seekTo(Duration.zero);
|
||||||
|
plPlayerController!.play();
|
||||||
|
}
|
||||||
// 播放完展示控制栏
|
// 播放完展示控制栏
|
||||||
try {
|
try {
|
||||||
PiPStatus currentStatus =
|
PiPStatus currentStatus =
|
||||||
@ -165,6 +185,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
videoIntroController.isPaused = false;
|
videoIntroController.isPaused = false;
|
||||||
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
plPlayerController!.seekTo(videoDetailController.defaultST);
|
||||||
plPlayerController?.play();
|
plPlayerController?.play();
|
||||||
}
|
}
|
||||||
plPlayerController?.addStatusLister(playerListener);
|
plPlayerController?.addStatusLister(playerListener);
|
||||||
@ -385,8 +406,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
const VideoIntroPanel(),
|
const VideoIntroPanel(),
|
||||||
] else if (videoDetailController.videoType ==
|
] else if (videoDetailController.videoType ==
|
||||||
SearchType.media_bangumi) ...[
|
SearchType.media_bangumi) ...[
|
||||||
BangumiIntroPanel(
|
Obx(() => BangumiIntroPanel(
|
||||||
cid: videoDetailController.cid)
|
cid: videoDetailController.cid.value)),
|
||||||
],
|
],
|
||||||
// if (videoDetailController.videoType ==
|
// if (videoDetailController.videoType ==
|
||||||
// SearchType.video) ...[
|
// SearchType.video) ...[
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:pilipala/models/video/play/url.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
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/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
@ -40,11 +41,14 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
|
Box videoStorage = GStrorage.video;
|
||||||
|
late List speedsList;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
videoInfo = widget.videoDetailCtr!.data;
|
videoInfo = widget.videoDetailCtr!.data;
|
||||||
|
speedsList = widget.controller!.speedsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置面板
|
/// 设置面板
|
||||||
@ -56,7 +60,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
builder: (_) {
|
builder: (_) {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 400,
|
height: 440,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
@ -149,13 +153,14 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
||||||
style: subTitleStyle),
|
style: subTitleStyle),
|
||||||
),
|
),
|
||||||
// ListTile(
|
ListTile(
|
||||||
// onTap: () {},
|
onTap: () => {Get.back(), showSetRepeat()},
|
||||||
// dense: true,
|
dense: true,
|
||||||
// enabled: false,
|
leading: const Icon(Icons.repeat, size: 20),
|
||||||
// leading: const Icon(Icons.play_circle_outline, size: 20),
|
title: Text('播放顺序', style: titleStyle),
|
||||||
// title: Text('播放设置', style: titleStyle),
|
subtitle: Text(widget.controller!.playRepeat.description,
|
||||||
// ),
|
style: subTitleStyle),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => {Get.back(), showSetDanmaku()},
|
onTap: () => {Get.back(), showSetDanmaku()},
|
||||||
dense: true,
|
dense: true,
|
||||||
@ -182,21 +187,25 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('播放速度'),
|
title: const Text('播放速度'),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 20, 0, 20),
|
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
return Column(
|
return Wrap(
|
||||||
mainAxisSize: MainAxisSize.min,
|
alignment: WrapAlignment.start,
|
||||||
|
spacing: 8,
|
||||||
|
runSpacing: 2,
|
||||||
children: [
|
children: [
|
||||||
Text('$currentSpeed倍'),
|
for (var i in speedsList) ...[
|
||||||
Slider(
|
if (i == currentSpeed) ...[
|
||||||
min: PlaySpeed.values.first.value,
|
FilledButton(
|
||||||
max: PlaySpeed.values.last.value,
|
onPressed: () => {setState(() => currentSpeed = i)},
|
||||||
value: currentSpeed,
|
child: Text(i.toString()),
|
||||||
divisions: PlaySpeed.values.length - 1,
|
),
|
||||||
label: '${currentSpeed}x',
|
] else ...[
|
||||||
onChanged: (double val) =>
|
FilledButton.tonal(
|
||||||
{setState(() => currentSpeed = val)},
|
onPressed: () => {setState(() => currentSpeed = i)},
|
||||||
)
|
child: Text(i.toString()),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -676,9 +685,9 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
),
|
),
|
||||||
child: Slider(
|
child: Slider(
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 6,
|
max: 8,
|
||||||
value: danmakuSpeedVal,
|
value: danmakuSpeedVal,
|
||||||
divisions: 10,
|
divisions: 14,
|
||||||
label: danmakuSpeedVal.toString(),
|
label: danmakuSpeedVal.toString(),
|
||||||
onChanged: (double val) {
|
onChanged: (double val) {
|
||||||
danmakuSpeedVal = val;
|
danmakuSpeedVal = val;
|
||||||
@ -704,6 +713,60 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 播放顺序
|
||||||
|
void showSetRepeat() async {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 250,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: Center(child: Text('选择播放顺序', style: titleStyle))),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
for (var i in PlayRepeat.values) ...[
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
widget.controller!.setPlayRepeat(i);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.only(left: 20, right: 20),
|
||||||
|
title: Text(i.description),
|
||||||
|
trailing: widget.controller!.playRepeat == i
|
||||||
|
? Icon(
|
||||||
|
Icons.done,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _ = widget.controller!;
|
final _ = widget.controller!;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/pages/home/index.dart';
|
|||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/cookie.dart';
|
import 'package:pilipala/utils/cookie.dart';
|
||||||
import 'package:pilipala/utils/event_bus.dart';
|
import 'package:pilipala/utils/event_bus.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/login.dart';
|
import 'package:pilipala/utils/login.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
@ -50,7 +51,19 @@ class WebviewController extends GetxController {
|
|||||||
// Update loading bar.
|
// Update loading bar.
|
||||||
loadProgress.value = progress;
|
loadProgress.value = progress;
|
||||||
},
|
},
|
||||||
onPageStarted: (String url) {},
|
onPageStarted: (String url) {
|
||||||
|
String str = Uri.parse(url).pathSegments[0];
|
||||||
|
Map matchRes = IdUtils.matchAvorBv(input: str);
|
||||||
|
List matchKeys = matchRes.keys.toList();
|
||||||
|
if (matchKeys.isNotEmpty) {
|
||||||
|
if (matchKeys.first == 'BV') {
|
||||||
|
Get.offAndToNamed(
|
||||||
|
'/searchResult',
|
||||||
|
parameters: {'keyword': matchRes['BV']},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
// 加载完成
|
// 加载完成
|
||||||
onUrlChange: (UrlChange urlChange) async {
|
onUrlChange: (UrlChange urlChange) async {
|
||||||
loadShow.value = false;
|
loadShow.value = false;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
@ -24,11 +25,20 @@ class _WebviewPageState extends State<WebviewPage> {
|
|||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
const SizedBox(width: 4),
|
||||||
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_webviewController.controller.reload();
|
_webviewController.controller.reload();
|
||||||
},
|
},
|
||||||
child: const Text('刷新'),
|
icon: Icon(Icons.refresh_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
launchUrl(Uri.parse(_webviewController.url));
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.open_in_browser_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.primary),
|
||||||
),
|
),
|
||||||
Obx(
|
Obx(
|
||||||
() => _webviewController.type.value == 'login'
|
() => _webviewController.type.value == 'login'
|
||||||
@ -38,7 +48,7 @@ class _WebviewPageState extends State<WebviewPage> {
|
|||||||
)
|
)
|
||||||
: const SizedBox(),
|
: const SizedBox(),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10)
|
const SizedBox(width: 12)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.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/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:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
@ -54,6 +55,7 @@ class PlPlayerController {
|
|||||||
final Rx<int> _playerCount = Rx(0);
|
final Rx<int> _playerCount = Rx(0);
|
||||||
|
|
||||||
final Rx<double> _playbackSpeed = 1.0.obs;
|
final Rx<double> _playbackSpeed = 1.0.obs;
|
||||||
|
final Rx<double> _longPressSpeed = 2.0.obs;
|
||||||
final Rx<double> _currentVolume = 1.0.obs;
|
final Rx<double> _currentVolume = 1.0.obs;
|
||||||
final Rx<double> _currentBrightness = 0.0.obs;
|
final Rx<double> _currentBrightness = 0.0.obs;
|
||||||
|
|
||||||
@ -126,6 +128,9 @@ class PlPlayerController {
|
|||||||
/// 视频播放速度
|
/// 视频播放速度
|
||||||
double get playbackSpeed => _playbackSpeed.value;
|
double get playbackSpeed => _playbackSpeed.value;
|
||||||
|
|
||||||
|
// 长按倍速
|
||||||
|
double get longPressSpeed => _longPressSpeed.value;
|
||||||
|
|
||||||
/// 视频缓冲
|
/// 视频缓冲
|
||||||
Rx<Duration> get buffered => _buffered;
|
Rx<Duration> get buffered => _buffered;
|
||||||
Stream<Duration> get onBufferedChanged => _buffered.stream;
|
Stream<Duration> get onBufferedChanged => _buffered.stream;
|
||||||
@ -208,6 +213,10 @@ class PlPlayerController {
|
|||||||
late double opacityVal;
|
late double opacityVal;
|
||||||
late double fontSizeVal;
|
late double fontSizeVal;
|
||||||
late double danmakuSpeedVal;
|
late double danmakuSpeedVal;
|
||||||
|
late List speedsList;
|
||||||
|
|
||||||
|
// 播放顺序相关
|
||||||
|
PlayRepeat playRepeat = PlayRepeat.pause;
|
||||||
|
|
||||||
// 添加一个私有构造函数
|
// 添加一个私有构造函数
|
||||||
PlPlayerController._() {
|
PlPlayerController._() {
|
||||||
@ -226,6 +235,23 @@ class PlPlayerController {
|
|||||||
// 弹幕速度
|
// 弹幕速度
|
||||||
danmakuSpeedVal =
|
danmakuSpeedVal =
|
||||||
localCache.get(LocalCacheKey.danmakuSpeed, defaultValue: 4.0);
|
localCache.get(LocalCacheKey.danmakuSpeed, defaultValue: 4.0);
|
||||||
|
playRepeat = PlayRepeat.values.toList().firstWhere(
|
||||||
|
(e) =>
|
||||||
|
e.value ==
|
||||||
|
videoStorage.get(VideoBoxKey.playRepeat,
|
||||||
|
defaultValue: PlayRepeat.pause.value),
|
||||||
|
);
|
||||||
|
_playbackSpeed.value =
|
||||||
|
videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
|
||||||
|
_longPressSpeed.value =
|
||||||
|
videoStorage.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0);
|
||||||
|
List speedsListTemp =
|
||||||
|
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
|
||||||
|
speedsList = List.from(speedsListTemp);
|
||||||
|
for (var i in PlaySpeed.values) {
|
||||||
|
speedsList.add(i.value);
|
||||||
|
}
|
||||||
|
|
||||||
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
||||||
// if (status == PlayerStatus.playing) {
|
// if (status == PlayerStatus.playing) {
|
||||||
// WakelockPlus.enable();
|
// WakelockPlus.enable();
|
||||||
@ -283,7 +309,7 @@ class PlPlayerController {
|
|||||||
_autoPlay = autoplay;
|
_autoPlay = autoplay;
|
||||||
_looping = looping;
|
_looping = looping;
|
||||||
// 初始化视频倍速
|
// 初始化视频倍速
|
||||||
_playbackSpeed.value = speed;
|
// _playbackSpeed.value = speed;
|
||||||
// 初始化数据加载状态
|
// 初始化数据加载状态
|
||||||
dataStatus.status.value = DataStatus.loading;
|
dataStatus.status.value = DataStatus.loading;
|
||||||
// 初始化全屏方向
|
// 初始化全屏方向
|
||||||
@ -351,7 +377,8 @@ class PlPlayerController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
var pp = player.platform as NativePlayer;
|
var pp = player.platform as NativePlayer;
|
||||||
|
// 解除倍速限制
|
||||||
|
await pp.setProperty("af", "scaletempo2=max-speed=8");
|
||||||
// 音轨
|
// 音轨
|
||||||
if (dataSource.audioSource != '' && dataSource.audioSource != null) {
|
if (dataSource.audioSource != '' && dataSource.audioSource != null) {
|
||||||
await pp.setProperty(
|
await pp.setProperty(
|
||||||
@ -418,6 +445,8 @@ class PlPlayerController {
|
|||||||
// 设置倍速
|
// 设置倍速
|
||||||
if (_playbackSpeed.value != 1.0) {
|
if (_playbackSpeed.value != 1.0) {
|
||||||
await setPlaybackSpeed(_playbackSpeed.value);
|
await setPlaybackSpeed(_playbackSpeed.value);
|
||||||
|
} else {
|
||||||
|
await setPlaybackSpeed(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (_looping) {
|
// if (_looping) {
|
||||||
@ -548,7 +577,8 @@ class PlPlayerController {
|
|||||||
duration: (currentOption.duration / speed) * playbackSpeed);
|
duration: (currentOption.duration / speed) * playbackSpeed);
|
||||||
danmakuController!.updateOption(updatedOption);
|
danmakuController!.updateOption(updatedOption);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_playbackSpeed.value = speed;
|
// fix 长按倍速后放开不恢复
|
||||||
|
// _playbackSpeed.value = speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 设置倍速
|
/// 设置倍速
|
||||||
@ -762,11 +792,10 @@ class PlPlayerController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_doubleSpeedStatus.value = val;
|
_doubleSpeedStatus.value = val;
|
||||||
double currentSpeed = playbackSpeed;
|
|
||||||
if (val) {
|
if (val) {
|
||||||
setPlaybackSpeed(currentSpeed * 2);
|
setPlaybackSpeed(longPressSpeed);
|
||||||
} else {
|
} else {
|
||||||
setPlaybackSpeed(currentSpeed / 2);
|
setPlaybackSpeed(playbackSpeed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -817,6 +846,10 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleFullScreen(true);
|
toggleFullScreen(true);
|
||||||
|
bool isValid =
|
||||||
|
direction.value == 'vertical' || mode == FullScreenMode.vertical
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
var result = await showDialog(
|
var result = await showDialog(
|
||||||
context: Get.context!,
|
context: Get.context!,
|
||||||
useSafeArea: false,
|
useSafeArea: false,
|
||||||
@ -824,12 +857,10 @@ class PlPlayerController {
|
|||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
// 忽略手机安全区域
|
// 忽略手机安全区域
|
||||||
|
top: isValid,
|
||||||
left: false,
|
left: false,
|
||||||
right: false,
|
right: false,
|
||||||
bottom:
|
bottom: isValid,
|
||||||
direction.value == 'vertical' || mode == FullScreenMode.vertical
|
|
||||||
? true
|
|
||||||
: false,
|
|
||||||
child: PLVideoPlayer(
|
child: PLVideoPlayer(
|
||||||
controller: this,
|
controller: this,
|
||||||
headerControl: headerControl,
|
headerControl: headerControl,
|
||||||
@ -908,6 +939,11 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPlayRepeat(PlayRepeat type) {
|
||||||
|
playRepeat = type;
|
||||||
|
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> dispose({String type = 'single'}) async {
|
Future<void> dispose({String type = 'single'}) async {
|
||||||
// 每次减1,最后销毁
|
// 每次减1,最后销毁
|
||||||
if (type == 'single' && playerCount.value > 1) {
|
if (type == 'single' && playerCount.value > 1) {
|
||||||
|
|||||||
25
lib/plugin/pl_player/models/play_repeat.dart
Normal file
25
lib/plugin/pl_player/models/play_repeat.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
enum PlayRepeat {
|
||||||
|
pause,
|
||||||
|
listOrder,
|
||||||
|
singleCycle,
|
||||||
|
listCycle,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlayRepeatExtension on PlayRepeat {
|
||||||
|
static final List<String> _descList = [
|
||||||
|
'播完暂停',
|
||||||
|
'顺序播放',
|
||||||
|
'单个循环',
|
||||||
|
'列表循环',
|
||||||
|
];
|
||||||
|
get description => _descList[index];
|
||||||
|
|
||||||
|
static final List<double> _valueList = [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
];
|
||||||
|
get value => _valueList[index];
|
||||||
|
get defaultValue => _valueList[1];
|
||||||
|
}
|
||||||
@ -9,35 +9,18 @@ enum PlaySpeed {
|
|||||||
onePointSevenFive,
|
onePointSevenFive,
|
||||||
|
|
||||||
two,
|
two,
|
||||||
twoPointTwoFive,
|
|
||||||
twoPointFive,
|
|
||||||
twoPointSevenFive,
|
|
||||||
|
|
||||||
twhree,
|
|
||||||
threePointTwoFive,
|
|
||||||
threePointFive,
|
|
||||||
threePointSevenFive,
|
|
||||||
|
|
||||||
four,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PlaySpeedExtension on PlaySpeed {
|
extension PlaySpeedExtension on PlaySpeed {
|
||||||
static final List<String> _descList = [
|
static final List<String> _descList = [
|
||||||
'0.25倍',
|
'0.25',
|
||||||
'0.5倍',
|
'0.5',
|
||||||
'0.75倍',
|
'0.75',
|
||||||
'正常速度',
|
'正常',
|
||||||
'1.25倍',
|
'1.25',
|
||||||
'1.5倍',
|
'1.5',
|
||||||
'2.0倍',
|
'1.75',
|
||||||
'2.25倍',
|
'2.0',
|
||||||
'2.5倍',
|
|
||||||
'2.75倍',
|
|
||||||
'3.0倍',
|
|
||||||
'3.25倍',
|
|
||||||
'3.5倍',
|
|
||||||
'3.75倍',
|
|
||||||
'4.0倍'
|
|
||||||
];
|
];
|
||||||
get description => _descList[index];
|
get description => _descList[index];
|
||||||
|
|
||||||
@ -50,14 +33,6 @@ extension PlaySpeedExtension on PlaySpeed {
|
|||||||
1.5,
|
1.5,
|
||||||
1.75,
|
1.75,
|
||||||
2.0,
|
2.0,
|
||||||
2.25,
|
|
||||||
2.5,
|
|
||||||
2.75,
|
|
||||||
3.0,
|
|
||||||
3.25,
|
|
||||||
3.5,
|
|
||||||
3.75,
|
|
||||||
4.0,
|
|
||||||
];
|
];
|
||||||
get value => _valueList[index];
|
get value => _valueList[index];
|
||||||
get defaultValue => _valueList[3];
|
get defaultValue => _valueList[3];
|
||||||
|
|||||||
@ -478,19 +478,13 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final tapPosition = details.localPosition.dx;
|
final tapPosition = details.localPosition.dx;
|
||||||
int curSliderPosition = _.sliderPosition.value.inSeconds;
|
int curSliderPosition = _.sliderPosition.value.inMilliseconds;
|
||||||
late int result;
|
double scale = 90000 / MediaQuery.of(context).size.width;
|
||||||
if (tapPosition - _initTapPositoin > 0) {
|
Duration pos = Duration(
|
||||||
// 快进
|
milliseconds:
|
||||||
/// TODO 优化屏幕越小效果越明显
|
curSliderPosition + (details.delta.dx * scale).round());
|
||||||
result = (curSliderPosition + 1)
|
Duration result = pos.clamp(Duration.zero, _.duration.value);
|
||||||
.clamp(0, _.duration.value.inSeconds);
|
_.onUodatedSliderProgress(result);
|
||||||
} else {
|
|
||||||
// 快退
|
|
||||||
result = (curSliderPosition - 1)
|
|
||||||
.clamp(0, _.duration.value.inSeconds);
|
|
||||||
}
|
|
||||||
_.onUodatedSliderProgress(Duration(seconds: result));
|
|
||||||
_.onChangedSliderStart();
|
_.onChangedSliderStart();
|
||||||
_initTapPositoin = tapPosition;
|
_initTapPositoin = tapPosition;
|
||||||
},
|
},
|
||||||
@ -547,7 +541,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
|
|
||||||
// 头部、底部控制条
|
// 头部、底部控制条
|
||||||
Obx(
|
SafeArea(
|
||||||
|
top: false,
|
||||||
|
bottom: false,
|
||||||
|
child: Obx(
|
||||||
() => Column(
|
() => Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.headerControl != null || _.headerControl != null)
|
if (widget.headerControl != null || _.headerControl != null)
|
||||||
@ -577,6 +574,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
/// 进度条 live模式下禁用
|
/// 进度条 live模式下禁用
|
||||||
Obx(
|
Obx(
|
||||||
|
|||||||
@ -10,14 +10,17 @@ import 'package:pilipala/pages/dynamics/index.dart';
|
|||||||
import 'package:pilipala/pages/fan/index.dart';
|
import 'package:pilipala/pages/fan/index.dart';
|
||||||
import 'package:pilipala/pages/fav/index.dart';
|
import 'package:pilipala/pages/fav/index.dart';
|
||||||
import 'package:pilipala/pages/favDetail/index.dart';
|
import 'package:pilipala/pages/favDetail/index.dart';
|
||||||
|
import 'package:pilipala/pages/fav_search/index.dart';
|
||||||
import 'package:pilipala/pages/follow/index.dart';
|
import 'package:pilipala/pages/follow/index.dart';
|
||||||
import 'package:pilipala/pages/history/index.dart';
|
import 'package:pilipala/pages/history/index.dart';
|
||||||
|
import 'package:pilipala/pages/history_search/index.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/hot/index.dart';
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
import 'package:pilipala/pages/html/index.dart';
|
import 'package:pilipala/pages/html/index.dart';
|
||||||
import 'package:pilipala/pages/later/index.dart';
|
import 'package:pilipala/pages/later/index.dart';
|
||||||
import 'package:pilipala/pages/liveRoom/view.dart';
|
import 'package:pilipala/pages/liveRoom/view.dart';
|
||||||
import 'package:pilipala/pages/member/index.dart';
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
|
import 'package:pilipala/pages/member_search/index.dart';
|
||||||
import 'package:pilipala/pages/preview/index.dart';
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/searchResult/index.dart';
|
import 'package:pilipala/pages/searchResult/index.dart';
|
||||||
@ -25,6 +28,7 @@ import 'package:pilipala/pages/setting/extra_setting.dart';
|
|||||||
import 'package:pilipala/pages/setting/pages/color_select.dart';
|
import 'package:pilipala/pages/setting/pages/color_select.dart';
|
||||||
import 'package:pilipala/pages/setting/pages/display_mode.dart';
|
import 'package:pilipala/pages/setting/pages/display_mode.dart';
|
||||||
import 'package:pilipala/pages/setting/pages/font_size_select.dart';
|
import 'package:pilipala/pages/setting/pages/font_size_select.dart';
|
||||||
|
import 'package:pilipala/pages/setting/pages/play_speed_set.dart';
|
||||||
import 'package:pilipala/pages/setting/play_setting.dart';
|
import 'package:pilipala/pages/setting/play_setting.dart';
|
||||||
import 'package:pilipala/pages/setting/privacy_setting.dart';
|
import 'package:pilipala/pages/setting/privacy_setting.dart';
|
||||||
import 'package:pilipala/pages/setting/style_setting.dart';
|
import 'package:pilipala/pages/setting/style_setting.dart';
|
||||||
@ -86,6 +90,7 @@ class Routes {
|
|||||||
CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
||||||
// 用户中心
|
// 用户中心
|
||||||
CustomGetPage(name: '/member', page: () => const MemberPage()),
|
CustomGetPage(name: '/member', page: () => const MemberPage()),
|
||||||
|
CustomGetPage(name: '/memberSearch', page: () => const MemberSearchPage()),
|
||||||
// 二级回复
|
// 二级回复
|
||||||
CustomGetPage(
|
CustomGetPage(
|
||||||
name: '/replyReply', page: () => const VideoReplyReplyPanel()),
|
name: '/replyReply', page: () => const VideoReplyReplyPanel()),
|
||||||
@ -110,6 +115,13 @@ class Routes {
|
|||||||
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
||||||
//
|
//
|
||||||
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
||||||
|
// 历史记录搜索
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/historySearch', page: () => const HistorySearchPage()),
|
||||||
|
|
||||||
|
CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()),
|
||||||
|
// 收藏搜索
|
||||||
|
CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,10 @@ class Em {
|
|||||||
return regCate(matchStr);
|
return regCate(matchStr);
|
||||||
}, onNonMatch: (String str) {
|
}, onNonMatch: (String str) {
|
||||||
if (str != '') {
|
if (str != '') {
|
||||||
|
str = str
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'");
|
||||||
Map map = {'type': 'text', 'text': str};
|
Map map = {'type': 'text', 'text': str};
|
||||||
res.add(map);
|
res.add(map);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,6 +104,8 @@ class SettingBoxKey {
|
|||||||
static const String enableAutoBrightness = 'enableAutoBrightness';
|
static const String enableAutoBrightness = 'enableAutoBrightness';
|
||||||
static const String enableAutoEnter = 'enableAutoEnter';
|
static const String enableAutoEnter = 'enableAutoEnter';
|
||||||
static const String enableAutoExit = 'enableAutoExit';
|
static const String enableAutoExit = 'enableAutoExit';
|
||||||
|
static const String p1080 = 'p1080';
|
||||||
|
|
||||||
// youtube 双击快进快退
|
// youtube 双击快进快退
|
||||||
static const String enableQuickDouble = 'enableQuickDouble';
|
static const String enableQuickDouble = 'enableQuickDouble';
|
||||||
static const String enableShowDanmaku = 'enableShowDanmaku';
|
static const String enableShowDanmaku = 'enableShowDanmaku';
|
||||||
@ -121,6 +123,7 @@ class SettingBoxKey {
|
|||||||
static const String enableWordRe = 'enableWordRe';
|
static const String enableWordRe = 'enableWordRe';
|
||||||
static const String enableSearchWord = 'enableSearchWord';
|
static const String enableSearchWord = 'enableSearchWord';
|
||||||
static const String enableRcmdDynamic = 'enableRcmdDynamic';
|
static const String enableRcmdDynamic = 'enableRcmdDynamic';
|
||||||
|
static const String enableSaveLastData = 'enableSaveLastData';
|
||||||
|
|
||||||
/// 外观
|
/// 外观
|
||||||
static const String themeMode = 'themeMode';
|
static const String themeMode = 'themeMode';
|
||||||
@ -129,6 +132,8 @@ class SettingBoxKey {
|
|||||||
static const String customColor = 'customColor'; // 自定义主题色
|
static const String customColor = 'customColor'; // 自定义主题色
|
||||||
static const String iosTransition = 'iosTransition'; // ios路由
|
static const String iosTransition = 'iosTransition'; // ios路由
|
||||||
static const String enableSingleRow = 'enableSingleRow'; // 首页单列
|
static const String enableSingleRow = 'enableSingleRow'; // 首页单列
|
||||||
|
static const String displayMode = 'displayMode';
|
||||||
|
static const String customRows = 'customRows'; // 自定义列
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalCacheKey {
|
class LocalCacheKey {
|
||||||
@ -156,4 +161,12 @@ class VideoBoxKey {
|
|||||||
static const String videoBrightness = 'videoBrightness';
|
static const String videoBrightness = 'videoBrightness';
|
||||||
// 倍速
|
// 倍速
|
||||||
static const String videoSpeed = 'videoSpeed';
|
static const String videoSpeed = 'videoSpeed';
|
||||||
|
// 播放顺序
|
||||||
|
static const String playRepeat = 'playRepeat';
|
||||||
|
// 默认倍速
|
||||||
|
static const String playSpeedDefault = 'playSpeedDefault';
|
||||||
|
// 默认长按倍速
|
||||||
|
static const String longPressSpeedDefault = 'longPressSpeedDefault';
|
||||||
|
// 自定义倍速集合
|
||||||
|
static const String customSpeedsList = 'customSpeedsList';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,7 +206,7 @@ class Utils {
|
|||||||
static Future<bool> checkUpdata() async {
|
static Future<bool> checkUpdata() async {
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
var currentInfo = await PackageInfo.fromPlatform();
|
var currentInfo = await PackageInfo.fromPlatform();
|
||||||
var result = await Request().get(Api.latestApp);
|
var result = await Request().get(Api.latestApp, extra: {'ua': 'mob'});
|
||||||
LatestDataModel data = LatestDataModel.fromJson(result.data);
|
LatestDataModel data = LatestDataModel.fromJson(result.data);
|
||||||
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
|
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
|
|||||||
44
pubspec.lock
44
pubspec.lock
@ -721,18 +721,18 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: loading_more_list
|
name: loading_more_list
|
||||||
sha256: aa680edc81cf024c394dccfa7ba1db701a5efb23ec1b8c657308428ce9da11d1
|
sha256: "6b49eb935345d6cf291e0367d3c238ef0a525a08b671ee41e09ee67d41888a7a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.0.3"
|
version: "6.0.0"
|
||||||
loading_more_list_library:
|
loading_more_list_library:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: loading_more_list_library
|
name: loading_more_list_library
|
||||||
sha256: "31348925a98748ffe04f661e4b47df37103fabad39442064fcf59148a5fee2dd"
|
sha256: de6b57edbab83022180f053ec3f598dd5e1192cfd6a285882b8155e3cb5dc581
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "3.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -761,74 +761,74 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit
|
name: media_kit
|
||||||
sha256: "92c7f59e075d74471b31e703f81ccc1d7102739ebcce945b30a6417fa2f751d5"
|
sha256: d652c2bdb0cd876bf1046e24d0b614651fefe59f7c3a2d9b7ed57217b9e7db94
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.7"
|
version: "1.1.8+1"
|
||||||
media_kit_libs_android_video:
|
media_kit_libs_android_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_android_video
|
name: media_kit_libs_android_video
|
||||||
sha256: "498a5062bc5f000bd23ada3be788ea886ab32c52f7a8252dde1264ca019b819b"
|
sha256: a7ef60926ac528e2fabe9ee7084e648e385422a881ba914c978a7a81e6595dee
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.3"
|
version: "1.3.5"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_ios_video
|
name: media_kit_libs_ios_video
|
||||||
sha256: fed403dc9d54462e51ee80e0cb23c12a53fadea9a8fa18aca2de9054176d1159
|
sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.4"
|
||||||
media_kit_libs_linux:
|
media_kit_libs_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_linux
|
name: media_kit_libs_linux
|
||||||
sha256: "3b7c272179639a914dc8a50bf8a3f2df0e9a503bd727c88fab499dbdf6cb1eb8"
|
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.3"
|
||||||
media_kit_libs_macos_video:
|
media_kit_libs_macos_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_macos_video
|
name: media_kit_libs_macos_video
|
||||||
sha256: c06e831f3c22a45296d375788d9bc07871b448f8e9ec98d77b11e5e118a83fb2
|
sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.3"
|
version: "1.1.4"
|
||||||
media_kit_libs_video:
|
media_kit_libs_video:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_video
|
name: media_kit_libs_video
|
||||||
sha256: d961c49bc0d454524014b76fd66db1aa06e673f03b616f5fdbc59c405178a878
|
sha256: f130964bd4c0907d0af645ba03c8080a914776bfd2e23761a5e22ac3c0c0906a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.3"
|
||||||
media_kit_libs_windows_video:
|
media_kit_libs_windows_video:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_libs_windows_video
|
name: media_kit_libs_windows_video
|
||||||
sha256: "923f068344d7d200184e0aaa2597f3de6c05982a3b1f18035d842ab53f2a1350"
|
sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.8"
|
version: "1.0.9"
|
||||||
media_kit_native_event_loop:
|
media_kit_native_event_loop:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: media_kit_native_event_loop
|
name: media_kit_native_event_loop
|
||||||
sha256: e37ce6fb5fa71b8cf513c6a6cd591367743a342a385e7da621a047dd8ef6f4a4
|
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.7"
|
version: "1.0.8"
|
||||||
media_kit_video:
|
media_kit_video:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: media_kit_video
|
name: media_kit_video
|
||||||
sha256: cd3ab78e7626146f115134b82c4029ac5987ba6351719c9067d86789723e0c12
|
sha256: b1a427f0540c5f052dfab73e4b76a5eb8efa7ebb5d83179cb23fc3932afc315a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.8"
|
version: "1.2.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
10
pubspec.yaml
10
pubspec.yaml
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.8
|
version: 1.0.10
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.6 <3.0.0"
|
sdk: ">=2.19.6 <3.0.0"
|
||||||
@ -70,7 +70,7 @@ dependencies:
|
|||||||
# 解决sliver滑动不同步
|
# 解决sliver滑动不同步
|
||||||
extended_nested_scroll_view: ^6.1.2
|
extended_nested_scroll_view: ^6.1.2
|
||||||
# 上拉加载
|
# 上拉加载
|
||||||
loading_more_list: ^5.0.3
|
loading_more_list: ^6.0.0
|
||||||
# 下拉刷新
|
# 下拉刷新
|
||||||
pull_to_refresh_notification: ^3.0.1
|
pull_to_refresh_notification: ^3.0.1
|
||||||
# 图标
|
# 图标
|
||||||
@ -84,9 +84,9 @@ dependencies:
|
|||||||
crypto: ^3.0.3
|
crypto: ^3.0.3
|
||||||
|
|
||||||
# 视频播放器
|
# 视频播放器
|
||||||
media_kit: ^1.1.7
|
media_kit: ^1.1.8 # Primary package.
|
||||||
media_kit_video: ^1.1.8
|
media_kit_video: ^1.2.1 # For video rendering.
|
||||||
media_kit_libs_video: ^1.0.1
|
media_kit_libs_video: ^1.0.3
|
||||||
|
|
||||||
# 音量、亮度、屏幕控制
|
# 音量、亮度、屏幕控制
|
||||||
flutter_volume_controller: ^1.2.7
|
flutter_volume_controller: ^1.2.7
|
||||||
|
|||||||
Reference in New Issue
Block a user