Compare commits

..

29 Commits

Author SHA1 Message Date
2ece96df21 fix: 合集顺序播放 2023-11-12 14:07:50 +08:00
c11c5695a2 mod: 合集连播 2023-11-08 23:02:13 +08:00
93581c2932 mod: 播放详情页样式 2023-11-03 23:40:47 +08:00
fd43a8cb31 mod: 历史记录搜索 2023-11-03 23:39:21 +08:00
6f34bacb64 mod: 样式 2023-10-15 16:15:16 +08:00
90314f89ed mod: 还原全屏方式 2023-10-14 17:18:00 +08:00
45c53de2c2 mod: 投稿搜索无内容样式 2023-10-14 17:04:35 +08:00
5c07cb4545 mod: 投稿搜索无内容样式 2023-10-14 16:59:03 +08:00
844053b138 mod: 首次登录时自动加载黑名单 2023-10-14 16:46:41 +08:00
6af8b91f63 fix: 黑名单个数 2023-10-14 16:15:14 +08:00
8aa02f7450 mod: 移除黑名单 2023-10-14 16:07:52 +08:00
38a1f2e1f7 fix: 搜索黑名单问题 2023-10-10 00:01:41 +08:00
b2e1d98f51 fix: 搜索黑名单问题 2023-10-10 00:00:38 +08:00
e19cf92992 mod: 首页布局 2023-10-08 23:16:39 +08:00
80e10aeaad mod: 历史记录删除逻辑 2023-10-08 22:43:44 +08:00
b1a9152a49 mod: 搜索结果拉黑用户逻辑 2023-10-08 22:21:40 +08:00
935b7577b3 mod: 历史记录多选选中样式 2023-10-02 22:12:20 +08:00
fd5c4463d2 mod: 历史记录多选选中样式 2023-10-01 14:27:21 +08:00
7fcbe4dd9d feat: 历史记录多选删除 2023-10-01 10:50:45 +08:00
9a0c9f4021 feat: 按照黑名单对搜索结果进行屏蔽 2023-09-27 23:24:55 +08:00
6b3773a074 mod: 个人主页隐藏背景图 2023-09-27 22:22:23 +08:00
7be8ebaa7e mod: 搜索up主投稿 2023-09-27 22:19:49 +08:00
092b1cee3d mod 2023-09-20 22:53:29 +08:00
252f39e8c7 mod 2023-09-17 20:26:00 +08:00
e631ca04a0 mod: 隐藏签名 2023-09-10 14:52:00 +08:00
3665d6a0f6 mod: 个人中心注释 2023-09-10 14:42:14 +08:00
e3c9e8c13b mod: 移除热搜、搜索提示词 2023-09-09 13:33:30 +08:00
39995bae23 mod: 隐藏番剧、直播搜索 2023-09-09 12:18:38 +08:00
f42d0d01ea mod: custom version 2 2023-09-09 11:46:24 +08:00
116 changed files with 2239 additions and 5592 deletions

View File

@ -1,4 +0,0 @@
## 1.0.9
### 修复
+ 长按倍速抬起后未恢复默认倍速

View File

@ -1,24 +0,0 @@
## 1.0.8
直播弹幕、循环播放等功能开发中
### 新功能
+ 用户拉黑功能
+ gif图片保存
+ 删除已看历史记录
### 修复
+ 弹幕数量较少
+ 弹幕屏蔽设置自动记忆
+ 动态页面渲染
+ 用户主页数据错乱
+ 大家都在搜空白
+ 默认自动全屏,顶部操作栏丢失
### 优化
+ 全屏状态栏区域显示优化
+ 图片保存至PiliPala文件夹
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -1,28 +0,0 @@
## 1.0.9
### 新功能
+ 自定义倍速、默认倍速
+ 历史记录搜索
+ 收藏夹搜索
+ 历史记录多选删除
+ 视频循环播放
+ 免登录看1080P
+ 评论区视频链接跳转
+ up主分组
+ up主投稿搜索
### 修复
+ 搜索视频标题乱码
+ 屏幕帧率
+ 动态页面渲染
### 优化
+ 快进手势
+ 视频简介链接匹配
+ 视频全屏时安全区域
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -1,8 +1,6 @@
PODS:
- appscheme (1.0.4):
- Flutter
- auto_orientation (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
@ -14,6 +12,8 @@ PODS:
- FMDB (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):
- Flutter
- media_kit_native_event_loop (1.0.0):
@ -28,8 +28,6 @@ PODS:
- permission_handler_apple (9.1.1):
- Flutter
- ReachabilitySwift (5.0.0)
- saver_gallery (0.0.1):
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
- share_plus (0.0.1):
@ -54,18 +52,17 @@ PODS:
DEPENDENCIES:
- appscheme (from `.symlinks/plugins/appscheme/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- 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_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- 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`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
@ -85,8 +82,6 @@ SPEC REPOS:
EXTERNAL SOURCES:
appscheme:
:path: ".symlinks/plugins/appscheme/ios"
auto_orientation:
:path: ".symlinks/plugins/auto_orientation/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
@ -95,6 +90,8 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_volume_controller:
:path: ".symlinks/plugins/flutter_volume_controller/ios"
image_gallery_saver:
:path: ".symlinks/plugins/image_gallery_saver/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop:
@ -107,8 +104,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
saver_gallery:
:path: ".symlinks/plugins/saver_gallery/ios"
screen_brightness_ios:
:path: ".symlinks/plugins/screen_brightness_ios/ios"
share_plus:
@ -132,12 +127,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
@ -145,7 +140,6 @@ SPEC CHECKSUMS:
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a

View File

@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
// ignore: must_be_immutable
class HtmlRender extends StatelessWidget {
@ -20,47 +20,35 @@ class HtmlRender extends StatelessWidget {
Widget build(BuildContext context) {
return Html(
data: htmlContent,
// tagsList: Html.tags..addAll(["form", "label", "input"]),
onLinkTap: (url, buildContext, attributes) => {},
extensions: [
TagExtension(
tagsToExtend: {"img"},
builder: (extensionContext) {
try {
Map attributes = extensionContext.attributes;
List key = attributes.keys.toList();
String? imgUrl = key.contains('src')
? attributes['src']
: attributes['data-src'];
if (imgUrl!.startsWith('//')) {
imgUrl = 'https:$imgUrl';
}
if (imgUrl.startsWith('http://')) {
imgUrl = imgUrl.replaceAll('http://', 'https://');
}
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
bool isEmote = imgUrl.contains('/emote/');
bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return const SizedBox();
}
// bool inTable =
// extensionContext.element!.previousElementSibling == null ||
// extensionContext.element!.nextElementSibling == null;
// imgUrl = Utils().imageUrl(imgUrl!);
// return Image.network(
// imgUrl,
// width: isEmote ? 22 : null,
// height: isEmote ? 22 : null,
// );
return NetworkImgLayer(
width: isEmote ? 22 : Get.size.width - 24,
height: isEmote ? 22 : 200,
src: imgUrl,
);
} catch (err) {
print(err);
return const SizedBox();
String? imgUrl = extensionContext.attributes['src'];
if (imgUrl!.startsWith('//')) {
imgUrl = 'https:$imgUrl';
}
if (imgUrl.startsWith('http://')) {
imgUrl = imgUrl.replaceAll('http://', 'https://');
}
print(imgUrl);
bool isEmote = imgUrl.contains('/emote/');
bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return SizedBox();
}
// bool inTable =
// extensionContext.element!.previousElementSibling == null ||
// extensionContext.element!.nextElementSibling == null;
// imgUrl = Utils().imageUrl(imgUrl!);
return Image.network(
imgUrl,
width: isEmote ? 22 : null,
height: isEmote ? 22 : null,
);
},
),
],
@ -75,13 +63,11 @@ class HtmlRender extends StatelessWidget {
textDecoration: TextDecoration.none,
),
"p": Style(
margin: Margins.only(bottom: 10),
margin: Margins.only(bottom: 0),
),
"span": Style(
fontSize: FontSize.medium,
height: Height(1.65),
),
"div": Style(height: Height.auto()),
"li > p": Style(
display: Display.inline,
),
@ -89,7 +75,61 @@ class HtmlRender extends StatelessWidget {
padding: HtmlPaddings.only(bottom: 4),
textAlign: TextAlign.justify,
),
"img": Style(margin: Margins.only(top: 4, bottom: 4)),
"image": Style(margin: Margins.only(top: 4, bottom: 4)),
"p > img": Style(margin: Margins.only(top: 4, bottom: 4)),
"code": Style(
backgroundColor: Theme.of(context).colorScheme.onInverseSurface),
"code > span": Style(textAlign: TextAlign.start),
"hr": Style(
margin: Margins.zero,
padding: HtmlPaddings.zero,
border: Border(
top: BorderSide(
width: 1.0,
color:
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
),
),
),
'table': Style(
border: Border(
right: BorderSide(
width: 0.5,
color:
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
),
bottom: BorderSide(
width: 0.5,
color:
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
),
),
),
'tr': Style(
border: Border(
top: BorderSide(
width: 1.0,
color:
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
),
left: BorderSide(
width: 1.0,
color:
Theme.of(context).colorScheme.onBackground.withOpacity(0.3),
),
),
),
'thead': Style(
backgroundColor: Theme.of(context).colorScheme.background,
),
'th': Style(
padding: HtmlPaddings.only(left: 3, right: 3),
),
'td': Style(
padding: HtmlPaddings.all(4.0),
alignment: Alignment.center,
textAlign: TextAlign.center,
),
},
);
}

View File

@ -7,6 +7,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/member/index.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -33,9 +34,10 @@ class VideoCardH extends StatelessWidget {
String heroTag = Utils.makeHeroTag(aid);
return GestureDetector(
onLongPress: () {
if (longPress != null) {
longPress!();
}
// if (longPress != null) {
// longPress!();
// }
MemberController().blockUser(videoItem.mid);
},
// onLongPressEnd: (details) {
// if (longPressEnd != null) {
@ -188,46 +190,7 @@ class VideoContent extends StatelessWidget {
color: Theme.of(context).colorScheme.outline,
),
),
],
),
Row(
children: [
StatView(
theme: 'gray',
view: videoItem.stat.view,
),
const SizedBox(width: 8),
StatDanMu(
theme: 'gray',
danmu: videoItem.stat.danmaku,
),
// Text(
// Utils.dateFormat(videoItem.pubdate!),
// style: TextStyle(
// fontSize: 11,
// color: Theme.of(context).colorScheme.outline),
// )
const Spacer(),
// SizedBox(
// width: 20,
// height: 20,
// child: IconButton(
// tooltip: '稍后再看',
// style: ButtonStyle(
// padding: MaterialStateProperty.all(EdgeInsets.zero),
// ),
// onPressed: () async {
// var res =
// await UserHttp.toViewLater(bvid: videoItem.bvid);
// SmartDialog.showToast(res['msg']);
// },
// icon: Icon(
// Icons.more_vert_outlined,
// color: Theme.of(context).colorScheme.outline,
// size: 14,
// ),
// ),
// ),
if (source == 'normal')
SizedBox(
width: 24,
@ -261,6 +224,20 @@ class VideoContent extends StatelessWidget {
],
),
),
// PopupMenuItem<String>(
// onTap: () async {
// MemberController().blockUser(videoItem.mid);
// },
// value: 'block',
// height: 35,
// child: const Row(
// children: [
// Icon(Icons.block, size: 16),
// SizedBox(width: 6),
// Text('拉黑up', style: TextStyle(fontSize: 13))
// ],
// ),
// ),
],
),
),

View File

@ -5,7 +5,6 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/dynamics.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/common/search_type.dart';
@ -28,11 +27,6 @@ class VideoCardV extends StatelessWidget {
this.longPressEnd,
}) : super(key: key);
bool isStringNumeric(String str) {
RegExp numericRegex = RegExp(r'^\d+$');
return numericRegex.hasMatch(str);
}
void onPushDetail(heroTag) async {
String goto = videoItem.goto;
switch (goto) {
@ -54,7 +48,7 @@ class VideoCardV extends StatelessWidget {
arguments: {
'pic': videoItem.pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
},
),
);
@ -68,39 +62,6 @@ class VideoCardV extends StatelessWidget {
'heroTag': heroTag,
});
break;
// 动态
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: {
'url': uri,
'title': videoItem.title,
'id': videoItem.param.toString(),
'dynamicType': dynamicType
});
break;
default:
SmartDialog.showToast(videoItem.goto);
Get.toNamed(
@ -317,18 +278,23 @@ class VideoStat extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RichText(
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
return Row(
children: [
Text(
'${videoItem.stat.view}观看',
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
children: [
TextSpan(text: '${videoItem.stat.view}观看'),
TextSpan(text: '${videoItem.stat.danmu}弹幕'),
],
),
Text(
'${videoItem.stat.danmu}弹幕',
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
);
}
}

View File

@ -97,9 +97,6 @@ class Api {
// 操作用户关系
static const String relationMod = '/x/relation/modify';
// 相互关系查询
static const String relationSearch = '/x/space/wbi/acc/relation';
// 评论列表
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
static const String replyList = '/x/v2/reply';
@ -129,14 +126,12 @@ class Api {
static const String userFavFolder = '/x/v3/fav/folder/created/list';
/// 收藏夹 详情
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
/// media_id int 收藏夹id
/// pn int 当前页
/// ps int pageSize
/// keyword String 搜索词
/// order String 排序方式 view 最多播放 mtime 最近收藏 pubtime 最近投稿
/// 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
static const String userFavFolderDetail = '/x/v3/fav/resource/list';
@ -172,9 +167,6 @@ class Api {
// 删除某条历史记录
static const String delHistory = '/x/v2/history/delete';
// 搜索历史记录
static const String searchHistory = '/x/web-goblin/history/search';
// 热搜
static const String hotSearchList =
'https://s.search.bilibili.com/main/hotword';
@ -312,19 +304,6 @@ class Api {
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';
// 搜索历史记录
static const String searchHistory = '/x/web-goblin/history/search';
}

View File

@ -2,37 +2,4 @@ class HttpString {
static const String baseUrl = 'https://www.bilibili.com';
static const String baseApiUrl = 'https://api.bilibili.com';
static const String tUrl = 'https://api.vc.bilibili.com';
static const List<int> validateStatusCodes = [
302,
304,
307,
400,
401,
403,
404,
405,
409,
412,
500,
503,
504,
509,
616,
617,
625,
626,
628,
629,
632,
643,
650,
652,
658,
662,
688,
689,
701,
799,
8888
];
}

View File

@ -17,11 +17,17 @@ class DanmakaHttp {
'oid': cid,
'segment_index': segmentIndex,
};
var response = await Request().get(
Api.webDanmaku,
data: params,
extra: {'resType': ResponseType.bytes},
);
return DmSegMobileReply.fromBuffer(response.data);
// 计算函数
Future<DmSegMobileReply> computeTask(Map<String, int> params) async {
var response = await Request().get(
Api.webDanmaku,
data: params,
extra: {'resType': ResponseType.bytes},
);
return DmSegMobileReply.fromBuffer(response.data);
}
return await compute(computeTask, params);
}
}

View File

@ -28,7 +28,6 @@ class DynamicsHttp {
'data': DynamicsDataModel.fromJson(res.data['data']),
};
} catch (err) {
print(err);
return {
'status': false,
'data': [],
@ -86,35 +85,4 @@ 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'],
};
}
}
}

View File

