Compare commits
29 Commits
v1.0.8.091
...
feature-bu
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ece96df21 | |||
| c11c5695a2 | |||
| 93581c2932 | |||
| fd43a8cb31 | |||
| 6f34bacb64 | |||
| 90314f89ed | |||
| 45c53de2c2 | |||
| 5c07cb4545 | |||
| 844053b138 | |||
| 6af8b91f63 | |||
| 8aa02f7450 | |||
| 38a1f2e1f7 | |||
| b2e1d98f51 | |||
| e19cf92992 | |||
| 80e10aeaad | |||
| b1a9152a49 | |||
| 935b7577b3 | |||
| fd5c4463d2 | |||
| 7fcbe4dd9d | |||
| 9a0c9f4021 | |||
| 6b3773a074 | |||
| 7be8ebaa7e | |||
| 092b1cee3d | |||
| 252f39e8c7 | |||
| e631ca04a0 | |||
| 3665d6a0f6 | |||
| e3c9e8c13b | |||
| 39995bae23 | |||
| f42d0d01ea |
@ -1,24 +0,0 @@
|
|||||||
## 1.0.8
|
|
||||||
|
|
||||||
直播弹幕、循环播放等功能开发中
|
|
||||||
|
|
||||||
### 新功能
|
|
||||||
+ 用户拉黑功能
|
|
||||||
+ gif图片保存
|
|
||||||
+ 删除已看历史记录
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 弹幕数量较少
|
|
||||||
+ 弹幕屏蔽设置自动记忆
|
|
||||||
+ 动态页面渲染
|
|
||||||
+ 用户主页数据错乱
|
|
||||||
+ 大家都在搜空白
|
|
||||||
+ 默认自动全屏,顶部操作栏丢失
|
|
||||||
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 全屏状态栏区域显示优化
|
|
||||||
+ 图片保存至PiliPala文件夹
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class HtmlRender extends StatelessWidget {
|
class HtmlRender extends StatelessWidget {
|
||||||
@ -20,47 +20,35 @@ class HtmlRender extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Html(
|
return Html(
|
||||||
data: htmlContent,
|
data: htmlContent,
|
||||||
|
// tagsList: Html.tags..addAll(["form", "label", "input"]),
|
||||||
onLinkTap: (url, buildContext, attributes) => {},
|
onLinkTap: (url, buildContext, attributes) => {},
|
||||||
extensions: [
|
extensions: [
|
||||||
TagExtension(
|
TagExtension(
|
||||||
tagsToExtend: {"img"},
|
tagsToExtend: {"img"},
|
||||||
builder: (extensionContext) {
|
builder: (extensionContext) {
|
||||||
try {
|
String? imgUrl = extensionContext.attributes['src'];
|
||||||
Map attributes = extensionContext.attributes;
|
|
||||||
List key = attributes.keys.toList();
|
|
||||||
String? imgUrl = key.contains('src')
|
|
||||||
? attributes['src']
|
|
||||||
: attributes['data-src'];
|
|
||||||
if (imgUrl!.startsWith('//')) {
|
if (imgUrl!.startsWith('//')) {
|
||||||
imgUrl = 'https:$imgUrl';
|
imgUrl = 'https:$imgUrl';
|
||||||
}
|
}
|
||||||
if (imgUrl.startsWith('http://')) {
|
if (imgUrl.startsWith('http://')) {
|
||||||
imgUrl = imgUrl.replaceAll('http://', 'https://');
|
imgUrl = imgUrl.replaceAll('http://', 'https://');
|
||||||
}
|
}
|
||||||
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
|
|
||||||
|
print(imgUrl);
|
||||||
bool isEmote = imgUrl.contains('/emote/');
|
bool isEmote = imgUrl.contains('/emote/');
|
||||||
bool isMall = imgUrl.contains('/mall/');
|
bool isMall = imgUrl.contains('/mall/');
|
||||||
if (isMall) {
|
if (isMall) {
|
||||||
return const SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
// bool inTable =
|
// bool inTable =
|
||||||
// extensionContext.element!.previousElementSibling == null ||
|
// extensionContext.element!.previousElementSibling == null ||
|
||||||
// extensionContext.element!.nextElementSibling == null;
|
// extensionContext.element!.nextElementSibling == null;
|
||||||
// imgUrl = Utils().imageUrl(imgUrl!);
|
// imgUrl = Utils().imageUrl(imgUrl!);
|
||||||
// return Image.network(
|
return Image.network(
|
||||||
// imgUrl,
|
imgUrl,
|
||||||
// width: isEmote ? 22 : null,
|
width: isEmote ? 22 : null,
|
||||||
// height: 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();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -75,13 +63,11 @@ class HtmlRender extends StatelessWidget {
|
|||||||
textDecoration: TextDecoration.none,
|
textDecoration: TextDecoration.none,
|
||||||
),
|
),
|
||||||
"p": Style(
|
"p": Style(
|
||||||
margin: Margins.only(bottom: 10),
|
margin: Margins.only(bottom: 0),
|
||||||
),
|
),
|
||||||
"span": Style(
|
"span": Style(
|
||||||
fontSize: FontSize.medium,
|
fontSize: FontSize.medium,
|
||||||
height: Height(1.65),
|
|
||||||
),
|
),
|
||||||
"div": Style(height: Height.auto()),
|
|
||||||
"li > p": Style(
|
"li > p": Style(
|
||||||
display: Display.inline,
|
display: Display.inline,
|
||||||
),
|
),
|
||||||
@ -89,7 +75,61 @@ class HtmlRender extends StatelessWidget {
|
|||||||
padding: HtmlPaddings.only(bottom: 4),
|
padding: HtmlPaddings.only(bottom: 4),
|
||||||
textAlign: TextAlign.justify,
|
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,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
|
|||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
@ -33,9 +34,10 @@ class VideoCardH extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(aid);
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (longPress != null) {
|
// if (longPress != null) {
|
||||||
longPress!();
|
// longPress!();
|
||||||
}
|
// }
|
||||||
|
MemberController().blockUser(videoItem.mid);
|
||||||
},
|
},
|
||||||
// onLongPressEnd: (details) {
|
// onLongPressEnd: (details) {
|
||||||
// if (longPressEnd != null) {
|
// if (longPressEnd != null) {
|
||||||
@ -188,46 +190,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
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(),
|
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')
|
if (source == 'normal')
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24,
|
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))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class VideoCardV extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': videoItem.pic,
|
'pic': videoItem.pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -62,15 +62,6 @@ class VideoCardV extends StatelessWidget {
|
|||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
// 动态
|
|
||||||
case 'picture':
|
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
|
||||||
'url': videoItem.uri,
|
|
||||||
'title': videoItem.title,
|
|
||||||
'id': videoItem.param.toString(),
|
|
||||||
'dynamicType': 'picture'
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
SmartDialog.showToast(videoItem.goto);
|
SmartDialog.showToast(videoItem.goto);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
|
|||||||
@ -97,9 +97,6 @@ class Api {
|
|||||||
// 操作用户关系
|
// 操作用户关系
|
||||||
static const String relationMod = '/x/relation/modify';
|
static const String relationMod = '/x/relation/modify';
|
||||||
|
|
||||||
// 相互关系查询
|
|
||||||
static const String relationSearch = '/x/space/wbi/acc/relation';
|
|
||||||
|
|
||||||
// 评论列表
|
// 评论列表
|
||||||
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
|
||||||
static const String replyList = '/x/v2/reply';
|
static const String replyList = '/x/v2/reply';
|
||||||
@ -245,6 +242,9 @@ class Api {
|
|||||||
// wts=1689767832
|
// wts=1689767832
|
||||||
static const String memberArchive = '/x/space/wbi/arc/search';
|
static const String memberArchive = '/x/space/wbi/arc/search';
|
||||||
|
|
||||||
|
// 用户动态搜索
|
||||||
|
static const String memberDynamicSearch = '/x/space/dynamic/search';
|
||||||
|
|
||||||
// 用户动态
|
// 用户动态
|
||||||
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
static const String memberDynamic = '/x/polymer/web-dynamic/v1/feed/space';
|
||||||
|
|
||||||
@ -303,4 +303,7 @@ class Api {
|
|||||||
static const String onlineTotal = '/x/player/online/total';
|
static const String onlineTotal = '/x/player/online/total';
|
||||||
|
|
||||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||||
|
|
||||||
|
// 搜索历史记录
|
||||||
|
static const String searchHistory = '/x/web-goblin/history/search';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,37 +2,4 @@ class HttpString {
|
|||||||
static const String baseUrl = 'https://www.bilibili.com';
|
static const String baseUrl = 'https://www.bilibili.com';
|
||||||
static const String baseApiUrl = 'https://api.bilibili.com';
|
static const String baseApiUrl = 'https://api.bilibili.com';
|
||||||
static const String tUrl = 'https://api.vc.bilibili.com';
|
static const String tUrl = 'https://api.vc.bilibili.com';
|
||||||
static const List<int> validateStatusCodes = [
|
|
||||||
302,
|
|
||||||
304,
|
|
||||||
307,
|
|
||||||
400,
|
|
||||||
401,
|
|
||||||
403,
|
|
||||||
404,
|
|
||||||
405,
|
|
||||||
409,
|
|
||||||
412,
|
|
||||||
500,
|
|
||||||
503,
|
|
||||||
504,
|
|
||||||
509,
|
|
||||||
616,
|
|
||||||
617,
|
|
||||||
625,
|
|
||||||
626,
|
|
||||||
628,
|
|
||||||
629,
|
|
||||||
632,
|
|
||||||
643,
|
|
||||||
650,
|
|
||||||
652,
|
|
||||||
658,
|
|
||||||
662,
|
|
||||||
688,
|
|
||||||
689,
|
|
||||||
701,
|
|
||||||
799,
|
|
||||||
8888
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,9 @@ class DanmakaHttp {
|
|||||||
'oid': cid,
|
'oid': cid,
|
||||||
'segment_index': segmentIndex,
|
'segment_index': segmentIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 计算函数
|
||||||
|
Future<DmSegMobileReply> computeTask(Map<String, int> params) async {
|
||||||
var response = await Request().get(
|
var response = await Request().get(
|
||||||
Api.webDanmaku,
|
Api.webDanmaku,
|
||||||
data: params,
|
data: params,
|
||||||
@ -24,4 +27,7 @@ class DanmakaHttp {
|
|||||||
);
|
);
|
||||||
return DmSegMobileReply.fromBuffer(response.data);
|
return DmSegMobileReply.fromBuffer(response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await compute(computeTask, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,8 @@ import 'package:html/parser.dart';
|
|||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
|
|
||||||
class HtmlHttp {
|
class HtmlHttp {
|
||||||
// article
|
static Future reqHtml(id) async {
|
||||||
static Future reqHtml(id, dynamicType) async {
|
var response = await Request().get("https://www.bilibili.com/opus/$id");
|
||||||
var response = await Request().get(
|
|
||||||
"https://www.bilibili.com/opus/$id",
|
|
||||||
extra: {'ua': 'pc'},
|
|
||||||
);
|
|
||||||
Document rootTree = parse(response.data);
|
Document rootTree = parse(response.data);
|
||||||
Element body = rootTree.body!;
|
Element body = rootTree.body!;
|
||||||
Element appDom = body.querySelector('#app')!;
|
Element appDom = body.querySelector('#app')!;
|
||||||
@ -38,46 +34,7 @@ class HtmlHttp {
|
|||||||
'uname': uname,
|
'uname': uname,
|
||||||
'updateTime': updateTime,
|
'updateTime': updateTime,
|
||||||
'content': opusContent,
|
'content': opusContent,
|
||||||
'commentId': int.parse(commentId)
|
'commentId': commentId
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// read
|
|
||||||
static Future reqReadHtml(id, dynamicType) async {
|
|
||||||
var response = await Request().get(
|
|
||||||
"https://www.bilibili.com/$dynamicType/$id/",
|
|
||||||
extra: {'ua': 'pc'},
|
|
||||||
);
|
|
||||||
Document rootTree = parse(response.data);
|
|
||||||
Element body = rootTree.body!;
|
|
||||||
Element appDom = body.querySelector('#app')!;
|
|
||||||
Element authorHeader = appDom.querySelector('.up-left')!;
|
|
||||||
// 头像
|
|
||||||
// String avatar =
|
|
||||||
// authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
|
|
||||||
// print(avatar);
|
|
||||||
// avatar = 'https:${avatar.split('@')[0]}';
|
|
||||||
String uname = authorHeader.querySelector('.up-name')!.text.trim();
|
|
||||||
// 动态详情
|
|
||||||
Element opusDetail = appDom.querySelector('.article-content')!;
|
|
||||||
// 发布时间
|
|
||||||
// String updateTime =
|
|
||||||
// opusDetail.querySelector('.opus-module-author__pub__text')!.text;
|
|
||||||
// print(updateTime);
|
|
||||||
|
|
||||||
//
|
|
||||||
String opusContent =
|
|
||||||
opusDetail.querySelector('#read-article-holder')!.innerHtml;
|
|
||||||
RegExp digitRegExp = RegExp(r'\d+');
|
|
||||||
Iterable<Match> matches = digitRegExp.allMatches(id);
|
|
||||||
String number = matches.first.group(0)!;
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'avatar': '',
|
|
||||||
'uname': uname,
|
|
||||||
'updateTime': '',
|
|
||||||
'content': opusContent,
|
|
||||||
'commentId': int.parse(number)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import 'dart:io';
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:cookie_jar/cookie_jar.dart';
|
import 'package:cookie_jar/cookie_jar.dart';
|
||||||
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -60,6 +59,9 @@ class Request {
|
|||||||
static Future<String> getCsrf() async {
|
static Future<String> getCsrf() async {
|
||||||
var cookies = await cookieManager.cookieJar
|
var cookies = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
|
.loadForRequest(Uri.parse(HttpString.baseApiUrl));
|
||||||
|
// for (var i in cookies) {
|
||||||
|
// print(i);
|
||||||
|
// }
|
||||||
String token = '';
|
String token = '';
|
||||||
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
|
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
|
||||||
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
|
||||||
@ -89,18 +91,15 @@ class Request {
|
|||||||
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
//响应流上前后两次接受到数据的间隔,单位为毫秒。
|
||||||
receiveTimeout: const Duration(milliseconds: 12000),
|
receiveTimeout: const Duration(milliseconds: 12000),
|
||||||
//Http请求头.
|
//Http请求头.
|
||||||
headers: {},
|
headers: {
|
||||||
|
'keep-alive': true,
|
||||||
|
'user-agent': headerUa('pc'),
|
||||||
|
'Accept-Encoding': 'gzip'
|
||||||
|
},
|
||||||
|
persistentConnection: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
dio = Dio(options)
|
dio = Dio(options);
|
||||||
|
|
||||||
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
|
|
||||||
..httpClientAdapter = Http2Adapter(
|
|
||||||
ConnectionManager(
|
|
||||||
idleTimeout: const Duration(milliseconds: 10000),
|
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
dio.interceptors.add(ApiInterceptor());
|
dio.interceptors.add(ApiInterceptor());
|
||||||
@ -114,26 +113,30 @@ class Request {
|
|||||||
|
|
||||||
dio.transformer = BackgroundTransformer();
|
dio.transformer = BackgroundTransformer();
|
||||||
dio.options.validateStatus = (status) {
|
dio.options.validateStatus = (status) {
|
||||||
return status! >= 200 && status < 300 ||
|
return status! >= 200 && status < 300 || status == 304 || status == 302;
|
||||||
HttpString.validateStatusCodes.contains(status);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get请求
|
* get请求
|
||||||
*/
|
*/
|
||||||
get(url, {data, options, cancelToken, extra}) async {
|
get(url, {data, cacheOptions, options, cancelToken, extra}) async {
|
||||||
Response response;
|
Response response;
|
||||||
Options options = Options();
|
Options options;
|
||||||
|
String ua = 'pc';
|
||||||
ResponseType resType = ResponseType.json;
|
ResponseType resType = ResponseType.json;
|
||||||
if (extra != null) {
|
if (extra != null) {
|
||||||
|
ua = extra!['ua'] ?? 'pc';
|
||||||
resType = extra!['resType'] ?? ResponseType.json;
|
resType = extra!['resType'] ?? ResponseType.json;
|
||||||
if (extra['ua'] != null) {
|
|
||||||
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (cacheOptions != null) {
|
||||||
|
cacheOptions.headers = {'user-agent': headerUa(ua)};
|
||||||
|
options = cacheOptions;
|
||||||
|
} else {
|
||||||
|
options = Options();
|
||||||
|
options.headers = {'user-agent': headerUa(ua)};
|
||||||
options.responseType = resType;
|
options.responseType = resType;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
response = await dio.get(
|
response = await dio.get(
|
||||||
url,
|
url,
|
||||||
@ -200,19 +203,15 @@ class Request {
|
|||||||
token.cancel("cancelled");
|
token.cancel("cancelled");
|
||||||
}
|
}
|
||||||
|
|
||||||
String headerUa({type = 'mob'}) {
|
String headerUa(ua) {
|
||||||
String headerUa = '';
|
String headerUa = '';
|
||||||
if (type == 'mob') {
|
if (ua == 'mob') {
|
||||||
if (Platform.isIOS) {
|
headerUa = Platform.isIOS
|
||||||
headerUa =
|
? '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 (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';
|
: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36';
|
||||||
} else {
|
} else {
|
||||||
headerUa =
|
headerUa =
|
||||||
'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Mobile Safari/537.36';
|
'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';
|
||||||
}
|
|
||||||
} 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';
|
|
||||||
}
|
}
|
||||||
return headerUa;
|
return headerUa;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,10 +46,7 @@ class ApiInterceptor extends Interceptor {
|
|||||||
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
void onError(DioException err, ErrorInterceptorHandler handler) async {
|
||||||
// 处理网络请求错误
|
// 处理网络请求错误
|
||||||
// handler.next(err);
|
// handler.next(err);
|
||||||
SmartDialog.showToast(
|
SmartDialog.showToast(await dioError(err));
|
||||||
await dioError(err),
|
|
||||||
displayType: SmartToastType.onlyRefresh,
|
|
||||||
);
|
|
||||||
super.onError(err, handler);
|
super.onError(err, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ class MemberHttp {
|
|||||||
int ps = 30,
|
int ps = 30,
|
||||||
int tid = 0,
|
int tid = 0,
|
||||||
int? pn,
|
int? pn,
|
||||||
String keyword = '',
|
String? keyword,
|
||||||
String order = 'pubdate',
|
String order = 'pubdate',
|
||||||
bool orderAvoided = true,
|
bool orderAvoided = true,
|
||||||
}) async {
|
}) async {
|
||||||
@ -74,7 +74,7 @@ class MemberHttp {
|
|||||||
'ps': ps,
|
'ps': ps,
|
||||||
'tid': tid,
|
'tid': tid,
|
||||||
'pn': pn,
|
'pn': pn,
|
||||||
'keyword': keyword,
|
'keyword': keyword ?? '',
|
||||||
'order': order,
|
'order': order,
|
||||||
'platform': 'web',
|
'platform': 'web',
|
||||||
'web_location': 1550101,
|
'web_location': 1550101,
|
||||||
@ -119,4 +119,27 @@ class MemberHttp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 搜索用户动态
|
||||||
|
static Future memberDynamicSearch({int? pn, int? ps, int? mid}) async {
|
||||||
|
var res = await Request().get(Api.memberDynamic, data: {
|
||||||
|
'keyword': '海拔',
|
||||||
|
'mid': mid,
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps,
|
||||||
|
'platform': 'web'
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': DynamicsDataModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/index.dart';
|
import 'package:pilipala/http/index.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/search/hot.dart';
|
import 'package:pilipala/models/search/hot.dart';
|
||||||
import 'package:pilipala/models/search/result.dart';
|
import 'package:pilipala/models/search/result.dart';
|
||||||
import 'package:pilipala/models/search/suggest.dart';
|
import 'package:pilipala/models/search/suggest.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class SearchHttp {
|
class SearchHttp {
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
static Future hotSearchList() async {
|
static Future hotSearchList() async {
|
||||||
var res = await Request().get(Api.hotSearchList);
|
var res = await Request().get(Api.hotSearchList);
|
||||||
if (res.data is String) {
|
if (res.data is String) {
|
||||||
@ -36,14 +39,16 @@ class SearchHttp {
|
|||||||
static Future searchSuggest({required term}) async {
|
static Future searchSuggest({required term}) async {
|
||||||
var res = await Request().get(Api.serachSuggest,
|
var res = await Request().get(Api.serachSuggest,
|
||||||
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
|
||||||
if (res.data['code'] == 0) {
|
if (res.data is String) {
|
||||||
if (res.data['result'] is Map) {
|
Map<String, dynamic> resultMap = json.decode(res.data);
|
||||||
res.data['result']['term'] = term;
|
if (resultMap['code'] == 0) {
|
||||||
|
if (resultMap['result'] is Map) {
|
||||||
|
resultMap['result']['term'] = term;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': res.data['result'] is Map
|
'data': resultMap['result'] is Map
|
||||||
? SearchSuggestModel.fromJson(res.data['result'])
|
? SearchSuggestModel.fromJson(resultMap['result'])
|
||||||
: [],
|
: [],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@ -53,6 +58,13 @@ class SearchHttp {
|
|||||||
'msg': '请求错误 🙅',
|
'msg': '请求错误 🙅',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': '请求错误 🙅',
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分类搜索
|
// 分类搜索
|
||||||
@ -78,6 +90,12 @@ class SearchHttp {
|
|||||||
try {
|
try {
|
||||||
switch (searchType) {
|
switch (searchType) {
|
||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
|
for (var i in res.data['data']['result']) {
|
||||||
|
// 屏蔽推广和拉黑用户
|
||||||
|
i['available'] = !blackMidsList.contains(i['mid']);
|
||||||
|
}
|
||||||
data = SearchVideoModel.fromJson(res.data['data']);
|
data = SearchVideoModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
case SearchType.live_room:
|
case SearchType.live_room:
|
||||||
@ -89,9 +107,9 @@ class SearchHttp {
|
|||||||
case SearchType.media_bangumi:
|
case SearchType.media_bangumi:
|
||||||
data = SearchMBangumiModel.fromJson(res.data['data']);
|
data = SearchMBangumiModel.fromJson(res.data['data']);
|
||||||
break;
|
break;
|
||||||
// case SearchType.article:
|
case SearchType.article:
|
||||||
// data = SearchArticleModel.fromJson(res.data['data']);
|
data = SearchArticleModel.fromJson(res.data['data']);
|
||||||
// break;
|
break;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import 'package:pilipala/models/user/fav_folder.dart';
|
|||||||
import 'package:pilipala/models/user/history.dart';
|
import 'package:pilipala/models/user/history.dart';
|
||||||
import 'package:pilipala/models/user/info.dart';
|
import 'package:pilipala/models/user/info.dart';
|
||||||
import 'package:pilipala/models/user/stat.dart';
|
import 'package:pilipala/models/user/stat.dart';
|
||||||
import 'package:pilipala/utils/wbi_sign.dart';
|
|
||||||
|
|
||||||
class UserHttp {
|
class UserHttp {
|
||||||
static Future<dynamic> userStat({required int mid}) async {
|
static Future<dynamic> userStat({required int mid}) async {
|
||||||
@ -238,7 +237,7 @@ class UserHttp {
|
|||||||
var res = await Request().post(
|
var res = await Request().post(
|
||||||
Api.delHistory,
|
Api.delHistory,
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'kid': 'archive_$kid',
|
'kid': kid,
|
||||||
'jsonp': 'jsonp',
|
'jsonp': 'jsonp',
|
||||||
'csrf': await Request.getCsrf(),
|
'csrf': await Request.getCsrf(),
|
||||||
},
|
},
|
||||||
@ -250,26 +249,19 @@ class UserHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 相互关系查询
|
// 搜索历史记录
|
||||||
static Future relationSearch(int mid) async {
|
static Future searchHistory(
|
||||||
Map params = await WbiSign().makSign({
|
{required int pn, required String keyword}) async {
|
||||||
'mid': mid,
|
|
||||||
'token': '',
|
|
||||||
'platform': 'web',
|
|
||||||
'web_location': 1550101,
|
|
||||||
});
|
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.relationSearch,
|
Api.searchHistory,
|
||||||
data: {
|
data: {
|
||||||
'mid': mid,
|
'pn': pn,
|
||||||
'w_rid': params['w_rid'],
|
'keyword': keyword,
|
||||||
'wts': params['wts'],
|
'business': 'all',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
// relation 主动状态
|
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
|
||||||
// 被动状态
|
|
||||||
return {'status': true, 'data': res.data['data']};
|
|
||||||
} else {
|
} else {
|
||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ enum SearchType {
|
|||||||
// 用户:bili_user
|
// 用户:bili_user
|
||||||
bili_user,
|
bili_user,
|
||||||
// 专栏:article
|
// 专栏:article
|
||||||
// article,
|
article,
|
||||||
// 相簿:photo
|
// 相簿:photo
|
||||||
// photo
|
// photo
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ class SearchVideoModel {
|
|||||||
List<SearchVideoItemModel>? list;
|
List<SearchVideoItemModel>? list;
|
||||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||||
list = json['result']
|
list = json['result']
|
||||||
|
.where((e) => e['available'] == true)
|
||||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -17,7 +18,7 @@ class SearchVideoItemModel {
|
|||||||
this.id,
|
this.id,
|
||||||
this.cid,
|
this.cid,
|
||||||
// this.author,
|
// this.author,
|
||||||
// this.mid,
|
this.mid,
|
||||||
// this.typeid,
|
// this.typeid,
|
||||||
// this.typename,
|
// this.typename,
|
||||||
this.arcurl,
|
this.arcurl,
|
||||||
@ -47,7 +48,7 @@ class SearchVideoItemModel {
|
|||||||
int? id;
|
int? id;
|
||||||
int? cid;
|
int? cid;
|
||||||
// String? author;
|
// String? author;
|
||||||
// String? mid;
|
int? mid;
|
||||||
// String? typeid;
|
// String? typeid;
|
||||||
// String? typename;
|
// String? typename;
|
||||||
String? arcurl;
|
String? arcurl;
|
||||||
@ -80,6 +81,7 @@ class SearchVideoItemModel {
|
|||||||
arcurl = json['arcurl'];
|
arcurl = json['arcurl'];
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
mid = json['mid'];
|
||||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
@ -397,6 +399,7 @@ class SearchArticleItemModel {
|
|||||||
this.pubTime,
|
this.pubTime,
|
||||||
this.like,
|
this.like,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.subTitle,
|
||||||
this.rankOffset,
|
this.rankOffset,
|
||||||
this.mid,
|
this.mid,
|
||||||
this.imageUrls,
|
this.imageUrls,
|
||||||
@ -414,6 +417,7 @@ class SearchArticleItemModel {
|
|||||||
int? pubTime;
|
int? pubTime;
|
||||||
int? like;
|
int? like;
|
||||||
List? title;
|
List? title;
|
||||||
|
String? subTitle;
|
||||||
int? rankOffset;
|
int? rankOffset;
|
||||||
int? mid;
|
int? mid;
|
||||||
List? imageUrls;
|
List? imageUrls;
|
||||||
@ -431,6 +435,7 @@ class SearchArticleItemModel {
|
|||||||
pubTime = json['pub_time'];
|
pubTime = json['pub_time'];
|
||||||
like = json['like'];
|
like = json['like'];
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
|
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
rankOffset = json['rank_offset'];
|
rankOffset = json['rank_offset'];
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
imageUrls = json['image_urls'];
|
imageUrls = json['image_urls'];
|
||||||
|
|||||||
@ -3,17 +3,23 @@ class HistoryData {
|
|||||||
this.cursor,
|
this.cursor,
|
||||||
this.tab,
|
this.tab,
|
||||||
this.list,
|
this.list,
|
||||||
|
this.page,
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor? cursor;
|
Cursor? cursor;
|
||||||
List<HisTabItem>? tab;
|
List<HisTabItem>? tab;
|
||||||
List<HisListItem>? list;
|
List<HisListItem>? list;
|
||||||
|
Map? page;
|
||||||
|
|
||||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||||
cursor = Cursor.fromJson(json['cursor']);
|
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
tab = json['tab'] != null
|
||||||
list =
|
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
: [];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||||
|
: [];
|
||||||
|
page = json['page'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +85,7 @@ class HisListItem {
|
|||||||
this.kid,
|
this.kid,
|
||||||
this.tagName,
|
this.tagName,
|
||||||
this.liveStatus,
|
this.liveStatus,
|
||||||
|
this.checked,
|
||||||
});
|
});
|
||||||
|
|
||||||
String? title;
|
String? title;
|
||||||
@ -105,6 +112,7 @@ class HisListItem {
|
|||||||
int? kid;
|
int? kid;
|
||||||
String? tagName;
|
String? tagName;
|
||||||
int? liveStatus;
|
int? liveStatus;
|
||||||
|
bool? checked;
|
||||||
|
|
||||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
@ -131,6 +139,7 @@ class HisListItem {
|
|||||||
kid = json['kid'];
|
kid = json['kid'];
|
||||||
tagName = json['tag_name'];
|
tagName = json['tag_name'];
|
||||||
liveStatus = json['live_status'];
|
liveStatus = json['live_status'];
|
||||||
|
checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -184,7 +184,7 @@ class AboutController extends GetxController {
|
|||||||
|
|
||||||
// 获取远程版本
|
// 获取远程版本
|
||||||
Future getRemoteApp() async {
|
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);
|
data = LatestDataModel.fromJson(result.data);
|
||||||
remoteAppInfo = data;
|
remoteAppInfo = data;
|
||||||
remoteVersion.value = data.tagName!;
|
remoteVersion.value = data.tagName!;
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import 'package:pilipala/models/bangumi/info.dart';
|
|||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -292,4 +293,31 @@ class BangumiIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
if (bangumiDetail.value.episodes != null) {
|
||||||
|
episodes = bangumiDetail.value.episodes!;
|
||||||
|
}
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
int currentIndex =
|
||||||
|
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
// 列表循环
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
if (nextIndex == episodes.length - 1) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextIndex <= episodes.length - 1 &&
|
||||||
|
platRepeat == PlayRepeat.listOrder) {}
|
||||||
|
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String bvid = episodes[nextIndex].bvid!;
|
||||||
|
int aid = episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(bvid, cid, aid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,11 +67,10 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 请求错误
|
// 请求错误
|
||||||
// return HttpError(
|
return HttpError(
|
||||||
// errMsg: snapshot.data['msg'],
|
errMsg: snapshot.data['msg'],
|
||||||
// fn: () => Get.back(),
|
fn: () => Get.back(),
|
||||||
// );
|
);
|
||||||
return SizedBox();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return BangumiInfo(
|
return BangumiInfo(
|
||||||
@ -261,15 +260,9 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
!widget.loadingStatus
|
!widget.loadingStatus
|
||||||
? (widget.bangumiDetail!.areas!
|
|
||||||
.isNotEmpty
|
|
||||||
? widget.bangumiDetail!.areas!
|
? widget.bangumiDetail!.areas!
|
||||||
.first['name']
|
.first['name']
|
||||||
: '')
|
: bangumiItem!.areas!.first['name'],
|
||||||
: (bangumiItem!.areas!.isNotEmpty
|
|
||||||
? bangumiItem!
|
|
||||||
.areas!.first['name']
|
|
||||||
: ''),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: t.colorScheme.outline,
|
color: t.colorScheme.outline,
|
||||||
|
|||||||
@ -62,7 +62,7 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
title: Obx(
|
title: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
'黑名单管理 - ${_blackListController.blackList.length}',
|
'黑名单管理 - ${_blackListController.total.value}',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -138,6 +138,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
|||||||
class BlackListController extends GetxController {
|
class BlackListController extends GetxController {
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int pageSize = 50;
|
int pageSize = 50;
|
||||||
|
RxInt total = 0.obs;
|
||||||
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
RxList<BlackListItem> blackList = [BlackListItem()].obs;
|
||||||
|
|
||||||
Future queryBlacklist({type = 'init'}) async {
|
Future queryBlacklist({type = 'init'}) async {
|
||||||
@ -148,6 +149,7 @@ class BlackListController extends GetxController {
|
|||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
blackList.value = result['data'].list;
|
blackList.value = result['data'].list;
|
||||||
|
total.value = result['data'].total;
|
||||||
} else {
|
} else {
|
||||||
blackList.addAll(result['data'].list);
|
blackList.addAll(result['data'].list);
|
||||||
}
|
}
|
||||||
@ -161,6 +163,7 @@ class BlackListController extends GetxController {
|
|||||||
var result = await BlackHttp.removeBlack(fid: mid);
|
var result = await BlackHttp.removeBlack(fid: mid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
blackList.removeWhere((e) => e.mid == mid);
|
blackList.removeWhere((e) => e.mid == mid);
|
||||||
|
total.value = total.value - 1;
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,32 +10,22 @@ class PlDanmakuController {
|
|||||||
// 按 6min 分段
|
// 按 6min 分段
|
||||||
int segCount = 0;
|
int segCount = 0;
|
||||||
List<DmSegMobileReply> dmSegList = [];
|
List<DmSegMobileReply> dmSegList = [];
|
||||||
int currentSegIndex = 1;
|
int currentSegIndex = 0;
|
||||||
int currentDmIndex = 0;
|
int currentDmIndex = 0;
|
||||||
|
|
||||||
void calcSegment() {
|
void calcSegment() {
|
||||||
dmSegList.clear();
|
|
||||||
// 视频分段数
|
|
||||||
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
|
segCount = (videoDuration.inSeconds / (60 * 6)).ceil();
|
||||||
dmSegList = List<DmSegMobileReply>.generate(
|
|
||||||
segCount < 1 ? 1 : segCount, (index) => DmSegMobileReply());
|
|
||||||
// 当前分段
|
|
||||||
try {
|
|
||||||
currentSegIndex =
|
|
||||||
(playerController.position.value.inSeconds / (60 * 6)).ceil();
|
|
||||||
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<DmSegMobileReply>> queryDanmaku() async {
|
Future<List<DmSegMobileReply>> queryDanmaku() async {
|
||||||
// dmSegList.clear();
|
dmSegList.clear();
|
||||||
|
for (int segIndex = 1; segIndex <= segCount; segIndex++) {
|
||||||
DmSegMobileReply result =
|
DmSegMobileReply result =
|
||||||
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: currentSegIndex);
|
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segIndex);
|
||||||
if (result.elems.isNotEmpty) {
|
if (result.elems.isNotEmpty) {
|
||||||
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
|
result.elems.sort((a, b) => (a.progress).compareTo(b.progress));
|
||||||
// dmSegList.add(result);
|
dmSegList.add(result);
|
||||||
currentSegIndex = currentSegIndex < 1 ? 1 : currentSegIndex;
|
}
|
||||||
dmSegList[currentSegIndex - 1] = result;
|
|
||||||
}
|
}
|
||||||
if (dmSegList.isNotEmpty) {
|
if (dmSegList.isNotEmpty) {
|
||||||
findClosestPositionIndex(playerController.position.value.inMilliseconds);
|
findClosestPositionIndex(playerController.position.value.inMilliseconds);
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -89,15 +88,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
PlDanmakuController ctr = _plDanmakuController;
|
PlDanmakuController ctr = _plDanmakuController;
|
||||||
int currentPosition = position.inMilliseconds;
|
int currentPosition = position.inMilliseconds;
|
||||||
blockTypes = playerController.blockTypes;
|
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) {
|
if (!playerController.isOpenDanmu.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -149,8 +140,6 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(builder: (context, box) {
|
|
||||||
double initDuration = box.maxWidth / 12;
|
|
||||||
return Obx(
|
return Obx(
|
||||||
() => AnimatedOpacity(
|
() => AnimatedOpacity(
|
||||||
opacity: playerController.isOpenDanmu.value ? 1 : 0,
|
opacity: playerController.isOpenDanmu.value ? 1 : 0,
|
||||||
@ -163,16 +152,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
|||||||
fontSize: 15 * fontSizeVal,
|
fontSize: 15 * fontSizeVal,
|
||||||
area: showArea,
|
area: showArea,
|
||||||
opacity: opacityVal,
|
opacity: opacityVal,
|
||||||
hideTop: blockTypes.contains(5),
|
duration: danmakuSpeedVal * widget.playerController.playbackSpeed,
|
||||||
hideScroll: blockTypes.contains(2),
|
|
||||||
hideBottom: blockTypes.contains(4),
|
|
||||||
duration: initDuration /
|
|
||||||
(danmakuSpeedVal * widget.playerController.playbackSpeed),
|
|
||||||
),
|
),
|
||||||
statusChanged: (isPlaying) {},
|
statusChanged: (isPlaying) {},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -149,30 +149,10 @@ class DynamicsController extends GetxController {
|
|||||||
case 'DYNAMIC_TYPE_ARTICLE':
|
case 'DYNAMIC_TYPE_ARTICLE':
|
||||||
String title = item.modules.moduleDynamic.major.opus.title;
|
String title = item.modules.moduleDynamic.major.opus.title;
|
||||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||||
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(
|
Get.toNamed(
|
||||||
'/webview',
|
'/webview',
|
||||||
parameters: {
|
parameters: {'url': 'https:$url', 'type': 'note', 'pageTitle': title},
|
||||||
'url': 'https:$url',
|
|
||||||
'type': 'note',
|
|
||||||
'pageTitle': title
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'DYNAMIC_TYPE_PGC':
|
case 'DYNAMIC_TYPE_PGC':
|
||||||
print('番剧');
|
print('番剧');
|
||||||
@ -234,7 +214,7 @@ class DynamicsController extends GetxController {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
@ -18,7 +17,6 @@ class DynamicDetailController extends GetxController {
|
|||||||
RxString noMore = ''.obs;
|
RxString noMore = ''.obs;
|
||||||
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
|
||||||
RxInt acount = 0.obs;
|
RxInt acount = 0.obs;
|
||||||
final ScrollController scrollController = ScrollController();
|
|
||||||
|
|
||||||
ReplySortType _sortType = ReplySortType.time;
|
ReplySortType _sortType = ReplySortType.time;
|
||||||
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
RxString sortTypeTitle = ReplySortType.time.titles.obs;
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
@ -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/deatil/index.dart';
|
||||||
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
|
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
|
||||||
|
|
||||||
import '../widgets/dynamic_panel.dart';
|
import '../widgets/dynamic_panel.dart';
|
||||||
|
|
||||||
@ -25,18 +21,15 @@ class DynamicDetailPage extends StatefulWidget {
|
|||||||
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
State<DynamicDetailPage> createState() => _DynamicDetailPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DynamicDetailPageState extends State<DynamicDetailPage>
|
class _DynamicDetailPageState extends State<DynamicDetailPage> {
|
||||||
with TickerProviderStateMixin {
|
late DynamicDetailController? _dynamicDetailController;
|
||||||
late DynamicDetailController _dynamicDetailController;
|
|
||||||
late AnimationController fabAnimationCtr;
|
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late StreamController<bool> titleStreamC; // appBar title
|
late StreamController<bool> titleStreamC; // appBar title
|
||||||
late ScrollController scrollController;
|
final ScrollController scrollController = ScrollController();
|
||||||
bool _visibleTitle = false;
|
bool _visibleTitle = false;
|
||||||
String? action;
|
String? action;
|
||||||
// 回复类型
|
// 回复类型
|
||||||
late int type;
|
late int type;
|
||||||
bool _isFabVisible = true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -57,30 +50,37 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
int commentType = 11;
|
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11;
|
||||||
try {
|
|
||||||
commentType = Get.arguments['item'].basic!['comment_type'];
|
|
||||||
} catch (_) {}
|
|
||||||
type = (commentType == 0) ? 11 : commentType;
|
type = (commentType == 0) ? 11 : commentType;
|
||||||
|
|
||||||
action =
|
action =
|
||||||
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
|
||||||
_dynamicDetailController =
|
_dynamicDetailController =
|
||||||
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
Get.put(DynamicDetailController(oid, type), tag: oid.toString());
|
||||||
_futureBuilderFuture = _dynamicDetailController.queryReplyList();
|
_futureBuilderFuture = _dynamicDetailController!.queryReplyList();
|
||||||
titleStreamC = StreamController<bool>();
|
titleStreamC = StreamController<bool>();
|
||||||
|
scrollController.addListener(_listen);
|
||||||
if (action == 'comment') {
|
if (action == 'comment') {
|
||||||
_visibleTitle = true;
|
_visibleTitle = true;
|
||||||
titleStreamC.add(true);
|
titleStreamC.add(true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fabAnimationCtr = AnimationController(
|
void _listen() async {
|
||||||
vsync: this,
|
if (scrollController.position.pixels >=
|
||||||
duration: const Duration(milliseconds: 300),
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
);
|
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
||||||
fabAnimationCtr.forward();
|
_dynamicDetailController!.queryReplyList(reqType: 'onLoad');
|
||||||
// 滚动事件监听
|
});
|
||||||
scrollListener();
|
}
|
||||||
|
|
||||||
|
if (scrollController.offset > 55 && !_visibleTitle) {
|
||||||
|
_visibleTitle = true;
|
||||||
|
titleStreamC.add(true);
|
||||||
|
} else if (scrollController.offset <= 55 && _visibleTitle) {
|
||||||
|
_visibleTitle = false;
|
||||||
|
titleStreamC.add(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void replyReply(replyItem) {
|
void replyReply(replyItem) {
|
||||||
@ -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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.removeListener(() {});
|
scrollController.removeListener(() {});
|
||||||
fabAnimationCtr.dispose();
|
|
||||||
scrollController.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,17 +136,15 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _dynamicDetailController.queryReplyList();
|
await _dynamicDetailController!.queryReplyList();
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: CustomScrollView(
|
||||||
children: [
|
|
||||||
CustomScrollView(
|
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
if (action != 'comment')
|
if (action != 'comment')
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: DynamicPanel(
|
child: DynamicPanel(
|
||||||
item: _dynamicDetailController.item,
|
item: _dynamicDetailController!.item,
|
||||||
source: 'detail',
|
source: 'detail',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -207,9 +156,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
border: Border(
|
border: Border(
|
||||||
top: BorderSide(
|
top: BorderSide(
|
||||||
width: 0.6,
|
width: 0.6,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||||
.dividerColor
|
|
||||||
.withOpacity(0.05),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -226,9 +173,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
scale: animation, child: child);
|
scale: animation, child: child);
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_dynamicDetailController.acount.value}',
|
'${_dynamicDetailController!.acount.value}',
|
||||||
key: ValueKey<int>(
|
key: ValueKey<int>(
|
||||||
_dynamicDetailController.acount.value),
|
_dynamicDetailController!.acount.value),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -238,11 +185,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
height: 35,
|
height: 35,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
_dynamicDetailController.queryBySort(),
|
_dynamicDetailController!.queryBySort(),
|
||||||
icon: const Icon(Icons.sort, size: 16),
|
icon: const Icon(Icons.sort, size: 16),
|
||||||
label: Obx(() => Text(
|
label: Obx(() => Text(
|
||||||
_dynamicDetailController
|
_dynamicDetailController!.sortTypeLabel.value,
|
||||||
.sortTypeLabel.value,
|
|
||||||
style: const TextStyle(fontSize: 13),
|
style: const TextStyle(fontSize: 13),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
@ -261,11 +207,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
// 请求成功
|
// 请求成功
|
||||||
return Obx(
|
return Obx(
|
||||||
() => _dynamicDetailController.replyList.isEmpty &&
|
() => _dynamicDetailController!.replyList.isEmpty &&
|
||||||
_dynamicDetailController.isLoadingMore
|
_dynamicDetailController!.isLoadingMore
|
||||||
? SliverList(
|
? SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate:
|
||||||
(context, index) {
|
SliverChildBuilderDelegate((context, index) {
|
||||||
return const VideoReplySkeleton();
|
return const VideoReplySkeleton();
|
||||||
}, childCount: 8),
|
}, childCount: 8),
|
||||||
)
|
)
|
||||||
@ -273,7 +219,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(context, index) {
|
(context, index) {
|
||||||
if (index ==
|
if (index ==
|
||||||
_dynamicDetailController
|
_dynamicDetailController!
|
||||||
.replyList.length) {
|
.replyList.length) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(
|
||||||
@ -287,7 +233,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
child: Center(
|
child: Center(
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => Text(
|
() => Text(
|
||||||
_dynamicDetailController
|
_dynamicDetailController!
|
||||||
.noMore.value,
|
.noMore.value,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@ -301,7 +247,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ReplyItem(
|
return ReplyItem(
|
||||||
replyItem: _dynamicDetailController
|
replyItem: _dynamicDetailController!
|
||||||
.replyList[index],
|
.replyList[index],
|
||||||
showReplyRow: true,
|
showReplyRow: true,
|
||||||
replyLevel: '1',
|
replyLevel: '1',
|
||||||
@ -309,15 +255,15 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
replyReply(replyItem),
|
replyReply(replyItem),
|
||||||
replyType: ReplyType.values[type],
|
replyType: ReplyType.values[type],
|
||||||
addReply: (replyItem) {
|
addReply: (replyItem) {
|
||||||
_dynamicDetailController
|
_dynamicDetailController!
|
||||||
.replyList[index].replies!
|
.replyList[index].replies!
|
||||||
.add(replyItem);
|
.add(replyItem);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
childCount: _dynamicDetailController
|
childCount:
|
||||||
.replyList.length +
|
_dynamicDetailController!.replyList.length +
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -341,52 +287,6 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,10 +225,8 @@ InlineSpan richNode(item, context) {
|
|||||||
width: box.maxWidth / 2,
|
width: box.maxWidth / 2,
|
||||||
height: box.maxWidth *
|
height: box.maxWidth *
|
||||||
0.5 *
|
0.5 *
|
||||||
(pictureItem.height != null &&
|
pictureItem.height! /
|
||||||
pictureItem.width != null
|
pictureItem.width!,
|
||||||
? pictureItem.height! / pictureItem.width!
|
|
||||||
: 1),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -16,16 +16,13 @@ class FansPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FansPageState extends State<FansPage> {
|
class _FansPageState extends State<FansPage> {
|
||||||
late String mid;
|
final FansController _fansController = Get.put(FansController());
|
||||||
late FansController _fansController;
|
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mid = Get.parameters['mid']!;
|
|
||||||
_fansController = Get.put(FansController(), tag: mid);
|
|
||||||
_futureBuilderFuture = _fansController.queryFans('init');
|
_futureBuilderFuture = _fansController.queryFans('init');
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@ -49,8 +49,8 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
Get.toNamed('/video', parameters: parameters, arguments: {
|
Get.toNamed('/video', parameters: parameters, arguments: {
|
||||||
'videoItem': videoItem,
|
'videoItem': videoItem,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType':
|
// 'videoType':
|
||||||
epId != null ? SearchType.media_bangumi : SearchType.video,
|
// epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -142,6 +142,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
Text(
|
Text(
|
||||||
videoItem.owner.name,
|
videoItem.owner.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -149,14 +151,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
StatView(
|
|
||||||
theme: 'gray',
|
|
||||||
view: videoItem.cntInfo['play'],
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 26,
|
width: 26,
|
||||||
|
|||||||
@ -16,16 +16,13 @@ class FollowPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FollowPageState extends State<FollowPage> {
|
class _FollowPageState extends State<FollowPage> {
|
||||||
late String mid;
|
final FollowController _followController = Get.put(FollowController());
|
||||||
late FollowController _followController;
|
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mid = Get.parameters['mid']!;
|
|
||||||
_followController = Get.put(FollowController(), tag: mid);
|
|
||||||
_futureBuilderFuture = _followController.queryFollowings('init');
|
_futureBuilderFuture = _followController.queryFollowings('init');
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() async {
|
() async {
|
||||||
|
|||||||
@ -27,12 +27,6 @@ Widget followItem({item}) {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: const TextStyle(fontSize: 14),
|
style: const TextStyle(fontSize: 14),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
|
||||||
item.sign,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
dense: true,
|
|
||||||
trailing: const SizedBox(width: 6),
|
trailing: const SizedBox(width: 6),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,11 +8,13 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
|
|
||||||
class HistoryController extends GetxController {
|
class HistoryController extends GetxController {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
RxBool isLoadingMore = false.obs;
|
RxBool isLoadingMore = false.obs;
|
||||||
RxBool pauseStatus = false.obs;
|
RxBool pauseStatus = false.obs;
|
||||||
Box localCache = GStrorage.localCache;
|
Box localCache = GStrorage.localCache;
|
||||||
RxBool isLoading = false.obs;
|
RxBool isLoading = false.obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
RxInt checkedCount = 0.obs;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -123,8 +125,15 @@ class HistoryController extends GetxController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除某条历史记录
|
// 删除某条历史记录
|
||||||
Future delHistory(kid) async {
|
Future delHistory(kid, business) async {
|
||||||
var res = await UserHttp.delHistory(kid);
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
historyList.removeWhere((e) => e.kid == kid);
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
SmartDialog.showToast(res['msg']);
|
SmartDialog.showToast(res['msg']);
|
||||||
@ -133,12 +142,61 @@ class HistoryController extends GetxController {
|
|||||||
|
|
||||||
// 删除已看历史记录
|
// 删除已看历史记录
|
||||||
Future onDelHistory() async {
|
Future onDelHistory() async {
|
||||||
|
/// TODO 优化
|
||||||
List<HisListItem> result =
|
List<HisListItem> result =
|
||||||
historyList.where((e) => e.progress == -1).toList();
|
historyList.where((e) => e.progress == -1).toList();
|
||||||
for (HisListItem i in result) {
|
for (HisListItem i in result) {
|
||||||
await UserHttp.delHistory(i.kid);
|
String resKid = 'archive_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
historyList.removeWhere((e) => e.kid == i.kid);
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
}
|
}
|
||||||
SmartDialog.showToast('操作完成');
|
SmartDialog.showToast('操作完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除选中的记录
|
||||||
|
Future onDelCheckedHistory() async {
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认删除所选历史记录吗?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
/// TODO 优化
|
||||||
|
await SmartDialog.dismiss();
|
||||||
|
SmartDialog.showLoading(msg: '请求中');
|
||||||
|
List<HisListItem> result =
|
||||||
|
historyList.where((e) => e.checked!).toList();
|
||||||
|
for (HisListItem i in result) {
|
||||||
|
String str = 'archive';
|
||||||
|
try {
|
||||||
|
str = i.history!.business!;
|
||||||
|
} catch (_) {}
|
||||||
|
String resKid = '${str}_${i.kid}';
|
||||||
|
await UserHttp.delHistory(resKid);
|
||||||
|
historyList.removeWhere((e) => e.kid == i.kid);
|
||||||
|
}
|
||||||
|
checkedCount.value = 0;
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
enableMultiple.value = false;
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,23 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
_historyController.enableMultiple.listen((p0) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中
|
||||||
|
onChoose(index) {
|
||||||
|
_historyController.historyList[index].checked =
|
||||||
|
!_historyController.historyList[index].checked!;
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.where((item) => item.checked!).length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新多选状态
|
||||||
|
onUpdateMultiple() {
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -48,14 +65,31 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBarWidget(
|
||||||
|
visible: _historyController.enableMultiple.value,
|
||||||
|
child1: AppBar(
|
||||||
titleSpacing: 0,
|
titleSpacing: 0,
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
icon: const Icon(Icons.arrow_back_outlined),
|
||||||
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
'观看记录',
|
'观看记录',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
|
// TextButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// _historyController.enableMultiple.value = true;
|
||||||
|
// setState(() {});
|
||||||
|
// },
|
||||||
|
// child: const Text('多选'),
|
||||||
|
// ),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed('/historySearch'),
|
||||||
|
icon: const Icon(Icons.search_outlined),
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
onSelected: (String type) {
|
onSelected: (String type) {
|
||||||
// 处理菜单项选择的逻辑
|
// 处理菜单项选择的逻辑
|
||||||
@ -69,6 +103,10 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
case 'del':
|
case 'del':
|
||||||
_historyController.onDelHistory();
|
_historyController.onDelHistory();
|
||||||
break;
|
break;
|
||||||
|
case 'multiple':
|
||||||
|
_historyController.enableMultiple.value = true;
|
||||||
|
setState(() {});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -89,11 +127,58 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
value: 'del',
|
value: 'del',
|
||||||
child: Text('删除已看记录'),
|
child: Text('删除已看记录'),
|
||||||
),
|
),
|
||||||
|
const PopupMenuItem<String>(
|
||||||
|
value: 'multiple',
|
||||||
|
child: Text('多选删除'),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child2: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
centerTitle: false,
|
||||||
|
leading: IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_historyController.enableMultiple.value = false;
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = false;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value = 0;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.close_outlined),
|
||||||
|
),
|
||||||
|
title: Obx(
|
||||||
|
() => Text(
|
||||||
|
'已选择${_historyController.checkedCount.value}项',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
for (var item in _historyController.historyList) {
|
||||||
|
item.checked = true;
|
||||||
|
}
|
||||||
|
_historyController.checkedCount.value =
|
||||||
|
_historyController.historyList.length;
|
||||||
|
_historyController.historyList.refresh();
|
||||||
|
},
|
||||||
|
child: const Text('全选'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _historyController.onDelCheckedHistory(),
|
||||||
|
child: Text(
|
||||||
|
'删除',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
await _historyController.onRefresh();
|
await _historyController.onRefresh();
|
||||||
@ -120,6 +205,8 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
videoItem:
|
videoItem:
|
||||||
_historyController.historyList[index],
|
_historyController.historyList[index],
|
||||||
ctr: _historyController,
|
ctr: _historyController,
|
||||||
|
onChoose: () => onChoose(index),
|
||||||
|
onUpdateMultiple: () => onUpdateMultiple(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount:
|
childCount:
|
||||||
@ -155,6 +242,36 @@ class _HistoryPageState extends State<HistoryPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// bottomNavigationBar: BottomAppBar(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBarWidget extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const AppBarWidget({
|
||||||
|
required this.child1,
|
||||||
|
required this.child2,
|
||||||
|
required this.visible,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final PreferredSizeWidget child1;
|
||||||
|
final PreferredSizeWidget child2;
|
||||||
|
final bool visible;
|
||||||
|
@override
|
||||||
|
Size get preferredSize => child1.preferredSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: animation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: !visible ? child1 : child2,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,13 +12,23 @@ import 'package:pilipala/models/common/business_type.dart';
|
|||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/pages/history/index.dart';
|
import 'package:pilipala/pages/history/index.dart';
|
||||||
|
import 'package:pilipala/pages/history_search/index.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class HistoryItem extends StatelessWidget {
|
class HistoryItem extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic ctr;
|
||||||
const HistoryItem({super.key, required this.videoItem, this.ctr});
|
final Function? onChoose;
|
||||||
|
final Function? onUpdateMultiple;
|
||||||
|
const HistoryItem({
|
||||||
|
super.key,
|
||||||
|
required this.videoItem,
|
||||||
|
this.ctr,
|
||||||
|
this.onChoose,
|
||||||
|
this.onUpdateMultiple,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -27,6 +37,11 @@ class HistoryItem extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(aid);
|
String heroTag = Utils.makeHeroTag(aid);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
if (ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (videoItem.history.business.contains('article')) {
|
if (videoItem.history.business.contains('article')) {
|
||||||
int cid = videoItem.history.cid ??
|
int cid = videoItem.history.cid ??
|
||||||
// videoItem.history.oid ??
|
// videoItem.history.oid ??
|
||||||
@ -74,7 +89,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -102,7 +117,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -117,6 +132,17 @@ class HistoryItem extends StatelessWidget {
|
|||||||
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
if (ctr is HistorySearchController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ctr!.enableMultiple.value) {
|
||||||
|
feedBack();
|
||||||
|
ctr!.enableMultiple.value = true;
|
||||||
|
onChoose!();
|
||||||
|
onUpdateMultiple!();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
@ -131,6 +157,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
@ -163,7 +191,8 @@ class HistoryItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// 右上角
|
// 右上角
|
||||||
if (BusinessType.showBadge.showBadge
|
if (BusinessType.showBadge.showBadge
|
||||||
.contains(videoItem.history.business) ||
|
.contains(
|
||||||
|
videoItem.history.business) ||
|
||||||
videoItem.history.business ==
|
videoItem.history.business ==
|
||||||
BusinessType.live.type)
|
BusinessType.live.type)
|
||||||
PBadge(
|
PBadge(
|
||||||
@ -178,6 +207,60 @@ class HistoryItem extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Obx(
|
||||||
|
() => Positioned.fill(
|
||||||
|
child: AnimatedOpacity(
|
||||||
|
opacity: ctr!.enableMultiple.value ? 1 : 0,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
ctr!.enableMultiple.value &&
|
||||||
|
videoItem.checked
|
||||||
|
? 0.6
|
||||||
|
: 0),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: AnimatedScale(
|
||||||
|
scale: videoItem.checked ? 1 : 0,
|
||||||
|
duration:
|
||||||
|
const Duration(milliseconds: 250),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
EdgeInsets.zero),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty
|
||||||
|
.resolveWith(
|
||||||
|
(states) {
|
||||||
|
return Colors.white
|
||||||
|
.withOpacity(0.8);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
feedBack();
|
||||||
|
onChoose!();
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.done_all_outlined,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -193,7 +276,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
final HistoryController? ctr;
|
final dynamic? ctr;
|
||||||
const VideoContent({super.key, required this.videoItem, this.ctr});
|
const VideoContent({super.key, required this.videoItem, this.ctr});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -247,10 +330,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
color: Theme.of(context).colorScheme.outline),
|
color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
if (videoItem.badge != '番剧' &&
|
|
||||||
!videoItem.tagName.contains('动画') &&
|
|
||||||
videoItem.history.business != 'live' &&
|
|
||||||
!videoItem.history.business.contains('article'))
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
@ -267,6 +346,10 @@ class VideoContent extends StatelessWidget {
|
|||||||
onSelected: (String type) {},
|
onSelected: (String type) {},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<PopupMenuEntry<String>>[
|
||||||
|
if (videoItem.badge != '番剧' &&
|
||||||
|
!videoItem.tagName.contains('动画') &&
|
||||||
|
videoItem.history.business != 'live' &&
|
||||||
|
!videoItem.history.business.contains('article'))
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var res = await UserHttp.toViewLater(
|
var res = await UserHttp.toViewLater(
|
||||||
@ -284,7 +367,8 @@ class VideoContent extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
onTap: () => ctr!.delHistory(videoItem.kid),
|
onTap: () => ctr!.delHistory(
|
||||||
|
videoItem.kid, videoItem.history.business),
|
||||||
value: 'pause',
|
value: 'pause',
|
||||||
height: 35,
|
height: 35,
|
||||||
child: const Row(
|
child: const Row(
|
||||||
|
|||||||
91
lib/pages/history_search/controller.dart
Normal file
91
lib/pages/history_search/controller.dart
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/models/user/history.dart';
|
||||||
|
|
||||||
|
class HistorySearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int pn = 1;
|
||||||
|
int count = 0;
|
||||||
|
RxList<HisListItem> historyList = <HisListItem>[].obs;
|
||||||
|
RxBool enableMultiple = false.obs;
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
pn = 1;
|
||||||
|
searchHistories();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchHistories({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await UserHttp.searchHistory(
|
||||||
|
pn: pn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' && pn == 1) {
|
||||||
|
historyList.value = res['data'].list;
|
||||||
|
} else {
|
||||||
|
historyList.addAll(res['data'].list);
|
||||||
|
}
|
||||||
|
count = res['data'].page['total'];
|
||||||
|
if (historyList.length == count) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
pn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
searchHistories(type: 'onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future delHistory(kid, business) async {
|
||||||
|
String resKid = 'archive_$kid';
|
||||||
|
if (business == 'live') {
|
||||||
|
resKid = 'live_$kid';
|
||||||
|
} else if (business.contains('article')) {
|
||||||
|
resKid = 'article_$kid';
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = await UserHttp.delHistory(resKid);
|
||||||
|
if (res['status']) {
|
||||||
|
historyList.removeWhere((e) => e.kid == kid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/history_search/index.dart
Normal file
4
lib/pages/history_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library history_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
174
lib/pages/history_search/view.dart
Normal file
174
lib/pages/history_search/view.dart
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/pages/history/widgets/item.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class HistorySearchPage extends StatefulWidget {
|
||||||
|
const HistorySearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HistorySearchPage> createState() => _HistorySearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||||
|
final HistorySearchController _historySearchCtr =
|
||||||
|
Get.put(HistorySearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _historySearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_historySearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.removeListener(() {});
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _historySearchCtr.submit(),
|
||||||
|
icon: const Icon(Icons.search_outlined, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _historySearchCtr.searchFocusNode,
|
||||||
|
controller: _historySearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _historySearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _historySearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _historySearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _historySearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _historySearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [const SizedBox()]
|
||||||
|
: [
|
||||||
|
Expanded(
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _historySearchCtr.searchHistories(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _historySearchCtr.historyList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_historySearchCtr.historyList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_historySearchCtr
|
||||||
|
.historyList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_historySearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return HistoryItem(
|
||||||
|
videoItem: _historySearchCtr
|
||||||
|
.historyList[index],
|
||||||
|
ctr: _historySearchCtr,
|
||||||
|
onChoose: null,
|
||||||
|
onUpdateMultiple: () => null,
|
||||||
|
);
|
||||||
|
;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _historySearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? const SizedBox(child: Text('加载中...'))
|
||||||
|
: const CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
NoData(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/http/black.dart';
|
||||||
import 'package:pilipala/models/common/tab_type.dart';
|
import 'package:pilipala/models/common/tab_type.dart';
|
||||||
|
import 'package:pilipala/models/user/black.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||||
@ -15,6 +17,13 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
RxBool userLogin = false.obs;
|
RxBool userLogin = false.obs;
|
||||||
RxString userFace = ''.obs;
|
RxString userFace = ''.obs;
|
||||||
var userInfo;
|
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
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -51,7 +60,33 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
void updateLoginStatus(val) async {
|
void updateLoginStatus(val) async {
|
||||||
userInfo = await userInfoCache.get('userInfoCache');
|
userInfo = await userInfoCache.get('userInfoCache');
|
||||||
userLogin.value = val ?? false;
|
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 : '';
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import 'package:pilipala/utils/feed_back.dart';
|
|||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({Key? key}) : super(key: key);
|
Function? callFn;
|
||||||
|
HomePage({Key? key, this.callFn}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HomePage> createState() => _HomePageState();
|
State<HomePage> createState() => _HomePageState();
|
||||||
@ -25,15 +26,16 @@ class _HomePageState extends State<HomePage>
|
|||||||
|
|
||||||
showUserBottonSheet() {
|
showUserBottonSheet() {
|
||||||
feedBack();
|
feedBack();
|
||||||
showModalBottomSheet(
|
widget.callFn!();
|
||||||
context: context,
|
// showModalBottomSheet(
|
||||||
builder: (_) => const SizedBox(
|
// context: context,
|
||||||
height: 450,
|
// builder: (_) => const SizedBox(
|
||||||
child: MinePage(),
|
// height: 450,
|
||||||
),
|
// child: MinePage(),
|
||||||
clipBehavior: Clip.hardEdge,
|
// ),
|
||||||
isScrollControlled: true,
|
// clipBehavior: Clip.hardEdge,
|
||||||
);
|
// isScrollControlled: true,
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -50,37 +52,6 @@ class _HomePageState extends State<HomePage>
|
|||||||
ctr: _homeController,
|
ctr: _homeController,
|
||||||
callback: showUserBottonSheet,
|
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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(child: SearchPage()),
|
|
||||||
const SizedBox(width: 10),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => ctr!.userLogin.value
|
() => ctr!.userLogin.value
|
||||||
? Stack(
|
? Stack(
|
||||||
@ -182,6 +151,8 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
const Expanded(child: SearchPage()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
25
lib/pages/home/widgets/left_drawer.dart
Normal file
25
lib/pages/home/widgets/left_drawer.dart
Normal 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()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,112 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:pilipala/http/html.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 {
|
class HtmlRenderController extends GetxController {
|
||||||
late String id;
|
late String id;
|
||||||
late String dynamicType;
|
|
||||||
late int type;
|
|
||||||
RxInt oid = (-1).obs;
|
|
||||||
late Map response;
|
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
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
id = Get.parameters['id']!;
|
id = Get.parameters['id']!;
|
||||||
dynamicType = Get.parameters['dynamicType']!;
|
|
||||||
type = dynamicType == 'picture' ? 11 : 12;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 请求动态内容
|
Future reqHtml() async {
|
||||||
Future reqHtml(id) async {
|
var res = await HtmlHttp.reqHtml(id);
|
||||||
late dynamic res;
|
|
||||||
if (dynamicType == 'opus' || dynamicType == 'picture') {
|
|
||||||
res = await HtmlHttp.reqHtml(id, dynamicType);
|
|
||||||
} else {
|
|
||||||
res = await HtmlHttp.reqReadHtml(id, dynamicType);
|
|
||||||
}
|
|
||||||
response = res;
|
response = res;
|
||||||
oid.value = res['commentId'];
|
|
||||||
return res;
|
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,7 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.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:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
|
||||||
import 'package:pilipala/common/widgets/html_render.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/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';
|
import 'controller.dart';
|
||||||
|
|
||||||
@ -24,104 +12,16 @@ class HtmlRenderPage extends StatefulWidget {
|
|||||||
State<HtmlRenderPage> createState() => _HtmlRenderPageState();
|
State<HtmlRenderPage> createState() => _HtmlRenderPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HtmlRenderPageState extends State<HtmlRenderPage>
|
class _HtmlRenderPageState extends State<HtmlRenderPage> {
|
||||||
with TickerProviderStateMixin {
|
HtmlRenderController htmlRenderCtr = Get.put(HtmlRenderController());
|
||||||
final HtmlRenderController _htmlRenderCtr = Get.put(HtmlRenderController());
|
|
||||||
late String title;
|
late String title;
|
||||||
late String id;
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
title = Get.parameters['title']!;
|
title = Get.parameters['title']!;
|
||||||
id = Get.parameters['id']!;
|
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
|
@override
|
||||||
@ -129,68 +29,16 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
centerTitle: false,
|
centerTitle: false,
|
||||||
titleSpacing: 0,
|
title: Text(title),
|
||||||
title: Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
|
||||||
),
|
),
|
||||||
actions: [
|
body: SingleChildScrollView(
|
||||||
const SizedBox(width: 4),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.toNamed('/webview', parameters: {
|
|
||||||
'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)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
SingleChildScrollView(
|
|
||||||
controller: scrollController,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: _futureBuilderFuture,
|
future: htmlRenderCtr.reqHtml(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
var data = snapshot.data;
|
var data = snapshot.data;
|
||||||
fabAnimationCtr.forward();
|
|
||||||
if (data['status']) {
|
if (data['status']) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -202,14 +50,13 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
width: 40,
|
width: 40,
|
||||||
height: 40,
|
height: 40,
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
src: _htmlRenderCtr.response['avatar']!,
|
src: htmlRenderCtr.response['avatar']!,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text(_htmlRenderCtr.response['uname'],
|
Text(htmlRenderCtr.response['uname'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@ -217,11 +64,10 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
.fontSize,
|
.fontSize,
|
||||||
)),
|
)),
|
||||||
Text(
|
Text(
|
||||||
_htmlRenderCtr.response['updateTime'],
|
htmlRenderCtr.response['updateTime'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.colorScheme
|
Theme.of(context).colorScheme.outline,
|
||||||
.outline,
|
|
||||||
fontSize: Theme.of(context)
|
fontSize: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.labelSmall!
|
.labelSmall!
|
||||||
@ -235,9 +81,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||||
child: HtmlRender(
|
child: HtmlRender(
|
||||||
htmlContent: _htmlRenderCtr.response['content'],
|
htmlContent: htmlRenderCtr.response['content'],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
@ -255,7 +101,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const Text('error');
|
return Text('error');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 骨架屏
|
||||||
@ -263,195 +109,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Obx(
|
|
||||||
() => _htmlRenderCtr.oid.value != -1
|
|
||||||
? 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: [
|
|
||||||
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(),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,9 +25,9 @@ class LiveController extends GetxController {
|
|||||||
|
|
||||||
// 获取推荐
|
// 获取推荐
|
||||||
Future queryLiveList(type) async {
|
Future queryLiveList(type) async {
|
||||||
// if (type == 'init') {
|
if (type == 'init') {
|
||||||
// _currentPage = 1;
|
_currentPage = 1;
|
||||||
// }
|
}
|
||||||
var res = await LiveHttp.liveList(
|
var res = await LiveHttp.liveList(
|
||||||
pn: _currentPage,
|
pn: _currentPage,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -21,15 +21,11 @@ class LivePage extends StatefulWidget {
|
|||||||
State<LivePage> createState() => _LivePageState();
|
State<LivePage> createState() => _LivePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LivePageState extends State<LivePage>
|
class _LivePageState extends State<LivePage> {
|
||||||
with AutomaticKeepAliveClientMixin {
|
|
||||||
final LiveController _liveController = Get.put(LiveController());
|
final LiveController _liveController = Get.put(LiveController());
|
||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -41,7 +37,7 @@ class _LivePageState extends State<LivePage>
|
|||||||
() {
|
() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 200) {
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () {
|
EasyThrottle.throttle('my-throttler', const Duration(seconds: 1), () {
|
||||||
_liveController.isLoadingMore = true;
|
_liveController.isLoadingMore = true;
|
||||||
_liveController.onLoad();
|
_liveController.onLoad();
|
||||||
});
|
});
|
||||||
@ -148,9 +144,9 @@ class _LivePageState extends State<LivePage>
|
|||||||
return SliverGrid(
|
return SliverGrid(
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
// 行间距
|
// 行间距
|
||||||
mainAxisSpacing: StyleString.safeSpace,
|
mainAxisSpacing: StyleString.cardSpace + 4,
|
||||||
// 列间距
|
// 列间距
|
||||||
crossAxisSpacing: StyleString.safeSpace,
|
crossAxisSpacing: StyleString.cardSpace + 4,
|
||||||
// 列数
|
// 列数
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
mainAxisExtent:
|
mainAxisExtent:
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class LiveCardV extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: crossAxisCount == 1 ? 0 : 1,
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -102,7 +102,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: crossAxisCount == 1
|
padding: crossAxisCount == 1
|
||||||
? const EdgeInsets.fromLTRB(9, 9, 9, 4)
|
? const EdgeInsets.fromLTRB(9, 9, 9, 4)
|
||||||
: const EdgeInsets.fromLTRB(5, 8, 5, 4),
|
: const EdgeInsets.fromLTRB(9, 8, 9, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|||||||
@ -4,53 +4,11 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.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/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class MainController extends GetxController {
|
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 =
|
final StreamController<bool> bottomBarStream =
|
||||||
StreamController<bool>.broadcast();
|
StreamController<bool>.broadcast();
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
import 'package:pilipala/pages/dynamics/index.dart';
|
||||||
import 'package:pilipala/pages/home/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/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/event_bus.dart';
|
import 'package:pilipala/utils/event_bus.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
@ -18,80 +19,16 @@ class MainApp extends StatefulWidget {
|
|||||||
|
|
||||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||||
final MainController _mainController = Get.put(MainController());
|
final MainController _mainController = Get.put(MainController());
|
||||||
final HomeController _homeController = Get.put(HomeController());
|
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
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;
|
|
||||||
int selectedIndex = 0;
|
int selectedIndex = 0;
|
||||||
int? _lastSelectTime; //上次点击时间
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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 {
|
void openDrawer() {
|
||||||
feedBack();
|
_scaffoldKey.currentState?.openDrawer();
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -113,55 +50,10 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
|||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () => _mainController.onBackPressed(context),
|
onWillPop: () => _mainController.onBackPressed(context),
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
key: _scaffoldKey,
|
||||||
extendBody: true,
|
extendBody: true,
|
||||||
body: FadeTransition(
|
drawer: const LeftDrawer(),
|
||||||
opacity: _fadeAnimation!,
|
body: HomePage(callFn: openDrawer),
|
||||||
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(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
class MediaPage extends StatefulWidget {
|
class MediaPage extends StatefulWidget {
|
||||||
const MediaPage({super.key});
|
const MediaPage({super.key});
|
||||||
@ -15,7 +12,6 @@ class MediaPage extends StatefulWidget {
|
|||||||
class _MediaPageState extends State<MediaPage>
|
class _MediaPageState extends State<MediaPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
late MediaController mediaController;
|
late MediaController mediaController;
|
||||||
late Future _futureBuilderFuture;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -24,13 +20,6 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
mediaController = Get.put(MediaController());
|
mediaController = Get.put(MediaController());
|
||||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
|
||||||
|
|
||||||
mediaController.userLogin.listen((status) {
|
|
||||||
setState(() {
|
|
||||||
_futureBuilderFuture = mediaController.queryFavFolder();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -38,22 +27,8 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
super.build(context);
|
super.build(context);
|
||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(toolbarHeight: 30),
|
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
for (var i in mediaController.list) ...[
|
for (var i in mediaController.list) ...[
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => i['onTap'](),
|
onTap: () => i['onTap'](),
|
||||||
@ -74,206 +49,8 @@ class _MediaPageState extends State<MediaPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
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: 170 * 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),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,26 +3,23 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/member.dart';
|
import 'package:pilipala/http/member.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/member/archive.dart';
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
import 'package:pilipala/models/member/info.dart';
|
import 'package:pilipala/models/member/info.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
|
||||||
|
|
||||||
class MemberController extends GetxController {
|
class MemberController extends GetxController {
|
||||||
late int mid;
|
late int mid;
|
||||||
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
|
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
|
||||||
Map? userStat;
|
Map? userStat;
|
||||||
RxString face = ''.obs;
|
String? face;
|
||||||
String? heroTag;
|
String? heroTag;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
late int ownerMid;
|
late int ownerMid;
|
||||||
// 投稿列表
|
// 投稿列表
|
||||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||||
var userInfo;
|
var userInfo;
|
||||||
RxInt attribute = (-1).obs;
|
Box setting = GStrorage.setting;
|
||||||
RxString attributeText = '关注'.obs;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -30,9 +27,8 @@ class MemberController extends GetxController {
|
|||||||
mid = int.parse(Get.parameters['mid']!);
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
ownerMid = userInfo != null ? userInfo.mid : -1;
|
ownerMid = userInfo != null ? userInfo.mid : -1;
|
||||||
face.value = Get.arguments['face'] ?? '';
|
face = Get.arguments['face'] ?? '';
|
||||||
heroTag = Get.arguments['heroTag'] ?? '';
|
heroTag = Get.arguments['heroTag'] ?? '';
|
||||||
relationSearch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户信息
|
// 获取用户信息
|
||||||
@ -41,7 +37,6 @@ class MemberController extends GetxController {
|
|||||||
var res = await MemberHttp.memberInfo(mid: mid);
|
var res = await MemberHttp.memberInfo(mid: mid);
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
memberInfo.value = res['data'];
|
memberInfo.value = res['data'];
|
||||||
face.value = res['data'].face;
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -69,25 +64,18 @@ class MemberController extends GetxController {
|
|||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (attribute.value == 128) {
|
|
||||||
blockUser();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SmartDialog.show(
|
SmartDialog.show(
|
||||||
useSystem: true,
|
useSystem: true,
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
content: Text(memberInfo.value.isFollowed! ? '取消关注该用户?' : '关注该用户?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: Text(
|
child: const Text('取消')),
|
||||||
'点错了',
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await VideoHttp.relationMod(
|
await VideoHttp.relationMod(
|
||||||
@ -96,7 +84,8 @@ class MemberController extends GetxController {
|
|||||||
reSrc: 11,
|
reSrc: 11,
|
||||||
);
|
);
|
||||||
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
||||||
relationSearch();
|
SmartDialog.dismiss();
|
||||||
|
SmartDialog.showLoading();
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
memberInfo.update((val) {});
|
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 {
|
Future blockUser(int mid) async {
|
||||||
if (userInfo == null) {
|
if (userInfoCache.get('userInfoCache') == null) {
|
||||||
SmartDialog.showToast('账号未登录');
|
SmartDialog.showToast('账号未登录');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -137,12 +109,12 @@ class MemberController extends GetxController {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('提示'),
|
title: const Text('提示'),
|
||||||
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
|
content: const Text('确认拉黑该用户?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => SmartDialog.dismiss(),
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
child: Text(
|
child: Text(
|
||||||
'点错了',
|
'取消',
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -150,16 +122,15 @@ class MemberController extends GetxController {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var res = await VideoHttp.relationMod(
|
var res = await VideoHttp.relationMod(
|
||||||
mid: mid,
|
mid: mid,
|
||||||
act: attribute.value != 128 ? 5 : 6,
|
act: 5,
|
||||||
reSrc: 11,
|
reSrc: 11,
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (res['status']) {
|
if (res['status']) {
|
||||||
attribute.value = attribute.value != 128 ? 128 : 0;
|
List<int> blackMidsList = setting
|
||||||
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
|
.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
memberInfo.value.isFollowed = false;
|
blackMidsList.add(mid);
|
||||||
relationSearch();
|
setting.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||||
memberInfo.update((val) {});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: const Text('确认'),
|
child: const Text('确认'),
|
||||||
@ -169,8 +140,4 @@ class MemberController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareUser() {
|
|
||||||
Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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/archive/view.dart';
|
||||||
import 'package:pilipala/pages/member/dynamic/index.dart';
|
import 'package:pilipala/pages/member/dynamic/index.dart';
|
||||||
import 'package:pilipala/pages/member/index.dart';
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
|
||||||
|
|
||||||
import 'widgets/profile.dart';
|
import 'widgets/profile.dart';
|
||||||
|
|
||||||
@ -21,8 +20,7 @@ class MemberPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _MemberPageState extends State<MemberPage>
|
class _MemberPageState extends State<MemberPage>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
late String heroTag;
|
final MemberController _memberController = Get.put(MemberController());
|
||||||
late MemberController _memberController;
|
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
final ScrollController _extendNestCtr = ScrollController();
|
final ScrollController _extendNestCtr = ScrollController();
|
||||||
late TabController _tabController;
|
late TabController _tabController;
|
||||||
@ -31,9 +29,6 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
heroTag =
|
|
||||||
Get.arguments['heroTag'] ?? Utils.makeHeroTag(Get.parameters['mid']);
|
|
||||||
_memberController = Get.put(MemberController(), tag: heroTag);
|
|
||||||
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||||
_futureBuilderFuture = _memberController.getInfo();
|
_futureBuilderFuture = _memberController.getInfo();
|
||||||
_extendNestCtr.addListener(
|
_extendNestCtr.addListener(
|
||||||
@ -82,13 +77,11 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
NetworkImgLayer(
|
||||||
() => NetworkImgLayer(
|
|
||||||
width: 35,
|
width: 35,
|
||||||
height: 35,
|
height: 35,
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
src: _memberController.face.value,
|
src: _memberController.face ?? '',
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Obx(
|
Obx(
|
||||||
@ -109,78 +102,79 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
onPressed: () => Get.toNamed(
|
||||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||||
if (_memberController.ownerMid !=
|
icon: const Icon(Icons.search_outlined),
|
||||||
_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),
|
const SizedBox(width: 4),
|
||||||
],
|
],
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
background: Stack(
|
background: Stack(
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
// if (_memberController.face != null)
|
||||||
() => _memberController.face.value != ''
|
// Positioned.fill(
|
||||||
? Positioned.fill(
|
// bottom: 10,
|
||||||
bottom: 10,
|
// child: Container(
|
||||||
child: Container(
|
// decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
// image: DecorationImage(
|
||||||
image: DecorationImage(
|
// fit: BoxFit.fitWidth,
|
||||||
fit: BoxFit.fitWidth,
|
// image: NetworkImage(_memberController.face!),
|
||||||
image: NetworkImage(
|
// alignment: Alignment.topCenter,
|
||||||
_memberController.face.value),
|
// isAntiAlias: true,
|
||||||
alignment: Alignment.topCenter,
|
// ),
|
||||||
isAntiAlias: true,
|
// ),
|
||||||
),
|
// foregroundDecoration: BoxDecoration(
|
||||||
),
|
// gradient: LinearGradient(
|
||||||
foregroundDecoration: BoxDecoration(
|
// colors: [
|
||||||
gradient: LinearGradient(
|
// Theme.of(context)
|
||||||
colors: [
|
// .colorScheme
|
||||||
Theme.of(context)
|
// .background
|
||||||
.colorScheme
|
// .withOpacity(0.44),
|
||||||
.background
|
// Theme.of(context).colorScheme.background,
|
||||||
.withOpacity(0.44),
|
// ],
|
||||||
Theme.of(context).colorScheme.background,
|
// begin: Alignment.topCenter,
|
||||||
],
|
// end: Alignment.bottomCenter,
|
||||||
begin: Alignment.topCenter,
|
// stops: const [0.0, 0.46],
|
||||||
end: Alignment.bottomCenter,
|
// ),
|
||||||
stops: const [0.0, 0.46],
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox(),
|
|
||||||
),
|
|
||||||
Positioned(
|
Positioned(
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@ -224,109 +218,109 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
fontWeight:
|
fontWeight:
|
||||||
FontWeight.bold),
|
FontWeight.bold),
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 2),
|
// const SizedBox(width: 2),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sex ==
|
// .memberInfo.value.sex ==
|
||||||
'女')
|
// '女')
|
||||||
const Icon(
|
// const Icon(
|
||||||
FontAwesomeIcons.venus,
|
// FontAwesomeIcons.venus,
|
||||||
size: 14,
|
// size: 14,
|
||||||
color: Colors.pink,
|
// color: Colors.pink,
|
||||||
),
|
// ),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sex ==
|
// .memberInfo.value.sex ==
|
||||||
'男')
|
// '男')
|
||||||
const Icon(
|
// const Icon(
|
||||||
FontAwesomeIcons.mars,
|
// FontAwesomeIcons.mars,
|
||||||
size: 14,
|
// size: 14,
|
||||||
color: Colors.blue,
|
// color: Colors.blue,
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 4),
|
// const SizedBox(width: 4),
|
||||||
Image.asset(
|
// Image.asset(
|
||||||
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
// 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
||||||
height: 11,
|
// height: 11,
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 6),
|
// const SizedBox(width: 6),
|
||||||
if (_memberController.memberInfo
|
// if (_memberController.memberInfo
|
||||||
.value.vip!.status ==
|
// .value.vip!.status ==
|
||||||
1 &&
|
// 1 &&
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans'] !=
|
// 'img_label_uri_hans'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
Image.network(
|
// Image.network(
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans'],
|
// 'img_label_uri_hans'],
|
||||||
height: 20,
|
// height: 20,
|
||||||
),
|
// ),
|
||||||
] else if (_memberController
|
// ] else if (_memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.vip!
|
// .vip!
|
||||||
.status ==
|
// .status ==
|
||||||
1 &&
|
// 1 &&
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans_static'] !=
|
// 'img_label_uri_hans_static'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
Image.network(
|
// Image.network(
|
||||||
_memberController.memberInfo
|
// _memberController.memberInfo
|
||||||
.value.vip!.label![
|
// .value.vip!.label![
|
||||||
'img_label_uri_hans_static'],
|
// 'img_label_uri_hans_static'],
|
||||||
height: 20,
|
// height: 20,
|
||||||
),
|
// ),
|
||||||
]
|
// ]
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (_memberController.memberInfo.value
|
// if (_memberController.memberInfo.value
|
||||||
.official!['title'] !=
|
// .official!['title'] !=
|
||||||
'') ...[
|
// '') ...[
|
||||||
const SizedBox(height: 6),
|
// const SizedBox(height: 6),
|
||||||
Text.rich(
|
// Text.rich(
|
||||||
maxLines: 2,
|
// maxLines: 2,
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
text: _memberController
|
// text: _memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.official!['role'] ==
|
// .official!['role'] ==
|
||||||
1
|
// 1
|
||||||
? '个人认证:'
|
// ? '个人认证:'
|
||||||
: '企业认证:',
|
// : '企业认证:',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
color: Theme.of(context)
|
// color: Theme.of(context)
|
||||||
.primaryColor,
|
// .primaryColor,
|
||||||
),
|
// ),
|
||||||
children: [
|
// children: [
|
||||||
TextSpan(
|
// TextSpan(
|
||||||
text: _memberController
|
// text: _memberController
|
||||||
.memberInfo
|
// .memberInfo
|
||||||
.value
|
// .value
|
||||||
.official!['title'],
|
// .official!['title'],
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
softWrap: true,
|
// softWrap: true,
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
const SizedBox(height: 4),
|
// const SizedBox(height: 4),
|
||||||
if (_memberController
|
// if (_memberController
|
||||||
.memberInfo.value.sign !=
|
// .memberInfo.value.sign !=
|
||||||
'')
|
// '')
|
||||||
SelectableRegion(
|
// SelectableRegion(
|
||||||
magnifierConfiguration:
|
// magnifierConfiguration:
|
||||||
const TextMagnifierConfiguration(),
|
// const TextMagnifierConfiguration(),
|
||||||
focusNode: FocusNode(),
|
// focusNode: FocusNode(),
|
||||||
selectionControls:
|
// selectionControls:
|
||||||
MaterialTextSelectionControls(),
|
// MaterialTextSelectionControls(),
|
||||||
child: Text(
|
// child: Text(
|
||||||
_memberController
|
// _memberController
|
||||||
.memberInfo.value.sign!,
|
// .memberInfo.value.sign!,
|
||||||
textAlign: TextAlign.left,
|
// textAlign: TextAlign.left,
|
||||||
maxLines: 2,
|
// maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
// overflow: TextOverflow.ellipsis,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -353,28 +347,29 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||||
},
|
},
|
||||||
onlyOneScrollInBody: true,
|
onlyOneScrollInBody: true,
|
||||||
body: Column(
|
// body: Column(
|
||||||
children: [
|
// children: [
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
width: double.infinity,
|
// width: double.infinity,
|
||||||
height: 50,
|
// height: 50,
|
||||||
child: TabBar(controller: _tabController, tabs: const [
|
// child: TabBar(controller: _tabController, tabs: const [
|
||||||
Tab(text: '主页'),
|
// Tab(text: '主页'),
|
||||||
Tab(text: '动态'),
|
// Tab(text: '动态'),
|
||||||
Tab(text: '投稿'),
|
// Tab(text: '投稿'),
|
||||||
]),
|
// ]),
|
||||||
),
|
// ),
|
||||||
Expanded(
|
// Expanded(
|
||||||
child: TabBarView(
|
// child: TabBarView(
|
||||||
controller: _tabController,
|
// controller: _tabController,
|
||||||
children: const [
|
// children: const [
|
||||||
Text('主页'),
|
// Text('主页'),
|
||||||
MemberDynamicPanel(),
|
// MemberDynamicPanel(),
|
||||||
ArchivePanel(),
|
// ArchivePanel(),
|
||||||
],
|
// ],
|
||||||
))
|
// ))
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
|
body: ArchivePanel(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
width: 90,
|
width: 90,
|
||||||
height: 90,
|
height: 90,
|
||||||
type: 'avatar',
|
type: 'avatar',
|
||||||
src: !loadingStatus ? memberInfo.face : ctr.face.value,
|
src: !loadingStatus ? memberInfo.face : ctr.face,
|
||||||
),
|
),
|
||||||
if (!loadingStatus &&
|
if (!loadingStatus &&
|
||||||
memberInfo.liveRoom != null &&
|
memberInfo.liveRoom != null &&
|
||||||
@ -70,126 +70,116 @@ Widget profile(ctr, {loadingStatus = false}) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
// Padding(
|
||||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
// padding: const EdgeInsets.only(left: 10, right: 10),
|
||||||
child: Row(
|
// child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
// mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
// mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
children: [
|
// children: [
|
||||||
InkWell(
|
// InkWell(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Get.toNamed(
|
// Get.toNamed(
|
||||||
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
// '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
||||||
},
|
// },
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
!loadingStatus
|
// !loadingStatus
|
||||||
? ctr.userStat!['following'].toString()
|
// ? ctr.userStat!['following'].toString()
|
||||||
: '-',
|
// : '-',
|
||||||
style: const TextStyle(
|
// style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold),
|
// fontWeight: FontWeight.bold),
|
||||||
),
|
// ),
|
||||||
Text(
|
// Text(
|
||||||
'关注',
|
// '关注',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: Theme.of(context)
|
// fontSize: Theme.of(context)
|
||||||
.textTheme
|
// .textTheme
|
||||||
.labelMedium!
|
// .labelMedium!
|
||||||
.fontSize),
|
// .fontSize),
|
||||||
)
|
// )
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
InkWell(
|
// InkWell(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
Get.toNamed(
|
// Get.toNamed(
|
||||||
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
// '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
|
||||||
},
|
// },
|
||||||
child: Column(
|
// child: Column(
|
||||||
children: [
|
// children: [
|
||||||
Text(
|
// Text(
|
||||||
!loadingStatus
|
// !loadingStatus
|
||||||
? Utils.numFormat(
|
// ? Utils.numFormat(
|
||||||
ctr.userStat!['follower'],
|
// ctr.userStat!['follower'],
|
||||||
)
|
// )
|
||||||
: '-',
|
// : '-',
|
||||||
style: const TextStyle(
|
// style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold)),
|
// fontWeight: FontWeight.bold)),
|
||||||
Text(
|
// Text('粉丝',
|
||||||
'粉丝',
|
// style: TextStyle(
|
||||||
style: TextStyle(
|
// fontSize: Theme.of(context)
|
||||||
fontSize: Theme.of(context)
|
// .textTheme
|
||||||
.textTheme
|
// .labelMedium!
|
||||||
.labelMedium!
|
// .fontSize))
|
||||||
.fontSize),
|
// ],
|
||||||
)
|
// ),
|
||||||
],
|
// ),
|
||||||
),
|
// Column(
|
||||||
),
|
// children: [
|
||||||
Column(
|
// const Text('-',
|
||||||
children: [
|
// style: TextStyle(fontWeight: FontWeight.bold)),
|
||||||
const Text('-',
|
// Text(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold)),
|
// '获赞',
|
||||||
Text(
|
// style: TextStyle(
|
||||||
'获赞',
|
// fontSize: Theme.of(context)
|
||||||
style: TextStyle(
|
// .textTheme
|
||||||
fontSize: Theme.of(context)
|
// .labelMedium!
|
||||||
.textTheme
|
// .fontSize),
|
||||||
.labelMedium!
|
// )
|
||||||
.fontSize),
|
// ],
|
||||||
)
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
],
|
// ),
|
||||||
),
|
// const SizedBox(height: 10),
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
if (ctr.ownerMid != ctr.mid) ...[
|
if (ctr.ownerMid != ctr.mid) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
TextButton(
|
||||||
() => Expanded(
|
|
||||||
child: TextButton(
|
|
||||||
onPressed: () => ctr.actionRelationMod(),
|
onPressed: () => ctr.actionRelationMod(),
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
foregroundColor: ctr.attribute.value == -1
|
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||||
? Colors.transparent
|
foregroundColor:
|
||||||
: ctr.attribute.value != 0
|
!loadingStatus && memberInfo.isFollowed!
|
||||||
? Theme.of(context).colorScheme.outline
|
? Theme.of(context).colorScheme.outline
|
||||||
: Theme.of(context)
|
: Theme.of(context).colorScheme.onPrimary,
|
||||||
.colorScheme
|
backgroundColor: !loadingStatus &&
|
||||||
.onPrimary,
|
memberInfo.isFollowed!
|
||||||
backgroundColor: ctr.attribute.value != 0
|
? Theme.of(context).colorScheme.onInverseSurface
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface
|
|
||||||
: Theme.of(context)
|
: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.primary, // 设置按钮背景色
|
.primary, // 设置按钮背景色
|
||||||
),
|
),
|
||||||
child: Obx(() => Text(ctr.attributeText.value)),
|
child: Text(!loadingStatus && memberInfo.isFollowed!
|
||||||
),
|
? '取关'
|
||||||
),
|
: '关注'),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
TextButton(
|
||||||
child: TextButton(
|
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context)
|
padding: const EdgeInsets.only(left: 42, right: 42),
|
||||||
.colorScheme
|
backgroundColor:
|
||||||
.onInverseSurface,
|
Theme.of(context).colorScheme.onInverseSurface,
|
||||||
),
|
),
|
||||||
child: const Text('发消息'),
|
child: const Text('发消息'),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
94
lib/pages/member_search/controller.dart
Normal file
94
lib/pages/member_search/controller.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/member.dart';
|
||||||
|
import 'package:pilipala/models/member/archive.dart';
|
||||||
|
|
||||||
|
class MemberSearchController extends GetxController {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
|
RxString searchKeyWord = ''.obs;
|
||||||
|
String hintText = '搜索';
|
||||||
|
RxString loadingStatus = 'init'.obs;
|
||||||
|
RxString loadingText = '加载中...'.obs;
|
||||||
|
bool hasRequest = false;
|
||||||
|
late int mid;
|
||||||
|
RxString uname = ''.obs;
|
||||||
|
int archivePn = 1;
|
||||||
|
int archiveCount = 0;
|
||||||
|
RxList<VListItemModel> archiveList = <VListItemModel>[].obs;
|
||||||
|
int dynamic_pn = 1;
|
||||||
|
RxList<VListItemModel> dynamicList = <VListItemModel>[].obs;
|
||||||
|
|
||||||
|
int ps = 30;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
mid = int.parse(Get.parameters['mid']!);
|
||||||
|
uname.value = Get.parameters['uname']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空搜索
|
||||||
|
void onClear() {
|
||||||
|
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
|
||||||
|
controller.value.clear();
|
||||||
|
searchKeyWord.value = '';
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChange(value) {
|
||||||
|
searchKeyWord.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交搜索内容
|
||||||
|
void submit() {
|
||||||
|
loadingStatus.value = 'loading';
|
||||||
|
if (hasRequest) {
|
||||||
|
archivePn = 1;
|
||||||
|
searchArchives();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索视频
|
||||||
|
Future searchArchives({type = 'init'}) async {
|
||||||
|
if (type == 'onLoad' && loadingText.value == '没有更多了') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var res = await MemberHttp.memberArchive(
|
||||||
|
mid: mid,
|
||||||
|
pn: archivePn,
|
||||||
|
keyword: controller.value.text,
|
||||||
|
order: 'pubdate',
|
||||||
|
);
|
||||||
|
if (res['status']) {
|
||||||
|
if (type == 'init' || archivePn == 1) {
|
||||||
|
archiveList.value = res['data'].list.vlist;
|
||||||
|
} else {
|
||||||
|
archiveList.addAll(res['data'].list.vlist);
|
||||||
|
}
|
||||||
|
archiveCount = res['data'].page['count'];
|
||||||
|
if (archiveList.length == archiveCount) {
|
||||||
|
loadingText.value = '没有更多了';
|
||||||
|
}
|
||||||
|
archivePn += 1;
|
||||||
|
hasRequest = true;
|
||||||
|
if (res['data'].list.vlist.isEmpty) {
|
||||||
|
loadingStatus.value = 'finish';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadingStatus.value = 'finish';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索动态
|
||||||
|
Future searchDynamic() async {}
|
||||||
|
|
||||||
|
//
|
||||||
|
onLoad() {
|
||||||
|
searchArchives(type: 'onLoad');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/member_search/index.dart
Normal file
4
lib/pages/member_search/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library member_search;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
208
lib/pages/member_search/view.dart
Normal file
208
lib/pages/member_search/view.dart
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/no_data.dart';
|
||||||
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MemberSearchPage extends StatefulWidget {
|
||||||
|
const MemberSearchPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemberSearchPage> createState() => _MemberSearchPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemberSearchPageState extends State<MemberSearchPage>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
final MemberSearchController _memberSearchCtr =
|
||||||
|
Get.put(MemberSearchController());
|
||||||
|
late ScrollController scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
scrollController = _memberSearchCtr.scrollController;
|
||||||
|
scrollController.addListener(
|
||||||
|
() {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
|
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
|
||||||
|
_memberSearchCtr.onLoad();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// _tabController = TabController(length: 2, vsync: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// _tabController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
titleSpacing: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _memberSearchCtr.submit(),
|
||||||
|
icon: const Icon(CupertinoIcons.search, size: 22)),
|
||||||
|
const SizedBox(width: 10)
|
||||||
|
],
|
||||||
|
title: Obx(
|
||||||
|
() => TextField(
|
||||||
|
autofocus: true,
|
||||||
|
focusNode: _memberSearchCtr.searchFocusNode,
|
||||||
|
controller: _memberSearchCtr.controller.value,
|
||||||
|
textInputAction: TextInputAction.search,
|
||||||
|
onChanged: (value) => _memberSearchCtr.onChange(value),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _memberSearchCtr.hintText,
|
||||||
|
border: InputBorder.none,
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear,
|
||||||
|
size: 22,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
onPressed: () => _memberSearchCtr.onClear(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: (String value) => _memberSearchCtr.submit(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Obx(
|
||||||
|
() => Column(
|
||||||
|
children: _memberSearchCtr.loadingStatus.value == 'init'
|
||||||
|
? [
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text('搜索「${_memberSearchCtr.uname.value}」的视频'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
// TabBar(
|
||||||
|
// controller: _tabController,
|
||||||
|
// tabs: const [
|
||||||
|
// Tab(text: "视频"),
|
||||||
|
// Tab(text: "动态"),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
// TabBarView(
|
||||||
|
// controller: _tabController,
|
||||||
|
// children: [
|
||||||
|
FutureBuilder(
|
||||||
|
future: _memberSearchCtr.searchArchives(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map data = snapshot.data as Map;
|
||||||
|
if (data['status']) {
|
||||||
|
return Obx(
|
||||||
|
() => _memberSearchCtr.archiveList.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
controller: scrollController,
|
||||||
|
itemCount:
|
||||||
|
_memberSearchCtr.archiveList.length +
|
||||||
|
1,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index ==
|
||||||
|
_memberSearchCtr
|
||||||
|
.archiveList.length) {
|
||||||
|
return Container(
|
||||||
|
height: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom +
|
||||||
|
60,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context)
|
||||||
|
.padding
|
||||||
|
.bottom),
|
||||||
|
child: Center(
|
||||||
|
child: Obx(
|
||||||
|
() => Text(
|
||||||
|
_memberSearchCtr
|
||||||
|
.loadingText.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline,
|
||||||
|
fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return VideoCardH(
|
||||||
|
videoItem: _memberSearchCtr
|
||||||
|
.archiveList[index]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: _memberSearchCtr.loadingStatus.value ==
|
||||||
|
'loading'
|
||||||
|
? ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(
|
||||||
|
height: 400,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'没有搜索到相关内容\n请尝试别的搜索词',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: <Widget>[
|
||||||
|
HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 10,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return const VideoCardHSkeleton();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -72,12 +72,8 @@ class _MinePageState extends State<MinePage> {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: LayoutBuilder(
|
body: SingleChildScrollView(
|
||||||
builder: (context, constraint) {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
child: SizedBox(
|
|
||||||
height: constraint.maxHeight,
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
@ -89,8 +85,7 @@ class _MinePageState extends State<MinePage> {
|
|||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
if (snapshot.data['status']) {
|
if (snapshot.data['status']) {
|
||||||
return Obx(
|
return Obx(() => userInfoBuild(mineController, context));
|
||||||
() => userInfoBuild(mineController, context));
|
|
||||||
} else {
|
} else {
|
||||||
return userInfoBuild(mineController, context);
|
return userInfoBuild(mineController, context);
|
||||||
}
|
}
|
||||||
@ -103,9 +98,6 @@ class _MinePageState extends State<MinePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget userInfoBuild(_mineController, context) {
|
Widget userInfoBuild(_mineController, context) {
|
||||||
@ -138,85 +130,9 @@ class _MinePageState extends State<MinePage> {
|
|||||||
_mineController.userInfo.value.uname ?? '点击头像登录',
|
_mineController.userInfo.value.uname ?? '点击头像登录',
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
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),
|
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(
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class SSearchController extends GetxController {
|
|||||||
final FocusNode searchFocusNode = FocusNode();
|
final FocusNode searchFocusNode = FocusNode();
|
||||||
RxString searchKeyWord = ''.obs;
|
RxString searchKeyWord = ''.obs;
|
||||||
Rx<TextEditingController> controller = TextEditingController().obs;
|
Rx<TextEditingController> controller = TextEditingController().obs;
|
||||||
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
|
RxList<HotSearchItem> hotSearchList = [HotSearchItem()].obs;
|
||||||
Box histiryWord = GStrorage.historyword;
|
Box histiryWord = GStrorage.historyword;
|
||||||
List historyCacheList = [];
|
List historyCacheList = [];
|
||||||
RxList historyList = [].obs;
|
RxList historyList = [].obs;
|
||||||
@ -27,9 +27,9 @@ class SSearchController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
|
// if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
|
||||||
searchDefault();
|
// searchDefault();
|
||||||
}
|
// }
|
||||||
// 其他页面跳转过来
|
// 其他页面跳转过来
|
||||||
if (Get.parameters.keys.isNotEmpty) {
|
if (Get.parameters.keys.isNotEmpty) {
|
||||||
if (Get.parameters['keyword'] != null) {
|
if (Get.parameters['keyword'] != null) {
|
||||||
@ -51,7 +51,7 @@ class SSearchController extends GetxController {
|
|||||||
searchSuggestList.value = [];
|
searchSuggestList.value = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_debouncer.call(() => querySearchSuggest(value));
|
// _debouncer.call(() => querySearchSuggest(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onClear() {
|
void onClear() {
|
||||||
@ -85,9 +85,7 @@ class SSearchController extends GetxController {
|
|||||||
// 获取热搜关键词
|
// 获取热搜关键词
|
||||||
Future queryHotSearchList() async {
|
Future queryHotSearchList() async {
|
||||||
var result = await SearchHttp.hotSearchList();
|
var result = await SearchHttp.hotSearchList();
|
||||||
if (result['status']) {
|
|
||||||
hotSearchList.value = result['data'].list;
|
hotSearchList.value = result['data'].list;
|
||||||
}
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,11 +103,9 @@ class SSearchController extends GetxController {
|
|||||||
Future querySearchSuggest(String value) async {
|
Future querySearchSuggest(String value) async {
|
||||||
var result = await SearchHttp.searchSuggest(term: value);
|
var result = await SearchHttp.searchSuggest(term: value);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
if (result['data'] is SearchSuggestModel) {
|
|
||||||
searchSuggestList.value = result['data'].tag;
|
searchSuggestList.value = result['data'].tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onSelect(word) {
|
onSelect(word) {
|
||||||
searchKeyWord.value = word;
|
searchKeyWord.value = word;
|
||||||
|
|||||||
@ -140,141 +140,18 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Column(
|
child: _history(),
|
||||||
children: [
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
// 搜索建议
|
|
||||||
_searchSuggest(),
|
|
||||||
// 热搜
|
|
||||||
Visibility(
|
|
||||||
visible: _searchController.enableHotKey,
|
|
||||||
child: hotSearch(_searchController)),
|
|
||||||
// 搜索历史
|
|
||||||
_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() {
|
Widget _history() {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => Container(
|
() => Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.fromLTRB(10, 25, 6, 0),
|
padding: const EdgeInsets.fromLTRB(10, 4, 6, 0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
|
|||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
import 'widgets/article_panel.dart';
|
|
||||||
import 'widgets/live_panel.dart';
|
import 'widgets/live_panel.dart';
|
||||||
import 'widgets/media_bangumi_panel.dart';
|
import 'widgets/media_bangumi_panel.dart';
|
||||||
import 'widgets/user_panel.dart';
|
import 'widgets/user_panel.dart';
|
||||||
@ -85,14 +84,12 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
ctr: _searchPanelController,
|
ctr: _searchPanelController,
|
||||||
list: list.value,
|
list: list.value,
|
||||||
);
|
);
|
||||||
case SearchType.media_bangumi:
|
// case SearchType.media_bangumi:
|
||||||
return searchMbangumiPanel(context, ctr, list);
|
// return searchMbangumiPanel(context, ctr, list);
|
||||||
case SearchType.bili_user:
|
case SearchType.bili_user:
|
||||||
return searchUserPanel(context, ctr, list);
|
return searchUserPanel(context, ctr, list);
|
||||||
case SearchType.live_room:
|
// case SearchType.live_room:
|
||||||
return searchLivePanel(context, ctr, list);
|
// return searchLivePanel(context, ctr, list);
|
||||||
// case SearchType.article:
|
|
||||||
// return searchArticlePanel(context, ctr, list);
|
|
||||||
default:
|
default:
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
@ -118,12 +115,12 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
switch (widget.searchType) {
|
switch (widget.searchType) {
|
||||||
case SearchType.video:
|
case SearchType.video:
|
||||||
return const VideoCardHSkeleton();
|
return const VideoCardHSkeleton();
|
||||||
case SearchType.media_bangumi:
|
// case SearchType.media_bangumi:
|
||||||
return const MediaBangumiSkeleton();
|
// return const MediaBangumiSkeleton();
|
||||||
case SearchType.bili_user:
|
case SearchType.bili_user:
|
||||||
return const VideoCardHSkeleton();
|
return const VideoCardHSkeleton();
|
||||||
case SearchType.live_room:
|
// case SearchType.live_room:
|
||||||
return const VideoCardHSkeleton();
|
// return const VideoCardHSkeleton();
|
||||||
default:
|
default:
|
||||||
return const VideoCardHSkeleton();
|
return const VideoCardHSkeleton();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
import 'package:flutter/material.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: () {},
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -124,7 +124,7 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
'bangumiItem': res['data'],
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -37,8 +37,6 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
|
|||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
i!.uname,
|
i!.uname,
|
||||||
@ -46,19 +44,6 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
|
|||||||
fontSize: 14,
|
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)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (i.officialVerify['desc'] != '')
|
if (i.officialVerify['desc'] != '')
|
||||||
Text(
|
Text(
|
||||||
i.officialVerify['desc'],
|
i.officialVerify['desc'],
|
||||||
|
|||||||
@ -13,18 +13,17 @@ import 'package:pilipala/models/video/play/quality.dart';
|
|||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
import 'package:pilipala/pages/video/detail/replyReply/index.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/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
||||||
import 'widgets/header_control.dart';
|
|
||||||
|
|
||||||
class VideoDetailController extends GetxController
|
class VideoDetailController extends GetxController
|
||||||
with GetSingleTickerProviderStateMixin {
|
with GetSingleTickerProviderStateMixin {
|
||||||
/// 路由传参
|
/// 路由传参
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid']!;
|
||||||
int cid = int.parse(Get.parameters['cid']!);
|
RxInt cid = int.parse(Get.parameters['cid']!).obs;
|
||||||
RxInt danmakuCid = 0.obs;
|
RxInt danmakuCid = 0.obs;
|
||||||
String heroTag = Get.arguments['heroTag'];
|
String heroTag = Get.arguments['heroTag'];
|
||||||
// 视频详情
|
// 视频详情
|
||||||
@ -109,9 +108,7 @@ class VideoDetailController extends GetxController
|
|||||||
localCache.get(LocalCacheKey.historyPause) == true) {
|
localCache.get(LocalCacheKey.historyPause) == true) {
|
||||||
enableHeart = false;
|
enableHeart = false;
|
||||||
}
|
}
|
||||||
danmakuCid.value = cid;
|
danmakuCid.value = cid.value;
|
||||||
|
|
||||||
///
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
floating = Floating();
|
floating = Floating();
|
||||||
}
|
}
|
||||||
@ -218,19 +215,16 @@ class VideoDetailController extends GetxController
|
|||||||
// 默认1倍速
|
// 默认1倍速
|
||||||
speed: 1.0,
|
speed: 1.0,
|
||||||
bvid: bvid,
|
bvid: bvid,
|
||||||
cid: cid,
|
cid: cid.value,
|
||||||
enableHeart: enableHeart,
|
enableHeart: enableHeart,
|
||||||
isFirstTime: isFirstTime,
|
isFirstTime: isFirstTime,
|
||||||
autoplay: autoplay,
|
autoplay: autoplay,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// 开启自动全屏时,在player初始化完成后立即传入headerControl
|
|
||||||
plPlayerController.headerControl = headerControl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 视频链接
|
// 视频链接
|
||||||
Future queryVideoUrl() async {
|
Future queryVideoUrl() async {
|
||||||
var result = await VideoHttp.videoUrl(cid: cid, bvid: bvid);
|
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
data = result['data'];
|
data = result['data'];
|
||||||
|
|
||||||
@ -276,8 +270,8 @@ class VideoDetailController extends GetxController
|
|||||||
currentDecodeFormats = flag
|
currentDecodeFormats = flag
|
||||||
? currentDecodeFormats
|
? currentDecodeFormats
|
||||||
: VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!;
|
: VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!;
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
SmartDialog.showToast('DecodeFormats error: $err');
|
print(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 取出符合当前解码格式的videoItem
|
/// 取出符合当前解码格式的videoItem
|
||||||
@ -289,7 +283,7 @@ class VideoDetailController extends GetxController
|
|||||||
}
|
}
|
||||||
videoUrl = firstVideo.baseUrl!;
|
videoUrl = firstVideo.baseUrl!;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.showToast('firstVideo error: $err');
|
print(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||||
@ -299,6 +293,7 @@ class VideoDetailController extends GetxController
|
|||||||
try {
|
try {
|
||||||
int resultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
int resultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||||
defaultValue: AudioQuality.hiRes.code);
|
defaultValue: AudioQuality.hiRes.code);
|
||||||
|
|
||||||
if (data.dash!.dolby?.audio?.isNotEmpty == true) {
|
if (data.dash!.dolby?.audio?.isNotEmpty == true) {
|
||||||
// 杜比
|
// 杜比
|
||||||
audiosList.insert(0, data.dash!.dolby!.audio!.first);
|
audiosList.insert(0, data.dash!.dolby!.audio!.first);
|
||||||
@ -312,20 +307,16 @@ class VideoDetailController extends GetxController
|
|||||||
if (audiosList.isNotEmpty) {
|
if (audiosList.isNotEmpty) {
|
||||||
List<int> numbers = audiosList.map((map) => map.id!).toList();
|
List<int> numbers = audiosList.map((map) => map.id!).toList();
|
||||||
int closestNumber = Utils.findClosestNumber(resultAudioQa, numbers);
|
int closestNumber = Utils.findClosestNumber(resultAudioQa, numbers);
|
||||||
if (!numbers.contains(resultAudioQa) &&
|
if (!numbers.contains(resultAudioQa)) {
|
||||||
numbers.any((e) => e > resultAudioQa)) {
|
|
||||||
closestNumber = 30280;
|
closestNumber = 30280;
|
||||||
}
|
}
|
||||||
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
|
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
|
||||||
} else {
|
|
||||||
firstAudio = AudioItem();
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (e) {
|
||||||
firstAudio = audiosList.isNotEmpty ? audiosList.first : AudioItem();
|
print(e);
|
||||||
SmartDialog.showToast('firstAudio error: $err');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
audioUrl = firstAudio.baseUrl ?? '';
|
audioUrl = firstAudio!.baseUrl ?? '';
|
||||||
//
|
//
|
||||||
if (firstAudio.id != null) {
|
if (firstAudio.id != null) {
|
||||||
currentAudioQa = AudioQualityCode.fromCode(firstAudio.id!)!;
|
currentAudioQa = AudioQualityCode.fromCode(firstAudio.id!)!;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:pilipala/models/user/fav_folder.dart';
|
|||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
@ -58,10 +59,14 @@ class VideoIntroController extends GetxController {
|
|||||||
RxString total = '1'.obs;
|
RxString total = '1'.obs;
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
bool isPaused = false;
|
bool isPaused = false;
|
||||||
|
String heroTag = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
|
try {
|
||||||
|
heroTag = Get.arguments['heroTag'];
|
||||||
|
} catch (_) {}
|
||||||
userInfo = userInfoCache.get('userInfoCache');
|
userInfo = userInfoCache.get('userInfoCache');
|
||||||
if (Get.arguments.isNotEmpty) {
|
if (Get.arguments.isNotEmpty) {
|
||||||
if (Get.arguments.containsKey('videoItem')) {
|
if (Get.arguments.containsKey('videoItem')) {
|
||||||
@ -442,7 +447,7 @@ class VideoIntroController extends GetxController {
|
|||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
videoDetailCtr.bvid = bvid;
|
videoDetailCtr.bvid = bvid;
|
||||||
videoDetailCtr.cid = cid;
|
videoDetailCtr.cid.value = cid;
|
||||||
videoDetailCtr.danmakuCid.value = cid;
|
videoDetailCtr.danmakuCid.value = cid;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
@ -486,4 +491,45 @@ class VideoIntroController extends GetxController {
|
|||||||
}
|
}
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 列表循环或者顺序播放时,自动播放下一个
|
||||||
|
void nextPlay() {
|
||||||
|
late List episodes;
|
||||||
|
bool isPages = false;
|
||||||
|
if (videoDetail.value.ugcSeason != null) {
|
||||||
|
UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
|
||||||
|
List<SectionItem> sections = ugcSeason.sections!;
|
||||||
|
episodes = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < sections.length; i++) {
|
||||||
|
List<EpisodeItem> episodesList = sections[i].episodes!;
|
||||||
|
episodes.addAll(episodesList);
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
int nextIndex = currentIndex + 1;
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
|
||||||
|
|
||||||
|
// 列表循环
|
||||||
|
if (nextIndex >= episodes.length) {
|
||||||
|
if (platRepeat == PlayRepeat.listCycle) {
|
||||||
|
nextIndex = 0;
|
||||||
|
}
|
||||||
|
if (platRepeat == PlayRepeat.listOrder) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int cid = episodes[nextIndex].cid!;
|
||||||
|
String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
|
||||||
|
int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
|
||||||
|
changeSeasonOrbangu(rBvid, cid, rAid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,9 +199,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
// 视频介绍
|
// 视频介绍
|
||||||
showIntroDetail() {
|
showIntroDetail() {
|
||||||
if (loadingStatus) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
feedBack();
|
feedBack();
|
||||||
showBottomSheet(
|
showBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@ -252,86 +249,83 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 20),
|
// const SizedBox(width: 20),
|
||||||
Opacity(
|
// SizedBox(
|
||||||
opacity: loadingStatus ? 0 : 1,
|
// width: 34,
|
||||||
child: SizedBox(
|
// height: 34,
|
||||||
width: 34,
|
// child: IconButton(
|
||||||
height: 34,
|
// style: ButtonStyle(
|
||||||
child: IconButton(
|
// padding:
|
||||||
style: ButtonStyle(
|
// MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
padding:
|
// backgroundColor:
|
||||||
MaterialStateProperty.all(EdgeInsets.zero),
|
// MaterialStateProperty.resolveWith((states) {
|
||||||
backgroundColor:
|
// return t.highlightColor.withOpacity(0.2);
|
||||||
MaterialStateProperty.resolveWith((states) {
|
// }),
|
||||||
return t.highlightColor.withOpacity(0.2);
|
// ),
|
||||||
}),
|
// onPressed: showIntroDetail,
|
||||||
),
|
// icon: Icon(
|
||||||
onPressed: showIntroDetail,
|
// Icons.more_horiz,
|
||||||
icon: Icon(
|
// color: Theme.of(context).colorScheme.primary,
|
||||||
Icons.more_horiz,
|
// ),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GestureDetector(
|
// GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
// behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => showIntroDetail(),
|
// onTap: () => showIntroDetail(),
|
||||||
child: Row(
|
// child: Row(
|
||||||
children: [
|
// children: [
|
||||||
StatView(
|
// StatView(
|
||||||
theme: 'gray',
|
// theme: 'gray',
|
||||||
view: !widget.loadingStatus
|
// view: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.view
|
// ? widget.videoDetail!.stat!.view
|
||||||
: videoItem['stat'].view,
|
// : videoItem['stat'].view,
|
||||||
size: 'medium',
|
// size: 'medium',
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 10),
|
// const SizedBox(width: 10),
|
||||||
StatDanMu(
|
// StatDanMu(
|
||||||
theme: 'gray',
|
// theme: 'gray',
|
||||||
danmu: !widget.loadingStatus
|
// danmu: !widget.loadingStatus
|
||||||
? widget.videoDetail!.stat!.danmaku
|
// ? widget.videoDetail!.stat!.danmaku
|
||||||
: videoItem['stat'].danmaku,
|
// : videoItem['stat'].danmaku,
|
||||||
size: 'medium',
|
// size: 'medium',
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 10),
|
// const SizedBox(width: 10),
|
||||||
Text(
|
// Text(
|
||||||
Utils.dateFormat(
|
// Utils.dateFormat(
|
||||||
!widget.loadingStatus
|
// !widget.loadingStatus
|
||||||
? widget.videoDetail!.pubdate
|
// ? widget.videoDetail!.pubdate
|
||||||
: videoItem['pubdate'],
|
// : videoItem['pubdate'],
|
||||||
formatType: 'detail'),
|
// formatType: 'detail'),
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
color: t.colorScheme.outline,
|
// color: t.colorScheme.outline,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 10),
|
// const SizedBox(width: 10),
|
||||||
if (videoIntroController.isShowOnlineTotal)
|
// if (videoIntroController.isShowOnlineTotal)
|
||||||
Obx(
|
// Obx(
|
||||||
() => Text(
|
// () => Text(
|
||||||
'${videoIntroController.total.value}人在看',
|
// '${videoIntroController.total.value}人在看',
|
||||||
style: TextStyle(
|
// style: TextStyle(
|
||||||
fontSize: 12,
|
// fontSize: 12,
|
||||||
color: t.colorScheme.outline,
|
// color: t.colorScheme.outline,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(height: 7),
|
// const SizedBox(height: 7),
|
||||||
// 点赞收藏转发 布局样式1
|
// 点赞收藏转发 布局样式1
|
||||||
SingleChildScrollView(
|
SingleChildScrollView(
|
||||||
padding: const EdgeInsets.only(top: 7, bottom: 7),
|
padding: const EdgeInsets.only(top: 10, bottom: 7),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: actionRow(
|
child: actionRow(
|
||||||
context,
|
context,
|
||||||
@ -388,14 +382,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(owner.name,
|
Text(owner.name,
|
||||||
style: const TextStyle(fontSize: 13)),
|
style: const TextStyle(fontSize: 13)),
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
follower,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
|
||||||
color: outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
AnimatedOpacity(
|
AnimatedOpacity(
|
||||||
opacity: loadingStatus ? 0 : 1,
|
opacity: loadingStatus ? 0 : 1,
|
||||||
@ -522,52 +508,52 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||||
return Row(children: [
|
return Row(children: [
|
||||||
Obx(
|
// Obx(
|
||||||
() => ActionRowItem(
|
// () => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
// icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
onTap: () => videoIntroController.actionLikeVideo(),
|
// onTap: () => videoIntroController.actionLikeVideo(),
|
||||||
selectStatus: videoIntroController.hasLike.value,
|
// selectStatus: videoIntroController.hasLike.value,
|
||||||
loadingStatus: loadingStatus,
|
// loadingStatus: loadingStatus,
|
||||||
text:
|
// text:
|
||||||
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
// !loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 8),
|
// const SizedBox(width: 8),
|
||||||
Obx(
|
// Obx(
|
||||||
() => ActionRowItem(
|
// () => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.b),
|
// icon: const Icon(FontAwesomeIcons.b),
|
||||||
onTap: () => videoIntroController.actionCoinVideo(),
|
// onTap: () => videoIntroController.actionCoinVideo(),
|
||||||
selectStatus: videoIntroController.hasCoin.value,
|
// selectStatus: videoIntroController.hasCoin.value,
|
||||||
loadingStatus: loadingStatus,
|
// loadingStatus: loadingStatus,
|
||||||
text:
|
// text:
|
||||||
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
// !loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 8),
|
// const SizedBox(width: 8),
|
||||||
Obx(
|
// Obx(
|
||||||
() => ActionRowItem(
|
// () => ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.heart),
|
// icon: const Icon(FontAwesomeIcons.heart),
|
||||||
onTap: () => showFavBottomSheet(),
|
// onTap: () => showFavBottomSheet(),
|
||||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
// onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||||
selectStatus: videoIntroController.hasFav.value,
|
// selectStatus: videoIntroController.hasFav.value,
|
||||||
loadingStatus: loadingStatus,
|
// loadingStatus: loadingStatus,
|
||||||
text: !loadingStatus
|
// text: !loadingStatus
|
||||||
? widget.videoDetail!.stat!.favorite!.toString()
|
// ? widget.videoDetail!.stat!.favorite!.toString()
|
||||||
: '-',
|
// : '-',
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 8),
|
// const SizedBox(width: 8),
|
||||||
ActionRowItem(
|
// ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.comment),
|
// icon: const Icon(FontAwesomeIcons.comment),
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
videoDetailCtr.tabCtr.animateTo(1);
|
// videoDetailCtr.tabCtr.animateTo(1);
|
||||||
},
|
// },
|
||||||
selectStatus: false,
|
// selectStatus: false,
|
||||||
loadingStatus: loadingStatus,
|
// loadingStatus: loadingStatus,
|
||||||
text:
|
// text:
|
||||||
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
|
// !loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
|
||||||
),
|
// ),
|
||||||
const SizedBox(width: 8),
|
// const SizedBox(width: 8),
|
||||||
ActionRowItem(
|
ActionRowItem(
|
||||||
icon: const Icon(FontAwesomeIcons.share),
|
icon: const Icon(FontAwesomeIcons.share),
|
||||||
onTap: () => videoIntroController.actionShareVideo(),
|
onTap: () => videoIntroController.actionShareVideo(),
|
||||||
|
|||||||
@ -57,20 +57,6 @@ class IntroDetail extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
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(
|
Text(
|
||||||
Utils.dateFormat(videoDetail!.pubdate,
|
Utils.dateFormat(videoDetail!.pubdate,
|
||||||
formatType: 'detail'),
|
formatType: 'detail'),
|
||||||
@ -79,8 +65,6 @@ class IntroDetail extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
|
||||||
class SeasonPanel extends StatefulWidget {
|
class SeasonPanel extends StatefulWidget {
|
||||||
@ -23,11 +24,16 @@ class SeasonPanel extends StatefulWidget {
|
|||||||
|
|
||||||
class _SeasonPanelState extends State<SeasonPanel> {
|
class _SeasonPanelState extends State<SeasonPanel> {
|
||||||
late List<EpisodeItem> episodes;
|
late List<EpisodeItem> episodes;
|
||||||
|
late int cid;
|
||||||
late int currentIndex;
|
late int currentIndex;
|
||||||
|
String heroTag = Get.arguments['heroTag'];
|
||||||
|
late VideoDetailController _videoDetailController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
cid = widget.cid!;
|
||||||
|
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
|
||||||
|
|
||||||
/// 根据 cid 找到对应集,找到对应 episodes
|
/// 根据 cid 找到对应集,找到对应 episodes
|
||||||
/// 有多个episodes时,只显示其中一个
|
/// 有多个episodes时,只显示其中一个
|
||||||
@ -48,6 +54,11 @@ class _SeasonPanelState extends State<SeasonPanel> {
|
|||||||
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
|
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
|
||||||
// .episodes!;
|
// .episodes!;
|
||||||
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
||||||
|
_videoDetailController.cid.listen((p0) {
|
||||||
|
cid = p0;
|
||||||
|
setState(() {});
|
||||||
|
currentIndex = episodes.indexWhere((e) => e.cid == cid);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFucCall(item, i) async {
|
void changeFucCall(item, i) async {
|
||||||
|
|||||||
@ -39,7 +39,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
bool _isFabVisible = true;
|
bool _isFabVisible = true;
|
||||||
String replyLevel = '1';
|
String replyLevel = '1';
|
||||||
late String heroTag;
|
|
||||||
|
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
@ -47,29 +46,22 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
|
||||||
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
|
int oid = widget.bvid != null ? IdUtils.bv2av(widget.bvid!) : 0;
|
||||||
heroTag = Get.arguments['heroTag'];
|
super.initState();
|
||||||
replyLevel = widget.replyLevel ?? '1';
|
replyLevel = widget.replyLevel ?? '1';
|
||||||
if (replyLevel == '2') {
|
if (replyLevel == '2') {
|
||||||
_videoReplyController = Get.put(
|
_videoReplyController = Get.put(
|
||||||
VideoReplyController(oid, widget.rpid.toString(), replyLevel),
|
VideoReplyController(oid, widget.rpid.toString(), replyLevel),
|
||||||
tag: widget.rpid.toString());
|
tag: widget.rpid.toString());
|
||||||
} else {
|
} else {
|
||||||
_videoReplyController =
|
_videoReplyController = Get.put(VideoReplyController(oid, '', replyLevel),
|
||||||
Get.put(VideoReplyController(oid, '', replyLevel), tag: heroTag);
|
tag: Get.arguments['heroTag']);
|
||||||
}
|
}
|
||||||
|
|
||||||
fabAnimationCtr = AnimationController(
|
fabAnimationCtr = AnimationController(
|
||||||
vsync: this, duration: const Duration(milliseconds: 300));
|
vsync: this, duration: const Duration(milliseconds: 300));
|
||||||
|
|
||||||
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||||
|
|
||||||
fabAnimationCtr.forward();
|
|
||||||
scrollListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
void scrollListener() {
|
|
||||||
scrollController = _videoReplyController.scrollController;
|
scrollController = _videoReplyController.scrollController;
|
||||||
scrollController.addListener(
|
scrollController.addListener(
|
||||||
() {
|
() {
|
||||||
@ -89,6 +81,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
fabAnimationCtr.forward();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFab() {
|
void _showFab() {
|
||||||
@ -108,7 +101,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
// 展示二级回复
|
// 展示二级回复
|
||||||
void replyReply(replyItem) {
|
void replyReply(replyItem) {
|
||||||
VideoDetailController videoDetailCtr =
|
VideoDetailController videoDetailCtr =
|
||||||
Get.find<VideoDetailController>(tag: heroTag);
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
if (replyItem != null) {
|
if (replyItem != null) {
|
||||||
videoDetailCtr.oid = replyItem.oid;
|
videoDetailCtr.oid = replyItem.oid;
|
||||||
videoDetailCtr.fRpid = replyItem.rpid!;
|
videoDetailCtr.fRpid = replyItem.rpid!;
|
||||||
@ -119,10 +112,9 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.removeListener(() {});
|
super.dispose();
|
||||||
fabAnimationCtr.dispose();
|
fabAnimationCtr.dispose();
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -136,7 +128,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
controller: scrollController,
|
controller: _videoReplyController.scrollController,
|
||||||
key: const PageStorageKey<String>('评论'),
|
key: const PageStorageKey<String>('评论'),
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
SliverPersistentHeader(
|
SliverPersistentHeader(
|
||||||
|
|||||||
@ -744,14 +744,11 @@ InlineSpan buildContent(
|
|||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () {
|
..onTap = () {
|
||||||
// 跳转到指定位置
|
// 跳转到指定位置
|
||||||
try {
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||||
Get.find<VideoDetailController>(
|
|
||||||
tag: Get.arguments['heroTag'])
|
|
||||||
.plPlayerController
|
.plPlayerController
|
||||||
.seekTo(
|
.seekTo(
|
||||||
Duration(seconds: Utils.duration(matchStr)),
|
Duration(seconds: Utils.duration(matchStr)),
|
||||||
);
|
);
|
||||||
} catch (_) {}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -26,6 +26,11 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 上拉加载
|
||||||
|
Future onLoad() async {
|
||||||
|
queryReplyList(type: 'onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
Future queryReplyList({type = 'init'}) async {
|
Future queryReplyList({type = 'init'}) async {
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
@ -44,11 +49,11 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
if (replyList.length == res['data'].page.count) {
|
if (replyList.length == res['data'].page.count) {
|
||||||
noMore.value = '没有更多了';
|
noMore.value = '没有更多了';
|
||||||
}
|
}
|
||||||
currentPage++;
|
|
||||||
} else {
|
} else {
|
||||||
// 未登录状态replies可能返回null
|
// 未登录状态replies可能返回null
|
||||||
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了';
|
||||||
}
|
}
|
||||||
|
currentPage++;
|
||||||
if (type == 'init') {
|
if (type == 'init') {
|
||||||
// List<ReplyItemModel> replies = res['data'].replies;
|
// List<ReplyItemModel> replies = res['data'].replies;
|
||||||
// 添加置顶回复
|
// 添加置顶回复
|
||||||
@ -67,10 +72,6 @@ class VideoReplyReplyController extends GetxController {
|
|||||||
// res['data'].replies = replies;
|
// res['data'].replies = replies;
|
||||||
replyList.value = replies;
|
replyList.value = replies;
|
||||||
} else {
|
} else {
|
||||||
// 每次回复之后,翻页请求有且只有相同的一条回复数据
|
|
||||||
if (replies.length == 1 && replies.last.rpid == replyList.last.rpid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
replyList.addAll(replies);
|
replyList.addAll(replies);
|
||||||
// res['data'].replies.addAll(replyList);
|
// res['data'].replies.addAll(replyList);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -55,9 +54,9 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
|
|||||||
() {
|
() {
|
||||||
if (scrollController.position.pixels >=
|
if (scrollController.position.pixels >=
|
||||||
scrollController.position.maxScrollExtent - 300) {
|
scrollController.position.maxScrollExtent - 300) {
|
||||||
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
|
if (!_videoReplyReplyController.isLoadingMore) {
|
||||||
_videoReplyReplyController.queryReplyList(type: 'onLoad');
|
_videoReplyReplyController.onLoad();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import 'package:pilipala/pages/video/detail/controller.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
import 'package:pilipala/pages/video/detail/introduction/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/related/index.dart';
|
import 'package:pilipala/pages/video/detail/related/index.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import 'widgets/app_bar.dart';
|
import 'widgets/app_bar.dart';
|
||||||
@ -53,7 +54,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
late Future _futureBuilderFuture;
|
late Future _futureBuilderFuture;
|
||||||
// 自动退出全屏
|
// 自动退出全屏
|
||||||
late bool autoExitFullcreen;
|
late bool autoExitFullcreen;
|
||||||
late bool autoPlayEnable;
|
Floating? floating;
|
||||||
|
late BangumiIntroController bangumiIntroController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -61,13 +63,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
heroTag = Get.arguments['heroTag'];
|
heroTag = Get.arguments['heroTag'];
|
||||||
videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
|
videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
|
||||||
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
||||||
|
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||||
statusBarHeight = localCache.get('statusBarHeight');
|
statusBarHeight = localCache.get('statusBarHeight');
|
||||||
autoExitFullcreen =
|
autoExitFullcreen =
|
||||||
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
setting.get(SettingBoxKey.enableAutoExit, defaultValue: false);
|
||||||
autoPlayEnable =
|
|
||||||
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
|
|
||||||
videoSourceInit();
|
videoSourceInit();
|
||||||
appbarStreamListen();
|
appbarStreamListen();
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
floating = Floating();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取视频资源,初始化播放器
|
// 获取视频资源,初始化播放器
|
||||||
@ -98,6 +102,23 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
if (autoExitFullcreen) {
|
if (autoExitFullcreen) {
|
||||||
plPlayerController!.triggerFullScreen(status: false);
|
plPlayerController!.triggerFullScreen(status: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 顺序播放 列表循环
|
||||||
|
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
|
||||||
|
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
|
||||||
|
if (videoDetailController.videoType == SearchType.video) {
|
||||||
|
videoIntroController.nextPlay();
|
||||||
|
}
|
||||||
|
if (videoDetailController.videoType == SearchType.media_bangumi) {
|
||||||
|
bangumiIntroController.nextPlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 单个循环
|
||||||
|
if (plPlayerController!.playRepeat == PlayRepeat.singleCycle) {
|
||||||
|
plPlayerController!.seekTo(Duration.zero);
|
||||||
|
plPlayerController!.play();
|
||||||
|
}
|
||||||
// 播放完展示控制栏
|
// 播放完展示控制栏
|
||||||
try {
|
try {
|
||||||
PiPStatus currentStatus =
|
PiPStatus currentStatus =
|
||||||
@ -130,8 +151,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
plPlayerController!.removeStatusLister(playerListener);
|
plPlayerController!.removeStatusLister(playerListener);
|
||||||
plPlayerController!.dispose();
|
plPlayerController!.dispose();
|
||||||
}
|
}
|
||||||
if (videoDetailController.floating != null) {
|
if (floating != null) {
|
||||||
videoDetailController.floating!.dispose();
|
floating!.dispose();
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@ -143,12 +164,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)) {
|
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)) {
|
||||||
videoDetailController.brightness = plPlayerController!.brightness.value;
|
videoDetailController.brightness = plPlayerController!.brightness.value;
|
||||||
}
|
}
|
||||||
if (plPlayerController != null) {
|
|
||||||
videoDetailController.defaultST = plPlayerController!.position.value;
|
videoDetailController.defaultST = plPlayerController!.position.value;
|
||||||
videoIntroController.isPaused = true;
|
videoIntroController.isPaused = true;
|
||||||
plPlayerController!.removeStatusLister(playerListener);
|
plPlayerController!.removeStatusLister(playerListener);
|
||||||
plPlayerController!.pause();
|
plPlayerController!.pause();
|
||||||
}
|
|
||||||
super.didPushNext();
|
super.didPushNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,18 +175,16 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
// 返回当前页面时
|
// 返回当前页面时
|
||||||
void didPopNext() async {
|
void didPopNext() async {
|
||||||
videoDetailController.isFirstTime = false;
|
videoDetailController.isFirstTime = false;
|
||||||
bool autoplay = autoPlayEnable;
|
bool autoplay =
|
||||||
|
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
|
||||||
videoDetailController.playerInit(autoplay: autoplay);
|
videoDetailController.playerInit(autoplay: autoplay);
|
||||||
|
videoDetailController.autoPlay.value = true;
|
||||||
/// 未开启自动播放时,未播放跳转下一页返回/播放后跳转下一页返回
|
|
||||||
videoDetailController.autoPlay.value =
|
|
||||||
!videoDetailController.isShowCover.value;
|
|
||||||
videoIntroController.isPaused = false;
|
videoIntroController.isPaused = false;
|
||||||
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
if (_extendNestCtr.position.pixels == 0 && autoplay) {
|
||||||
await Future.delayed(const Duration(milliseconds: 300));
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
plPlayerController?.play();
|
plPlayerController!.play();
|
||||||
}
|
}
|
||||||
plPlayerController?.addStatusLister(playerListener);
|
plPlayerController!.addStatusLister(playerListener);
|
||||||
super.didPopNext();
|
super.didPopNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,9 +242,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: PLVideoPlayer(
|
: PLVideoPlayer(
|
||||||
controller: plPlayerController!,
|
controller: plPlayerController!,
|
||||||
headerControl:
|
headerControl: HeaderControl(
|
||||||
videoDetailController
|
controller:
|
||||||
.headerControl,
|
plPlayerController,
|
||||||
|
videoDetailCtr:
|
||||||
|
videoDetailController,
|
||||||
|
floating: floating,
|
||||||
|
),
|
||||||
danmuWidget: Obx(
|
danmuWidget: Obx(
|
||||||
() => PlDanmaku(
|
() => PlDanmaku(
|
||||||
key: Key(
|
key: Key(
|
||||||
@ -349,45 +370,19 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
},
|
},
|
||||||
onlyOneScrollInBody: true,
|
onlyOneScrollInBody: true,
|
||||||
body: Container(
|
body: Container(
|
||||||
key: Key(heroTag),
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
child: Column(
|
child: CustomScrollView(
|
||||||
children: [
|
|
||||||
Opacity(
|
|
||||||
opacity: 0,
|
|
||||||
child: SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 0,
|
|
||||||
child: Obx(
|
|
||||||
() => TabBar(
|
|
||||||
controller: videoDetailController.tabCtr,
|
|
||||||
dividerColor: Colors.transparent,
|
|
||||||
indicatorColor:
|
|
||||||
Theme.of(context).colorScheme.background,
|
|
||||||
tabs: videoDetailController.tabs
|
|
||||||
.map((String name) => Tab(text: name))
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: TabBarView(
|
|
||||||
controller: videoDetailController.tabCtr,
|
|
||||||
children: [
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return CustomScrollView(
|
|
||||||
key: const PageStorageKey<String>('简介'),
|
key: const PageStorageKey<String>('简介'),
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
if (videoDetailController.videoType ==
|
if (videoDetailController.videoType ==
|
||||||
SearchType.video) ...[
|
SearchType.video) ...[
|
||||||
const VideoIntroPanel(),
|
const VideoIntroPanel(),
|
||||||
] else if (videoDetailController.videoType ==
|
] else
|
||||||
SearchType.media_bangumi) ...[
|
// if (videoDetailController.videoType ==
|
||||||
BangumiIntroPanel(
|
// SearchType.media_bangumi) ...[
|
||||||
cid: videoDetailController.cid)
|
// BangumiIntroPanel(
|
||||||
],
|
// cid: videoDetailController.cid)
|
||||||
|
// ],
|
||||||
// if (videoDetailController.videoType ==
|
// if (videoDetailController.videoType ==
|
||||||
// SearchType.video) ...[
|
// SearchType.video) ...[
|
||||||
// SliverPersistentHeader(
|
// SliverPersistentHeader(
|
||||||
@ -404,22 +399,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
child: Divider(
|
child: Divider(
|
||||||
indent: 12,
|
indent: 12,
|
||||||
endIndent: 12,
|
endIndent: 12,
|
||||||
color: Theme.of(context)
|
color:
|
||||||
.dividerColor
|
Theme.of(context).dividerColor.withOpacity(0.06),
|
||||||
.withOpacity(0.06),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const RelatedVideoPanel(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
VideoReplyPanel(
|
|
||||||
bvid: videoDetailController.bvid,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// const RelatedVideoPanel(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:pilipala/models/video/play/url.dart';
|
|||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
|
||||||
@ -149,13 +150,14 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
||||||
style: subTitleStyle),
|
style: subTitleStyle),
|
||||||
),
|
),
|
||||||
// ListTile(
|
ListTile(
|
||||||
// onTap: () {},
|
onTap: () => {Get.back(), showSetRepeat()},
|
||||||
// dense: true,
|
dense: true,
|
||||||
// enabled: false,
|
leading: const Icon(Icons.repeat, size: 20),
|
||||||
// leading: const Icon(Icons.play_circle_outline, size: 20),
|
title: Text('播放顺序', style: titleStyle),
|
||||||
// title: Text('播放设置', style: titleStyle),
|
subtitle: Text(widget.controller!.playRepeat.description,
|
||||||
// ),
|
style: subTitleStyle),
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
onTap: () => {Get.back(), showSetDanmaku()},
|
onTap: () => {Get.back(), showSetDanmaku()},
|
||||||
dense: true,
|
dense: true,
|
||||||
@ -704,6 +706,60 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 播放顺序
|
||||||
|
void showSetRepeat() async {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
elevation: 0,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 250,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 45,
|
||||||
|
child: Center(child: Text('选择播放顺序', style: titleStyle))),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
for (var i in PlayRepeat.values) ...[
|
||||||
|
ListTile(
|
||||||
|
onTap: () {
|
||||||
|
widget.controller!.setPlayRepeat(i);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
dense: true,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.only(left: 20, right: 20),
|
||||||
|
title: Text(i.description),
|
||||||
|
trailing: widget.controller!.playRepeat == i
|
||||||
|
? Icon(
|
||||||
|
Icons.done,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final _ = widget.controller!;
|
final _ = widget.controller!;
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class WebviewController extends GetxController {
|
|||||||
|
|
||||||
webviewInit() {
|
webviewInit() {
|
||||||
controller
|
controller
|
||||||
..setUserAgent(Request().headerUa())
|
..setUserAgent(Request().headerUa('mob'))
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
..setNavigationDelegate(
|
..setNavigationDelegate(
|
||||||
NavigationDelegate(
|
NavigationDelegate(
|
||||||
@ -99,8 +99,6 @@ class WebviewController extends GetxController {
|
|||||||
HomeController homeCtr = Get.find<HomeController>();
|
HomeController homeCtr = Get.find<HomeController>();
|
||||||
homeCtr.updateLoginStatus(true);
|
homeCtr.updateLoginStatus(true);
|
||||||
homeCtr.userFace.value = result['data'].face;
|
homeCtr.userFace.value = result['data'].face;
|
||||||
MediaController mediaCtr = Get.find<MediaController>();
|
|
||||||
mediaCtr.mid = result['data'].mid;
|
|
||||||
await LoginUtils.refreshLoginStatus(true);
|
await LoginUtils.refreshLoginStatus(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.show(builder: (context) {
|
SmartDialog.show(builder: (context) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import 'package:media_kit_video/media_kit_video.dart';
|
|||||||
import 'package:ns_danmaku/ns_danmaku.dart';
|
import 'package:ns_danmaku/ns_danmaku.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
@ -209,6 +210,9 @@ class PlPlayerController {
|
|||||||
late double fontSizeVal;
|
late double fontSizeVal;
|
||||||
late double danmakuSpeedVal;
|
late double danmakuSpeedVal;
|
||||||
|
|
||||||
|
// 播放顺序相关
|
||||||
|
PlayRepeat playRepeat = PlayRepeat.pause;
|
||||||
|
|
||||||
// 添加一个私有构造函数
|
// 添加一个私有构造函数
|
||||||
PlPlayerController._() {
|
PlPlayerController._() {
|
||||||
_videoType = videoType;
|
_videoType = videoType;
|
||||||
@ -226,6 +230,12 @@ class PlPlayerController {
|
|||||||
// 弹幕速度
|
// 弹幕速度
|
||||||
danmakuSpeedVal =
|
danmakuSpeedVal =
|
||||||
localCache.get(LocalCacheKey.danmakuSpeed, defaultValue: 4.0);
|
localCache.get(LocalCacheKey.danmakuSpeed, defaultValue: 4.0);
|
||||||
|
playRepeat = PlayRepeat.values.toList().firstWhere(
|
||||||
|
(e) =>
|
||||||
|
e.value ==
|
||||||
|
videoStorage.get(VideoBoxKey.playRepeat,
|
||||||
|
defaultValue: PlayRepeat.pause.value),
|
||||||
|
);
|
||||||
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
// _playerEventSubs = onPlayerStatusChanged.listen((PlayerStatus status) {
|
||||||
// if (status == PlayerStatus.playing) {
|
// if (status == PlayerStatus.playing) {
|
||||||
// WakelockPlus.enable();
|
// WakelockPlus.enable();
|
||||||
@ -449,9 +459,7 @@ class PlPlayerController {
|
|||||||
for (var element in _statusListeners) {
|
for (var element in _statusListeners) {
|
||||||
element(event ? PlayerStatus.playing : PlayerStatus.paused);
|
element(event ? PlayerStatus.playing : PlayerStatus.paused);
|
||||||
}
|
}
|
||||||
if (videoPlayerController!.state.position.inSeconds != 0) {
|
|
||||||
makeHeartBeat(_position.value.inSeconds, type: 'status');
|
makeHeartBeat(_position.value.inSeconds, type: 'status');
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
videoPlayerController!.stream.completed.listen((event) {
|
videoPlayerController!.stream.completed.listen((event) {
|
||||||
if (event) {
|
if (event) {
|
||||||
@ -512,7 +520,6 @@ class PlPlayerController {
|
|||||||
position = Duration.zero;
|
position = Duration.zero;
|
||||||
}
|
}
|
||||||
_position.value = position;
|
_position.value = position;
|
||||||
_heartDuration = position.inSeconds;
|
|
||||||
if (duration.value.inSeconds != 0) {
|
if (duration.value.inSeconds != 0) {
|
||||||
if (type != 'slider') {
|
if (type != 'slider') {
|
||||||
/// 拖动进度条调节时,不等待第一帧,防止抖动
|
/// 拖动进度条调节时,不等待第一帧,防止抖动
|
||||||
@ -823,9 +830,6 @@ class PlPlayerController {
|
|||||||
builder: (context) => Dialog.fullscreen(
|
builder: (context) => Dialog.fullscreen(
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
// 忽略手机安全区域
|
|
||||||
left: false,
|
|
||||||
right: false,
|
|
||||||
bottom:
|
bottom:
|
||||||
direction.value == 'vertical' || mode == FullScreenMode.vertical
|
direction.value == 'vertical' || mode == FullScreenMode.vertical
|
||||||
? true
|
? true
|
||||||
@ -881,7 +885,7 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 记录播放记录
|
// 记录播放记录
|
||||||
Future makeHeartBeat(int progress, {type = 'playing'}) async {
|
Future makeHeartBeat(progress, {type = 'playing'}) async {
|
||||||
if (!_enableHeart) {
|
if (!_enableHeart) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -908,6 +912,11 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPlayRepeat(PlayRepeat type) {
|
||||||
|
playRepeat = type;
|
||||||
|
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> dispose({String type = 'single'}) async {
|
Future<void> dispose({String type = 'single'}) async {
|
||||||
// 每次减1,最后销毁
|
// 每次减1,最后销毁
|
||||||
if (type == 'single' && playerCount.value > 1) {
|
if (type == 'single' && playerCount.value > 1) {
|
||||||
|
|||||||
25
lib/plugin/pl_player/models/play_repeat.dart
Normal file
25
lib/plugin/pl_player/models/play_repeat.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
enum PlayRepeat {
|
||||||
|
pause,
|
||||||
|
listOrder,
|
||||||
|
singleCycle,
|
||||||
|
listCycle,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PlayRepeatExtension on PlayRepeat {
|
||||||
|
static final List<String> _descList = [
|
||||||
|
'播完暂停',
|
||||||
|
'顺序播放',
|
||||||
|
'单个循环',
|
||||||
|
'列表循环',
|
||||||
|
];
|
||||||
|
get description => _descList[index];
|
||||||
|
|
||||||
|
static final List<double> _valueList = [
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
];
|
||||||
|
get value => _valueList[index];
|
||||||
|
get defaultValue => _valueList[1];
|
||||||
|
}
|
||||||
@ -48,26 +48,25 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late AnimationController animationController;
|
late AnimationController animationController;
|
||||||
late VideoController videoController;
|
late VideoController videoController;
|
||||||
final PLVideoPlayerController _ctr = Get.put(PLVideoPlayerController());
|
|
||||||
|
|
||||||
// bool _mountSeekBackwardButton = false;
|
bool _mountSeekBackwardButton = false;
|
||||||
// bool _mountSeekForwardButton = false;
|
bool _mountSeekForwardButton = false;
|
||||||
// bool _hideSeekBackwardButton = false;
|
bool _hideSeekBackwardButton = false;
|
||||||
// bool _hideSeekForwardButton = false;
|
bool _hideSeekForwardButton = false;
|
||||||
|
|
||||||
// double _brightnessValue = 0.0;
|
double _brightnessValue = 0.0;
|
||||||
// bool _brightnessIndicator = false;
|
bool _brightnessIndicator = false;
|
||||||
Timer? _brightnessTimer;
|
Timer? _brightnessTimer;
|
||||||
|
|
||||||
// double _volumeValue = 0.0;
|
double _volumeValue = 0.0;
|
||||||
// bool _volumeIndicator = false;
|
bool _volumeIndicator = false;
|
||||||
Timer? _volumeTimer;
|
Timer? _volumeTimer;
|
||||||
|
|
||||||
double _distance = 0.0;
|
double _distance = 0.0;
|
||||||
// 初始手指落下位置
|
// 初始手指落下位置
|
||||||
double _initTapPositoin = 0.0;
|
double _initTapPositoin = 0.0;
|
||||||
|
|
||||||
// bool _volumeInterceptEventStream = false;
|
bool _volumeInterceptEventStream = false;
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
late FullScreenMode mode;
|
late FullScreenMode mode;
|
||||||
@ -76,11 +75,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
late bool enableBackgroundPlay;
|
late bool enableBackgroundPlay;
|
||||||
|
|
||||||
void onDoubleTapSeekBackward() {
|
void onDoubleTapSeekBackward() {
|
||||||
_ctr.onDoubleTapSeekBackward();
|
setState(() {
|
||||||
|
_mountSeekBackwardButton = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDoubleTapSeekForward() {
|
void onDoubleTapSeekForward() {
|
||||||
_ctr.onDoubleTapSeekForward();
|
setState(() {
|
||||||
|
_mountSeekForwardButton = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 双击播放、暂停
|
// 双击播放、暂停
|
||||||
@ -132,10 +135,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
try {
|
try {
|
||||||
FlutterVolumeController.showSystemUI = true;
|
FlutterVolumeController.showSystemUI = true;
|
||||||
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
_volumeValue = (await FlutterVolumeController.getVolume())!;
|
||||||
FlutterVolumeController.addListener((value) {
|
FlutterVolumeController.addListener((value) {
|
||||||
if (mounted && !_ctr.volumeInterceptEventStream.value) {
|
if (mounted && !_volumeInterceptEventStream) {
|
||||||
_ctr.volumeValue.value = value;
|
setState(() {
|
||||||
|
_volumeValue = value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -143,10 +148,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
|
|
||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
try {
|
try {
|
||||||
_ctr.brightnessValue.value = await ScreenBrightness().current;
|
_brightnessValue = await ScreenBrightness().current;
|
||||||
ScreenBrightness().onCurrentBrightnessChanged.listen((value) {
|
ScreenBrightness().onCurrentBrightnessChanged.listen((value) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_ctr.brightnessValue.value = value;
|
setState(() {
|
||||||
|
_brightnessValue = value;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
@ -158,14 +165,18 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
FlutterVolumeController.showSystemUI = false;
|
FlutterVolumeController.showSystemUI = false;
|
||||||
await FlutterVolumeController.setVolume(value);
|
await FlutterVolumeController.setVolume(value);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_ctr.volumeValue.value = value;
|
setState(() {
|
||||||
_ctr.volumeIndicator.value = true;
|
_volumeValue = value;
|
||||||
_ctr.volumeInterceptEventStream.value = true;
|
_volumeIndicator = true;
|
||||||
|
_volumeInterceptEventStream = true;
|
||||||
|
});
|
||||||
_volumeTimer?.cancel();
|
_volumeTimer?.cancel();
|
||||||
_volumeTimer = Timer(const Duration(milliseconds: 200), () {
|
_volumeTimer = Timer(const Duration(milliseconds: 200), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_ctr.volumeIndicator.value = false;
|
setState(() {
|
||||||
_ctr.volumeInterceptEventStream.value = false;
|
_volumeIndicator = false;
|
||||||
|
_volumeInterceptEventStream = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -174,11 +185,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
try {
|
try {
|
||||||
await ScreenBrightness().setScreenBrightness(value);
|
await ScreenBrightness().setScreenBrightness(value);
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
_ctr.brightnessIndicator.value = true;
|
setState(() {
|
||||||
|
_brightnessIndicator = true;
|
||||||
|
});
|
||||||
_brightnessTimer?.cancel();
|
_brightnessTimer?.cancel();
|
||||||
_brightnessTimer = Timer(const Duration(milliseconds: 200), () {
|
_brightnessTimer = Timer(const Duration(milliseconds: 200), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_ctr.brightnessIndicator.value = false;
|
setState(() {
|
||||||
|
_brightnessIndicator = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
widget.controller.brightness.value = value;
|
widget.controller.brightness.value = value;
|
||||||
@ -307,12 +322,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// 音量🔊 控制条展示
|
/// 音量🔊 控制条展示
|
||||||
Obx(
|
Align(
|
||||||
() => Align(
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
opacity: _ctr.volumeIndicator.value ? 1.0 : 0.0,
|
opacity: _volumeIndicator ? 1.0 : 0.0,
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@ -332,9 +346,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
width: 28.0,
|
width: 28.0,
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
_ctr.volumeValue.value == 0.0
|
_volumeValue == 0.0
|
||||||
? Icons.volume_off
|
? Icons.volume_off
|
||||||
: _ctr.volumeValue.value < 0.5
|
: _volumeValue < 0.5
|
||||||
? Icons.volume_down
|
? Icons.volume_down
|
||||||
: Icons.volume_up,
|
: Icons.volume_up,
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
@ -343,7 +357,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${(_ctr.volumeValue.value * 100.0).round()}%',
|
'${(_volumeValue * 100.0).round()}%',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13.0,
|
fontSize: 13.0,
|
||||||
@ -357,15 +371,13 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
/// 亮度🌞 控制条展示
|
/// 亮度🌞 控制条展示
|
||||||
Obx(
|
Align(
|
||||||
() => Align(
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: AnimatedOpacity(
|
child: AnimatedOpacity(
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
opacity: _ctr.brightnessIndicator.value ? 1.0 : 0.0,
|
opacity: _brightnessIndicator ? 1.0 : 0.0,
|
||||||
duration: const Duration(milliseconds: 150),
|
duration: const Duration(milliseconds: 150),
|
||||||
child: Container(
|
child: Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@ -385,9 +397,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
width: 28.0,
|
width: 28.0,
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
_ctr.brightnessValue.value < 1.0 / 3.0
|
_brightnessValue < 1.0 / 3.0
|
||||||
? Icons.brightness_low
|
? Icons.brightness_low
|
||||||
: _ctr.brightnessValue.value < 2.0 / 3.0
|
: _brightnessValue < 2.0 / 3.0
|
||||||
? Icons.brightness_medium
|
? Icons.brightness_medium
|
||||||
: Icons.brightness_high,
|
: Icons.brightness_high,
|
||||||
color: const Color(0xFFFFFFFF),
|
color: const Color(0xFFFFFFFF),
|
||||||
@ -397,7 +409,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
const SizedBox(width: 2.0),
|
const SizedBox(width: 2.0),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${(_ctr.brightnessValue.value * 100.0).round()}%',
|
'${(_brightnessValue * 100.0).round()}%',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13.0,
|
fontSize: 13.0,
|
||||||
@ -411,7 +423,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
|
|
||||||
// Obx(() {
|
// Obx(() {
|
||||||
// if (_.buffered.value == Duration.zero) {
|
// if (_.buffered.value == Duration.zero) {
|
||||||
@ -514,7 +525,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}
|
}
|
||||||
if (tapPosition < sectionWidth) {
|
if (tapPosition < sectionWidth) {
|
||||||
// 左边区域 👈
|
// 左边区域 👈
|
||||||
final brightness = _ctr.brightnessValue.value - delta / 100.0;
|
final brightness = _brightnessValue - delta / 100.0;
|
||||||
final result = brightness.clamp(0.0, 1.0);
|
final result = brightness.clamp(0.0, 1.0);
|
||||||
setBrightness(result);
|
setBrightness(result);
|
||||||
} else if (tapPosition < sectionWidth * 2) {
|
} else if (tapPosition < sectionWidth * 2) {
|
||||||
@ -537,7 +548,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
_distance = dy;
|
_distance = dy;
|
||||||
} else {
|
} else {
|
||||||
// 右边区域 👈
|
// 右边区域 👈
|
||||||
final volume = _ctr.volumeValue.value - delta / 100.0;
|
final volume = _volumeValue - delta / 100.0;
|
||||||
final result = volume.clamp(0.0, 1.0);
|
final result = volume.clamp(0.0, 1.0);
|
||||||
setVolume(result);
|
setVolume(result);
|
||||||
}
|
}
|
||||||
@ -550,14 +561,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
Obx(
|
Obx(
|
||||||
() => Column(
|
() => Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.headerControl != null || _.headerControl != null)
|
if (widget.headerControl != null)
|
||||||
ClipRect(
|
ClipRect(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
child: AppBarAni(
|
child: AppBarAni(
|
||||||
controller: animationController,
|
controller: animationController,
|
||||||
visible: !_.controlsLock.value && _.showControls.value,
|
visible: !_.controlsLock.value && _.showControls.value,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
child: widget.headerControl ?? _.headerControl!,
|
child: widget.headerControl!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
@ -693,20 +704,16 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
}),
|
}),
|
||||||
|
|
||||||
/// 点击 快进/快退
|
/// 点击 快进/快退
|
||||||
Obx(
|
if (_mountSeekBackwardButton || _mountSeekForwardButton)
|
||||||
() => Visibility(
|
Positioned.fill(
|
||||||
visible: _ctr.mountSeekBackwardButton.value ||
|
|
||||||
_ctr.mountSeekForwardButton.value,
|
|
||||||
child: Positioned.fill(
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _ctr.mountSeekBackwardButton.value
|
child: _mountSeekBackwardButton
|
||||||
? TweenAnimationBuilder<double>(
|
? TweenAnimationBuilder<double>(
|
||||||
tween: Tween<double>(
|
tween: Tween<double>(
|
||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end:
|
end: _hideSeekBackwardButton ? 0.0 : 1.0,
|
||||||
_ctr.hideSeekBackwardButton.value ? 0.0 : 1.0,
|
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
builder: (context, value, child) => Opacity(
|
builder: (context, value, child) => Opacity(
|
||||||
@ -714,9 +721,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
onEnd: () {
|
onEnd: () {
|
||||||
if (_ctr.hideSeekBackwardButton.value) {
|
if (_hideSeekBackwardButton) {
|
||||||
_ctr.hideSeekBackwardButton.value = false;
|
setState(() {
|
||||||
_ctr.mountSeekBackwardButton.value = false;
|
_hideSeekBackwardButton = false;
|
||||||
|
_mountSeekBackwardButton = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: BackwardSeekIndicator(
|
child: BackwardSeekIndicator(
|
||||||
@ -724,7 +733,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
// _seekBarDeltaValueNotifier.value = -value;
|
// _seekBarDeltaValueNotifier.value = -value;
|
||||||
},
|
},
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
_ctr.hideSeekBackwardButton.value = true;
|
setState(() {
|
||||||
|
_hideSeekBackwardButton = true;
|
||||||
|
});
|
||||||
Player player =
|
Player player =
|
||||||
widget.controller.videoPlayerController!;
|
widget.controller.videoPlayerController!;
|
||||||
var result = player.state.position - value;
|
var result = player.state.position - value;
|
||||||
@ -745,11 +756,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _ctr.mountSeekForwardButton.value
|
child: _mountSeekForwardButton
|
||||||
? TweenAnimationBuilder<double>(
|
? TweenAnimationBuilder<double>(
|
||||||
tween: Tween<double>(
|
tween: Tween<double>(
|
||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: _ctr.hideSeekForwardButton.value ? 0.0 : 1.0,
|
end: _hideSeekForwardButton ? 0.0 : 1.0,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 500),
|
duration: const Duration(milliseconds: 500),
|
||||||
builder: (context, value, child) => Opacity(
|
builder: (context, value, child) => Opacity(
|
||||||
@ -757,9 +768,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
onEnd: () {
|
onEnd: () {
|
||||||
if (_ctr.hideSeekForwardButton.value) {
|
if (_hideSeekForwardButton) {
|
||||||
_ctr.hideSeekForwardButton.value = false;
|
setState(() {
|
||||||
_ctr.mountSeekForwardButton.value = false;
|
_hideSeekForwardButton = false;
|
||||||
|
_mountSeekForwardButton = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: ForwardSeekIndicator(
|
child: ForwardSeekIndicator(
|
||||||
@ -767,7 +780,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
// _seekBarDeltaValueNotifier.value = value;
|
// _seekBarDeltaValueNotifier.value = value;
|
||||||
},
|
},
|
||||||
onSubmitted: (value) {
|
onSubmitted: (value) {
|
||||||
_ctr.hideSeekForwardButton.value = true;
|
setState(() {
|
||||||
|
_hideSeekForwardButton = true;
|
||||||
|
});
|
||||||
Player player =
|
Player player =
|
||||||
widget.controller.videoPlayerController!;
|
widget.controller.videoPlayerController!;
|
||||||
var result = player.state.position + value;
|
var result = player.state.position + value;
|
||||||
@ -785,37 +800,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PLVideoPlayerController extends GetxController {
|
|
||||||
RxBool mountSeekBackwardButton = false.obs;
|
|
||||||
RxBool mountSeekForwardButton = false.obs;
|
|
||||||
RxBool hideSeekBackwardButton = false.obs;
|
|
||||||
RxBool hideSeekForwardButton = false.obs;
|
|
||||||
|
|
||||||
RxDouble brightnessValue = 0.0.obs;
|
|
||||||
RxBool brightnessIndicator = false.obs;
|
|
||||||
|
|
||||||
RxDouble volumeValue = 0.0.obs;
|
|
||||||
RxBool volumeIndicator = false.obs;
|
|
||||||
|
|
||||||
RxDouble distance = 0.0.obs;
|
|
||||||
// 初始手指落下位置
|
|
||||||
RxDouble initTapPositoin = 0.0.obs;
|
|
||||||
|
|
||||||
RxBool volumeInterceptEventStream = false.obs;
|
|
||||||
|
|
||||||
// 双击快进 展示样式
|
|
||||||
void onDoubleTapSeekForward() {
|
|
||||||
mountSeekForwardButton.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDoubleTapSeekBackward() {
|
|
||||||
mountSeekBackwardButton.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import 'package:pilipala/pages/html/index.dart';
|
|||||||
import 'package:pilipala/pages/later/index.dart';
|
import 'package:pilipala/pages/later/index.dart';
|
||||||
import 'package:pilipala/pages/liveRoom/view.dart';
|
import 'package:pilipala/pages/liveRoom/view.dart';
|
||||||
import 'package:pilipala/pages/member/index.dart';
|
import 'package:pilipala/pages/member/index.dart';
|
||||||
|
import 'package:pilipala/pages/member_search/index.dart';
|
||||||
import 'package:pilipala/pages/preview/index.dart';
|
import 'package:pilipala/pages/preview/index.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/searchResult/index.dart';
|
import 'package:pilipala/pages/searchResult/index.dart';
|
||||||
@ -35,6 +36,8 @@ import 'package:pilipala/pages/setting/index.dart';
|
|||||||
import 'package:pilipala/pages/media/index.dart';
|
import 'package:pilipala/pages/media/index.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
import '../pages/history_search/index.dart';
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
bool iosTransition =
|
bool iosTransition =
|
||||||
setting.get(SettingBoxKey.iosTransition, defaultValue: false);
|
setting.get(SettingBoxKey.iosTransition, defaultValue: false);
|
||||||
@ -42,7 +45,7 @@ bool iosTransition =
|
|||||||
class Routes {
|
class Routes {
|
||||||
static final List<GetPage> getPages = [
|
static final List<GetPage> getPages = [
|
||||||
// 首页(推荐)
|
// 首页(推荐)
|
||||||
CustomGetPage(name: '/', page: () => const HomePage()),
|
CustomGetPage(name: '/', page: () => HomePage()),
|
||||||
// 热门
|
// 热门
|
||||||
CustomGetPage(name: '/hot', page: () => const HotPage()),
|
CustomGetPage(name: '/hot', page: () => const HotPage()),
|
||||||
// 视频详情
|
// 视频详情
|
||||||
@ -86,6 +89,7 @@ class Routes {
|
|||||||
CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
|
||||||
// 用户中心
|
// 用户中心
|
||||||
CustomGetPage(name: '/member', page: () => const MemberPage()),
|
CustomGetPage(name: '/member', page: () => const MemberPage()),
|
||||||
|
CustomGetPage(name: '/memberSearch', page: () => const MemberSearchPage()),
|
||||||
// 二级回复
|
// 二级回复
|
||||||
CustomGetPage(
|
CustomGetPage(
|
||||||
name: '/replyReply', page: () => const VideoReplyReplyPanel()),
|
name: '/replyReply', page: () => const VideoReplyReplyPanel()),
|
||||||
@ -110,6 +114,9 @@ class Routes {
|
|||||||
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
CustomGetPage(name: '/about', page: () => const AboutPage()),
|
||||||
//
|
//
|
||||||
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
CustomGetPage(name: '/htmlRender', page: () => const HtmlRenderPage()),
|
||||||
|
// 历史记录搜索
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/historySearch', page: () => const HistorySearchPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -119,7 +119,7 @@ class PiliSchame {
|
|||||||
arguments: {
|
arguments: {
|
||||||
'pic': bangumiDetail.cover,
|
'pic': bangumiDetail.cover,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
'videoType': SearchType.media_bangumi,
|
// 'videoType': SearchType.media_bangumi,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import 'dart:typed_data';
|
|||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:saver_gallery/saver_gallery.dart';
|
|
||||||
|
|
||||||
class DownloadUtils {
|
class DownloadUtils {
|
||||||
// 获取存储权限
|
// 获取存储权限
|
||||||
@ -15,32 +15,25 @@ class DownloadUtils {
|
|||||||
statuses[Permission.storage].toString();
|
statuses[Permission.storage].toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> downloadImg(String imgUrl,
|
static Future<bool> downloadImg(String imgUrl) async {
|
||||||
{String imgType = 'cover'}) async {
|
|
||||||
try {
|
|
||||||
await requestStoragePer();
|
await requestStoragePer();
|
||||||
SmartDialog.showLoading(msg: '保存中');
|
SmartDialog.showLoading(msg: '保存中');
|
||||||
var response = await Dio()
|
var response = await Dio()
|
||||||
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
.get(imgUrl, options: Options(responseType: ResponseType.bytes));
|
||||||
String picName =
|
String picName =
|
||||||
"plpl_${imgType}_${DateTime.now().toString().split('-').join()}";
|
"plpl_cover_${DateTime.now().toString().split('-').join()}.png";
|
||||||
final SaveResult result = await SaverGallery.saveImage(
|
final result = await ImageGallerySaver.saveImage(
|
||||||
Uint8List.fromList(response.data),
|
Uint8List.fromList(response.data),
|
||||||
quality: 60,
|
quality: 100,
|
||||||
name: picName,
|
name: picName,
|
||||||
// 保存到 PiliPala文件夹
|
|
||||||
androidRelativePath: "Pictures/PiliPala",
|
|
||||||
androidExistNotSave: false,
|
|
||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result.isSuccess) {
|
if (result != null) {
|
||||||
|
if (result['isSuccess']) {
|
||||||
|
// ignore: avoid_print
|
||||||
await SmartDialog.showToast('「$picName」已保存 ');
|
await SmartDialog.showToast('「$picName」已保存 ');
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
} catch (err) {
|
|
||||||
SmartDialog.dismiss();
|
|
||||||
SmartDialog.showToast(err.toString());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/pages/dynamics/index.dart';
|
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/home/index.dart';
|
||||||
import 'package:pilipala/pages/media/index.dart';
|
|
||||||
import 'package:pilipala/pages/mine/index.dart';
|
import 'package:pilipala/pages/mine/index.dart';
|
||||||
|
|
||||||
class LoginUtils {
|
class LoginUtils {
|
||||||
@ -17,12 +15,6 @@ class LoginUtils {
|
|||||||
|
|
||||||
MineController mineCtr = Get.find<MineController>();
|
MineController mineCtr = Get.find<MineController>();
|
||||||
mineCtr.userLogin.value = status;
|
mineCtr.userLogin.value = status;
|
||||||
|
|
||||||
DynamicsController dynamicsCtr = Get.find<DynamicsController>();
|
|
||||||
dynamicsCtr.userLogin.value = status;
|
|
||||||
|
|
||||||
MediaController mediaCtr = Get.find<MediaController>();
|
|
||||||
mediaCtr.userLogin.value = status;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}');
|
SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -156,4 +156,6 @@ class VideoBoxKey {
|
|||||||
static const String videoBrightness = 'videoBrightness';
|
static const String videoBrightness = 'videoBrightness';
|
||||||
// 倍速
|
// 倍速
|
||||||
static const String videoSpeed = 'videoSpeed';
|
static const String videoSpeed = 'videoSpeed';
|
||||||
|
// 播放顺序
|
||||||
|
static const String playRepeat = 'playRepeat';
|
||||||
}
|
}
|
||||||
|
|||||||
32
pubspec.lock
32
pubspec.lock
@ -337,14 +337,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
dio_http2_adapter:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: dio_http2_adapter
|
|
||||||
sha256: "3d81128cf389649ae6ac5cce23bcf5f9b254882b7f27185ca3b0d443ee9b825c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1+1"
|
|
||||||
dismissible_page:
|
dismissible_page:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -629,14 +621,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
http2:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http2
|
|
||||||
sha256: "38db0c4aa9f1cd238a5d2e86aa0cc7cc91c77e0c6c94ba64bbe85e4ff732a952"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.2.0"
|
|
||||||
http_client_helper:
|
http_client_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -669,6 +653,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.17"
|
version: "4.0.17"
|
||||||
|
image_gallery_saver:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_gallery_saver
|
||||||
|
sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
intl:
|
intl:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1086,14 +1078,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
saver_gallery:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: saver_gallery
|
|
||||||
sha256: "3131bba4257f69901437c0f1ebd692201ca5f34512d42667513a3802f1c171d1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.0.1"
|
|
||||||
screen_brightness:
|
screen_brightness:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.8
|
version: 1.0.7
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.19.6 <3.0.0"
|
sdk: ">=2.19.6 <3.0.0"
|
||||||
@ -45,12 +45,11 @@ dependencies:
|
|||||||
cookie_jar: ^4.0.8
|
cookie_jar: ^4.0.8
|
||||||
dio_cookie_manager: ^3.1.0
|
dio_cookie_manager: ^3.1.0
|
||||||
connectivity_plus: ^4.0.1
|
connectivity_plus: ^4.0.1
|
||||||
dio_http2_adapter: ^2.3.1+1
|
|
||||||
|
|
||||||
# 图片
|
# 图片
|
||||||
cached_network_image: ^3.2.3
|
cached_network_image: ^3.2.3
|
||||||
extended_image: ^8.0.2
|
extended_image: ^8.0.2
|
||||||
saver_gallery: ^2.0.1
|
image_gallery_saver: ^2.0.3
|
||||||
|
|
||||||
# 存储
|
# 存储
|
||||||
path_provider: ^2.0.14
|
path_provider: ^2.0.14
|
||||||
|
|||||||
Reference in New Issue
Block a user