@ -3,101 +3,38 @@ import 'package:html/parser.dart';
import 'package:pilipala/http/index.dart';
class HtmlHttp {
// article
static Future reqHtml(id, dynamicType) async {
var response = await Request().get(
"https://www.bilibili.com/opus/$id",
extra: {'ua': 'pc'},
);
if (response.data.contains('Redirecting to')) {
RegExp regex = RegExp(r'//([\w\.]+)/(\w+)/(\w+)');
Match match = regex.firstMatch(response.data)!;
String matchedString = match.group(0)!;
response = await Request().get(
'https:$matchedString' + '/',
extra: {'ua': 'pc'},
);
}
try {
Document rootTree = parse(response.data);
// log(response.data.body.toString());
Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
// 头像
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
avatar = 'https:${avatar.split('@')[0]}';
String uname = authorHeader
.querySelector('.fixed-author-header__author__name')!
.text;
// 动态详情
Element opusDetail = appDom.querySelector('.opus-detail')!;
// 发布时间
String updateTime =
opusDetail.querySelector('.opus-module-author__pub__text')!.text;
//
String opusContent =
opusDetail.querySelector('.opus-module-content')!.innerHtml;
String test = opusDetail
.querySelector('.horizontal-scroll-album__pic__img')!
.innerHtml;
String commentId = opusDetail
.querySelector('.bili-comment-container')!
.className
.split(' ')[1]
.split('-')[2];
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
return {
'status': true,
'avatar': avatar,
'uname': uname,
'updateTime': updateTime,
'content': test + opusContent,
'commentId': int.parse(commentId)
};
} catch (err) {
print('err: $err');
}
}
// read
static Future reqReadHtml(id, dynamicType) async {
var response = await Request().get(
"https://www.bilibili.com/$dynamicType/$id/",
extra: {'ua': 'pc'},
);
static Future reqHtml(id) async {
var response = await Request().get("https://www.bilibili.com/opus/$id");
Document rootTree = parse(response.data);
Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.up-left')!;
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
// 头像
// String avatar =
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
// print(avatar);
// avatar = 'https:${avatar.split('@')[0]}';
String uname = authorHeader.querySelector('.up-name')!.text.trim();
String avatar = authorHeader.querySelector('img')!.attributes['src']!;
avatar = 'https:${avatar.split('@')[0]}';
String uname =
authorHeader.querySelector('.fixed-author-header__author__name')!.text;
// 动态详情
Element opusDetail = appDom.querySelector('.article-content')!;
Element opusDetail = appDom.querySelector('.opus-detail')!;
// 发布时间
// String updateTime =
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
// print(updateTime);
String updateTime =
opusDetail.querySelector('.opus-module-author__pub__text')!.text;
//
String opusContent =
opusDetail.querySelector('#read-article-holder')!.innerHtml;
RegExp digitRegExp = RegExp(r'\d+');
Iterable<Match> matches = digitRegExp.allMatches(id);
String number = matches.first.group(0)!;
opusDetail.querySelector('.opus-module-content')!.innerHtml;
String commentId = opusDetail
.querySelector('.bili-comment-container')!
.className
.split(' ')[1]
.split('-')[2];
// List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
return {
'status': true,
'avatar': '',
'avatar': avatar,
'uname': uname,
'updateTime': '',
'updateTime': updateTime,
'content': opusContent,
'commentId': int.parse(number)
'commentId': commentId
};
}
}

View File

@ -4,7 +4,6 @@ import 'dart:io';
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
@ -60,6 +59,9 @@ class Request {
static Future<String> getCsrf() async {
var cookies = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
// for (var i in cookies) {
// print(i);
// }
String token = '';
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
@ -89,18 +91,15 @@ class Request {
//响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: const Duration(milliseconds: 12000),
//Http请求头.
headers: {},
headers: {
'keep-alive': true,
'user-agent': headerUa('pc'),
'Accept-Encoding': 'gzip'
},
persistentConnection: true,
);
dio = Dio(options)
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
..httpClientAdapter = Http2Adapter(
ConnectionManager(
idleTimeout: const Duration(milliseconds: 10000),
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
),
);
dio = Dio(options);
//添加拦截器
dio.interceptors.add(ApiInterceptor());
@ -114,26 +113,30 @@ class Request {
dio.transformer = BackgroundTransformer();
dio.options.validateStatus = (status) {
return status! >= 200 && status < 300 ||
HttpString.validateStatusCodes.contains(status);
return status! >= 200 && status < 300 || status == 304 || status == 302;
};
}
/*
* get请求
*/
get(url, {data, options, cancelToken, extra}) async {
get(url, {data, cacheOptions, options, cancelToken, extra}) async {
Response response;
Options options = Options();
Options options;
String ua = 'pc';
ResponseType resType = ResponseType.json;
if (extra != null) {
ua = extra!['ua'] ?? 'pc';
resType = extra!['resType'] ?? ResponseType.json;
if (extra['ua'] != null) {
options.headers = {'user-agent': headerUa(type: extra['ua'])};
}
}
options.responseType = resType;
if (cacheOptions != null) {
cacheOptions.headers = {'user-agent': headerUa(ua)};
options = cacheOptions;
} else {
options = Options();
options.headers = {'user-agent': headerUa(ua)};
options.responseType = resType;
}
try {
response = await dio.get(
url,
@ -200,19 +203,15 @@ class Request {
token.cancel("cancelled");
}
String headerUa({type = 'mob'}) {
String headerUa(ua) {
String headerUa = '';
if (type == 'mob') {
if (Platform.isIOS) {
headerUa =
'Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1';
} else {
headerUa =
'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
}
if (ua == 'mob') {
headerUa = Platform.isIOS
? 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'
: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
} else {
headerUa =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15';
}
return headerUa;
}

View File

@ -46,10 +46,7 @@ class ApiInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误
// handler.next(err);
SmartDialog.showToast(
await dioError(err),
displayType: SmartToastType.onlyRefresh,
);
SmartDialog.showToast(await dioError(err));
super.onError(err, handler);
}

View File

@ -1,9 +1,7 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/models/follow/result.dart';
import 'package:pilipala/models/member/archive.dart';
import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/models/member/tags.dart';
import 'package:pilipala/utils/wbi_sign.dart';
class MemberHttp {
@ -20,7 +18,6 @@ class MemberHttp {
var res = await Request().get(
Api.memberInfo,
data: params,
extra: {'ua': 'pc'},
);
if (res.data['code'] == 0) {
return {
@ -86,7 +83,6 @@ class MemberHttp {
var res = await Request().get(
Api.memberArchive,
data: params,
extra: {'ua': 'pc'},
);
if (res.data['code'] == 0) {
return {
@ -146,73 +142,4 @@ class MemberHttp {
};
}
}
// 查询分组
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'],
};
}
}
}

View File

@ -26,7 +26,7 @@ class ReplyHttp {
Map errMap = {
-400: '请求错误',
-404: '无此项',
12002: '当前页面评论功能已关闭',
12002: '当前页面评论功能已关闭"',
12009: '评论主体的type不合法',
12061: 'UP主已关闭评论区',
};

View File

@ -39,16 +39,25 @@ class SearchHttp {
static Future searchSuggest({required term}) async {
var res = await Request().get(Api.serachSuggest,
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
if (res.data['code'] == 0) {
if (res.data['result'] is Map) {
res.data['result']['term'] = term;
if (res.data is String) {
Map<String, dynamic> resultMap = json.decode(res.data);
if (resultMap['code'] == 0) {
if (resultMap['result'] is Map) {
resultMap['result']['term'] = term;
}
return {
'status': true,
'data': resultMap['result'] is Map
? SearchSuggestModel.fromJson(resultMap['result'])
: [],
};
} else {
return {
'status': false,
'data': [],
'msg': '请求错误 🙅',
};
}
return {
'status': true,
'data': res.data['result'] is Map
? SearchSuggestModel.fromJson(res.data['result'])
: [],
};
} else {
return {
'status': false,

View File

@ -8,7 +8,6 @@ import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/user/history.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/utils/wbi_sign.dart';
class UserHttp {
static Future<dynamic> userStat({required int mid}) async {
@ -71,15 +70,14 @@ class UserHttp {
required int pn,
required int ps,
String keyword = '',
String order = 'mtime',
int type = 0}) async {
String order = 'mtime'}) async {
var res = await Request().get(Api.userFavFolderDetail, data: {
'media_id': mediaId,
'pn': pn,
'ps': ps,
'keyword': keyword,
'order': order,
'type': type,
'type': 0,
'tid': 0,
'platform': 'web'
});
@ -251,31 +249,6 @@ class UserHttp {
}
}
// 相互关系查询
static Future relationSearch(int mid) async {
Map params = await WbiSign().makSign({
'mid': mid,
'token': '',
'platform': 'web',
'web_location': 1550101,
});
var res = await Request().get(
Api.relationSearch,
data: {
'mid': mid,
'w_rid': params['w_rid'],
'wts': params['wts'],
},
);
if (res.data['code'] == 0) {
// relation 主动状态
// 被动状态
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 搜索历史记录
static Future searchHistory(
{required int pn, required String keyword}) async {

View File

@ -22,7 +22,6 @@ class VideoHttp {
static Box setting = GStrorage.setting;
static bool enableRcmdDynamic =
setting.get(SettingBoxKey.enableRcmdDynamic, defaultValue: true);
static Box userInfoCache = GStrorage.userInfo;
// 首页推荐视频
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
@ -134,11 +133,6 @@ class VideoHttp {
// 'platform': '',
// 'high_quality': ''
};
// 免登录查看1080p
if (userInfoCache.get('userInfoCache') == null &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
try {
var res = await Request().get(Api.videoUrl, data: data);
if (res.data['code'] == 0) {

View File

@ -1,7 +1,4 @@
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@ -64,23 +61,6 @@ class MyApp extends StatelessWidget {
double textScale =
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(
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
ColorScheme? lightColorScheme;

View File

@ -244,9 +244,7 @@ class Vote {
choiceCnt = json['choice_cnt'];
share = json['share'];
defaultShare = json['default_share'];
endTime = json['end_time'] is int
? json['end_time']
: int.parse(json['end_time']);
endTime = json['end_time'];
joinNum = json['join_num'];
status = json['status'];
type = json['type'];

View File

@ -8,7 +8,7 @@ class FollowDataModel {
List<FollowItemModel>? list;
FollowDataModel.fromJson(Map<String, dynamic> json) {
total = json['total'] ?? 0;
total = json['total'];
list = json['list']
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
.toList();
@ -19,7 +19,7 @@ class FollowItemModel {
FollowItemModel({
this.mid,
this.attribute,
// this.mtime,
this.mtime,
this.tag,
this.special,
this.uname,
@ -30,7 +30,7 @@ class FollowItemModel {
int? mid;
int? attribute;
// int? mtime;
int? mtime;
List? tag;
int? special;
String? uname;
@ -41,7 +41,7 @@ class FollowItemModel {
FollowItemModel.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
attribute = json['attribute'];
// mtime = json['mtime'];
mtime = json['mtime'];
tag = json['tag'];
special = json['special'];
uname = json['uname'];

View File

@ -1,23 +0,0 @@
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;
}
}

View File

@ -184,7 +184,7 @@ class AboutController extends GetxController {
// 获取远程版本
Future getRemoteApp() async {
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
var result = await Request().get(Api.latestApp);
data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data;
remoteVersion.value = data.tagName!;

View File

@ -22,7 +22,7 @@ class BangumiIntroController extends GetxController {
? int.parse(Get.parameters['seasonId']!)
: null;
var epId = Get.parameters['epId'] != null
? int.tryParse(Get.parameters['epId']!)
? int.parse(Get.parameters['epId']!)
: null;
// 是否预渲染 骨架屏
@ -258,7 +258,7 @@ class BangumiIntroController extends GetxController {
VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
videoDetailCtr.bvid = bvid;
videoDetailCtr.cid.value = cid;
videoDetailCtr.cid = cid;
videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.queryVideoUrl();
// 重新请求评论

View File

@ -34,12 +34,10 @@ class BangumiIntroPanel extends StatefulWidget {
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
with AutomaticKeepAliveClientMixin {
late BangumiIntroController bangumiIntroController;
late VideoDetailController videoDetailCtr;
final BangumiIntroController bangumiIntroController =
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
BangumiInfoModel? bangumiDetail;
late Future _futureBuilderFuture;
late int cid;
late String heroTag;
// 添加页面缓存
@override
@ -48,19 +46,10 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
@override
void 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) {
bangumiDetail = value;
});
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
videoDetailCtr.cid.listen((p0) {
print('🐶🐶$p0');
cid = p0;
setState(() {});
});
}
@override
@ -72,25 +61,22 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data['status']) {
// 请求成功
return BangumiInfo(
loadingStatus: false,
bangumiDetail: bangumiDetail,
cid: cid,
);
} else {
// 请求错误
// return HttpError(
// errMsg: snapshot.data['msg'],
// fn: () => Get.back(),
// );
return SizedBox();
return HttpError(
errMsg: snapshot.data['msg'],
fn: () => Get.back(),
);
}
} else {
return BangumiInfo(
loadingStatus: true,
bangumiDetail: bangumiDetail,
cid: cid,
cid: widget.cid,
);
}
},
@ -131,12 +117,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
bangumiItem = bangumiIntroController.bangumiItem;
sheetHeight = localCache.get('sheetHeight');
cid = widget.cid!;
print('cid: $cid');
videoDetailCtr.cid.listen((p0) {
cid = p0;
print('cid: $cid');
setState(() {});
});
}
// 收藏
@ -280,15 +260,9 @@ class _BangumiInfoState extends State<BangumiInfo> {
children: [
Text(
!widget.loadingStatus
? (widget.bangumiDetail!.areas!
.isNotEmpty
? widget.bangumiDetail!.areas!
.first['name']
: '')
: (bangumiItem!.areas!.isNotEmpty
? bangumiItem!
.areas!.first['name']
: ''),
? widget.bangumiDetail!.areas!
.first['name']
: bangumiItem!.areas!.first['name'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,

View File

@ -1,9 +1,7 @@
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/models/bangumi/info.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/utils/storage.dart';
class BangumiPanel extends StatefulWidget {
@ -32,28 +30,16 @@ class _BangumiPanelState extends State<BangumiPanel> {
dynamic userInfo;
// 默认未开通
int vipStatus = 0;
late int cid;
String heroTag = Get.arguments['heroTag'];
late final VideoDetailController videoDetailCtr;
@override
void initState() {
super.initState();
cid = widget.cid!;
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
scrollToIndex();
userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null) {
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

View File

@ -62,7 +62,7 @@ class BangumiCardV extends StatelessWidget {
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);

View File

@ -10,32 +10,22 @@ class PlDanmakuController {
// 按 6min 分段
int segCount = 0;
List<DmSegMobileReply> dmSegList = [];
int currentSegIndex = 1;
int currentSegIndex = 0;
int currentDmIndex = 0;
void calcSegment() {
dmSegList.clear();
// 视频分段数
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
dmSegList = List<DmSegMobileReply>.generate(
segCount < 1 ? 1 : segCount, (index) => DmSegMobileReply());
// 当前分段
try {
currentSegIndex =
(playerController.position.value.inSeconds / (60 * 6)).ceil();
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
} catch (_) {}
}
Future<List<DmSegMobileReply>> queryDanmaku() async {
// dmSegList.clear();
DmSegMobileReply result =
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: currentSegIndex);
if (result.elems.isNotEmpty) {
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
// dmSegList.add(result);
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
dmSegList[currentSegIndex - 1] = result;
dmSegList.clear();
for (int segIndex = 1; segIndex <= segCount; segIndex++) {
DmSegMobileReply result =
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segIndex);
if (result.elems.isNotEmpty) {
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
dmSegList.add(result);
}
}
if (dmSegList.isNotEmpty) {
findClosestPositionIndex(playerController.position.value.inMilliseconds);

View File

@ -1,4 +1,3 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@ -86,20 +85,12 @@ class _PlDanmakuState extends State<PlDanmaku> {
_controller!.onResume();
danmuPlayStatus = true;
}
if (!playerController.isOpenDanmu.value) {
return;
}
PlDanmakuController ctr = _plDanmakuController;
int currentPosition = position.inMilliseconds;
blockTypes = playerController.blockTypes;
// 根据position判断是否有已缓存弹幕。没有则请求对应段
int segIndex = (currentPosition / (6 * 60 * 1000)).ceil();
segIndex = segIndex < 1 ? 1 : segIndex;
if (ctr.dmSegList[segIndex - 1].elems.isEmpty) {
ctr.currentSegIndex = segIndex;
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
ctr.queryDanmaku();
});
if (!playerController.isOpenDanmu.value) {
return;
}
// 超出分段数返回
if (ctr.currentSegIndex >= ctr.dmSegList.length) {
@ -149,30 +140,23 @@ class _PlDanmakuState extends State<PlDanmaku> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, box) {
double initDuration = box.maxWidth / 12;
return Obx(
() => AnimatedOpacity(
opacity: playerController.isOpenDanmu.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuView(
createdController: (DanmakuController e) async {
widget.playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: 15 * fontSizeVal,
area: showArea,
opacity: opacityVal,
hideTop: blockTypes.contains(5),
hideScroll: blockTypes.contains(2),
hideBottom: blockTypes.contains(4),
duration: initDuration /
(danmakuSpeedVal * widget.playerController.playbackSpeed),
),
statusChanged: (isPlaying) {},
return Obx(
() => AnimatedOpacity(
opacity: playerController.isOpenDanmu.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuView(
createdController: (DanmakuController e) async {
widget.playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: 15 * fontSizeVal,
area: showArea,
opacity: opacityVal,
duration: danmakuSpeedVal * widget.playerController.playbackSpeed,
),
statusChanged: (isPlaying) {},
),
);
});
),
);
}
}

View File

@ -149,30 +149,10 @@ class DynamicsController extends GetxController {
case 'DYNAMIC_TYPE_ARTICLE':
String title = item.modules.moduleDynamic.major.opus.title;
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
if (url.contains('opus') || url.contains('read')) {
RegExp digitRegExp = RegExp(r'\d+');
Iterable<Match> matches = digitRegExp.allMatches(url);
String number = matches.first.group(0)!;
if (url.contains('read')) {
number = 'cv$number';
}
Get.toNamed('/htmlRender', parameters: {
'url': url.startsWith('//') ? url.split('//').last : url,
'title': title,
'id': number,
'dynamicType': url.split('//').last.split('/')[1]
});
} else {
Get.toNamed(
'/webview',
parameters: {
'url': 'https:$url',
'type': 'note',
'pageTitle': title
},
);
}
Get.toNamed(
'/webview',
parameters: {'url': 'https:$url', 'type': 'note', 'pageTitle': title},
);
break;
case 'DYNAMIC_TYPE_PGC':
print('番剧');
@ -234,7 +214,7 @@ class DynamicsController extends GetxController {
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/reply.dart';
@ -18,7 +17,6 @@ class DynamicDetailController extends GetxController {
RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController();
ReplySortType _sortType = ReplySortType.time;
RxString sortTypeTitle = ReplySortType.time.titles.obs;

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/http_error.dart';
@ -10,10 +9,7 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/dynamics/deatil/index.dart';
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';
import '../widgets/dynamic_panel.dart';
@ -25,18 +21,15 @@ class DynamicDetailPage extends StatefulWidget {
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
}
class _DynamicDetailPageState extends State<DynamicDetailPage>
with TickerProviderStateMixin {
late DynamicDetailController _dynamicDetailController;
late AnimationController fabAnimationCtr;
class _DynamicDetailPageState extends State<DynamicDetailPage> {
late DynamicDetailController? _dynamicDetailController;
Future? _futureBuilderFuture;
late StreamController<bool> titleStreamC; // appBar title
late ScrollController scrollController;
final ScrollController scrollController = ScrollController();
bool _visibleTitle = false;
String? action;
// 回复类型
late int type;
bool _isFabVisible = true;
@override
void initState() {
@ -57,30 +50,37 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
}
} catch (_) {}
}
int commentType = 11;
try {
commentType = Get.arguments['item'].basic!['comment_type'];
} catch (_) {}
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
type = (commentType == 0) ? 11 : commentType;
action =
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
_dynamicDetailController =
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
_futureBuilderFuture = _dynamicDetailController.queryReplyList();
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
titleStreamC = StreamController<bool>();
scrollController.addListener(_listen);
if (action == 'comment') {
_visibleTitle = true;
titleStreamC.add(true);
}
}
fabAnimationCtr = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
fabAnimationCtr.forward();
// 滚动事件监听
scrollListener();
void _listen() async {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
_dynamicDetailController!.queryReplyList(reqType: 'onLoad');
});
}
if (scrollController.offset > 55 && !_visibleTitle) {
_visibleTitle = true;
titleStreamC.add(true);
} else if (scrollController.offset <= 55 && _visibleTitle) {
_visibleTitle = false;
titleStreamC.add(false);
}
}
void replyReply(replyItem) {
@ -107,58 +107,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
);
}
void scrollListener() {
scrollController = _dynamicDetailController.scrollController;
scrollController.addListener(
() {
// 分页加载
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
_dynamicDetailController.queryReplyList(reqType: 'onLoad');
});
}
// 标题
if (scrollController.offset > 55 && !_visibleTitle) {
_visibleTitle = true;
titleStreamC.add(true);
} else if (scrollController.offset <= 55 && _visibleTitle) {
_visibleTitle = false;
titleStreamC.add(false);
}
// fab按钮
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
_showFab();
} else if (direction == ScrollDirection.reverse) {
_hideFab();
}
},
);
}
void _showFab() {
if (!_isFabVisible) {
_isFabVisible = true;
fabAnimationCtr.forward();
}
}
void _hideFab() {
if (_isFabVisible) {
_isFabVisible = false;
fabAnimationCtr.reverse();
}
}
@override
void dispose() {
scrollController.removeListener(() {});
fabAnimationCtr.dispose();
scrollController.dispose();
super.dispose();
}
@ -177,7 +128,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: AuthorPanel(item: _dynamicDetailController.item),
child: author(_dynamicDetailController!.item, context),
);
},
),
@ -185,206 +136,155 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
),
body: RefreshIndicator(
onRefresh: () async {
await _dynamicDetailController.queryReplyList();
await _dynamicDetailController!.queryReplyList();
},
child: Stack(
children: [
CustomScrollView(
controller: scrollController,
slivers: [
if (action != 'comment')
SliverToBoxAdapter(
child: DynamicPanel(
item: _dynamicDetailController.item,
source: 'detail',
child: CustomScrollView(
controller: scrollController,
slivers: [
if (action != 'comment')
SliverToBoxAdapter(
child: DynamicPanel(
item: _dynamicDetailController!.item,
source: 'detail',
),
),
SliverPersistentHeader(
delegate: _MySliverPersistentHeaderDelegate(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
width: 0.6,
color: Theme.of(context).dividerColor.withOpacity(0.05),
),
),
),
SliverPersistentHeader(
delegate: _MySliverPersistentHeaderDelegate(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
width: 0.6,
color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
height: 45,
padding: const EdgeInsets.only(left: 12, right: 6),
child: Row(
children: [
Obx(
() => AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
'${_dynamicDetailController!.acount.value}',
key: ValueKey<int>(
_dynamicDetailController!.acount.value),
),
),
),
height: 45,
padding: const EdgeInsets.only(left: 12, right: 6),
child: Row(
children: [
Obx(
() => AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
'${_dynamicDetailController.acount.value}',
key: ValueKey<int>(
_dynamicDetailController.acount.value),
),
),
),
const Text('条回复'),
const Spacer(),
SizedBox(
height: 35,
child: TextButton.icon(
onPressed: () =>
_dynamicDetailController.queryBySort(),
icon: const Icon(Icons.sort, size: 16),
label: Obx(() => Text(
_dynamicDetailController
.sortTypeLabel.value,
style: const TextStyle(fontSize: 13),
)),
),
)
],
),
),
const Text('条回复'),
const Spacer(),
SizedBox(
height: 35,
child: TextButton.icon(
onPressed: () =>
_dynamicDetailController!.queryBySort(),
icon: const Icon(Icons.sort, size: 16),
label: Obx(() => Text(
_dynamicDetailController!.sortTypeLabel.value,
style: const TextStyle(fontSize: 13),
)),
),
)
],
),
pinned: true,
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (snapshot.data['status']) {
// 请求成功
return Obx(
() => _dynamicDetailController.replyList.isEmpty &&
_dynamicDetailController.isLoadingMore
? SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return const VideoReplySkeleton();
}, childCount: 8),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index ==
_dynamicDetailController
.replyList.length) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context)
.padding
.bottom),
height: MediaQuery.of(context)
.padding
.bottom +
100,
child: Center(
child: Obx(
() => Text(
_dynamicDetailController
.noMore.value,
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
);
} else {
return ReplyItem(
replyItem: _dynamicDetailController
.replyList[index],
showReplyRow: true,
replyLevel: '1',
replyReply: (replyItem) =>
replyReply(replyItem),
replyType: ReplyType.values[type],
addReply: (replyItem) {
_dynamicDetailController
.replyList[index].replies!
.add(replyItem);
},
);
}
},
childCount: _dynamicDetailController
.replyList.length +
1,
),
),
);
} else {
// 请求错误
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoReplySkeleton();
}, childCount: 8),
);
}
},
)
],
),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return VideoReplyNewDialog(
oid: _dynamicDetailController.oid ??
IdUtils.bv2av(Get.parameters['bvid']!),
root: 0,
parent: 0,
replyType: ReplyType.values[type],
);
},
).then(
(value) => {
// 完成评论,数据添加
if (value != null && value['data'] != null)
{
_dynamicDetailController.replyList
.add(value['data']),
_dynamicDetailController.acount.value++
}
},
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
),
),
pinned: true,
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (snapshot.data['status']) {
// 请求成功
return Obx(
() => _dynamicDetailController!.replyList.isEmpty &&
_dynamicDetailController!.isLoadingMore
? SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return const VideoReplySkeleton();
}, childCount: 8),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index ==
_dynamicDetailController!
.replyList.length) {
return Container(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context)
.padding
.bottom),
height: MediaQuery.of(context)
.padding
.bottom +
100,
child: Center(
child: Obx(
() => Text(
_dynamicDetailController!
.noMore.value,
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
);
} else {
return ReplyItem(
replyItem: _dynamicDetailController!
.replyList[index],
showReplyRow: true,
replyLevel: '1',
replyReply: (replyItem) =>
replyReply(replyItem),
replyType: ReplyType.values[type],
addReply: (replyItem) {
_dynamicDetailController!
.replyList[index].replies!
.add(replyItem);
},
);
}
},
childCount:
_dynamicDetailController!.replyList.length +
1,
),
),
);
} else {
// 请求错误
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoReplySkeleton();
}, childCount: 8),
);
}
},
)
],
),
),

View File

@ -1,8 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/search.dart';
/// TODO 点击跳转
Widget addWidget(item, context, type, {floor = 1}) {
@ -22,27 +19,8 @@ Widget addWidget(item, context, type, {floor = 1}) {
: Theme.of(context).colorScheme.background;
switch (type) {
case 'ADDITIONAL_TYPE_UGC':
// 转发的投稿
return InkWell(
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.");
}
},
onTap: () {},
child: Container(
padding:
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
@ -83,111 +61,101 @@ Widget addWidget(item, context, type, {floor = 1}) {
);
case 'ADDITIONAL_TYPE_RESERVE':
return dynamicProperty[type].state != -1
? dynamicProperty[type].title != null
? Padding(
padding: const EdgeInsets.only(top: 8),
child: InkWell(
onTap: () {},
child: Container(
width: double.infinity,
padding: const EdgeInsets.only(
left: 12, top: 10, right: 12, bottom: 10),
color: bgColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
dynamicProperty[type].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 1),
Text.rich(
TextSpan(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
children: [
if (dynamicProperty[type].desc1 != null)
TextSpan(
text:
dynamicProperty[type].desc1['text']),
const TextSpan(text: ' '),
if (dynamicProperty[type].desc2 != null)
TextSpan(
text:
dynamicProperty[type].desc2['text']),
],
),
)
],
? Padding(
padding: const EdgeInsets.only(top: 8),
child: InkWell(
onTap: () {},
child: Container(
width: double.infinity,
padding: const EdgeInsets.only(
left: 12, top: 10, right: 12, bottom: 10),
color: bgColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
dynamicProperty[type].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
// TextButton(onPressed: () {}, child: Text('123'))
),
const SizedBox(height: 1),
Text.rich(
TextSpan(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
children: [
TextSpan(text: dynamicProperty[type].desc1['text']),
const TextSpan(text: ' '),
TextSpan(text: dynamicProperty[type].desc2['text']),
],
),
)
],
),
)
: const SizedBox()
// TextButton(onPressed: () {}, child: Text('123'))
),
),
)
: const SizedBox();
case 'ADDITIONAL_TYPE_GOODS':
// 商品
return const SizedBox();
// return Padding(
// padding: const EdgeInsets.only(top: 6),
// child: InkWell(
// onTap: () {},
// child: Container(
// padding:
// const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
// decoration: BoxDecoration(
// color: bgColor,
// borderRadius: const BorderRadius.all(Radius.circular(6)),
// ),
// child: Row(
// children: [
// NetworkImgLayer(
// width: 75,
// height: 75,
// src: dynamicProperty[type].items.first.cover,
// ),
// const SizedBox(width: 10),
// Expanded(
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Text(
// dynamicProperty[type].items.first.name,
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// ),
// Text(
// dynamicProperty[type].items.first.brief,
// maxLines: 1,
// style: TextStyle(
// color: Theme.of(context).colorScheme.outline,
// fontSize: Theme.of(context)
// .textTheme
// .labelMedium!
// .fontSize,
// ),
// ),
// const SizedBox(height: 2),
// Text(
// dynamicProperty[type].items.first.price,
// style: TextStyle(
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// ],
// ),
// ),
// ],
// ),
// ),
// ),);
return Padding(
padding: const EdgeInsets.only(top: 6),
child: InkWell(
onTap: () {},
child: Container(
padding:
const EdgeInsets.only(left: 12, top: 8, right: 12, bottom: 8),
decoration: BoxDecoration(
color: bgColor,
borderRadius: const BorderRadius.all(Radius.circular(6)),
),
child: Row(
children: [
NetworkImgLayer(
width: 75,
height: 75,
src: dynamicProperty[type].items.first.cover,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
dynamicProperty[type].items.first.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
Text(
dynamicProperty[type].items.first.brief,
maxLines: 1,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize,
),
),
const SizedBox(height: 2),
Text(
dynamicProperty[type].items.first.price,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
],
),
),
));
case 'ADDITIONAL_TYPE_MATCH':
return const SizedBox();
case 'ADDITIONAL_TYPE_COMMON':

View File

@ -1,159 +1,65 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/utils.dart';
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);
return Row(
children: [
GestureDetector(
onTap: () {
feedBack();
Get.toNamed(
'/member?mid=${item.modules.moduleAuthor.mid}',
arguments: {
'face': item.modules.moduleAuthor.face,
'heroTag': heroTag
},
);
},
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.moduleAuthor.face,
),
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.modules.moduleAuthor.name,
style: TextStyle(
color: item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip['status'] > 0
? const Color.fromARGB(255, 251, 100, 163)
: Theme.of(context).colorScheme.onBackground,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
DefaultTextStyle.merge(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
),
child: Row(
children: [
Text(item.modules.moduleAuthor.pubTime),
if (item.modules.moduleAuthor.pubTime != '' &&
item.modules.moduleAuthor.pubAction != '')
const Text(' '),
Text(item.modules.moduleAuthor.pubAction),
],
),
)
],
),
const Spacer(),
if (item.type == 'DYNAMIC_TYPE_AV')
SizedBox(
width: 32,
height: 32,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: item);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
),
],
);
}
}
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()}');
}
Widget author(item, context) {
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
return Row(
children: [
GestureDetector(
onTap: () {
feedBack();
Get.toNamed(
'/member?mid=${item.modules.moduleAuthor.mid}',
arguments: {
'face': item.modules.moduleAuthor.face,
'heroTag': heroTag
},
minLeadingWidth: 0,
// dense: true,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title: Text(
'稍后再看',
style: Theme.of(context).textTheme.titleSmall,
);
},
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.moduleAuthor.face,
),
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.modules.moduleAuthor.name,
style: TextStyle(
color: item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip['status'] > 0
? const Color.fromARGB(255, 251, 100, 163)
: Theme.of(context).colorScheme.onBackground,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
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,
DefaultTextStyle.merge(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
),
),
child: Row(
children: [
Text(item.modules.moduleAuthor.pubTime),
if (item.modules.moduleAuthor.pubTime != '' &&
item.modules.moduleAuthor.pubAction != '')
const Text(' '),
Text(item.modules.moduleAuthor.pubAction),
],
),
)
],
),
);
}
],
);
}

View File

@ -1,183 +1,42 @@
// 内容
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';
// 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(color: Theme.of(context).colorScheme.primary);
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.item.modules.moduleDynamic.topic != null) ...[
GestureDetector(
child: Text(
'#${widget.item.modules.moduleDynamic.topic.name}',
style: authorStyle,
),
),
],
IgnorePointer(
// 禁用SelectableRegion的触摸交互功能
ignoring: widget.source == 'detail' ? false : true,
child: SelectableRegion(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text.rich(
/// fix 默认20px高度
style: const TextStyle(height: 0),
richNode(widget.item, context),
maxLines: widget.source == 'detail' ? 999 : 3,
overflow: TextOverflow.ellipsis,
),
Widget content(item, context, source) {
TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary);
return Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(12, 0, 12, 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.modules.moduleDynamic.topic != null) ...[
GestureDetector(
child: Text(
'#${item.modules.moduleDynamic.topic.name}',
style: authorStyle,
),
),
if (hasPics) ...[
Text.rich(picsNodes()),
]
],
),
);
}
IgnorePointer(
// 禁用SelectableRegion的触摸交互功能
ignoring: source == 'detail' ? false : true,
child: SelectableRegion(
magnifierConfiguration: const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text.rich(
/// fix 默认20px高度
style: const TextStyle(height: 0),
richNode(item, context),
maxLines: source == 'detail' ? 999 : 3,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
);
}

View File

@ -39,11 +39,11 @@ class DynamicPanel extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: AuthorPanel(item: item),
child: author(item, context),
),
if (item!.modules!.moduleDynamic!.desc != null ||
item!.modules!.moduleDynamic!.major != null)
Content(item: item, source: source),
content(item, context, source),
forWard(item, context, _dynamicsController, source),
const SizedBox(height: 2),
if (source == null) ActionPanel(item: item),

View File

@ -27,9 +27,8 @@ InlineSpan richNode(item, context) {
} else {
for (var i in richTextNodes) {
/// fix 渲染专栏时内容会重复
// if (item.modules.moduleDynamic.major.opus.title == null &&
// i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
if (i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
if (item.modules.moduleDynamic.major.opus.title == null &&
i.type == 'RICH_TEXT_NODE_TYPE_TEXT') {
spanChilds.add(
TextSpan(text: i.origText, style: const TextStyle(height: 1.65)));
}
@ -110,18 +109,16 @@ InlineSpan richNode(item, context) {
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
onTap: () {
try {
String dynamicId = item.basic['comment_id_str'];
Get.toNamed(
'/webview',
parameters: {
'url':
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=$dynamicId&isWeb=1',
'type': 'vote',
'pageTitle': '投票'
},
);
} catch (_) {}
String dynamicId = item.basic['comment_id_str'];
Get.toNamed(
'/webview',
parameters: {
'url':
'https://t.bilibili.com/vote/h5/index/#/result?vote_id=${i.rid}&dynamic_id=$dynamicId&isWeb=1',
'type': 'vote',
'pageTitle': '投票'
},
);
},
child: Text(
'投票:${i.text}',
@ -196,118 +193,116 @@ InlineSpan richNode(item, context) {
);
}
}
// if (contentType == 'major' &&
// item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
// // 图片可能跟其他widget重复渲染
// List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
// int len = pics.length;
// List<String> picList = [];
if (contentType == 'major' &&
item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {
// 图片可能跟其他widget重复渲染
List<OpusPicsModel> pics = item.modules.moduleDynamic.major.opus.pics;
int len = pics.length;
List<String> picList = [];
// if (len == 1) {
// OpusPicsModel pictureItem = pics.first;
// picList.add(pictureItem.url!);
// spanChilds.add(const TextSpan(text: '\n'));
// spanChilds.add(
// WidgetSpan(
// child: LayoutBuilder(
// builder: (context, BoxConstraints box) {
// return GestureDetector(
// onTap: () {
// showDialog(
// useSafeArea: false,
// context: context,
// builder: (context) {
// return ImagePreview(initialPage: 0, imgList: picList);
// },
// );
// },
// child: Padding(
// padding: const EdgeInsets.only(top: 4),
// child: NetworkImgLayer(
// src: pictureItem.url,
// width: box.maxWidth / 2,
// height: box.maxWidth *
// 0.5 *
// (pictureItem.height != null &&
// pictureItem.width != null
// ? pictureItem.height! / pictureItem.width!
// : 1),
// ),
// ),
// );
// },
// ),
// ),
// );
// }
// if (len > 1) {
// List<Widget> list = [];
// for (var i = 0; i < len; i++) {
// picList.add(pics[i].url!);
// list.add(
// LayoutBuilder(
// builder: (context, BoxConstraints box) {
// return GestureDetector(
// onTap: () {
// showDialog(
// useSafeArea: false,
// context: context,
// builder: (context) {
// return ImagePreview(initialPage: i, imgList: picList);
// },
// );
// },
// child: NetworkImgLayer(
// src: pics[i].url,
// width: box.maxWidth,
// height: box.maxWidth,
// ),
// );
// },
// ),
// );
// }
// spanChilds.add(
// WidgetSpan(
// child: LayoutBuilder(
// builder: (context, BoxConstraints box) {
// double maxWidth = box.maxWidth;
// double crossCount = len < 3 ? 2 : 3;
// double height = maxWidth /
// crossCount *
// (len % crossCount == 0
// ? len ~/ crossCount
// : len ~/ crossCount + 1) +
// 6;
// return Container(
// padding: const EdgeInsets.only(top: 6),
// height: height,
// child: GridView.count(
// padding: EdgeInsets.zero,
// physics: const NeverScrollableScrollPhysics(),
// crossAxisCount: crossCount.toInt(),
// mainAxisSpacing: 4.0,
// crossAxisSpacing: 4.0,
// childAspectRatio: 1,
// children: list,
// ),
// );
// },
// ),
// ),
// );
// }
// spanChilds.add(
// WidgetSpan(
// child: NetworkImgLayer(
// src: pics.first.url,
// type: 'emote',
// width: 100,
// height: 200,
// ),
// ),
// );
// }
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! /
pictureItem.width!,
),
),
);
},
),
),
);
}
if (len > 1) {
List<Widget> list = [];
for (var i = 0; i < len; i++) {
picList.add(pics[i].url!);
list.add(
LayoutBuilder(
builder: (context, BoxConstraints box) {
return GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(initialPage: i, imgList: picList);
},
);
},
child: NetworkImgLayer(
src: pics[i].url,
width: box.maxWidth,
height: box.maxWidth,
),
);
},
),
);
}
spanChilds.add(
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth;
double crossCount = len < 3 ? 2 : 3;
double height = maxWidth /
crossCount *
(len % crossCount == 0
? len ~/ crossCount
: len ~/ crossCount + 1) +
6;
return Container(
padding: const EdgeInsets.only(top: 6),
height: height,
child: GridView.count(
padding: EdgeInsets.zero,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: crossCount.toInt(),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
childAspectRatio: 1,
children: list,
),
);
},
),
),
);
}
// spanChilds.add(
// WidgetSpan(
// child: NetworkImgLayer(
// src: pics.first.url,
// type: 'emote',
// width: 100,
// height: 200,
// ),
// ),
// );
}
return TextSpan(
children: spanChilds,
);

View File

@ -91,10 +91,7 @@ class _UpPanelState extends State<UpPanel> {
),
Material(
child: InkWell(
onTap: () => {
feedBack(),
Get.toNamed('/follow?mid=${userInfo.mid}')
},
onTap: () => {feedBack(), Get.toNamed('/follow')},
child: Container(
height: 100,
padding: const EdgeInsets.only(left: 10, right: 10),

View File

@ -16,16 +16,13 @@ class FansPage extends StatefulWidget {
}
class _FansPageState extends State<FansPage> {
late String mid;
late FansController _fansController;
final FansController _fansController = Get.put(FansController());
final ScrollController scrollController = ScrollController();
Future? _futureBuilderFuture;
@override
void initState() {
super.initState();
mid = Get.parameters['mid']!;
_fansController = Get.put(FansController(), tag: mid);
_futureBuilderFuture = _fansController.queryFans('init');
scrollController.addListener(
() async {

View File

@ -44,14 +44,6 @@ class _FavPageState extends State<FavPage> {
'我的收藏',
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(
future: _futureBuilderFuture,

View File

@ -92,18 +92,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
);
},
),
actions: [
IconButton(
onPressed: () => Get.toNamed(
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'),
icon: const Icon(Icons.search_outlined),
),
// IconButton(
// onPressed: () {},
// icon: const Icon(Icons.more_vert),
// ),
const SizedBox(width: 6),
],
// actions: [
// IconButton(
// onPressed: () {},
// icon: const Icon(Icons.more_vert),
// ),
// const SizedBox(width: 4)
// ],
flexibleSpace: FlexibleSpaceBar(
background: Container(
decoration: BoxDecoration(

View File

@ -49,8 +49,8 @@ class FavVideoCardH extends StatelessWidget {
Get.toNamed('/video', parameters: parameters, arguments: {
'videoItem': videoItem,
'heroTag': heroTag,
'videoType':
epId != null ? SearchType.media_bangumi : SearchType.video,
// 'videoType':
// epId != null ? SearchType.media_bangumi : SearchType.video,
});
},
child: Column(
@ -142,21 +142,15 @@ class VideoContent extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
videoItem.owner.name,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
Row(
children: [
StatView(
theme: 'gray',
view: videoItem.cntInfo['play'],
Text(
videoItem.owner.name,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(width: 8),
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
const Spacer(),
SizedBox(
width: 26,

View File

@ -1,75 +0,0 @@
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');
}
}

View File

@ -1,4 +0,0 @@
library fav_search;
export './controller.dart';
export './view.dart';

View File

@ -1,116 +0,0 @@
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(),
],
),
),
);
}
}

View File

@ -1,28 +1,20 @@
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/http/follow.dart';
import 'package:pilipala/http/member.dart';
import 'package:pilipala/models/follow/result.dart';
import 'package:pilipala/models/member/tags.dart';
import 'package:pilipala/utils/storage.dart';
/// 查看自己的关注时,可以查看分类
/// 查看其他人的关注时,只可以看全部
class FollowController extends GetxController with GetTickerProviderStateMixin {
class FollowController extends GetxController {
Box userInfoCache = GStrorage.userInfo;
int pn = 1;
int ps = 20;
int total = 0;
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
RxList<FollowItemModel> followList = [FollowItemModel()].obs;
late int mid;
late String name;
var userInfo;
RxString loadingText = '加载中...'.obs;
RxBool isOwner = false.obs;
late List<MemberTagItemModel> followTags;
late TabController tabController;
@override
void onInit() {
@ -31,7 +23,6 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
mid = Get.parameters['mid'] != null
? int.parse(Get.parameters['mid']!)
: userInfo.mid;
isOwner.value = mid == userInfo.mid;
name = Get.parameters['name'] ?? userInfo.uname;
}
@ -65,20 +56,4 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
}
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;
}
}
}

View File

@ -1,8 +1,12 @@
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 'controller.dart';
import 'widgets/follow_list.dart';
import 'widgets/owner_follow_list.dart';
import 'widgets/follow_item.dart';
class FollowPage extends StatefulWidget {
const FollowPage({super.key});
@ -12,15 +16,30 @@ class FollowPage extends StatefulWidget {
}
class _FollowPageState extends State<FollowPage> {
late String mid;
late FollowController _followController;
final FollowController _followController = Get.put(FollowController());
final ScrollController scrollController = ScrollController();
Future? _futureBuilderFuture;
@override
void initState() {
super.initState();
mid = Get.parameters['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
@ -32,57 +51,73 @@ class _FollowPageState extends State<FollowPage> {
titleSpacing: 0,
centerTitle: false,
title: Text(
_followController.isOwner.value
? '我的关注'
: '${_followController.name}的关注',
'${_followController.name}的关注',
style: Theme.of(context).textTheme.titleMedium,
),
),
body: Obx(
() => !_followController.isOwner.value
? FollowList(ctr: _followController)
: FutureBuilder(
future: _followController.followUpTags(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {
return Column(
children: [
TabBar(
controller: _followController.tabController,
isScrollable: true,
tabs: [
for (var i in data['data']) ...[
Tab(text: i.name),
]
]),
Expanded(
child: TabBarView(
controller: _followController.tabController,
children: [
for (var i = 0;
i < _followController.tabController.length;
i++) ...[
OwnerFollowList(
ctr: _followController,
tagItem: _followController.followTags[i],
)
]
],
),
body: RefreshIndicator(
onRefresh: () async =>
await _followController.queryFollowings('init'),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {
List<FollowItemModel> list = _followController.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(
_followController.loadingText.value,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline,
fontSize: 13),
),
),
),
);
} else {
return followItem(item: list[index]);
}
},
)
: const CustomScrollView(
slivers: [NoData()],
),
],
);
} else {
return const SizedBox();
}
} else {
return const SizedBox();
}
},
),
),
);
} else {
return CustomScrollView(
slivers: [
HttpError(
errMsg: data['msg'],
fn: () => _followController.queryFollowings('init'),
)
],
);
}
} else {
// 骨架屏
return const SizedBox();
}
},
)),
);
}
}

View File

@ -1,45 +1,32 @@
import 'package:flutter/material.dart';
import 'package:get/get.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/utils.dart';
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);
return ListTile(
onTap: () {
feedBack();
Get.toNamed('/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag});
},
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: item.face,
),
Widget followItem({item}) {
String heroTag = Utils.makeHeroTag(item!.mid);
return ListTile(
onTap: () {
feedBack();
Get.toNamed('/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag});
},
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: item.face,
),
title: Text(
item.uname!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
item.sign!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
dense: true,
trailing: const SizedBox(width: 6),
);
}
),
title: Text(
item.uname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 14),
),
trailing: const SizedBox(width: 6),
);
}

View File

@ -1,111 +0,0 @@
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();
}
},
),
);
}
}

View File

@ -1,128 +0,0 @@
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();
}
},
),
);
}
}

View File

@ -79,6 +79,13 @@ class _HistoryPageState extends State<HistoryPage> {
style: Theme.of(context).textTheme.titleMedium,
),
actions: [
// TextButton(
// onPressed: () {
// _historyController.enableMultiple.value = true;
// setState(() {});
// },
// child: const Text('多选'),
// ),
IconButton(
onPressed: () => Get.toNamed('/historySearch'),
icon: const Icon(Icons.search_outlined),

View File

@ -89,7 +89,7 @@ class HistoryItem extends StatelessWidget {
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
},
);
} else {
@ -117,7 +117,7 @@ class HistoryItem extends StatelessWidget {
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);
@ -276,7 +276,7 @@ class HistoryItem extends StatelessWidget {
class VideoContent extends StatelessWidget {
final dynamic videoItem;
final dynamic ctr;
final dynamic? ctr;
const VideoContent({super.key, required this.videoItem, this.ctr});
@override
@ -297,8 +297,7 @@ class VideoContent extends StatelessWidget {
maxLines: videoItem.videos > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
if (videoItem.showTitle != null) ...[
const SizedBox(height: 2),
if (videoItem.showTitle != null)
Text(
videoItem.showTitle,
textAlign: TextAlign.start,
@ -306,24 +305,21 @@ class VideoContent extends StatelessWidget {
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
fontWeight: FontWeight.w400,
color: Theme.of(context).colorScheme.outline),
maxLines: 1,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
const Spacer(),
if (videoItem.authorName != '')
Row(
children: [
Text(
videoItem.authorName,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
Row(
children: [
Text(
videoItem.authorName,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
],
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/black.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/models/user/black.dart';
import 'package:pilipala/utils/storage.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin {
@ -15,6 +17,13 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
RxBool userLogin = false.obs;
RxString userFace = ''.obs;
var userInfo;
static Box setting = GStrorage.setting;
late List<int> blackMidsList;
int currentPage = 1;
int pageSize = 50;
int total = 0;
List<BlackListItem> blackList = [BlackListItem()];
@override
void onInit() {
@ -51,7 +60,33 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
void updateLoginStatus(val) async {
userInfo = await userInfoCache.get('userInfoCache');
userLogin.value = val ?? false;
if (val) return;
if (val) {
// 获取黑名单
await queryBlacklist();
blackMidsList = blackList.map<int>((e) => e.mid!).toList();
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
return;
}
userFace.value = userInfo != null ? userInfo.face : '';
}
Future queryBlacklist({type = 'init'}) async {
if (type == 'init') {
currentPage = 1;
}
var result = await BlackHttp.blackList(pn: currentPage, ps: pageSize);
if (result['status']) {
if (type == 'init') {
blackList = result['data'].list;
total = result['data'].total;
} else {
blackList.addAll(result['data'].list);
}
currentPage += 1;
if (blackList.length < total) {
await queryBlacklist(type: 'onLoad');
}
}
return result;
}
}

View File

@ -8,7 +8,8 @@ import 'package:pilipala/utils/feed_back.dart';
import './controller.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
Function? callFn;
HomePage({Key? key, this.callFn}) : super(key: key);
@override
State<HomePage> createState() => _HomePageState();
@ -25,15 +26,16 @@ class _HomePageState extends State<HomePage>
showUserBottonSheet() {
feedBack();
showModalBottomSheet(
context: context,
builder: (_) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
);
widget.callFn!();
// showModalBottomSheet(
// context: context,
// builder: (_) => const SizedBox(
// height: 450,
// child: MinePage(),
// ),
// clipBehavior: Clip.hardEdge,
// isScrollControlled: true,
// );
}
@override
@ -50,37 +52,6 @@ class _HomePageState extends State<HomePage>
ctr: _homeController,
callback: showUserBottonSheet,
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
height: 42,
child: Align(
alignment: Alignment.center,
child: TabBar(
controller: _homeController.tabController,
tabs: [
for (var i in _homeController.tabs) Tab(text: i['label'])
],
isScrollable: true,
dividerColor: Colors.transparent,
enableFeedback: true,
splashBorderRadius: BorderRadius.circular(10),
onTap: (value) {
feedBack();
if (_homeController.initialIndex == value) {
_homeController.tabsCtrList[value]().animateToTop();
}
_homeController.initialIndex = value;
},
),
),
),
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: _homeController.tabsPageList,
),
),
],
),
);
@ -128,8 +99,6 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
),
child: Row(
children: [
const Expanded(child: SearchPage()),
const SizedBox(width: 10),
Obx(
() => ctr!.userLogin.value
? Stack(
@ -182,6 +151,8 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
),
),
),
const SizedBox(width: 10),
const Expanded(child: SearchPage()),
],
),
),

View File

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/mine/index.dart';
class LeftDrawer extends StatefulWidget {
const LeftDrawer({super.key});
@override
State<LeftDrawer> createState() => _LeftDrawerState();
}
class _LeftDrawerState extends State<LeftDrawer> {
@override
Widget build(BuildContext context) {
return Drawer(
width: MediaQuery.of(context).size.width * 0.84,
child: const Column(
children: [
Expanded(child: MinePage()),
Expanded(child: MediaPage()),
],
),
);
}
}

View File

@ -1,112 +1,19 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/html.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class HtmlRenderController extends GetxController {
late String id;
late String dynamicType;
late int type;
RxInt oid = (-1).obs;
late Map response;
int? floor;
int currentPage = 0;
bool isLoadingMore = false;
RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController();
ReplySortType _sortType = ReplySortType.time;
RxString sortTypeTitle = ReplySortType.time.titles.obs;
RxString sortTypeLabel = ReplySortType.time.labels.obs;
Box setting = GStrorage.setting;
@override
void onInit() {
super.onInit();
id = Get.parameters['id']!;
dynamicType = Get.parameters['dynamicType']!;
type = dynamicType == 'picture' ? 11 : 12;
}
// 请求动态内容
Future reqHtml(id) async {
late dynamic res;
if (dynamicType == 'opus' || dynamicType == 'picture') {
res = await HtmlHttp.reqHtml(id, dynamicType);
} else {
res = await HtmlHttp.reqReadHtml(id, dynamicType);
}
Future reqHtml() async {
var res = await HtmlHttp.reqHtml(id);
response = res;
oid.value = res['commentId'];
return res;
}
// 请求评论
Future queryReplyList({reqType = 'init'}) async {
var res = await ReplyHttp.replyList(
oid: oid.value,
pageNum: currentPage + 1,
type: type,
sort: _sortType.index,
);
if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies;
acount.value = res['data'].page.acount;
if (replies.isNotEmpty) {
currentPage++;
noMore.value = '加载中...';
if (replies.length < 20) {
noMore.value = '没有更多了';
}
} else {
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
}
if (reqType == 'init') {
// 添加置顶回复
if (res['data'].upper.top != null) {
bool flag = res['data']
.topReplies
.any((reply) => reply.rpid == res['data'].upper.top.rpid);
if (!flag) {
replies.insert(0, res['data'].upper.top);
}
}
replies.insertAll(0, res['data'].topReplies);
replyList.value = replies;
} else {
replyList.addAll(replies);
}
}
isLoadingMore = false;
return res;
}
// 排序搜索评论
queryBySort() {
feedBack();
switch (_sortType) {
case ReplySortType.time:
_sortType = ReplySortType.like;
break;
case ReplySortType.like:
_sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
_sortType = ReplySortType.time;
break;
default:
}
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
currentPage = 0;
replyList.clear();
queryReplyList(reqType: 'init');
}
}

View File

@ -1,19 +1,7 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/html_render.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'controller.dart';
@ -24,104 +12,16 @@ class HtmlRenderPage extends StatefulWidget {
State<HtmlRenderPage> createState() => _HtmlRenderPageState();
}
class _HtmlRenderPageState extends State<HtmlRenderPage>
with TickerProviderStateMixin {
final HtmlRenderController _htmlRenderCtr = Get.put(HtmlRenderController());
class _HtmlRenderPageState extends State<HtmlRenderPage> {
HtmlRenderController htmlRenderCtr = Get.put(HtmlRenderController());
late String title;
late String id;
late String url;
late String dynamicType;
late int type;
bool _isFabVisible = true;
late Future _futureBuilderFuture;
late ScrollController scrollController;
late AnimationController fabAnimationCtr;
@override
void initState() {
super.initState();
title = Get.parameters['title']!;
id = Get.parameters['id']!;
url = Get.parameters['url']!;
dynamicType = Get.parameters['dynamicType']!;
type = dynamicType == 'picture' ? 11 : 12;
_futureBuilderFuture = _htmlRenderCtr.reqHtml(id);
fabAnimationCtr = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
scrollListener();
}
void scrollListener() {
scrollController = _htmlRenderCtr.scrollController;
scrollController.addListener(
() {
// 分页加载
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
_htmlRenderCtr.queryReplyList(reqType: 'onLoad');
});
}
// 标题
// if (scrollController.offset > 55 && !_visibleTitle) {
// _visibleTitle = true;
// titleStreamC.add(true);
// } else if (scrollController.offset <= 55 && _visibleTitle) {
// _visibleTitle = false;
// titleStreamC.add(false);
// }
// fab按钮
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
_showFab();
} else if (direction == ScrollDirection.reverse) {
_hideFab();
}
},
);
}
void _showFab() {
if (!_isFabVisible) {
_isFabVisible = true;
fabAnimationCtr.forward();
}
}
void _hideFab() {
if (_isFabVisible) {
_isFabVisible = false;
fabAnimationCtr.reverse();
}
}
void replyReply(replyItem) {
int oid = replyItem.oid;
int rpid = replyItem.rpid!;
Get.to(
() => Scaffold(
appBar: AppBar(
titleSpacing: 0,
centerTitle: false,
title: Text(
'评论详情',
style: Theme.of(context).textTheme.titleMedium,
),
),
body: VideoReplyReplyPanel(
oid: oid,
rpid: rpid,
source: 'dynamic',
replyType: ReplyType.values[type],
firstFloor: replyItem,
),
),
);
}
@override
@ -129,328 +29,88 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
return Scaffold(
appBar: AppBar(
centerTitle: false,
titleSpacing: 0,
title: Text(
title,
style: Theme.of(context).textTheme.titleMedium,
),
actions: [
const SizedBox(width: 4),
IconButton(
onPressed: () {
Get.toNamed('/webview', parameters: {
'url': url.startsWith('http') ? url : 'https:$url',
'type': 'url',
'pageTitle': title,
});
},
icon: const Icon(Icons.open_in_browser_outlined, size: 19),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
onTap: () => {
Clipboard.setData(ClipboardData(text: url)),
SmartDialog.showToast('已复制'),
},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.copy_rounded, size: 19),
SizedBox(width: 10),
Text('复制链接'),
],
),
),
PopupMenuItem(
onTap: () => {},
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.share_outlined, size: 19),
SizedBox(width: 10),
Text('分享'),
],
),
),
],
),
const SizedBox(width: 6)
],
title: Text(title),
),
body: Stack(
children: [
SingleChildScrollView(
controller: scrollController,
child: Column(
children: [
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
fabAnimationCtr.forward();
if (data['status']) {
return Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: Row(
body: SingleChildScrollView(
child: Column(
children: [
FutureBuilder(
future: htmlRenderCtr.reqHtml(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {
return Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: Row(
children: [
NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: htmlRenderCtr.response['avatar']!,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: _htmlRenderCtr.response['avatar']!,
Text(htmlRenderCtr.response['uname'],
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleSmall!
.fontSize,
)),
Text(
htmlRenderCtr.response['updateTime'],
style: TextStyle(
color:
Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(_htmlRenderCtr.response['uname'],
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.titleSmall!
.fontSize,
)),
Text(
_htmlRenderCtr.response['updateTime'],
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize,
),
),
],
),
const Spacer(),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
child: HtmlRender(
htmlContent: _htmlRenderCtr.response['content'],
),
),
Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 8,
color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
),
),
),
),
],
);
} else {
return const Text('error');
}
} else {
// 骨架屏
return const SizedBox();
}
},
),
Obx(
() => _htmlRenderCtr.oid.value != -1
? Container(
const Spacer(),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
child: HtmlRender(
htmlContent: htmlRenderCtr.response['content'],
),
),
Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
border: Border(
top: BorderSide(
width: 0.6,
bottom: BorderSide(
width: 8,
color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
),
),
),
height: 45,
padding: const EdgeInsets.only(left: 12, right: 6),
child: Row(
children: [
const Text('回复'),
const Spacer(),
SizedBox(
height: 35,
child: TextButton.icon(
onPressed: () => _htmlRenderCtr.queryBySort(),
icon: const Icon(Icons.sort, size: 16),
label: Obx(
() => Text(
_htmlRenderCtr.sortTypeLabel.value,
style: const TextStyle(fontSize: 13),
),
),
),
)
],
),
)
: const SizedBox(),
),
Obx(
() => _htmlRenderCtr.oid.value != -1
? FutureBuilder(
future: _htmlRenderCtr.queryReplyList(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
Map data = snapshot.data as Map;
if (snapshot.data['status']) {
// 请求成功
return Obx(
() => _htmlRenderCtr.replyList.isEmpty &&
_htmlRenderCtr.isLoadingMore
? ListView.builder(
itemCount: 5,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
)
: ListView.builder(
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemCount:
_htmlRenderCtr.replyList.length +
1,
itemBuilder: (context, index) {
if (index ==
_htmlRenderCtr
.replyList.length) {
return Container(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context)
.padding
.bottom),
height: MediaQuery.of(context)
.padding
.bottom +
100,
child: Center(
child: Obx(
() => Text(
_htmlRenderCtr
.noMore.value,
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.colorScheme
.outline,
),
),
),
),
);
} else {
return ReplyItem(
replyItem: _htmlRenderCtr
.replyList[index],
showReplyRow: true,
replyLevel: '1',
replyReply: (replyItem) =>
replyReply(replyItem),
replyType:
ReplyType.values[type],
addReply: (replyItem) {
_htmlRenderCtr
.replyList[index].replies!
.add(replyItem);
},
);
}
},
),
);
} else {
// 请求错误
return CustomScrollView(
slivers: [
HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
)
],
);
}
} else {
// 骨架屏
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: 5,
itemBuilder: (context, index) {
return const VideoReplySkeleton();
},
);
}
},
)
: const SizedBox(),
)
],
),
],
);
} else {
return Text('error');
}
} else {
// 骨架屏
return const SizedBox();
}
},
),
),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return VideoReplyNewDialog(
oid: _htmlRenderCtr.oid.value,
root: 0,
parent: 0,
replyType: ReplyType.values[type],
);
},
).then(
(value) => {
// 完成评论,数据添加
if (value != null && value['data'] != null)
{
_htmlRenderCtr.replyList.add(value['data']),
_htmlRenderCtr.acount.value++
}
},
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
),
),
),
],
],
),
),
);
}

View File

@ -20,14 +20,14 @@ class LiveController extends GetxController {
void onInit() {
super.onInit();
crossAxisCount.value =
setting.get(SettingBoxKey.customRows, defaultValue: 2);
setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2;
}
// 获取推荐
Future queryLiveList(type) async {
// if (type == 'init') {
// _currentPage = 1;
// }
if (type == 'init') {
_currentPage = 1;
}
var res = await LiveHttp.liveList(
pn: _currentPage,
);

View File

@ -21,15 +21,11 @@ class LivePage extends StatefulWidget {
State<LivePage> createState() => _LivePageState();
}
class _LivePageState extends State<LivePage>
with AutomaticKeepAliveClientMixin {
class _LivePageState extends State<LivePage> {
final LiveController _liveController = Get.put(LiveController());
late Future _futureBuilderFuture;
late ScrollController scrollController;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
@ -41,7 +37,7 @@ class _LivePageState extends State<LivePage>
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () {
EasyThrottle.throttle('my-throttler', const Duration(seconds: 1), () {
_liveController.isLoadingMore = true;
_liveController.onLoad();
});
@ -148,9 +144,9 @@ class _LivePageState extends State<LivePage>
return SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
// 行间距
mainAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.cardSpace + 4,
// 列间距
crossAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.cardSpace + 4,
// 列数
crossAxisCount: crossAxisCount,
mainAxisExtent:

View File

@ -24,7 +24,7 @@ class LiveCardV extends StatelessWidget {
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(liveItem.roomId);
return Card(
elevation: 0,
elevation: crossAxisCount == 1 ? 0 : 1,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.zero,
child: GestureDetector(
@ -102,7 +102,7 @@ class LiveContent extends StatelessWidget {
child: Padding(
padding: crossAxisCount == 1
? const EdgeInsets.fromLTRB(9, 9, 9, 4)
: const EdgeInsets.fromLTRB(5, 8, 5, 4),
: const EdgeInsets.fromLTRB(9, 8, 9, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -120,18 +120,15 @@ class LiveContent extends StatelessWidget {
if (crossAxisCount == 1) const SizedBox(height: 4),
Row(
children: [
Expanded(
child: Text(
liveItem.uname,
textAlign: TextAlign.start,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
Text(
liveItem.uname,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (crossAxisCount == 1) ...[
Text(
@ -172,7 +169,7 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10),
padding: const EdgeInsets.only(top: 22, left: 10, right: 10),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
@ -184,17 +181,18 @@ class VideoStat extends StatelessWidget {
tileMode: TileMode.mirror,
),
),
child: RichText(
maxLines: 1,
textAlign: TextAlign.justify,
softWrap: false,
text: TextSpan(
style: const TextStyle(fontSize: 11, color: Colors.white),
children: [
TextSpan(text: liveItem!.areaName!),
TextSpan(text: liveItem!.watchedShow!['text_small']),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
liveItem!.areaName!,
style: const TextStyle(fontSize: 11, color: Colors.white),
),
Text(
liveItem!.watchedShow!['text_small'],
style: const TextStyle(fontSize: 11, color: Colors.white),
),
],
),
);
}

View File

@ -4,53 +4,11 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart';
// import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[
const HomePage(),
const DynamicsPage(),
const MediaPage(),
];
RxList navigationBars = [
{
'icon': const Icon(
Icons.favorite_outline,
size: 21,
),
'selectIcon': const Icon(
Icons.favorite,
size: 21,
),
'label': "首页",
},
{
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
},
{
'icon': const Icon(
Icons.folder_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.folder,
size: 21,
),
'label': "媒体库",
}
].obs;
final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast();
Box setting = GStrorage.setting;

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/home/widgets/left_drawer.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart';
@ -18,80 +19,16 @@ class MainApp extends StatefulWidget {
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
final MainController _mainController = Get.put(MainController());
final HomeController _homeController = Get.put(HomeController());
final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController());
PageController? _pageController;
late AnimationController? _animationController;
late Animation<double>? _fadeAnimation;
late Animation<double>? _slideAnimation;
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int selectedIndex = 0;
int? _lastSelectTime; //上次点击时间
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 800),
reverseDuration: const Duration(milliseconds: 0),
value: 1,
vsync: this,
);
_fadeAnimation =
Tween<double>(begin: 0.8, end: 1.0).animate(_animationController!);
_slideAnimation =
Tween(begin: 0.8, end: 1.0).animate(_animationController!);
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_pageController = PageController(initialPage: selectedIndex);
}
void setIndex(int value) async {
feedBack();
if (selectedIndex != value) {
selectedIndex = value;
_animationController!.reverse().then((_) {
selectedIndex = value;
_animationController!.forward();
});
setState(() {});
}
_pageController!.jumpToPage(value);
var currentPage = _mainController.pages[value];
if (currentPage is HomePage) {
if (_homeController.flag) {
// 单击返回顶部 双击并刷新
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_homeController.onRefresh();
} else {
_homeController.animateToTop();
}
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_homeController.flag = true;
} else {
_homeController.flag = false;
}
if (currentPage is DynamicsPage) {
if (_dynamicController.flag) {
// 单击返回顶部 双击并刷新
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_dynamicController.onRefresh();
} else {
_dynamicController.animateToTop();
}
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_dynamicController.flag = true;
} else {
_dynamicController.flag = false;
}
if (currentPage is MediaPage) {
_mediaController.queryFavFolder();
}
void openDrawer() {
_scaffoldKey.currentState?.openDrawer();
}
@override
@ -113,55 +50,10 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
return WillPopScope(
onWillPop: () => _mainController.onBackPressed(context),
child: Scaffold(
key: _scaffoldKey,
extendBody: true,
body: FadeTransition(
opacity: _fadeAnimation!,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 0.5),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: _slideAnimation!,
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.linear,
),
),
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
onPageChanged: (index) {
selectedIndex = index;
setState(() {});
},
children: _mainController.pages,
),
),
),
bottomNavigationBar: StreamBuilder(
stream: _mainController.bottomBarStream.stream,
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
return AnimatedSlide(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 1000),
offset: Offset(0, snapshot.data ? 0 : 1),
child: NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
);
},
),
drawer: const LeftDrawer(),
body: HomePage(callFn: openDrawer),
),
);
}

View File

@ -1,9 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/utils.dart';
class MediaPage extends StatefulWidget {
const MediaPage({super.key});
@ -15,7 +12,6 @@ class MediaPage extends StatefulWidget {
class _MediaPageState extends State<MediaPage>
with AutomaticKeepAliveClientMixin {
late MediaController mediaController;
late Future _futureBuilderFuture;
@override
bool get wantKeepAlive => true;
@ -24,13 +20,6 @@ class _MediaPageState extends State<MediaPage>
void initState() {
super.initState();
mediaController = Get.put(MediaController());
_futureBuilderFuture = mediaController.queryFavFolder();
mediaController.userLogin.listen((status) {
setState(() {
_futureBuilderFuture = mediaController.queryFavFolder();
});
});
}
@override
@ -38,243 +27,29 @@ class _MediaPageState extends State<MediaPage>
super.build(context);
Color primary = Theme.of(context).colorScheme.primary;
return Scaffold(
appBar: AppBar(toolbarHeight: 30),
body: SingleChildScrollView(
child: Column(
children: [
body: Column(
children: [
for (var i in mediaController.list) ...[
ListTile(
leading: null,
title: Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(
'媒体库',
style: TextStyle(
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
fontWeight: FontWeight.bold,
),
onTap: () => i['onTap'](),
dense: true,
leading: Padding(
padding: const EdgeInsets.only(left: 15),
child: Icon(
i['icon'],
color: primary,
),
),
contentPadding:
const EdgeInsets.only(left: 15, top: 2, bottom: 2),
minLeadingWidth: 0,
title: Text(
i['title'],
style: const TextStyle(fontSize: 15),
),
),
for (var i in mediaController.list) ...[
ListTile(
onTap: () => i['onTap'](),
dense: true,
leading: Padding(
padding: const EdgeInsets.only(left: 15),
child: Icon(
i['icon'],
color: primary,
),
),
contentPadding:
const EdgeInsets.only(left: 15, top: 2, bottom: 2),
minLeadingWidth: 0,
title: Text(
i['title'],
style: const TextStyle(fontSize: 15),
),
),
],
Obx(() => mediaController.userLogin.value
? favFolder(mediaController, context)
: const SizedBox())
],
),
),
);
}
Widget favFolder(mediaController, context) {
return Column(
children: [
Divider(
height: 35,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
ListTile(
onTap: () {},
leading: null,
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Obx(
() => Text.rich(
TextSpan(
children: [
TextSpan(
text: '收藏夹 ',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold),
),
if (mediaController.favFolderData.value.count != null)
TextSpan(
text: mediaController.favFolderData.value.count
.toString(),
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
),
trailing: IconButton(
onPressed: () {
setState(() {
_futureBuilderFuture = mediaController.queryFavFolder();
});
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
),
// const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 200 * MediaQuery.of(context).textScaleFactor,
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
List favFolderList =
mediaController.favFolderData.value.list!;
int favFolderCount =
mediaController.favFolderData.value.count!;
bool flag = favFolderCount > favFolderList.length;
return Obx(() => ListView.builder(
itemCount:
mediaController.favFolderData.value.list!.length +
(flag ? 1 : 0),
itemBuilder: (context, index) {
if (flag && index == favFolderList.length) {
return Padding(
padding: const EdgeInsets.only(
right: 14, bottom: 35),
child: Center(
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.5);
}),
),
onPressed: () => Get.toNamed('/fav'),
icon: Icon(
Icons.arrow_forward_ios,
size: 18,
color: Theme.of(context)
.colorScheme
.primary,
),
),
));
} else {
return FavFolderItem(
item: mediaController
.favFolderData.value.list![index],
index: index);
}
},
scrollDirection: Axis.horizontal,
));
} else {
return SizedBox(
height: 160,
child: Center(child: Text(data['msg'])),
);
}
} else {
// 骨架屏
return const SizedBox();
}
}),
),
],
);
}
}
class FavFolderItem extends StatelessWidget {
const FavFolderItem({super.key, this.item, this.index});
final FavFolderItemData? item;
final int? index;
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item!.fid);
return Container(
margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
child: GestureDetector(
onTap: () => Get.toNamed('/favDetail',
arguments: item,
parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Container(
width: 180,
height: 110,
margin: const EdgeInsets.only(bottom: 8),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.onInverseSurface,
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.onInverseSurface,
offset: const Offset(4, -12), // 阴影与容器的距离
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。
),
],
),
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
return Hero(
tag: heroTag,
child: NetworkImgLayer(
src: item!.cover,
width: box.maxWidth,
height: box.maxHeight,
),
);
},
),
),
Text(
' ${item!.title}',
overflow: TextOverflow.fade,
maxLines: 1,
),
Text(
'${item!.mediaCount}条视频',
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(color: Theme.of(context).colorScheme.outline),
)
],
),
],
),
);
}

View File

@ -2,43 +2,23 @@ import 'package:get/get.dart';
import 'package:pilipala/http/member.dart';
class ArchiveController extends GetxController {
ArchiveController(this.mid);
int? mid;
int pn = 1;
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
void onInit() {
super.onInit();
mid ??= int.parse(Get.parameters['mid']!);
print('🐶🐶: $mid');
currentOrder.value = orderList.first;
mid = int.parse(Get.parameters['mid']!);
}
// 获取用户投稿
Future getMemberArchive() async {
var res = await MemberHttp.memberArchive(
mid: mid, pn: pn, order: currentOrder['type']!);
var res = await MemberHttp.memberArchive(mid: mid, pn: pn);
if (res['status']) {
count = res['data'].page['count'];
pn += 1;
}
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];
}
}
}

View File

@ -5,12 +5,10 @@ import 'package:loading_more_list/loading_more_list.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/models/member/archive.dart';
import 'package:pilipala/pages/member/archive/index.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
class ArchivePanel extends StatefulWidget {
final int? mid;
const ArchivePanel({super.key, this.mid});
const ArchivePanel({super.key});
@override
State<ArchivePanel> createState() => _ArchivePanelState();
@ -19,21 +17,11 @@ class ArchivePanel extends StatefulWidget {
class _ArchivePanelState extends State<ArchivePanel>
with AutomaticKeepAliveClientMixin {
DateTime lastRefreshTime = DateTime.now();
late final LoadMoreListSource source;
late final ArchiveController _archiveController;
late final LoadMoreListSource source = LoadMoreListSource();
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
print('🐶🐶: ${widget.mid}');
_archiveController = Get.put(ArchiveController(widget.mid),
tag: Utils.makeHeroTag(widget.mid));
source = LoadMoreListSource(_archiveController);
}
@override
Widget build(BuildContext context) {
super.build(context);
@ -52,63 +40,14 @@ class _ArchivePanelState extends State<ArchivePanel>
// return PullToRefreshHeader(info, lastRefreshTime);
// },
// ),
Padding(
padding:
const EdgeInsets.only(left: 14, top: 8, bottom: 8, right: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('排序方式'),
SizedBox(
height: 35,
width: 85,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
// _archiveController.order = 'click';
// _archiveController.pn = 1;
_archiveController.toggleSort();
source.refresh(true);
// LoadMoreListSource().loadData();
},
child: Obx(
() => AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
_archiveController.currentOrder['label']!,
key: ValueKey<String>(
_archiveController.currentOrder['label']!),
),
),
),
),
),
],
),
),
const SizedBox(height: 4),
Expanded(
child: LoadingMoreList<VListItemModel>(
ListConfig<VListItemModel>(
sourceList: source,
itemBuilder:
(BuildContext c, VListItemModel item, int index) {
if (index == 0) {
return Column(
children: [
const SizedBox(height: 6),
VideoCardH(videoItem: item)
],
);
} else {
return VideoCardH(videoItem: item);
}
return VideoCardH(videoItem: item);
},
indicatorBuilder: _buildIndicator,
),
@ -203,18 +142,14 @@ class _ArchivePanelState extends State<ArchivePanel>
}
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
late ArchiveController ctr;
LoadMoreListSource(this.ctr);
bool forceRefresh = false;
final ArchiveController _archiveController =
Get.put(ArchiveController(), tag: Get.arguments['heroTag']);
@override
Future<bool> loadData([bool isloadMoreAction = false]) async {
bool isSuccess = false;
var res = await ctr.getMemberArchive();
var res = await _archiveController.getMemberArchive();
if (res['status']) {
if (ctr.pn == 2) {
clear();
}
addAll(res['data'].list.vlist);
}
if (length < res['data'].page['count']) {
@ -224,17 +159,4 @@ class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
}
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;
}
}

View File

@ -3,26 +3,23 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/member.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/member/archive.dart';
import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart';
class MemberController extends GetxController {
late int mid;
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
Map? userStat;
RxString face = ''.obs;
String? face;
String? heroTag;
Box userInfoCache = GStrorage.userInfo;
late int ownerMid;
// 投稿列表
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
var userInfo;
RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs;
Box setting = GStrorage.setting;
@override
void onInit() {
@ -30,9 +27,8 @@ class MemberController extends GetxController {
mid = int.parse(Get.parameters['mid']!);
userInfo = userInfoCache.get('userInfoCache');
ownerMid = userInfo != null ? userInfo.mid : -1;
face.value = Get.arguments['face'] ?? '';
face = Get.arguments['face'] ?? '';
heroTag = Get.arguments['heroTag'] ?? '';
relationSearch();
}
// 获取用户信息
@ -41,7 +37,6 @@ class MemberController extends GetxController {
var res = await MemberHttp.memberInfo(mid: mid);
if (res['status']) {
memberInfo.value = res['data'];
face.value = res['data'].face;
}
return res;
}
@ -69,25 +64,18 @@ class MemberController extends GetxController {
SmartDialog.showToast('账号未登录');
return;
}
if (attribute.value == 128) {
blockUser();
return;
}
SmartDialog.show(
useSystem: true,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
content: Text(memberInfo.value.isFollowed! ? '取消关注该用户?' : '关注该用户?'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: Text(
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
onPressed: () => SmartDialog.dismiss(),
child: const Text('取消')),
TextButton(
onPressed: () async {
await VideoHttp.relationMod(
@ -96,7 +84,8 @@ class MemberController extends GetxController {
reSrc: 11,
);
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
relationSearch();
SmartDialog.dismiss();
SmartDialog.showLoading();
SmartDialog.dismiss();
memberInfo.update((val) {});
},
@ -108,26 +97,9 @@ class MemberController extends GetxController {
);
}
// 关系查询
Future relationSearch() async {
if (userInfo == null) return;
if (mid == ownerMid) return;
var res = await UserHttp.relationSearch(mid);
if (res['status']) {
attribute.value = res['data']['relation']['attribute'];
attributeText.value = attribute.value == 0
? '关注'
: attribute.value == 2
? '已关注'
: attribute.value == 6
? '已互粉'
: '已拉黑';
}
}
// 拉黑用户
Future blockUser() async {
if (userInfo == null) {
Future blockUser(int mid) async {
if (userInfoCache.get('userInfoCache') == null) {
SmartDialog.showToast('账号未登录');
return;
}
@ -137,12 +109,12 @@ class MemberController extends GetxController {
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: Text(attribute.value != 128 ? '拉黑UP主?' : '从黑名单移除UP主'),
content: const Text('拉黑该用户?'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: Text(
'点错了',
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
@ -150,16 +122,15 @@ class MemberController extends GetxController {
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: mid,
act: attribute.value != 128 ? 5 : 6,
act: 5,
reSrc: 11,
);
SmartDialog.dismiss();
if (res['status']) {
attribute.value = attribute.value != 128 ? 128 : 0;
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
memberInfo.value.isFollowed = false;
relationSearch();
memberInfo.update((val) {});
List<int> blackMidsList = setting
.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
blackMidsList.add(mid);
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
}
},
child: const Text('确认'),
@ -169,8 +140,4 @@ class MemberController extends GetxController {
},
);
}
void shareUser() {
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
}
}

View File

@ -2,7 +2,6 @@ import 'package:get/get.dart';
import 'package:pilipala/http/member.dart';
class MemberDynamicPanelController extends GetxController {
MemberDynamicPanelController(this.mid);
int? mid;
String offset = '';
int count = 0;
@ -10,7 +9,7 @@ class MemberDynamicPanelController extends GetxController {
@override
void onInit() {
super.onInit();
mid ??= int.parse(Get.parameters['mid']!);
mid = int.parse(Get.parameters['mid']!);
}
Future getMemberDynamic() async {

View File

@ -4,13 +4,11 @@ import 'package:get/get.dart';
import 'package:loading_more_list/loading_more_list.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:pilipala/utils/utils.dart';
import 'controller.dart';
class MemberDynamicPanel extends StatefulWidget {
final int? mid;
const MemberDynamicPanel({super.key, this.mid});
const MemberDynamicPanel({super.key});
@override
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
@ -19,20 +17,11 @@ class MemberDynamicPanel extends StatefulWidget {
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
with AutomaticKeepAliveClientMixin {
DateTime lastRefreshTime = DateTime.now();
late final LoadMoreListSource source;
late final MemberDynamicPanelController _dynamicController;
late final LoadMoreListSource source = LoadMoreListSource();
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_dynamicController = Get.put(MemberDynamicPanelController(widget.mid),
tag: Utils.makeHeroTag(widget.mid));
source = LoadMoreListSource(_dynamicController);
}
@override
Widget build(BuildContext context) {
super.build(context);
@ -129,13 +118,13 @@ class _MemberDynamicPanelState extends State<MemberDynamicPanel>
}
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
late MemberDynamicPanelController ctr;
LoadMoreListSource(this.ctr);
final _dynamicController =
Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']);
@override
Future<bool> loadData([bool isloadMoreAction = false]) async {
bool isSuccess = false;
var res = await ctr.getMemberDynamic();
var res = await _dynamicController.getMemberDynamic();
if (res['status']) {
addAll(res['data'].items);
}

View File

@ -8,7 +8,6 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/member/archive/view.dart';
import 'package:pilipala/pages/member/dynamic/index.dart';
import 'package:pilipala/pages/member/index.dart';
import 'package:pilipala/utils/utils.dart';
import 'widgets/profile.dart';
@ -21,26 +20,21 @@ class MemberPage extends StatefulWidget {
class _MemberPageState extends State<MemberPage>
with SingleTickerProviderStateMixin {
late String heroTag;
late MemberController _memberController;
final MemberController _memberController = Get.put(MemberController());
Future? _futureBuilderFuture;
final ScrollController _extendNestCtr = ScrollController();
late TabController _tabController;
final StreamController<bool> appbarStream = StreamController<bool>();
late int mid;
@override
void initState() {
super.initState();
mid = int.parse(Get.parameters['mid']!);
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
_memberController = Get.put(MemberController(), tag: heroTag);
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
_futureBuilderFuture = _memberController.getInfo();
_extendNestCtr.addListener(
() {
double offset = _extendNestCtr.position.pixels;
if (offset > 230) {
if (offset > 250) {
appbarStream.add(true);
} else {
appbarStream.add(false);
@ -69,7 +63,7 @@ class _MemberPageState extends State<MemberPage>
elevation: 0,
scrolledUnderElevation: 1,
forceElevated: innerBoxIsScrolled,
expandedHeight: 290,
expandedHeight: 320,
titleSpacing: 0,
title: StreamBuilder(
stream: appbarStream.stream,
@ -83,13 +77,11 @@ class _MemberPageState extends State<MemberPage>
children: [
Row(
children: [
Obx(
() => NetworkImgLayer(
width: 35,
height: 35,
type: 'avatar',
src: _memberController.face.value,
),
NetworkImgLayer(
width: 35,
height: 35,
type: 'avatar',
src: _memberController.face ?? '',
),
const SizedBox(width: 10),
Obx(
@ -115,78 +107,74 @@ class _MemberPageState extends State<MemberPage>
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_memberController.ownerMid !=
_memberController.mid) ...[
PopupMenuItem(
onTap: () => _memberController.blockUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.block, size: 19),
const SizedBox(width: 10),
Text(_memberController.attribute.value != 128
? '加入黑名单'
: '移除黑名单'),
],
),
)
],
PopupMenuItem(
onTap: () => _memberController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(_memberController.ownerMid !=
_memberController.mid
? '分享UP主'
: '分享我的主页'),
],
),
),
],
),
// PopupMenuButton(
// icon: const Icon(Icons.more_vert),
// itemBuilder: (BuildContext context) => <PopupMenuEntry>[
// if (_memberController.ownerMid !=
// _memberController.mid) ...[
// PopupMenuItem(
// onTap: () => _memberController.blockUser(),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.block, size: 19),
// const SizedBox(width: 10),
// Text(_memberController.attribute.value != 128
// ? '加入黑名单'
// : '移除黑名单'),
// ],
// ),
// )
// ],
// PopupMenuItem(
// onTap: () => _memberController.shareUser(),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// const Icon(Icons.share_outlined, size: 19),
// const SizedBox(width: 10),
// Text(_memberController.ownerMid !=
// _memberController.mid
// ? '分享UP主'
// : '分享我的主页'),
// ],
// ),
// ),
// ],
// ),
const SizedBox(width: 4),
],
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: [
Obx(
() => _memberController.face.value != ''
? Positioned.fill(
bottom: 10,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fitWidth,
image: NetworkImage(
_memberController.face.value),
alignment: Alignment.topCenter,
isAntiAlias: true,
),
),
foregroundDecoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context)
.colorScheme
.background
.withOpacity(0.44),
Theme.of(context).colorScheme.background,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: const [0.0, 0.46],
),
),
),
)
: const SizedBox(),
),
// if (_memberController.face != null)
// Positioned.fill(
// bottom: 10,
// child: Container(
// decoration: BoxDecoration(
// image: DecorationImage(
// fit: BoxFit.fitWidth,
// image: NetworkImage(_memberController.face!),
// alignment: Alignment.topCenter,
// isAntiAlias: true,
// ),
// ),
// foregroundDecoration: BoxDecoration(
// gradient: LinearGradient(
// colors: [
// Theme.of(context)
// .colorScheme
// .background
// .withOpacity(0.44),
// Theme.of(context).colorScheme.background,
// ],
// begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
// stops: const [0.0, 0.46],
// ),
// ),
// ),
// ),
Positioned(
left: 0,
right: 0,
@ -196,7 +184,159 @@ class _MemberPageState extends State<MemberPage>
color: Theme.of(context).colorScheme.background,
),
),
profileWidget(),
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 !=
// '')
// 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);
}
},
),
)
],
),
),
@ -207,167 +347,29 @@ class _MemberPageState extends State<MemberPage>
return MediaQuery.of(context).padding.top + kToolbarHeight;
},
onlyOneScrollInBody: true,
body: Column(
children: [
SizedBox(
width: double.infinity,
height: 50,
child: TabBar(controller: _tabController, tabs: const [
Tab(text: '主页'),
Tab(text: '动态'),
Tab(text: '投稿'),
]),
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
const Text('主页'),
MemberDynamicPanel(mid: mid),
ArchivePanel(mid: mid),
],
))
],
),
),
);
}
Widget profileWidget() {
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);
}
},
// body: Column(
// children: [
// SizedBox(
// width: double.infinity,
// height: 50,
// child: TabBar(controller: _tabController, tabs: const [
// Tab(text: '主页'),
// Tab(text: '动态'),
// Tab(text: '投稿'),
// ]),
// ),
// Expanded(
// child: TabBarView(
// controller: _tabController,
// children: const [
// Text('主页'),
// MemberDynamicPanel(),
// ArchivePanel(),
// ],
// ))
// ],
// ),
body: ArchivePanel(),
),
);
}

View File

@ -15,181 +15,171 @@ Widget profile(ctr, {loadingStatus = false}) {
child: Row(
children: [
Hero(
tag: ctr.heroTag!,
child: Stack(
children: [
NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus ? memberInfo.face : ctr.face.value,
),
if (!loadingStatus &&
memberInfo.liveRoom != null &&
memberInfo.liveRoom!.liveStatus == 1)
Positioned(
bottom: 0,
left: 14,
child: GestureDetector(
onTap: () {
LiveItemModel liveItem = LiveItemModel.fromJson({
'title': memberInfo.liveRoom!.title,
'uname': memberInfo.name,
'face': memberInfo.face,
'roomid': memberInfo.liveRoom!.roomId,
'watched_show': memberInfo.liveRoom!.watchedShow,
});
Get.toNamed(
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
arguments: {'liveItem': liveItem},
);
},
child: Container(
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(10)),
),
child: Row(children: [
Image.asset(
'assets/images/live.gif',
height: 10,
tag: ctr.heroTag!,
child: Stack(
children: [
NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus ? memberInfo.face : ctr.face,
),
if (!loadingStatus &&
memberInfo.liveRoom != null &&
memberInfo.liveRoom!.liveStatus == 1)
Positioned(
bottom: 0,
left: 14,
child: GestureDetector(
onTap: () {
LiveItemModel liveItem = LiveItemModel.fromJson({
'title': memberInfo.liveRoom!.title,
'uname': memberInfo.name,
'face': memberInfo.face,
'roomid': memberInfo.liveRoom!.roomId,
'watched_show': memberInfo.liveRoom!.watchedShow,
});
Get.toNamed(
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
arguments: {'liveItem': liveItem},
);
},
child: Container(
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius:
const BorderRadius.all(Radius.circular(10)),
),
Text(
' 直播中',
style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
child: Row(children: [
Image.asset(
'assets/images/live.gif',
height: 10,
),
Text(
' 直播中',
style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
),
),
),
)
],
),
),
)
],
)),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Get.toNamed(
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['following'].toString()
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold),
),
Text(
'关注',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
InkWell(
onTap: () {
Get.toNamed(
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? Utils.numFormat(
ctr.userStat!['follower'],
)
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'粉丝',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
),
Column(
children: [
const Text('-',
style: TextStyle(fontWeight: FontWeight.bold)),
Text(
'获赞',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
),
],
),
),
const SizedBox(height: 10),
// Padding(
// padding: const EdgeInsets.only(left: 10, right: 10),
// child: Row(
// mainAxisSize: MainAxisSize.max,
// mainAxisAlignment: MainAxisAlignment.spaceAround,
// children: [
// InkWell(
// onTap: () {
// Get.toNamed(
// '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
// },
// child: Column(
// children: [
// Text(
// !loadingStatus
// ? ctr.userStat!['following'].toString()
// : '-',
// style: const TextStyle(
// fontWeight: FontWeight.bold),
// ),
// Text(
// '关注',
// style: TextStyle(
// fontSize: Theme.of(context)
// .textTheme
// .labelMedium!
// .fontSize),
// )
// ],
// ),
// ),
// InkWell(
// onTap: () {
// Get.toNamed(
// '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
// },
// child: Column(
// children: [
// Text(
// !loadingStatus
// ? Utils.numFormat(
// ctr.userStat!['follower'],
// )
// : '-',
// style: const TextStyle(
// fontWeight: FontWeight.bold)),
// Text('粉丝',
// style: TextStyle(
// fontSize: Theme.of(context)
// .textTheme
// .labelMedium!
// .fontSize))
// ],
// ),
// ),
// Column(
// children: [
// const Text('-',
// style: TextStyle(fontWeight: FontWeight.bold)),
// Text(
// '获赞',
// style: TextStyle(
// fontSize: Theme.of(context)
// .textTheme
// .labelMedium!
// .fontSize),
// )
// ],
// ),
// ],
// ),
// ),
// const SizedBox(height: 10),
if (ctr.ownerMid != ctr.mid) ...[
Row(
children: [
Obx(
() => Expanded(
child: TextButton(
onPressed: () => ctr.actionRelationMod(),
style: TextButton.styleFrom(
foregroundColor: ctr.attribute.value == -1
? Colors.transparent
: ctr.attribute.value != 0
? Theme.of(context).colorScheme.outline
: Theme.of(context)
.colorScheme
.onPrimary,
backgroundColor: ctr.attribute.value != 0
? Theme.of(context)
.colorScheme
.onInverseSurface
: Theme.of(context)
.colorScheme
.primary, // 设置按钮背景色
),
child: Obx(() => Text(ctr.attributeText.value)),
),
TextButton(
onPressed: () => ctr.actionRelationMod(),
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 42, right: 42),
foregroundColor:
!loadingStatus && memberInfo.isFollowed!
? Theme.of(context).colorScheme.outline
: Theme.of(context).colorScheme.onPrimary,
backgroundColor: !loadingStatus &&
memberInfo.isFollowed!
? Theme.of(context).colorScheme.onInverseSurface
: Theme.of(context)
.colorScheme
.primary, // 设置按钮背景色
),
child: Text(!loadingStatus && memberInfo.isFollowed!
? '取关'
: '关注'),
),
const SizedBox(width: 8),
Expanded(
child: TextButton(
onPressed: () {},
style: TextButton.styleFrom(
backgroundColor: Theme.of(context)
.colorScheme
.onInverseSurface,
),
child: const Text('发消息'),
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 42, right: 42),
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text('发消息'),
)
],
)

View File

@ -75,7 +75,11 @@ class MemberSearchController extends GetxController {
}
archivePn += 1;
hasRequest = true;
if (res['data'].list.vlist.isEmpty) {
loadingStatus.value = 'finish';
}
}
// loadingStatus.value = 'finish';
return res;
}

View File

@ -85,7 +85,7 @@ class _MemberSearchPageState extends State<MemberSearchPage>
? [
Expanded(
child: Center(
child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'),
child: Text('搜索「${_memberSearchCtr.uname.value}」的视频'),
),
),
]
@ -157,9 +157,22 @@ class _MemberSearchPageState extends State<MemberSearchPage>
return const VideoCardHSkeleton();
},
)
: const CustomScrollView(
: CustomScrollView(
slivers: <Widget>[
NoData(),
SliverToBoxAdapter(
child: SizedBox(
height: 400,
child: Center(
child: Text(
'没有搜索到相关内容\n请尝试别的搜索词',
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.titleSmall,
),
),
),
),
],
),
);

View File

@ -1,5 +1,4 @@
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/http/user.dart';
@ -112,20 +111,4 @@ class MineController extends GetxController {
}
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}');
}
}

View File

@ -72,38 +72,30 @@ class _MinePageState extends State<MinePage> {
const SizedBox(width: 10),
],
),
body: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: SizedBox(
height: constraint.maxHeight,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
return Obx(
() => userInfoBuild(mineController, context));
} else {
return userInfoBuild(mineController, context);
}
} else {
return userInfoBuild(mineController, context);
}
},
),
],
),
body: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
return Obx(() => userInfoBuild(mineController, context));
} else {
return userInfoBuild(mineController, context);
}
} else {
return userInfoBuild(mineController, context);
}
},
),
);
},
],
),
),
);
}
@ -138,85 +130,9 @@ class _MinePageState extends State<MinePage> {
_mineController.userInfo.value.uname ?? '点击头像登录',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(width: 4),
Image.asset(
'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png',
height: 10,
),
],
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(TextSpan(children: [
TextSpan(
text: '硬币: ',
style:
TextStyle(color: Theme.of(context).colorScheme.outline)),
TextSpan(
text: (_mineController.userInfo.value.money ?? 'pilipala')
.toString(),
style:
TextStyle(color: Theme.of(context).colorScheme.primary)),
]))
],
),
const SizedBox(height: 25),
if (_mineController.userInfo.value.levelInfo != null) ...[
LayoutBuilder(
builder: (context, BoxConstraints box) {
LevelInfo levelInfo = _mineController.userInfo.value.levelInfo;
return SizedBox(
width: box.maxWidth,
height: 24,
child: Stack(
children: [
Positioned(
top: 0,
right: 0,
bottom: 0,
child: Container(
color: Theme.of(context).colorScheme.primary,
height: 24,
constraints:
const BoxConstraints(minWidth: 100), // 设置最小宽度为100
width: box.maxWidth *
(1 - (levelInfo.currentExp! / levelInfo.nextExp!)),
child: Center(
child: Text(
'${levelInfo.currentExp!}/${levelInfo.nextExp!}',
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 12,
),
),
),
),
),
Positioned(
top: 23,
left: 0,
bottom: 0,
child: Container(
width: box.maxWidth *
(_mineController
.userInfo.value.levelInfo!.currentExp! /
_mineController
.userInfo.value.levelInfo!.nextExp!),
height: 1,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
);
},
),
],
const SizedBox(height: 30),
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: LayoutBuilder(
@ -264,7 +180,7 @@ class _MinePageState extends State<MinePage> {
),
),
InkWell(
onTap: () => _mineController.pushFollow(),
onTap: () => Get.toNamed('/follow'),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -294,7 +210,7 @@ class _MinePageState extends State<MinePage> {
),
),
InkWell(
onTap: () => _mineController.pushFans(),
onTap: () => Get.toNamed('/fan'),
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,

View File

@ -14,13 +14,12 @@ class RcmdController extends GetxController {
Box recVideo = GStrorage.recVideo;
Box setting = GStrorage.setting;
RxInt crossAxisCount = 2.obs;
late bool enableSaveLastData;
@override
void onInit() {
super.onInit();
crossAxisCount.value =
setting.get(SettingBoxKey.customRows, defaultValue: 2);
setting.get(SettingBoxKey.enableSingleRow, defaultValue: false) ? 1 : 2;
if (recVideo.get('cacheList') != null &&
recVideo.get('cacheList').isNotEmpty) {
List<RecVideoItemAppModel> list = [];
@ -29,8 +28,6 @@ class RcmdController extends GetxController {
}
videoList.value = list;
}
enableSaveLastData =
setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false);
}
// 获取推荐
@ -52,11 +49,7 @@ class RcmdController extends GetxController {
videoList.value = res['data'];
}
} else if (type == 'onRefresh') {
if (enableSaveLastData) {
videoList.insertAll(0, res['data']);
} else {
videoList.value = res['data'];
}
videoList.value = res['data'];
} else if (type == 'onLoad') {
videoList.addAll(res['data']);
}

View File

@ -12,7 +12,7 @@ class SSearchController extends GetxController {
final FocusNode searchFocusNode = FocusNode();
RxString searchKeyWord = ''.obs;
Rx<TextEditingController> controller = TextEditingController().obs;
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
RxList<HotSearchItem> hotSearchList = [HotSearchItem()].obs;
Box histiryWord = GStrorage.historyword;
List historyCacheList = [];
RxList historyList = [].obs;
@ -27,9 +27,9 @@ class SSearchController extends GetxController {
@override
void onInit() {
super.onInit();
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault();
}
// if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
// searchDefault();
// }
// 其他页面跳转过来
if (Get.parameters.keys.isNotEmpty) {
if (Get.parameters['keyword'] != null) {
@ -51,7 +51,7 @@ class SSearchController extends GetxController {
searchSuggestList.value = [];
return;
}
_debouncer.call(() => querySearchSuggest(value));
// _debouncer.call(() => querySearchSuggest(value));
}
void onClear() {
@ -85,9 +85,7 @@ class SSearchController extends GetxController {
// 获取热搜关键词
Future queryHotSearchList() async {
var result = await SearchHttp.hotSearchList();
if (result['status']) {
hotSearchList.value = result['data'].list;
}
hotSearchList.value = result['data'].list;
return result;
}
@ -105,9 +103,7 @@ class SSearchController extends GetxController {
Future querySearchSuggest(String value) async {
var result = await SearchHttp.searchSuggest(term: value);
if (result['status']) {
if (result['data'] is SearchSuggestModel) {
searchSuggestList.value = result['data'].tag;
}
searchSuggestList.value = result['data'].tag;
}
}

View File

@ -140,141 +140,18 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
Visibility(
visible: _searchController.enableHotKey,
child: hotSearch(_searchController)),
// 搜索历史
_history()
],
),
child: _history(),
),
);
},
);
}
Widget _searchSuggest() {
SSearchController _ssCtr = _searchController;
return Obx(
() => _ssCtr.searchSuggestList.isNotEmpty &&
_ssCtr.searchSuggestList.first.term != null &&
_ssCtr.controller.value.text != ''
? ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _ssCtr.searchSuggestList.length,
itemBuilder: (context, index) {
return InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
onTap: () => _ssCtr
.onClickKeyword(_ssCtr.searchSuggestList[index].term!),
child: Padding(
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
child: _ssCtr.searchSuggestList[index].textRich,
),
);
},
)
: const SizedBox(),
);
}
Widget hotSearch(ctr) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 14, 4, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(6, 0, 6, 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'大家都在搜',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
),
SizedBox(
height: 34,
child: TextButton.icon(
style: ButtonStyle(
padding: MaterialStateProperty.all(const EdgeInsets.only(
left: 10, top: 6, bottom: 6, right: 10)),
),
onPressed: () => ctr.queryHotSearchList(),
icon: const Icon(Icons.refresh_outlined, size: 18),
label: const Text('刷新'),
),
),
],
),
),
LayoutBuilder(
builder: (context, boxConstraints) {
final double width = boxConstraints.maxWidth;
return FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => HotKeyword(
width: width,
hotSearchList: _searchController.hotSearchList.value,
onClick: (keyword) async {
_searchController.searchFocusNode.unfocus();
await Future.delayed(
const Duration(milliseconds: 150));
_searchController.onClickKeyword(keyword);
},
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else {
// 缓存数据
if (_searchController.hotSearchList.isNotEmpty) {
return HotKeyword(
width: width,
hotSearchList: _searchController.hotSearchList,
);
} else {
return const SizedBox();
}
}
},
);
},
),
],
),
);
}
Widget _history() {
return Obx(
() => Container(
width: double.infinity,
padding: const EdgeInsets.fromLTRB(10, 25, 6, 0),
padding: const EdgeInsets.fromLTRB(10, 4, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -7,7 +7,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'controller.dart';
import 'widgets/article_panel.dart';
import 'widgets/live_panel.dart';
import 'widgets/media_bangumi_panel.dart';
import 'widgets/user_panel.dart';
@ -85,14 +84,12 @@ class _SearchPanelState extends State<SearchPanel>
ctr: _searchPanelController,
list: list.value,
);
case SearchType.media_bangumi:
return searchMbangumiPanel(context, ctr, list);
// case SearchType.media_bangumi:
// return searchMbangumiPanel(context, ctr, list);
case SearchType.bili_user:
return searchUserPanel(context, ctr, list);
case SearchType.live_room:
return searchLivePanel(context, ctr, list);
case SearchType.article:
return searchArticlePanel(context, ctr, list);
// case SearchType.live_room:
// return searchLivePanel(context, ctr, list);
default:
return const SizedBox();
}
@ -118,12 +115,12 @@ class _SearchPanelState extends State<SearchPanel>
switch (widget.searchType) {
case SearchType.video:
return const VideoCardHSkeleton();
case SearchType.media_bangumi:
return const MediaBangumiSkeleton();
// case SearchType.media_bangumi:
// return const MediaBangumiSkeleton();
case SearchType.bili_user:
return const VideoCardHSkeleton();
case SearchType.live_room:
return const VideoCardHSkeleton();
// case SearchType.live_room:
// return const VideoCardHSkeleton();
default:
return const VideoCardHSkeleton();
}

View File

@ -1,107 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
Widget searchArticlePanel(BuildContext context, ctr, list) {
TextStyle textStyle = TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline);
return ListView.builder(
controller: ctr!.scrollController,
itemCount: list.length,
itemBuilder: (context, index) {
return InkWell(
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(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
MediaQuery.of(context).textScaleFactor) /
2;
return Container(
constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (list[index].imageUrls != null &&
list[index].imageUrls.isNotEmpty)
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return NetworkImgLayer(
width: maxWidth,
height: maxHeight,
src: list[index].imageUrls.first,
);
}),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
maxLines: 2,
text: TextSpan(
children: [
for (var i in list[index].title) ...[
TextSpan(
text: i['text'],
style: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
]
],
),
),
const Spacer(),
Text(
Utils.dateFormat(list[index].pubTime,
formatType: 'detail'),
style: textStyle),
Row(
children: [
Text('${list[index].view}浏览', style: textStyle),
Text('', style: textStyle),
Text('${list[index].reply}评论', style: textStyle),
],
),
],
),
),
),
],
),
);
}),
),
);
},
);
}

View File

@ -124,7 +124,7 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
arguments: {
'pic': pic,
'heroTag': heroTag,
'videoType': SearchType.media_bangumi,
// 'videoType': SearchType.media_bangumi,
'bangumiItem': res['data'],
},
);

View File

@ -38,26 +38,11 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Text(
i!.uname,
style: const TextStyle(
fontSize: 14,
),
),
const SizedBox(width: 6),
Image.asset(
'assets/images/lv/lv${i!.level}.png',
height: 11,
),
],
),
Row(
children: [
Text('粉丝:${i.fans} ', style: style),
Text(' 视频:${i.videos}', style: style)
],
Text(
i!.uname,
style: const TextStyle(
fontSize: 14,
),
),
if (i.officialVerify['desc'] != '')
Text(

View File

@ -79,12 +79,6 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.enableWordRe,
defaultVal: false,
),
const SetSwitchItem(
title: '首页推荐刷新',
subTitle: '下拉刷新时保留上次内容',
setKey: SettingBoxKey.enableSaveLastData,
defaultVal: false,
),
ListTile(
dense: false,
title: Text('评论展示', style: titleStyle),

View File

@ -2,8 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
class SetDiaplayMode extends StatefulWidget {
const SetDiaplayMode({super.key});
@ -16,7 +14,6 @@ class _SetDiaplayModeState extends State<SetDiaplayMode> {
List<DisplayMode> modes = <DisplayMode>[];
DisplayMode? active;
DisplayMode? preferred;
Box setting = GStrorage.setting;
final ValueNotifier<int> page = ValueNotifier<int>(0);
late final PageController controller = PageController()
@ -32,36 +29,24 @@ class _SetDiaplayModeState extends State<SetDiaplayMode> {
});
}
// 获取所有的mode
Future<void> fetchAll() async {
preferred = await FlutterDisplayMode.preferred;
active = await FlutterDisplayMode.active;
await setting.put(SettingBoxKey.displayMode, preferred.toString());
// GStorage().setDisplayModeType(preferred!);
setState(() {});
}
// 初始化mode/手动设置
Future<void> init() async {
try {
modes = await FlutterDisplayMode.supported;
} on PlatformException catch (e) {
print(e);
}
var res = await getDisplayModeType(modes);
preferred = modes.toList().firstWhere((el) => el == res);
// var res = await GStorage().getDisplayModeType();
// preferred = modes.toList().firstWhere((el) => el == res);
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
Widget build(BuildContext context) {
return Scaffold(

View File

@ -1,304 +0,0 @@
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),
),
),
),
),
],
),
),
);
}
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
@ -55,18 +54,6 @@ class _PlaySettingState extends State<PlaySetting> {
),
body: ListView(
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(
title: '自动播放',
subTitle: '进入详情页自动播放',

View File

@ -22,14 +22,12 @@ class _StyleSettingState extends State<StyleSetting> {
Box setting = GStrorage.setting;
late int picQuality;
late ThemeType _tempThemeValue;
late dynamic defaultCustomRows;
@override
void initState() {
super.initState();
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
_tempThemeValue = settingController.themeType.value;
defaultCustomRows = setting.get(SettingBoxKey.customRows, defaultValue: 2);
}
@override
@ -78,37 +76,12 @@ class _StyleSettingState extends State<StyleSetting> {
setKey: SettingBoxKey.iosTransition,
defaultVal: false,
),
// SetSwitchItem(
// title: '首页单列',
// subTitle: '每行展示一个内容卡片',
// setKey: SettingBoxKey.enableSingleRow,
// defaultVal: false,
// 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()),
),
]
],
),
SetSwitchItem(
title: '首页单列',
subTitle: '每行展示一个内容卡片',
setKey: SettingBoxKey.enableSingleRow,
defaultVal: false,
callFn: (val) => {SmartDialog.showToast('下次启动时生效')},
),
ListTile(
dense: false,

View File

@ -13,13 +13,12 @@ import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/video/detail/widgets/header_control.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'widgets/header_control.dart';
class VideoDetailController extends GetxController
with GetSingleTickerProviderStateMixin {
/// 路由传参
@ -110,8 +109,6 @@ class VideoDetailController extends GetxController
enableHeart = false;
}
danmakuCid.value = cid.value;
///
if (Platform.isAndroid) {
floating = Floating();
}
@ -215,15 +212,14 @@ class VideoDetailController extends GetxController
direction: (firstVideo.width! - firstVideo.height!) > 0
? 'horizontal'
: 'vertical',
// 默认1倍速
speed: 1.0,
bvid: bvid,
cid: cid.value,
enableHeart: enableHeart,
isFirstTime: isFirstTime,
autoplay: autoplay,
);
/// 开启自动全屏时在player初始化完成后立即传入headerControl
plPlayerController.headerControl = headerControl;
}
// 视频链接
@ -274,8 +270,8 @@ class VideoDetailController extends GetxController
currentDecodeFormats = flag
? currentDecodeFormats
: VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!;
} catch (err) {
SmartDialog.showToast('DecodeFormats error: $err');
} catch (e) {
print(e);
}
/// 取出符合当前解码格式的videoItem
@ -287,7 +283,7 @@ class VideoDetailController extends GetxController
}
videoUrl = firstVideo.baseUrl!;
} catch (err) {
SmartDialog.showToast('firstVideo error: $err');
print(err);
}
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
@ -297,6 +293,7 @@ class VideoDetailController extends GetxController
try {
int resultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code);
if (data.dash!.dolby?.audio?.isNotEmpty == true) {
// 杜比
audiosList.insert(0, data.dash!.dolby!.audio!.first);
@ -310,20 +307,16 @@ class VideoDetailController extends GetxController
if (audiosList.isNotEmpty) {
List<int> numbers = audiosList.map((map) => map.id!).toList();
int closestNumber = Utils.findClosestNumber(resultAudioQa, numbers);
if (!numbers.contains(resultAudioQa) &&
numbers.any((e) => e > resultAudioQa)) {
if (!numbers.contains(resultAudioQa)) {
closestNumber = 30280;
}
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
} else {
firstAudio = AudioItem();
}
} catch (err) {
firstAudio = audiosList.isNotEmpty ? audiosList.first : AudioItem();
SmartDialog.showToast('firstAudio error: $err');
} catch (e) {
print(e);
}
audioUrl = firstAudio.baseUrl ?? '';
audioUrl = firstAudio!.baseUrl ?? '';
//
if (firstAudio.id != null) {
currentAudioQa = AudioQualityCode.fromCode(firstAudio.id!)!;

View File

@ -17,8 +17,6 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart';
import 'widgets/group_panel.dart';
class VideoIntroController extends GetxController {
// 视频bvid
String bvid = Get.parameters['bvid']!;
@ -61,11 +59,14 @@ class VideoIntroController extends GetxController {
RxString total = '1'.obs;
Timer? timer;
bool isPaused = false;
String heroTag = Get.arguments['heroTag'];
String heroTag = '';
@override
void onInit() {
super.onInit();
try {
heroTag = Get.arguments['heroTag'];
} catch (_) {}
userInfo = userInfoCache.get('userInfoCache');
if (Get.arguments.isNotEmpty) {
if (Get.arguments.containsKey('videoItem')) {
@ -106,10 +107,9 @@ class VideoIntroController extends GetxController {
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
}
// Get.find<VideoDetailController>(tag: heroTag).tabs.value = [
// '简介',
// '评论 ${result['data']!.stat!.reply}'
// ];
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
.tabs
.value = ['简介', '评论 ${result['data']!.stat!.reply}'];
// 获取到粉丝数再返回
await queryUserStat();
}
@ -430,20 +430,6 @@ class VideoIntroController extends GetxController {
}
followStatus['attribute'] = actionStatus;
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();
},
@ -459,7 +445,7 @@ class VideoIntroController extends GetxController {
Future changeSeasonOrbangu(bvid, cid, aid) async {
// 重新获取视频资源
VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
videoDetailCtr.bvid = bvid;
videoDetailCtr.cid.value = cid;
videoDetailCtr.danmakuCid.value = cid;
@ -468,7 +454,7 @@ class VideoIntroController extends GetxController {
try {
/// 未渲染回复组件时可能异常
VideoReplyController videoReplyCtr =
Get.find<VideoReplyController>(tag: heroTag);
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
videoReplyCtr.aid = aid;
videoReplyCtr.queryReplyList(type: 'init');
} catch (_) {}
@ -509,19 +495,7 @@ class VideoIntroController extends GetxController {
/// 列表循环或者顺序播放时,自动播放下一个
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;
// }
// }
// }
// }
bool isPages = false;
if (videoDetail.value.ugcSeason != null) {
UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
List<SectionItem> sections = ugcSeason.sections!;
@ -531,6 +505,11 @@ class VideoIntroController extends GetxController {
List<EpisodeItem> episodesList = sections[i].episodes!;
episodes.addAll(episodesList);
}
} else if (videoDetail.value.pages != null) {
isPages = true;
List<Part> pages = videoDetail.value.pages!;
episodes = [];
episodes.addAll(pages);
}
int currentIndex = episodes.indexWhere((e) => e.cid == lastPlayCid.value);
@ -549,16 +528,8 @@ class VideoIntroController extends GetxController {
}
}
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,
);
String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
changeSeasonOrbangu(rBvid, cid, rAid);
}
}

View File

@ -199,9 +199,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 视频介绍
showIntroDetail() {
if (loadingStatus) {
return;
}
feedBack();
showBottomSheet(
context: context,
@ -238,82 +235,106 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(),
child: Padding(
padding: const EdgeInsets.only(bottom: 6),
child: Text(
!loadingStatus
? widget.videoDetail!.title
: videoItem['title'],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(),
child: Row(
children: [
StatView(
theme: 'gray',
view: !widget.loadingStatus
? widget.videoDetail!.stat!.view
: videoItem['stat'].view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'gray',
danmu: !widget.loadingStatus
? widget.videoDetail!.stat!.danmaku
: videoItem['stat'].danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!widget.loadingStatus
? widget.videoDetail!.pubdate
: videoItem['pubdate'],
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 10),
if (videoIntroController.isShowOnlineTotal)
Obx(
() => Text(
'${videoIntroController.total.value}人在看',
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
Expanded(
child: Text(
!loadingStatus
? widget.videoDetail!.title
: videoItem['title'],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// const SizedBox(width: 20),
// SizedBox(
// width: 34,
// height: 34,
// child: IconButton(
// style: ButtonStyle(
// padding:
// MaterialStateProperty.all(EdgeInsets.zero),
// backgroundColor:
// MaterialStateProperty.resolveWith((states) {
// return t.highlightColor.withOpacity(0.2);
// }),
// ),
// onPressed: showIntroDetail,
// icon: Icon(
// Icons.more_horiz,
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// ),
],
),
),
const SizedBox(height: 7),
// 点赞收藏转发 布局样式1
// SingleChildScrollView(
// padding: const EdgeInsets.only(top: 7, bottom: 7),
// scrollDirection: Axis.horizontal,
// child: actionRow(
// context,
// videoIntroController,
// videoDetailCtr,
// GestureDetector(
// behavior: HitTestBehavior.translucent,
// onTap: () => showIntroDetail(),
// child: Row(
// children: [
// StatView(
// theme: 'gray',
// view: !widget.loadingStatus
// ? widget.videoDetail!.stat!.view
// : videoItem['stat'].view,
// size: 'medium',
// ),
// const SizedBox(width: 10),
// StatDanMu(
// theme: 'gray',
// danmu: !widget.loadingStatus
// ? widget.videoDetail!.stat!.danmaku
// : videoItem['stat'].danmaku,
// size: 'medium',
// ),
// const SizedBox(width: 10),
// Text(
// Utils.dateFormat(
// !widget.loadingStatus
// ? widget.videoDetail!.pubdate
// : videoItem['pubdate'],
// formatType: 'detail'),
// style: TextStyle(
// fontSize: 12,
// color: t.colorScheme.outline,
// ),
// ),
// const SizedBox(width: 10),
// if (videoIntroController.isShowOnlineTotal)
// Obx(
// () => Text(
// '${videoIntroController.total.value}人在看',
// style: TextStyle(
// fontSize: 12,
// color: t.colorScheme.outline,
// ),
// ),
// ),
// ],
// ),
// ),
// const SizedBox(height: 7),
// 点赞收藏转发 布局样式1
SingleChildScrollView(
padding: const EdgeInsets.only(top: 10, bottom: 7),
scrollDirection: Axis.horizontal,
child: actionRow(
context,
videoIntroController,
videoDetailCtr,
),
),
// 点赞收藏转发 布局样式2
actionGrid(context, videoIntroController),
// actionGrid(context, videoIntroController),
// 合集
if (!loadingStatus &&
widget.videoDetail!.ugcSeason != null) ...[
@ -361,14 +382,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
const SizedBox(width: 10),
Text(owner.name,
style: const TextStyle(fontSize: 13)),
const SizedBox(width: 6),
Text(
follower,
style: TextStyle(
fontSize: t.textTheme.labelSmall!.fontSize,
color: outline,
),
),
const Spacer(),
AnimatedOpacity(
opacity: loadingStatus ? 0 : 1,
@ -431,7 +444,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Widget actionGrid(BuildContext context, videoIntroController) {
return LayoutBuilder(builder: (context, constraints) {
return Container(
margin: const EdgeInsets.only(top: 6, bottom: 4),
padding: const EdgeInsets.only(top: 6, bottom: 10),
height: constraints.maxWidth / 5 * 0.8,
child: GridView.count(
primary: false,
@ -450,12 +463,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
? widget.videoDetail!.stat!.like!.toString()
: '-'),
),
// ActionItem(
// icon: const Icon(FontAwesomeIcons.clock),
// onTap: () => videoIntroController.actionShareVideo(),
// selectStatus: false,
// loadingStatus: loadingStatus,
// text: '稍后再看'),
ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: loadingStatus,
text: '稍后再看'),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
@ -471,28 +484,22 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
() => ActionItem(
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
// onTap: () => videoIntroController.actionFavVideo(),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: loadingStatus,
text: !loadingStatus
? 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(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: loadingStatus,
text: '分享'),
text: !loadingStatus
? widget.videoDetail!.stat!.share!.toString()
: '-'),
],
),
);
@ -501,52 +508,52 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
return Row(children: [
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
onTap: () => videoIntroController.actionLikeVideo(),
selectStatus: videoIntroController.hasLike.value,
loadingStatus: loadingStatus,
text:
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.b),
onTap: () => videoIntroController.actionCoinVideo(),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: loadingStatus,
text:
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: loadingStatus,
text: !loadingStatus
? widget.videoDetail!.stat!.favorite!.toString()
: '-',
),
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.comment),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: loadingStatus,
text:
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
),
const SizedBox(width: 8),
// Obx(
// () => ActionRowItem(
// icon: const Icon(FontAwesomeIcons.thumbsUp),
// onTap: () => videoIntroController.actionLikeVideo(),
// selectStatus: videoIntroController.hasLike.value,
// loadingStatus: loadingStatus,
// text:
// !loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
// ),
// ),
// const SizedBox(width: 8),
// Obx(
// () => ActionRowItem(
// icon: const Icon(FontAwesomeIcons.b),
// onTap: () => videoIntroController.actionCoinVideo(),
// selectStatus: videoIntroController.hasCoin.value,
// loadingStatus: loadingStatus,
// text:
// !loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
// ),
// ),
// const SizedBox(width: 8),
// Obx(
// () => ActionRowItem(
// icon: const Icon(FontAwesomeIcons.heart),
// onTap: () => showFavBottomSheet(),
// onLongPress: () => showFavBottomSheet(type: 'longPress'),
// selectStatus: videoIntroController.hasFav.value,
// loadingStatus: loadingStatus,
// text: !loadingStatus
// ? widget.videoDetail!.stat!.favorite!.toString()
// : '-',
// ),
// ),
// const SizedBox(width: 8),
// ActionRowItem(
// icon: const Icon(FontAwesomeIcons.comment),
// onTap: () {
// videoDetailCtr.tabCtr.animateTo(1);
// },
// selectStatus: false,
// loadingStatus: loadingStatus,
// text:
// !loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
// ),
// const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.share),
onTap: () => videoIntroController.actionShareVideo(),

View File

@ -6,7 +6,6 @@ class ActionItem extends StatelessWidget {
final Icon? icon;
final Icon? selectIcon;
final Function? onTap;
final Function? onLongPress;
final bool? loadingStatus;
final String? text;
final bool selectStatus;
@ -16,7 +15,6 @@ class ActionItem extends StatelessWidget {
this.icon,
this.selectIcon,
this.onTap,
this.onLongPress,
this.loadingStatus,
this.text,
this.selectStatus = false,
@ -29,9 +27,6 @@ class ActionItem extends StatelessWidget {
feedBack(),
onTap!(),
},
onLongPress: () => {
if (onLongPress != null) {onLongPress!()}
},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,

View File

@ -1,156 +0,0 @@
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 ? '保存至默认分组' : '保存'),
),
],
),
),
],
),
);
}
}

View File

@ -1,6 +1,5 @@
import 'package:flutter/gestures.dart';
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/stat/danmu.dart';
@ -58,29 +57,13 @@ class IntroDetail extends StatelessWidget {
),
),
const SizedBox(height: 6),
Row(
children: [
StatView(
theme: 'gray',
view: videoDetail!.stat!.view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'gray',
danmu: videoDetail!.stat!.danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(height: 20),
SizedBox(
@ -130,50 +113,7 @@ class IntroDetail extends StatelessWidget {
final currentDesc = descV2[index];
switch (currentDesc.type) {
case 1:
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;
return TextSpan(text: currentDesc.rawText);
case 2:
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
final heroTag = Utils.makeHeroTag(currentDesc.bizId);

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/index.dart';
class PagesPanel extends StatefulWidget {
final List<Part> pages;
@ -23,23 +22,13 @@ class PagesPanel extends StatefulWidget {
class _PagesPanelState extends State<PagesPanel> {
late List<Part> episodes;
late int cid;
late int currentIndex;
String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController;
@override
void initState() {
super.initState();
cid = widget.cid!;
episodes = widget.pages;
_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);
});
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
}
void changeFucCall(item, i) async {

View File

@ -42,7 +42,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
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 == cid) {
if (episodesList[j].cid == widget.cid) {
episodes = episodesList;
continue;
}
@ -53,7 +53,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
// episodes = widget.ugcSeason.sections!
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
// .episodes!;
currentIndex = episodes.indexWhere((e) => e.cid == cid);
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
_videoDetailController.cid.listen((p0) {
cid = p0;
setState(() {});
@ -68,9 +68,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
item.aid,
);
currentIndex = i;
setState(() {});
Get.back();
setState(() {});
}
@override

View File

@ -96,8 +96,8 @@ class VideoReplyController extends GetxController {
} else {
replyList.addAll(replies);
}
count.value = res['data'].page.count;
}
count.value = res['data'].page.count;
isLoadingMore = false;
return res;
}

View File

@ -39,7 +39,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
Future? _futureBuilderFuture;
bool _isFabVisible = true;
String replyLevel = '1';
late String heroTag;
// 添加页面缓存
@override
@ -47,29 +46,22 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
@override
void initState() {
super.initState();
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
heroTag = Get.arguments['heroTag'];
super.initState();
replyLevel = widget.replyLevel ?? '1';
if (replyLevel == '2') {
_videoReplyController = Get.put(
VideoReplyController(oid, widget.rpid.toString(), replyLevel),
tag: widget.rpid.toString());
} else {
_videoReplyController =
Get.put(VideoReplyController(oid, '', replyLevel), tag: heroTag);
_videoReplyController = Get.put(VideoReplyController(oid, '', replyLevel),
tag: Get.arguments['heroTag']);
}
fabAnimationCtr = AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
_futureBuilderFuture = _videoReplyController.queryReplyList();
fabAnimationCtr.forward();
scrollListener();
}
void scrollListener() {
scrollController = _videoReplyController.scrollController;
scrollController.addListener(
() {
@ -89,6 +81,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
}
},
);
fabAnimationCtr.forward();
}
void _showFab() {
@ -108,7 +101,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
// 展示二级回复
void replyReply(replyItem) {
VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
if (replyItem != null) {
videoDetailCtr.oid = replyItem.oid;
videoDetailCtr.fRpid = replyItem.rpid!;
@ -119,10 +112,9 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
@override
void dispose() {
scrollController.removeListener(() {});
super.dispose();
fabAnimationCtr.dispose();
scrollController.dispose();
super.dispose();
}
@override
@ -136,7 +128,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
child: Stack(
children: [
CustomScrollView(
controller: scrollController,
controller: _videoReplyController.scrollController,
key: const PageStorageKey<String>('评论'),
slivers: <Widget>[
SliverPersistentHeader(
@ -195,7 +187,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
Map data = snapshot.data as Map;
if (data['status']) {
// 请求成功
return Obx(

View File

@ -11,7 +11,6 @@ import 'package:pilipala/pages/preview/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.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/utils.dart';
@ -668,11 +667,10 @@ InlineSpan buildContent(
// 匹配 jumpUrl
String matchUrl = matchMember;
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
List urlKeys = content.jumpUrl.keys.toList().reversed.toList();
List urlKeys = content.jumpUrl.keys.toList();
matchUrl = matchMember.splitMapJoin(
/// RegExp.escape() 转义特殊字符
RegExp(urlKeys.map((key) => key).join("|")),
// RegExp(RegExp.escape(urlKeys.join("|"))),
RegExp(RegExp.escape(urlKeys.join("|"))),
onMatch: (Match match) {
String matchStr = match[0]!;
String appUrlSchema = content.jumpUrl[matchStr]['app_url_schema'];
@ -690,26 +688,14 @@ InlineSpan buildContent(
recognizer: TapGestureRecognizer()
..onTap = () {
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(
'/webview',
parameters: {
'url': matchStr,
'type': 'url',
'pageTitle': ''
},
);
}
Get.toNamed(
'/webview',
parameters: {
'url': matchStr,
'type': 'url',
'pageTitle': ''
},
);
} else {
if (appUrlSchema.startsWith('bilibili://search') &&
enableWordRe) {
@ -758,14 +744,11 @@ InlineSpan buildContent(
recognizer: TapGestureRecognizer()
..onTap = () {
// 跳转到指定位置
try {
Get.find<VideoDetailController>(
tag: Get.arguments['heroTag'])
.plPlayerController
.seekTo(
Duration(seconds: Utils.duration(matchStr)),
);
} catch (_) {}
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
.plPlayerController
.seekTo(
Duration(seconds: Utils.duration(matchStr)),
);
},
),
);

View File

@ -26,6 +26,11 @@ class VideoReplyReplyController extends GetxController {
currentPage = 0;
}
// 上拉加载
Future onLoad() async {
queryReplyList(type: 'onLoad');
}
Future queryReplyList({type = 'init'}) async {
if (type == 'init') {
currentPage = 0;
@ -44,11 +49,11 @@ class VideoReplyReplyController extends GetxController {
if (replyList.length == res['data'].page.count) {
noMore.value = '没有更多了';
}
currentPage++;
} else {
// 未登录状态replies可能返回null
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
}
currentPage++;
if (type == 'init') {
// List<ReplyItemModel> replies = res['data'].replies;
// 添加置顶回复
@ -67,10 +72,6 @@ class VideoReplyReplyController extends GetxController {
// res['data'].replies = replies;
replyList.value = replies;
} else {
// 每次回复之后,翻页请求有且只有相同的一条回复数据
if (replies.length == 1 && replies.last.rpid == replyList.last.rpid) {
return;
}
replyList.addAll(replies);
// res['data'].replies.addAll(replyList);
}

View File

@ -1,4 +1,3 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@ -55,9 +54,9 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
_videoReplyReplyController.queryReplyList(type: 'onLoad');
});
if (!_videoReplyReplyController.isLoadingMore) {
_videoReplyReplyController.onLoad();
}
}
},
);

Some files were not shown because too many files have changed in this diff Show More