mod: 侧边栏、动态重构,排行改为首页分区,平板、折叠屏、竖屏视频新适配,播放页可隐藏黑边、截图、点踩,弹幕粗细调整,默认关闭后台播放,弹窗接受返回

This commit is contained in:
orz12
2024-05-20 14:46:31 +08:00
parent fd51cddeca
commit 074bf03946
97 changed files with 4105 additions and 2672 deletions

View File

@@ -8,86 +8,79 @@ class VideoCardHSkeleton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Skeleton( return Skeleton(
child: Padding( child: LayoutBuilder(
padding: const EdgeInsets.fromLTRB( builder: (context, boxConstraints) {
StyleString.safeSpace, 7, StyleString.safeSpace, 7), double width =
child: LayoutBuilder( (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
builder: (context, boxConstraints) { return SizedBox(
double width = height: width / StyleString.aspectRatio,
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; child: Row(
return SizedBox( mainAxisAlignment: MainAxisAlignment.start,
height: width / StyleString.aspectRatio, crossAxisAlignment: CrossAxisAlignment.start,
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.start, AspectRatio(
crossAxisAlignment: CrossAxisAlignment.start, aspectRatio: StyleString.aspectRatio,
children: [ child: LayoutBuilder(
AspectRatio( builder: (context, boxConstraints) {
aspectRatio: StyleString.aspectRatio, return Container(
child: LayoutBuilder( decoration: BoxDecoration(
builder: (context, boxConstraints) { color: Theme.of(context).colorScheme.onInverseSurface,
return Container( borderRadius:
decoration: BoxDecoration( BorderRadius.circular(StyleString.imgRadius.x),
),
);
},
),
),
// VideoContent(videoItem: videoItem)
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: color:
Theme.of(context).colorScheme.onInverseSurface, Theme.of(context).colorScheme.onInverseSurface,
borderRadius: width: 40,
BorderRadius.circular(StyleString.imgRadius.x), height: 13,
margin: const EdgeInsets.only(right: 8),
), ),
); Container(
}, color:
), Theme.of(context).colorScheme.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
), ),
// VideoContent(videoItem: videoItem) )),
Expanded( ],
child: Padding( ),
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4), );
child: Column( },
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
),
)),
],
),
);
},
),
), ),
); );
} }

View File

@@ -1,4 +1,7 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../utils/download.dart'; import '../../utils/download.dart';
import '../constants.dart'; import '../constants.dart';
import 'network_img_layer.dart'; import 'network_img_layer.dart';
@@ -11,9 +14,10 @@ class OverlayPop extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2; final double imgWidth = min(Get.height,Get.width) - 8 * 2;
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 8), margin: const EdgeInsets.symmetric(horizontal: 8),
width: imgWidth,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(10.0), borderRadius: BorderRadius.circular(10.0),
@@ -70,6 +74,7 @@ class OverlayPop extends StatelessWidget {
tooltip: '保存封面图', tooltip: '保存封面图',
onPressed: () async { onPressed: () async {
await DownloadUtils.downloadImg( await DownloadUtils.downloadImg(
context,
videoItem.pic != null videoItem.pic != null
? videoItem.pic as String ? videoItem.pic as String
: videoItem.cover as String, : videoItem.cover as String,

View File

@@ -72,88 +72,73 @@ class VideoCardH extends StatelessWidget {
SmartDialog.showToast(err.toString()); SmartDialog.showToast(err.toString());
} }
}, },
child: Padding( child: LayoutBuilder(
padding: const EdgeInsets.fromLTRB( builder: (BuildContext context, BoxConstraints boxConstraints) {
StyleString.safeSpace, 5, StyleString.safeSpace, 5), return Row(
child: LayoutBuilder( mainAxisAlignment: MainAxisAlignment.start,
builder: crossAxisAlignment: CrossAxisAlignment.start,
(BuildContext context, BoxConstraints boxConstraints) { children: <Widget>[
final double width = (boxConstraints.maxWidth - AspectRatio(
StyleString.cardSpace * aspectRatio: StyleString.aspectRatio,
6 / child: LayoutBuilder(
MediaQuery.textScalerOf(context).scale(1.0)) / builder: (BuildContext context,
2; BoxConstraints boxConstraints) {
return Container( final double maxWidth = boxConstraints.maxWidth;
constraints: const BoxConstraints(minHeight: 88), final double maxHeight = boxConstraints.maxHeight;
height: width / StyleString.aspectRatio, return Stack(
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.start, Hero(
crossAxisAlignment: CrossAxisAlignment.start, tag: heroTag,
children: <Widget>[ child: NetworkImgLayer(
AspectRatio( src: videoItem.pic as String,
aspectRatio: StyleString.aspectRatio, width: maxWidth,
child: LayoutBuilder( height: maxHeight,
builder: (BuildContext context, ),
BoxConstraints boxConstraints) { ),
final double maxWidth = boxConstraints.maxWidth; if (videoItem.duration != 0)
final double maxHeight = PBadge(
boxConstraints.maxHeight; text: Utils.timeFormat(videoItem.duration!),
return Stack( right: 6.0,
children: [ bottom: 6.0,
Hero( type: 'gray',
tag: heroTag, ),
child: NetworkImgLayer( if (type != 'video')
src: videoItem.pic as String, PBadge(
width: maxWidth, text: type,
height: maxHeight, left: 6.0,
), bottom: 6.0,
), type: 'primary',
if (videoItem.duration != 0) ),
PBadge( // if (videoItem.rcmdReason != null &&
text: Utils.timeFormat(videoItem.duration!), // videoItem.rcmdReason.content != '')
right: 6.0, // pBadge(videoItem.rcmdReason.content, context,
bottom: 6.0, // 6.0, 6.0, null, null),
type: 'gray', ],
), );
if (type != 'video') },
PBadge( ),
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
// 6.0, 6.0, null, null),
],
);
},
),
),
VideoContent(
videoItem: videoItem,
source: source,
showOwner: showOwner,
showView: showView,
showDanmaku: showDanmaku,
showPubdate: showPubdate,
)
],
), ),
); VideoContent(
}, videoItem: videoItem,
), source: source,
showOwner: showOwner,
showView: showView,
showDanmaku: showDanmaku,
showPubdate: showPubdate,
)
],
);
},
), ),
), ),
)), )),
if (source == 'normal') if (source == 'normal')
Positioned( Positioned(
bottom: 1, bottom: 0,
right: 10, right: 10,
child: VideoPopupMenu( child: VideoPopupMenu(
size: 30, size: 29,
iconSize: 16, iconSize: 17,
videoItem: videoItem, videoItem: videoItem,
), ),
), ),
@@ -182,6 +167,10 @@ class VideoContent extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String pubdate = showPubdate
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
: '';
if (pubdate != '') pubdate += ' ';
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0), padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
@@ -247,7 +236,7 @@ class VideoContent extends StatelessWidget {
Expanded( Expanded(
flex: 0, flex: 0,
child: Text( child: Text(
"${showPubdate ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') + ' ' : ''} ${showOwner ? videoItem.owner.name : ''}", "${pubdate}${showOwner ? videoItem.owner.name : ''}",
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
@@ -276,7 +265,7 @@ class VideoContent extends StatelessWidget {
if (source == 'normal') const SizedBox(width: 24), if (source == 'normal') const SizedBox(width: 24),
], ],
), ),
const SizedBox(height: 2), const SizedBox(height: 5),
], ],
), ),
), ),

View File

@@ -191,8 +191,8 @@ class VideoCardV extends StatelessWidget {
right: 0, right: 0,
bottom: 0, bottom: 0,
child: VideoPopupMenu( child: VideoPopupMenu(
size: 30, size: 29,
iconSize: 16, iconSize: 17,
videoItem: videoItem, videoItem: videoItem,
)), )),
]); ]);
@@ -224,7 +224,8 @@ class VideoContent extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 2), const Spacer(),
// const SizedBox(height: 2),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
), ),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../http/user.dart'; import '../../http/user.dart';
import '../../http/video.dart'; import '../../http/video.dart';
@@ -11,7 +12,7 @@ class VideoPopupMenu extends StatelessWidget {
final double? size; final double? size;
final double? iconSize; final double? iconSize;
final dynamic videoItem; final dynamic videoItem;
final double menuItemHeight = 50; final double menuItemHeight = 45;
const VideoPopupMenu({ const VideoPopupMenu({
Key? key, Key? key,
@@ -53,10 +54,44 @@ class VideoPopupMenu extends StatelessWidget {
), ),
PopupMenuItem<String>( PopupMenuItem<String>(
onTap: () async { onTap: () async {
SmartDialog.show( Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: {
useSystem: true, 'face': videoItem.owner.face,
animationType: SmartAnimationType.centerFade_otherSlide, 'heroTag': '${videoItem.owner.mid}',
builder: (BuildContext context) { });
},
value: 'visit',
height: menuItemHeight,
child: Row(
children: [
const Icon(CupertinoIcons.person, size: 16),
const SizedBox(width: 6),
Text('访问:${videoItem.owner.name}',
style: const TextStyle(fontSize: 13))
],
),
),
// 不感兴趣
PopupMenuItem<String>(
onTap: () async {
// var res = await VideoHttp.dislike(bvid: videoItem.bvid as String);
// SmartDialog.showToast(res['msg']);
SmartDialog.showToast("暂未实现");
},
value: 'dislike',
height: menuItemHeight,
child: const Row(
children: [
Icon(CupertinoIcons.hand_thumbsdown, size: 16),
SizedBox(width: 6),
Text('不感兴趣', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () async {
await showDialog(
context: context,
builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text( content: Text(
@@ -64,7 +99,7 @@ class VideoPopupMenu extends StatelessWidget {
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'), '\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle( style: TextStyle(
@@ -86,7 +121,7 @@ class VideoPopupMenu extends StatelessWidget {
blackMidsList.insert(0, videoItem.owner.mid); blackMidsList.insert(0, videoItem.owner.mid);
GStrorage.setting GStrorage.setting
.put(SettingBoxKey.blackMidsList, blackMidsList); .put(SettingBoxKey.blackMidsList, blackMidsList);
SmartDialog.dismiss(); Get.back();
SmartDialog.showToast(res['msg'] ?? '成功'); SmartDialog.showToast(res['msg'] ?? '成功');
}, },
child: const Text('确认'), child: const Text('确认'),

View File

@@ -42,6 +42,12 @@ class Api {
// 视频点踩 web端不支持 // 视频点踩 web端不支持
// 点踩 Post(app端)
/// access_key str APP登录Token 必要
/// aid num 稿件avid 必要
///
static const String dislikeVideo = '${HttpString.appBaseUrl}/x/v2/view/dislike';
// 投币视频web端POST // 投币视频web端POST
/// aid num 稿件avid 必要(可选) avid与bvid任选一个 /// aid num 稿件avid 必要(可选) avid与bvid任选一个
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个 /// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
@@ -334,10 +340,26 @@ class Api {
static const String webDanmaku = '/x/v2/dm/web/seg.so'; static const String webDanmaku = '/x/v2/dm/web/seg.so';
//发送视频弹幕 // 发送视频弹幕
//https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md //https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md
static const String shootDanmaku = '/x/v2/dm/post'; static const String shootDanmaku = '/x/v2/dm/post';
// 弹幕屏蔽查询Get
static const String danmakuFilter = '/x/dm/filter/user';
// 弹幕屏蔽词添加Post
// 表单内容:
// type: 0关键词1正则2用户
// filter: 屏蔽内容
// csrf
static const String danmakuFilterAdd = '/x/dm/filter/user/add';
// 弹幕屏蔽词删除Post
// 表单内容:
// ids: 被删除条目编号
// csrf
static const String danmakuFilterDel = '/x/dm/filter/user/del';
// up主分组 // up主分组
static const String followUpTag = '/x/relation/tags'; static const String followUpTag = '/x/relation/tags';

View File

@@ -0,0 +1,63 @@
import '../models/user/danmaku_block.dart';
import 'index.dart';
class DanmakuFilterHttp {
static Future danmakuFilter() async {
var res = await Request().get(Api.danmakuFilter);
if (res.data['code'] == 0) {
return {
'status': true,
'data': DanmakuBlockDataModel.fromJson(res.data['data'])
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
static Future danmakuFilterDel({required int ids}) async {
var res = await Request().post(
Api.danmakuFilterDel,
queryParameters: {
'ids': ids,
'csrf': await Request.getCsrf(),
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'msg': '操作成功',
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
static Future danmakuFilterAdd({required String filter, required int type}) async {
var res = await Request().post(
Api.danmakuFilterAdd,
queryParameters: {
'type': type,
'filter': filter,
'csrf': await Request.getCsrf(),
},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': Rule.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
}

View File

@@ -5,15 +5,13 @@ import 'index.dart';
class DynamicsHttp { class DynamicsHttp {
static Future followDynamic({ static Future followDynamic({
String? type, String? type,
int? page,
String? offset, String? offset,
int? mid, int? mid,
}) async { }) async {
Map<String, dynamic> data = { Map<String, dynamic> data = {
'type': type ?? 'all', 'type': type ?? 'all',
'page': page ?? 1,
'timezone_offset': '-480', 'timezone_offset': '-480',
'offset': page == 1 ? '' : offset, 'offset': offset,
'features': 'itemOpusStyle' 'features': 'itemOpusStyle'
}; };
if (mid != -1) { if (mid != -1) {

View File

@@ -10,6 +10,7 @@ import '../models/user/fav_folder.dart';
import '../models/video/ai.dart'; import '../models/video/ai.dart';
import '../models/video/play/url.dart'; import '../models/video/play/url.dart';
import '../models/video_detail_res.dart'; import '../models/video_detail_res.dart';
import '../utils/id_utils.dart';
import '../utils/recommend_filter.dart'; import '../utils/recommend_filter.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
@@ -319,6 +320,29 @@ class VideoHttp {
} }
} }
// (取消)点踩
static Future dislikeVideo({required String bvid, required bool type}) async {
String? accessKey = GStrorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
if (accessKey == null || accessKey == "") {
return {'status': false, 'data': [], 'msg': "本操作使用app端接口请前往【隐私设置】刷新access_key"};
}
var res = await Request().post(
Api.dislikeVideo,
queryParameters: {
'aid': IdUtils.bv2av(bvid),
'dislike': type ? 0 : 1,
'access_key': accessKey,
},
);
print(res);
if (res.data is! String && res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
// (取消)收藏 // (取消)收藏
static Future favVideo( static Future favVideo(
{required int aid, String? addIds, String? delIds}) async { {required int aid, String? addIds, String? delIds}) async {

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
final List<Map<String, dynamic>> colorThemeTypes = [ final List<Map<String, dynamic>> colorThemeTypes = [
{'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'}, {'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'},
{'color': Colors.pink, 'label': '粉红色'}, {'color': const Color.fromARGB(255, 251, 114, 153), 'label': '粉红色'},
{'color': Colors.red, 'label': '红色'}, {'color': Colors.red, 'label': '红色'},
{'color': Colors.orange, 'label': '橙色'}, {'color': Colors.orange, 'label': '橙色'},
{'color': Colors.amber, 'label': '琥珀色'}, {'color': Colors.amber, 'label': '琥珀色'},

View File

@@ -1,11 +1,56 @@
import 'package:get/get.dart';
import '../../pages/dynamics/tab/controller.dart';
import '../../pages/dynamics/tab/view.dart';
enum DynamicsType { enum DynamicsType {
all, all,
video, video,
pgc, pgc,
article, article,
up,
} }
extension BusinessTypeExtension on DynamicsType { extension BusinessTypeExtension on DynamicsType {
String get values => ['all', 'video', 'pgc', 'article'][index]; String get values => ['all', 'video', 'pgc', 'article', 'up'][index];
String get labels => ['全部', '投稿', '番剧', '专栏'][index]; String get labels => ['全部', '投稿', '番剧', '专栏', 'Up'][index];
} }
List tabsConfig = [
{
'value': DynamicsType.all,
'label': '全部',
'enabled': true,
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'all'),
'page': const DynamicsTabPage(dynamicsType: 'all'),
},
{
'value': DynamicsType.video,
'label': '投稿',
'enabled': true,
'ctr':
Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'video'),
'page': const DynamicsTabPage(dynamicsType: 'video'),
},
{
'value': DynamicsType.pgc,
'label': '番剧',
'enabled': true,
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'pgc'),
'page': const DynamicsTabPage(dynamicsType: 'pgc'),
},
{
'value': DynamicsType.article,
'label': '专栏',
'enabled': true,
'ctr':
Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'article'),
'page': const DynamicsTabPage(dynamicsType: 'article'),
},
{
'value': DynamicsType.up,
'label': 'Up',
'enabled': true,
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'up'),
'page': const DynamicsTabPage(dynamicsType: 'up'),
},
];

View File

@@ -5,30 +5,17 @@ List defaultNavigationBars = [
'id': 0, 'id': 0,
'icon': const Icon( 'icon': const Icon(
Icons.home_outlined, Icons.home_outlined,
size: 21, size: 23,
), ),
'selectIcon': const Icon( 'selectIcon': const Icon(
Icons.home, Icons.home,
size: 21, size: 23,
), ),
'label': "首页", 'label': "首页",
'count': 0, 'count': 0,
}, },
{ {
'id': 1, 'id': 1,
'icon': const Icon(
Icons.leaderboard_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.leaderboard,
size: 21,
),
'label': "排行榜",
'count': 0,
},
{
'id': 2,
'icon': const Icon( 'icon': const Icon(
Icons.motion_photos_on_outlined, Icons.motion_photos_on_outlined,
size: 21, size: 21,
@@ -41,7 +28,7 @@ List defaultNavigationBars = [
'count': 0, 'count': 0,
}, },
{ {
'id': 3, 'id': 2,
'icon': const Icon( 'icon': const Icon(
Icons.video_collection_outlined, Icons.video_collection_outlined,
size: 20, size: 20,

View File

@@ -74,7 +74,7 @@ List tabsConfig = [
), ),
'label': '全站', 'label': '全站',
'type': RandType.all, 'type': RandType.all,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '0'),
'page': const ZonePage(rid: 0), 'page': const ZonePage(rid: 0),
}, },
{ {
@@ -84,7 +84,7 @@ List tabsConfig = [
), ),
'label': '国创', 'label': '国创',
'type': RandType.creation, 'type': RandType.creation,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '168'),
'page': const ZonePage(rid: 168), 'page': const ZonePage(rid: 168),
}, },
{ {
@@ -94,7 +94,7 @@ List tabsConfig = [
), ),
'label': '动画', 'label': '动画',
'type': RandType.animation, 'type': RandType.animation,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '1'),
'page': const ZonePage(rid: 1), 'page': const ZonePage(rid: 1),
}, },
{ {
@@ -104,7 +104,7 @@ List tabsConfig = [
), ),
'label': '音乐', 'label': '音乐',
'type': RandType.music, 'type': RandType.music,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '3'),
'page': const ZonePage(rid: 3), 'page': const ZonePage(rid: 3),
}, },
{ {
@@ -114,7 +114,7 @@ List tabsConfig = [
), ),
'label': '舞蹈', 'label': '舞蹈',
'type': RandType.dance, 'type': RandType.dance,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '129'),
'page': const ZonePage(rid: 129), 'page': const ZonePage(rid: 129),
}, },
{ {
@@ -124,7 +124,7 @@ List tabsConfig = [
), ),
'label': '游戏', 'label': '游戏',
'type': RandType.game, 'type': RandType.game,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '4'),
'page': const ZonePage(rid: 4), 'page': const ZonePage(rid: 4),
}, },
{ {
@@ -134,7 +134,7 @@ List tabsConfig = [
), ),
'label': '知识', 'label': '知识',
'type': RandType.knowledge, 'type': RandType.knowledge,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '36'),
'page': const ZonePage(rid: 36), 'page': const ZonePage(rid: 36),
}, },
{ {
@@ -144,7 +144,7 @@ List tabsConfig = [
), ),
'label': '科技', 'label': '科技',
'type': RandType.technology, 'type': RandType.technology,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '188'),
'page': const ZonePage(rid: 188), 'page': const ZonePage(rid: 188),
}, },
{ {
@@ -154,7 +154,7 @@ List tabsConfig = [
), ),
'label': '运动', 'label': '运动',
'type': RandType.sport, 'type': RandType.sport,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '234'),
'page': const ZonePage(rid: 234), 'page': const ZonePage(rid: 234),
}, },
{ {
@@ -164,7 +164,7 @@ List tabsConfig = [
), ),
'label': '汽车', 'label': '汽车',
'type': RandType.car, 'type': RandType.car,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '223'),
'page': const ZonePage(rid: 223), 'page': const ZonePage(rid: 223),
}, },
{ {
@@ -174,7 +174,7 @@ List tabsConfig = [
), ),
'label': '生活', 'label': '生活',
'type': RandType.life, 'type': RandType.life,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '160'),
'page': const ZonePage(rid: 160), 'page': const ZonePage(rid: 160),
}, },
{ {
@@ -184,7 +184,7 @@ List tabsConfig = [
), ),
'label': '美食', 'label': '美食',
'type': RandType.food, 'type': RandType.food,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '211'),
'page': const ZonePage(rid: 211), 'page': const ZonePage(rid: 211),
}, },
{ {
@@ -194,7 +194,7 @@ List tabsConfig = [
), ),
'label': '动物', 'label': '动物',
'type': RandType.animal, 'type': RandType.animal,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '217'),
'page': const ZonePage(rid: 217), 'page': const ZonePage(rid: 217),
}, },
{ {
@@ -204,7 +204,7 @@ List tabsConfig = [
), ),
'label': '鬼畜', 'label': '鬼畜',
'type': RandType.madness, 'type': RandType.madness,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '119'),
'page': const ZonePage(rid: 119), 'page': const ZonePage(rid: 119),
}, },
{ {
@@ -214,7 +214,7 @@ List tabsConfig = [
), ),
'label': '时尚', 'label': '时尚',
'type': RandType.fashion, 'type': RandType.fashion,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '155'),
'page': const ZonePage(rid: 155), 'page': const ZonePage(rid: 155),
}, },
{ {
@@ -224,7 +224,7 @@ List tabsConfig = [
), ),
'label': '娱乐', 'label': '娱乐',
'type': RandType.entertainment, 'type': RandType.entertainment,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '5'),
'page': const ZonePage(rid: 5), 'page': const ZonePage(rid: 5),
}, },
{ {
@@ -234,7 +234,7 @@ List tabsConfig = [
), ),
'label': '影视', 'label': '影视',
'type': RandType.film, 'type': RandType.film,
'ctr': Get.put<ZoneController>, 'ctr': Get.put<ZoneController>(ZoneController(), tag: '181'),
'page': const ZonePage(rid: 181), 'page': const ZonePage(rid: 181),
} }
]; ];

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/pages/rank/index.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPalaX/pages/bangumi/index.dart'; import 'package:PiliPalaX/pages/bangumi/index.dart';
@@ -5,11 +6,11 @@ import 'package:PiliPalaX/pages/hot/index.dart';
import 'package:PiliPalaX/pages/live/index.dart'; import 'package:PiliPalaX/pages/live/index.dart';
import 'package:PiliPalaX/pages/rcmd/index.dart'; import 'package:PiliPalaX/pages/rcmd/index.dart';
enum TabType { live, rcmd, hot, bangumi } enum TabType { live, rcmd, hot, rank, bangumi }
extension TabTypeDesc on TabType { extension TabTypeDesc on TabType {
String get description => ['直播', '推荐', '热门', '番剧'][index]; String get description => ['直播', '推荐', '热门', '分区', '番剧'][index];
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index]; String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
} }
List tabsConfig = [ List tabsConfig = [
@@ -43,6 +44,16 @@ List tabsConfig = [
'ctr': Get.find<HotController>, 'ctr': Get.find<HotController>,
'page': const HotPage(), 'page': const HotPage(),
}, },
{
'icon': const Icon(
Icons.category_outlined,
size: 15,
),
'label': '分区',
'type': TabType.rank,
'ctr': Get.find<RankController>,
'page': const RankPage(),
},
{ {
'icon': const Icon( 'icon': const Icon(
Icons.play_circle_outlined, Icons.play_circle_outlined,

View File

@@ -0,0 +1,15 @@
enum UpPanelPosition {
leftFixed,
rightFixed,
leftDrawer,
rightDrawer,
}
extension UpPanelPositionDesc on UpPanelPosition {
String get values => ['left_fixed', 'right_fixed', 'left_drawer', 'right_drawer'][index];
String get labels => ['左侧常驻','右侧常驻','左侧抽屉','右侧抽屉'][index];
}
extension UpPanelPositionCode on UpPanelPosition {
int get code => [0, 1, 2, 3][index];
}

View File

@@ -0,0 +1,94 @@
class DanmakuBlockDataModel {
List<Rule>? rule;
String? toast;
int? valid;
int? ver;
DanmakuBlockDataModel({this.rule, this.toast, this.valid, this.ver});
DanmakuBlockDataModel.fromJson(Map<String, dynamic> json) {
if (json['rule'] != null) {
rule = <Rule>[];
json['rule'].forEach((v) {
rule!.add(Rule.fromJson(v));
});
}
toast = json['toast'];
valid = json['valid'];
ver = json['ver'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
if (rule != null) {
data['rule'] = rule!.map((v) => v.toJson()).toList();
}
data['toast'] = toast;
data['valid'] = valid;
data['ver'] = ver;
return data;
}
}
class Rule {
int? id;
int? mid;
int? type;
String? filter;
String? comment;
int? ctime;
int? mtime;
Rule(
{this.id,
this.mid,
this.type,
this.filter,
this.comment,
this.ctime,
this.mtime});
Rule.fromJson(Map<String, dynamic> json) {
id = json['id'];
mid = json['mid'];
type = json['type'];
filter = json['filter'];
comment = json['comment'];
ctime = json['ctime'];
mtime = json['mtime'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['id'] = id;
data['mid'] = mid;
data['type'] = type;
data['filter'] = filter;
data['comment'] = comment;
data['ctime'] = ctime;
data['mtime'] = mtime;
return data;
}
}
class SimpleRule {
final int id;
final int type;
final String filter;
SimpleRule(this.id, this.type, this.filter);
Map<String, dynamic> toMap() {
return {
'id': id,
'type': type,
'filter': filter,
};
}
factory SimpleRule.fromMap(Map<String, dynamic> map) {
return SimpleRule(
map['id'],
map['type'],
map['filter'],
);
}
}

View File

@@ -127,7 +127,7 @@ class _AboutPageState extends State<AboutPage> {
), ),
), ),
ListTile( ListTile(
onTap: () => _aboutController.feedback(), onTap: () => _aboutController.feedback(context),
leading: const Icon(Icons.feedback_outlined), leading: const Icon(Icons.feedback_outlined),
title: const Text('问题反馈'), title: const Text('问题反馈'),
trailing: Icon( trailing: Icon(
@@ -174,7 +174,7 @@ class _AboutPageState extends State<AboutPage> {
), ),
ListTile( ListTile(
onTap: () async { onTap: () async {
await CacheManage().clearCacheAll(); await CacheManage().clearCacheAll(context);
getCacheSize(); getCacheSize();
}, },
leading: const Icon(Icons.delete_outline), leading: const Icon(Icons.delete_outline),
@@ -185,17 +185,17 @@ class _AboutPageState extends State<AboutPage> {
title: const Text('导入/导出设置'), title: const Text('导入/导出设置'),
dense: false, dense: false,
leading: const Icon(Icons.import_export_outlined), leading: const Icon(Icons.import_export_outlined),
onTap: () { onTap: () async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
builder: (BuildContext context) { builder: (context) {
return SimpleDialog( return SimpleDialog(
title: const Text('导入/导出设置'), title: const Text('导入/导出设置'),
children: [ children: [
ListTile( ListTile(
title: const Text('导出设置至剪贴板'), title: const Text('导出设置至剪贴板'),
onTap: () async { onTap: () async {
SmartDialog.dismiss(); Get.back();
String data = await GStrorage.exportAllSettings(); String data = await GStrorage.exportAllSettings();
Clipboard.setData(ClipboardData(text: data)); Clipboard.setData(ClipboardData(text: data));
SmartDialog.showToast('已复制到剪贴板'); SmartDialog.showToast('已复制到剪贴板');
@@ -204,30 +204,35 @@ class _AboutPageState extends State<AboutPage> {
ListTile( ListTile(
title: const Text('从剪贴板导入设置'), title: const Text('从剪贴板导入设置'),
onTap: () async { onTap: () async {
SmartDialog.dismiss(); Get.back();
ClipboardData? data = await Clipboard.getData('text/plain'); ClipboardData? data =
if (data == null || data.text == null || data.text!.isEmpty) { await Clipboard.getData('text/plain');
if (data == null ||
data.text == null ||
data.text!.isEmpty) {
SmartDialog.showToast('剪贴板无数据'); SmartDialog.showToast('剪贴板无数据');
return; return;
} }
SmartDialog.show( if (!context.mounted) return;
useSystem: true, await showDialog(
builder: (BuildContext context) { context: context,
builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('是否导入如下设置?'), title: const Text('是否导入如下设置?'),
content: Text(data.text!), content: Text(data.text!),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Get.back();
try{ try {
await GStrorage.importAllSettings(data.text!); await GStrorage.importAllSettings(
data.text!);
SmartDialog.showToast('导入成功'); SmartDialog.showToast('导入成功');
} catch (e) { } catch (e) {
SmartDialog.showToast('导入失败:$e'); SmartDialog.showToast('导入失败:$e');
@@ -249,27 +254,27 @@ class _AboutPageState extends State<AboutPage> {
ListTile( ListTile(
title: const Text('重置所有设置'), title: const Text('重置所有设置'),
leading: const Icon(Icons.settings_backup_restore_outlined), leading: const Icon(Icons.settings_backup_restore_outlined),
onTap: () { onTap: () async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
builder: (BuildContext context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('重置所有设置'), title: const Text('重置所有设置'),
content: const Text('是否重置所有设置?'), content: const Text('是否重置所有设置?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Get.back();
GStrorage.setting.clear(); GStrorage.setting.clear();
GStrorage.localCache.clear(); GStrorage.localCache.clear();
GStrorage.video.clear(); GStrorage.video.clear();
SmartDialog.showToast('重置成功'); SmartDialog.showToast('重置成功');
SmartDialog.dismiss();
}, },
child: const Text('确定'), child: const Text('确定'),
), ),
@@ -384,11 +389,10 @@ class AboutController extends GetxController {
} }
// 问题反馈 // 问题反馈
feedback() { feedback(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return SimpleDialog( return SimpleDialog(
title: const Text('问题反馈'), title: const Text('问题反馈'),
children: [ children: [

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@@ -231,9 +232,29 @@ class BangumiIntroController extends GetxController {
// 分享视频 // 分享视频
Future actionShareVideo() async { Future actionShareVideo() async {
var result = await Share.share('${HttpString.baseUrl}/video/$bvid') showDialog(
.whenComplete(() {}); context: Get.context!,
return result; builder: (context) {
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
return AlertDialog(
title: const Text('分享方式'),
actions: [
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: videoUrl));
SmartDialog.showToast('已复制');
},
child: const Text('复制链接到剪贴板')),
TextButton(
onPressed: () async {
var result =
await Share.share(videoUrl).whenComplete(() {});
return result;
},
child: const Text('分享视频')),
],
);
});
} }
// 选择文件夹 // 选择文件夹

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/plugin/pl_player/index.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -283,56 +284,35 @@ class _BangumiInfoState extends State<BangumiInfo> {
: bangumiItem!.stat!['danmakus'], : bangumiItem!.stat!['danmakus'],
size: 'medium', size: 'medium',
), ),
if (isLandscape) ...[
const SizedBox(width: 6),
AreasAndPubTime(
widget: widget,
bangumiItem: bangumiItem,
t: t),
const SizedBox(width: 6),
NewEpDesc(
widget: widget,
bangumiItem: bangumiItem,
t: t),
]
], ],
), ),
SizedBox(height: isLandscape ? 2 : 6), SizedBox(height: isLandscape ? 2 : 6),
Row( if (!isLandscape)
children: [ AreasAndPubTime(
Text( widget: widget,
!widget.loadingStatus bangumiItem: bangumiItem,
? (widget.bangumiDetail!.areas! t: t),
.isNotEmpty if (!isLandscape)
? widget.bangumiDetail!.areas! NewEpDesc(
.first['name'] widget: widget,
: '') bangumiItem: bangumiItem,
: (bangumiItem!.areas!.isNotEmpty t: t),
? bangumiItem!
.areas!.first['name']
: ''),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 6),
Text(
!widget.loadingStatus
? widget.bangumiDetail!
.publish!['pub_time_show']
: bangumiItem!
.publish!['pub_time_show'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
],
),
// const SizedBox(height: 4),
Text(
!widget.loadingStatus
? widget.bangumiDetail!.newEp!['desc']
: bangumiItem!.newEp!['desc'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
// const SizedBox(height: 10),
const Spacer(), const Spacer(),
Text( Text(
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}', '简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
maxLines: isLandscape ? 1 : 3, maxLines: isLandscape ? 2 : 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
@@ -525,3 +505,73 @@ class _BangumiInfoState extends State<BangumiInfo> {
]); ]);
} }
} }
class AreasAndPubTime extends StatelessWidget {
const AreasAndPubTime({
super.key,
required this.widget,
required this.bangumiItem,
required this.t,
});
final BangumiInfo widget;
final BangumiInfoModel? bangumiItem;
final ThemeData t;
@override
Widget build(BuildContext context) {
return Row(
children: [
Text(
!widget.loadingStatus
? (widget.bangumiDetail!.areas!.isNotEmpty
? widget.bangumiDetail!.areas!.first['name']
: '')
: (bangumiItem!.areas!.isNotEmpty
? bangumiItem!.areas!.first['name']
: ''),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 6),
Text(
!widget.loadingStatus
? widget.bangumiDetail!.publish!['pub_time_show']
: bangumiItem!.publish!['pub_time_show'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
],
);
}
}
class NewEpDesc extends StatelessWidget {
const NewEpDesc({
super.key,
required this.widget,
required this.bangumiItem,
required this.t,
});
final BangumiInfo widget;
final BangumiInfoModel? bangumiItem;
final ThemeData t;
@override
Widget build(BuildContext context) {
return Text(
!widget.loadingStatus
? widget.bangumiDetail!.newEp!['desc']
: bangumiItem!.newEp!['desc'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
);
}
}

View File

@@ -115,7 +115,7 @@ class _BangumiPageState extends State<BangumiPage>
), ),
), ),
SizedBox( SizedBox(
height: 268, height: Grid.maxRowWidth * 1,
child: FutureBuilder( child: FutureBuilder(
future: _futureBuilderFutureFollow, future: _futureBuilderFutureFollow,
builder: builder:
@@ -135,8 +135,8 @@ class _BangumiPageState extends State<BangumiPage>
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return Container( return Container(
width: Get.size.width / 3, width: Grid.maxRowWidth / 2,
height: 254, height: Grid.maxRowWidth * 1,
margin: EdgeInsets.only( margin: EdgeInsets.only(
left: StyleString.safeSpace, left: StyleString.safeSpace,
right: index == right: index ==
@@ -219,17 +219,16 @@ class _BangumiPageState extends State<BangumiPage>
} }
Widget contentGrid(ctr, bangumiList) { Widget contentGrid(ctr, bangumiList) {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距 // 行间距
mainAxisSpacing: StyleString.cardSpace - 2, mainAxisSpacing: StyleString.cardSpace - 2,
// 列间距 // 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
// 最大宽度 // 最大宽度
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2, maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth / 3 * 2, StyleString.safeSpace) / 0.65+ childAspectRatio: 0.65,
MediaQuery.textScalerOf(context).scale(60), mainAxisExtent: MediaQuery.textScalerOf(context).scale(60),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -36,6 +36,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
late double fontSizeVal; late double fontSizeVal;
late double danmakuDurationVal; late double danmakuDurationVal;
late double strokeWidth; late double strokeWidth;
late int fontWeight;
int latestAddedPosition = -1; int latestAddedPosition = -1;
@override @override
@@ -68,6 +69,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
opacityVal = playerController.opacityVal; opacityVal = playerController.opacityVal;
fontSizeVal = playerController.fontSizeVal; fontSizeVal = playerController.fontSizeVal;
strokeWidth = playerController.strokeWidth; strokeWidth = playerController.strokeWidth;
fontWeight = playerController.fontWeight;
danmakuDurationVal = playerController.danmakuDurationVal; danmakuDurationVal = playerController.danmakuDurationVal;
} }
@@ -132,6 +134,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
}, },
option: DanmakuOption( option: DanmakuOption(
fontSize: 15 * fontSizeVal, fontSize: 15 * fontSizeVal,
fontWeight: fontWeight,
area: showArea, area: showArea,
opacity: opacityVal, opacity: opacityVal,
hideTop: blockTypes.contains(5), hideTop: blockTypes.contains(5),

View File

@@ -0,0 +1,182 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/utils/storage.dart';
import '../../http/danmaku_block.dart';
import '../../models/user/danmaku_block.dart';
class DanmakuBlockPage extends StatefulWidget {
const DanmakuBlockPage({super.key});
@override
State<DanmakuBlockPage> createState() => _DanmakuBlockPageState();
}
class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
final DanmakuBlockController _danmakuBlockController =
Get.put(DanmakuBlockController());
final ScrollController scrollController = ScrollController();
Box setting = GStrorage.setting;
@override
void initState() {
super.initState();
_danmakuBlockController.queryDanmakuFilter();
}
@override
void dispose() {
List<Map<String, dynamic>> simpleRuleList =
_danmakuBlockController.danmakuRules.map<Map<String, dynamic>>((e) {
return SimpleRule(e.id!, e.type!, e.filter!).toMap();
}).toList();
setting.put(SettingBoxKey.danmakuFilterRule, simpleRuleList);
scrollController.removeListener(() {});
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Obx(
() => TabBar(
controller: _danmakuBlockController.tabController,
dividerColor: Colors.transparent,
tabs: [
Tab(text: '文本(${_danmakuBlockController.textRules.length})'),
Tab(text: '正则(${_danmakuBlockController.regexRules.length})'),
Tab(text: '用户(${_danmakuBlockController.userRules.length})'),
],
),
),
),
body: RefreshIndicator(
onRefresh: () async =>
await _danmakuBlockController.queryDanmakuFilter(),
child: TabBarView(
controller: _danmakuBlockController.tabController,
children: [
Obx(() => tabViewBuilder(0, _danmakuBlockController.textRules)),
Obx(() => tabViewBuilder(1, _danmakuBlockController.regexRules)),
Obx(() => tabViewBuilder(2, _danmakuBlockController.userRules)),
],
),
),
);
}
Widget tabViewBuilder(int index, List<SimpleRule> list) {
return ListView.builder(
controller: scrollController,
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
list[index].filter,
style: Theme.of(context).textTheme.subtitle1,
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
await _danmakuBlockController.danmakuFilterDel(
1, list[index].id);
},
),
);
},
);
}
}
class DanmakuBlockController extends GetxController
with GetTickerProviderStateMixin {
RxList<Rule> danmakuRules = <Rule>[].obs;
RxList<SimpleRule> textRules = <SimpleRule>[].obs;
RxList<SimpleRule> regexRules = <SimpleRule>[].obs;
RxList<SimpleRule> userRules = <SimpleRule>[].obs;
late TabController tabController;
@override
void onInit() {
super.onInit();
tabController = TabController(length: 3, vsync: this);
}
@override
void onClose() {
tabController.dispose();
super.onClose();
}
Future queryDanmakuFilter() async {
var result = await DanmakuFilterHttp.danmakuFilter();
if (result['status']) {
danmakuRules.value = result['data'].rule;
danmakuRules.map((e) {
SimpleRule simpleRule = SimpleRule(e.id!, e.type!, e.filter!);
switch (e.type!) {
case 0:
textRules.add(simpleRule);
break;
case 1:
regexRules.add(simpleRule);
break;
case 2:
userRules.add(simpleRule);
break;
default:
SmartDialog.showToast('未知的规则类型:${e.type},内容为:${e.filter}');
}
}).toList();
SmartDialog.showToast(result['data'].toast);
}
return result;
}
Future danmakuFilterDel(int type, int id) async {
var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id);
if (result['status']) {
danmakuRules.removeWhere((e) => e.id == id);
switch (type) {
case 0:
textRules.removeWhere((e) => e.id == id);
break;
case 1:
regexRules.removeWhere((e) => e.id == id);
break;
case 2:
userRules.removeWhere((e) => e.id == id);
break;
}
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast(result['msg']);
}
}
Future danmakuFilterAdd({required String filter, required int type}) async {
var result =
await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type);
if (result['status']) {
Rule data = result['data'];
danmakuRules.add(data);
SimpleRule simpleRule = SimpleRule(data.id!, data.type!, data.filter!);
switch (data.type!) {
case 0:
textRules.add(simpleRule);
break;
case 1:
regexRules.add(simpleRule);
break;
case 2:
userRules.add(simpleRule);
break;
}
SmartDialog.showToast('添加成功');
} else {
SmartDialog.showToast(result['msg']);
}
}
}

View File

@@ -1,5 +1,6 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'package:PiliPalaX/pages/dynamics/tab/index.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -17,39 +18,17 @@ import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
class DynamicsController extends GetxController { class DynamicsController extends GetxController
int page = 1; with GetTickerProviderStateMixin {
String? offset = ''; String? offset = '';
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
RxString dynamicsTypeLabel = '全部'.obs;
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
Rx<FollowUpModel> upData = FollowUpModel().obs; Rx<FollowUpModel> upData = FollowUpModel().obs;
// 默认获取全部动态 // 默认获取全部动态
RxInt mid = (-1).obs; RxInt mid = (-1).obs;
Rx<UpItem> upInfo = UpItem().obs; Rx<UpItem> upInfo = UpItem().obs;
List filterTypeList = [ late TabController tabController;
{ RxList<int> tempBannedList = <int>[].obs;
'label': DynamicsType.all.labels, late List<Widget> tabsPageList;
'value': DynamicsType.all,
'enabled': true
},
{
'label': DynamicsType.video.labels,
'value': DynamicsType.video,
'enabled': true
},
{
'label': DynamicsType.pgc.labels,
'value': DynamicsType.pgc,
'enabled': true
},
{
'label': DynamicsType.article.labels,
'value': DynamicsType.article,
'enabled': true
},
];
bool flag = false; bool flag = false;
RxInt initialValue = 0.obs; RxInt initialValue = 0.obs;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
@@ -63,53 +42,23 @@ class DynamicsController extends GetxController {
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
super.onInit(); super.onInit();
initialValue.value =
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0); tabController = TabController(
dynamicsType = DynamicsType.values[initialValue.value].obs; length: tabsConfig.length,
vsync: this,
initialIndex:
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0));
tabsPageList = tabsConfig.map((e) {
return e['page'] as Widget;
}).toList();
} }
Future queryFollowDynamic({type = 'init'}) async { void refreshNotifier() {
if (!userLogin.value) { queryFollowUp();
return {'status': false, 'msg': '账号未登录'};
}
if (type == 'init') {
dynamicsList.clear();
}
// 下拉刷新数据渲染时会触发onLoad
if (type == 'onLoad' && page == 1) {
return;
}
isLoadingDynamic.value = true;
var res = await DynamicsHttp.followDynamic(
page: type == 'init' ? 1 : page,
type: dynamicsType.value.values,
offset: offset,
mid: mid.value,
);
isLoadingDynamic.value = false;
if (res['status']) {
if (type == 'onLoad' && res['data'].items.isEmpty) {
SmartDialog.showToast('没有更多了');
return;
}
if (type == 'init') {
dynamicsList.value = res['data'].items;
} else {
dynamicsList.addAll(res['data'].items);
}
offset = res['data'].offset;
page++;
}
return res;
} }
onSelectType(value) async { onSelectType(value) async {
dynamicsType.value = filterTypeList[value]['value'];
dynamicsList.value = [DynamicItemModel()];
page = 1;
initialValue.value = value; initialValue.value = value;
await queryFollowDynamic();
scrollController.jumpTo(0);
} }
pushDetail(item, floor, {action = 'all'}) async { pushDetail(item, floor, {action = 'all'}) async {
@@ -276,21 +225,27 @@ class DynamicsController extends GetxController {
} }
onSelectUp(mid) async { onSelectUp(mid) async {
dynamicsType.value = DynamicsType.values[0]; if (mid == this.mid.value) {
dynamicsList.value = [DynamicItemModel()]; this.mid.refresh();
page = 1; return;
queryFollowDynamic(); }
this.mid.value = mid;
tabController.index = (mid == -1 ? 0 : 4);
} }
onRefresh() async { onRefresh() async {
page = 1;
print('onRefresh'); print('onRefresh');
await queryFollowUp(); print(tabsConfig[tabController.index]['ctr']);
await queryFollowDynamic(); await Future.wait(<Future>[
queryFollowUp(),
tabsConfig[tabController.index]['ctr'].onRefresh()
]);
} }
// 返回顶部并刷新 // 返回顶部并刷新
void animateToTop() async { void animateToTop() async {
tabsConfig[tabController.index]['ctr'].animateToTop();
if (!scrollController.hasClients) return;
if (scrollController.offset >= if (scrollController.offset >=
MediaQuery.of(Get.context!).size.height * 5) { MediaQuery.of(Get.context!).size.height * 5) {
scrollController.jumpTo(0); scrollController.jumpTo(0);
@@ -299,14 +254,4 @@ class DynamicsController extends GetxController {
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut); duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
} }
} }
// 重置搜索
void resetSearch() {
mid.value = -1;
dynamicsType.value = DynamicsType.values[0];
initialValue.value = 0;
SmartDialog.showToast('还原默认加载');
dynamicsList.value = [DynamicItemModel()];
queryFollowDynamic();
}
} }

View File

@@ -0,0 +1,76 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
// import 'package:hive/hive.dart';
import '../../../http/dynamics.dart';
import '../../../models/dynamics/result.dart';
// import '../../../utils/storage.dart';
class DynamicsTabController extends GetxController {
String offset = '';
ScrollController scrollController = ScrollController();
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
RxBool isLoadingMore = false.obs;
String dynamicsType = 'all';
// Box userInfoCache = GStrorage.userInfo;
// bool userLogin = false;
int mid = -1;
Future queryFollowDynamic(String type, String dynamicsType, int? mid) async {
this.dynamicsType = dynamicsType;
if (mid != null) this.mid = mid;
if (type != 'onLoad') {
dynamicsList.clear();
offset = '';
}
// // 下拉刷新数据渲染时会触发onLoad
// if (type == 'onLoad' && page == 1) {
// return;
// }
isLoadingMore.value = true;
var res = await DynamicsHttp.followDynamic(
type: dynamicsType == "up" ? "all" : dynamicsType,
offset: offset,
mid: dynamicsType == "up" ? mid : -1,
);
isLoadingMore.value = false;
if (res['status']) {
if (type == 'onLoad' && res['data'].items.isEmpty) {
SmartDialog.showToast('没有更多了');
return;
}
if (type == 'onLoad') {
dynamicsList.addAll(res['data'].items);
} else {
dynamicsList.value = res['data'].items;
}
// print('dynamicsList: $dynamicsList');
dynamicsList.refresh();
offset = res['data'].offset;
// print("page: $page[dynamicsType]!");
}
return res;
}
// 下拉刷新
Future onRefresh() async {
await queryFollowDynamic('onRefresh', dynamicsType, mid);
}
// 上拉加载
Future onLoad() async {
await queryFollowDynamic('onLoad', dynamicsType, mid);
}
// 返回顶部并刷新
void animateToTop() async {
if (scrollController.offset >=
MediaQuery.of(Get.context!).size.height * 5) {
scrollController.jumpTo(0);
} else {
await scrollController.animateTo(0,
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
}
}
}

View File

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

View File

@@ -0,0 +1,234 @@
import 'dart:async';
import 'dart:math';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/pages/home/index.dart';
import 'package:PiliPalaX/pages/main/index.dart';
import 'package:PiliPalaX/common/widgets/no_data.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
import '../../../common/skeleton/dynamic_card.dart';
import '../../../models/dynamics/result.dart';
import '../../../utils/grid.dart';
import '../index.dart';
import '../widgets/dynamic_panel.dart';
import 'controller.dart';
class DynamicsTabPage extends StatefulWidget {
const DynamicsTabPage({Key? key, required this.dynamicsType})
: super(key: key);
final String dynamicsType;
@override
State<DynamicsTabPage> createState() => _DynamicsTabPageState();
}
class _DynamicsTabPageState extends State<DynamicsTabPage>
with AutomaticKeepAliveClientMixin {
late DynamicsTabController _dynamicsTabController;
late Future _futureBuilderFuture;
late ScrollController scrollController;
late bool dynamicsWaterfallFlow;
late final DynamicsController dynamicsController;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_dynamicsTabController =
Get.put(DynamicsTabController(), tag: widget.dynamicsType);
dynamicsController = Get.find<DynamicsController>();
_futureBuilderFuture = _dynamicsTabController.queryFollowDynamic(
'init', widget.dynamicsType, dynamicsController.mid.value);
scrollController = _dynamicsTabController.scrollController
..addListener(() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_dynamicsTabController.isLoadingMore.value) {
EasyThrottle.throttle('_dynamicsTabController_onLoad',
const Duration(milliseconds: 500), () {
_dynamicsTabController.isLoadingMore.value = true;
_dynamicsTabController.onLoad();
_dynamicsTabController.isLoadingMore.value = false;
});
}
}
});
dynamicsController.mid.listen((mid) {
print('midListen: $mid');
scrollController.jumpTo(0);
_futureBuilderFuture = _dynamicsTabController.queryFollowDynamic(
'init', widget.dynamicsType, mid);
});
dynamicsWaterfallFlow = GStrorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
}
@override
void dispose() {
scrollController.removeListener(() {});
dynamicsController.mid.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
// print(widget.dynamicsType + widget.mid.value.toString());
return RefreshIndicator(
// key:
// ValueKey<String>(widget.dynamicsType + widget.mid.value.toString()),
onRefresh: () async {
dynamicsWaterfallFlow = GStrorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
await Future.wait(<Future>[
_dynamicsTabController.onRefresh(),
dynamicsController.queryFollowUp()
]);
},
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _dynamicsTabController.scrollController,
slivers: [
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
// print(snapshot);
// print(widget.dynamicsType + "${widget.mid?.value}");
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const NoData();
}
Map data = snapshot.data;
// print('data: $data');
if (data['status']) {
List<DynamicItemModel> list =
_dynamicsTabController.dynamicsList;
// print('list: $list');
return Obx(
() {
if (list.isEmpty) {
if (_dynamicsTabController.isLoadingMore.value) {
return skeleton();
}
return const NoData();
}
if (!dynamicsWaterfallFlow) {
return SliverCrossAxisGroup(
slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.maxRowWidth * 2,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
if ((dynamicsController
.tabController.index ==
4 &&
dynamicsController.mid.value !=
-1) ||
!dynamicsController.tempBannedList
.contains(list[index]
.modules
?.moduleAuthor
?.mid)) {
return DynamicPanel(
item: list[index]);
}
return const SizedBox();
},
childCount: list.length,
),
)),
const SliverFillRemaining(),
],
);
}
return SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.maxRowWidth * 2,
//cacheExtent: 0.0,
crossAxisSpacing: StyleString.cardSpace / 2,
mainAxisSpacing: StyleString.cardSpace / 2,
lastChildLayoutTypeBuilder: (index) =>
index == list.length
? LastChildLayoutType.foot
: LastChildLayoutType.none,
children: [
if (dynamicsController.tabController.index == 4 &&
dynamicsController.mid.value != -1) ...[
for (var i in list) DynamicPanel(item: i),
] else ...[
for (var i in list)
if (!dynamicsController.tempBannedList
.contains(i.modules?.moduleAuthor?.mid))
DynamicPanel(item: i),
]
],
);
},
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () {
// setState(() {
_futureBuilderFuture =
_dynamicsTabController.queryFollowDynamic(
'init',
widget.dynamicsType,
dynamicsController.mid.value);
// });
dynamicsController.onRefresh();
},
);
}
} else {
// 骨架屏
return skeleton();
}
},
)
],
));
}
Widget skeleton() {
if (!dynamicsWaterfallFlow) {
return SliverCrossAxisGroup(slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.maxRowWidth * 2,
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const DynamicCardSkeleton();
}, childCount: 10)),
),
const SliverFillRemaining()
]);
}
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
crossAxisSpacing: StyleString.cardSpace / 2,
mainAxisSpacing: StyleString.cardSpace / 2,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio,
mainAxisExtent: 50),
delegate: SliverChildBuilderDelegate((context, index) {
return const DynamicCardSkeleton();
}, childCount: 10),
);
}
}

View File

@@ -1,24 +1,15 @@
import 'dart:async'; import 'dart:async';
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:PiliPalaX/models/common/dynamics_type.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:PiliPalaX/models/common/up_panel_position.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:waterfall_flow/waterfall_flow.dart';
import 'package:PiliPalaX/common/skeleton/dynamic_card.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/no_data.dart';
import 'package:PiliPalaX/models/dynamics/result.dart';
import 'package:PiliPalaX/pages/main/index.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import '../../common/constants.dart';
import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/dynamic_panel.dart';
import 'widgets/up_panel.dart'; import 'widgets/up_panel.dart';
class DynamicsPage extends StatefulWidget { class DynamicsPage extends StatefulWidget {
@@ -29,12 +20,12 @@ class DynamicsPage extends StatefulWidget {
} }
class _DynamicsPageState extends State<DynamicsPage> class _DynamicsPageState extends State<DynamicsPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());
late Future _futureBuilderFuture;
late Future _futureBuilderFutureUp; late Future _futureBuilderFutureUp;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
late ScrollController scrollController; late ScrollController scrollController;
late UpPanelPosition upPanelPosition;
@override @override
bool get wantKeepAlive => true; bool get wantKeepAlive => true;
@@ -42,269 +33,125 @@ class _DynamicsPageState extends State<DynamicsPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp = _dynamicsController.queryFollowUp(); _futureBuilderFutureUp = _dynamicsController.queryFollowUp();
scrollController = _dynamicsController.scrollController; // _dynamicsController.tabController =
StreamController<bool> mainStream = // TabController(vsync: this, length: DynamicsType.values.length);
Get.find<MainController>().bottomBarStream; // ..addListener(() {
scrollController.addListener( // if (!_dynamicsController.tabController.indexIsChanging) {
() async { // // if (!mounted) return;
if (scrollController.position.pixels >= // // print('indexChanging: ${_dynamicsController.tabController.index}');
scrollController.position.maxScrollExtent - 200) { // _dynamicsController
EasyThrottle.throttle( // .onSelectType(_dynamicsController.tabController.index);
'queryFollowDynamic', const Duration(seconds: 1), () { // }
_dynamicsController.queryFollowDynamic(type: 'onLoad'); // });
});
}
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
mainStream.add(true);
} else if (direction == ScrollDirection.reverse) {
mainStream.add(false);
}
},
);
_dynamicsController.userLogin.listen((status) { _dynamicsController.userLogin.listen((status) {
if (mounted) { if (mounted) {
setState(() { setState(() {
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp = _dynamicsController.queryFollowUp(); _futureBuilderFutureUp = _dynamicsController.queryFollowUp();
}); });
} }
}); });
upPanelPosition = UpPanelPosition.values[setting.get(
SettingBoxKey.upPanelPosition,
defaultValue: UpPanelPosition.leftFixed.code)];
print('upPanelPosition: $upPanelPosition');
scrollController = _dynamicsController.scrollController;
} }
@override @override
void dispose() { void dispose() {
scrollController.removeListener(() {}); _dynamicsController.tabController.removeListener(() {});
_dynamicsController.tabController.dispose();
super.dispose(); super.dispose();
} }
Widget upPanelPart() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Container(
//抽屉模式增加底色
color: upPanelPosition.code > 1? Theme.of(context).colorScheme.surface: Colors.transparent,
width: 56,
child: FutureBuilder(
future: _futureBuilderFutureUp,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data;
if (data['status']) {
return Obx(() => UpPanel(
_dynamicsController.upData.value,
scrollController));
} else {
return const SizedBox();
}
} else {
return const SizedBox(
width: 56,
child: UpPanelSkeleton(),
);
}
},
),
));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print('upPanelPosition1: $upPanelPosition');
super.build(context); super.build(context);
return Scaffold( return Scaffold(
appBar: AppBar( backgroundColor: Colors.transparent,
elevation: 0, appBar: AppBar(
scrolledUnderElevation: 0, toolbarHeight: 50,
titleSpacing: 0, elevation: 0,
title: SizedBox( backgroundColor: Colors.transparent,
height: 34, title: SizedBox(
child: Stack( height: 50,
children: [ child: TabBar(
Row( controller: _dynamicsController.tabController,
mainAxisAlignment: MainAxisAlignment.center, isScrollable: true,
children: [ dividerColor: Colors.transparent,
Obx(() { dividerHeight: 0,
if (_dynamicsController.mid.value != -1 && tabAlignment: TabAlignment.center,
_dynamicsController.upInfo.value.uname != null) { indicatorColor: Theme.of(context).colorScheme.primary,
return SizedBox( labelColor: Theme.of(context).colorScheme.primary,
height: 36, unselectedLabelColor: Theme.of(context).colorScheme.onSurface,
child: AnimatedSwitcher( labelStyle: TextStyle(
duration: const Duration(milliseconds: 300), fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
transitionBuilder: ),
(Widget child, Animation<double> animation) { tabs: DynamicsType.values
return ScaleTransition( .map((e) => Tab(text: e.labels))
scale: animation, child: child); .toList(),
}, onTap: (index) {
child: Text( print('index: $index');
'${_dynamicsController.upInfo.value.uname!}的动态', feedBack();
key: ValueKey<String>( tabsConfig[_dynamicsController.tabController.index]['ctr'].animateToTop();
_dynamicsController.upInfo.value.uname!), // _dynamicsController.tabController
style: TextStyle( // _dynamicsController.tabController.index = index;
fontSize: Theme.of(context) // _dynamicsController.onSelectType(index);
.textTheme // _
.labelLarge!
.fontSize,
)),
),
);
} else {
return const SizedBox();
}
}),
Obx(
() => _dynamicsController.userLogin.value
? Visibility(
visible: _dynamicsController.mid.value == -1,
child: Theme(
data: ThemeData(
splashColor:
Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor:
Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
child: CustomSlidingSegmentedControl<int>(
initialValue:
_dynamicsController.initialValue.value,
children: {
0: Text(
'全部',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
),
1: Text('投稿',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize)),
2: Text('番剧',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize)),
3: Text('专栏',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize)),
},
padding: 13.0,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surfaceVariant
.withOpacity(0.7),
borderRadius: BorderRadius.circular(20),
),
thumbDecoration: BoxDecoration(
color:
Theme.of(context).colorScheme.background,
borderRadius: BorderRadius.circular(20),
),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
onValueChanged: (v) {
feedBack();
_dynamicsController.onSelectType(v);
},
),
),
)
: Text('动态',
style: Theme.of(context).textTheme.titleMedium),
)
],
),
],
),
),
),
body: RefreshIndicator(
onRefresh: () => _dynamicsController.onRefresh(),
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _dynamicsController.scrollController,
slivers: [
FutureBuilder(
future: _futureBuilderFutureUp,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
Map data = snapshot.data;
if (data['status']) {
return Obx(() => UpPanel(_dynamicsController.upData.value));
} else {
return const SliverToBoxAdapter(
child: SizedBox(height: 80),
);
}
} else {
return const SliverToBoxAdapter(
child: SizedBox(
height: 90,
child: UpPanelSkeleton(),
));
} }
}, )),
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
Map data = snapshot.data;
if (data['status']) {
List<DynamicItemModel> list =
_dynamicsController.dynamicsList;
return Obx(
() {
if (list.isEmpty) {
if (_dynamicsController.isLoadingDynamic.value) {
return skeleton();
} else {
return const NoData();
}
} else {
return SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.maxRowWidth * 2,
//cacheExtent: 0.0,
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.cardSpace,
/// follow max child trailing layout offset and layout with full cross axis extend
/// last child as loadmore item/no more item in [GridView] and [WaterfallFlow]
/// with full cross axis extend
// LastChildLayoutType.fullCrossAxisExtend,
/// as foot at trailing and layout with full cross axis extend
/// show no more item at trailing when children are not full of viewport
/// if children is full of viewport, it's the same as fullCrossAxisExtend
// LastChildLayoutType.foot,
lastChildLayoutTypeBuilder: (index) =>
index == list.length
? LastChildLayoutType.foot
: LastChildLayoutType.none,
children: [
for (var i in list) DynamicPanel(item: i),
]);
}
},
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () {
setState(() {
_futureBuilderFuture =
_dynamicsController.queryFollowDynamic();
_futureBuilderFutureUp =
_dynamicsController.queryFollowUp();
});
},
);
}
} else {
// 骨架屏
return skeleton();
}
},
),
const SliverToBoxAdapter(child: SizedBox(height: 40))
],
), ),
), drawer: upPanelPosition == UpPanelPosition.leftDrawer ?
); upPanelPart(): null,
} drawerEnableOpenDragGesture: true,
endDrawer: upPanelPosition == UpPanelPosition.rightDrawer ?
Widget skeleton() { upPanelPart(): null,
return SliverList( endDrawerEnableOpenDragGesture: true,
delegate: SliverChildBuilderDelegate((context, index) { body: Row(children: [
return const DynamicCardSkeleton(); if (upPanelPosition == UpPanelPosition.leftFixed)
}, childCount: 5), upPanelPart(),
); Expanded(
child: TabBarView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _dynamicsController.tabController,
children: _dynamicsController.tabsPageList,
)),
if (upPanelPosition == UpPanelPosition.rightFixed)
upPanelPart(),
]));
} }
} }

View File

@@ -5,10 +5,15 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/user.dart'; import 'package:PiliPalaX/http/user.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:share_plus/share_plus.dart';
import '../../../http/constants.dart';
import '../controller.dart';
class AuthorPanel extends StatelessWidget { class AuthorPanel extends StatelessWidget {
final dynamic item; final dynamic item;
const AuthorPanel({super.key, required this.item}); final Function? addBannedList;
const AuthorPanel({super.key, required this.item, this.addBannedList});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -77,28 +82,27 @@ class AuthorPanel extends StatelessWidget {
], ],
), ),
const Spacer(), const Spacer(),
if (item.type == 'DYNAMIC_TYPE_AV') SizedBox(
SizedBox( width: 32,
width: 32, height: 32,
height: 32, child: IconButton(
child: IconButton( tooltip: '更多',
tooltip: '更多', style: ButtonStyle(
style: ButtonStyle( padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: item);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
), ),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: item);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
), ),
),
], ],
); );
} }
@@ -132,24 +136,56 @@ class MorePanel extends StatelessWidget {
), ),
), ),
), ),
if (item.type == 'DYNAMIC_TYPE_AV')
ListTile(
onTap: () async {
try {
String bvid = item.modules.moduleDynamic.major.archive.bvid;
var res = await UserHttp.toViewLater(bvid: bvid);
SmartDialog.showToast(res['msg']);
Get.back();
} catch (err) {
SmartDialog.showToast('出错了:${err.toString()}');
}
},
minLeadingWidth: 0,
// dense: true,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title: Text(
'稍后再看',
style: Theme.of(context).textTheme.titleSmall,
),
),
ListTile( ListTile(
onTap: () async {
try {
String bvid = item.modules.moduleDynamic.major.archive.bvid;
var res = await UserHttp.toViewLater(bvid: bvid);
SmartDialog.showToast(res['msg']);
Get.back();
} catch (err) {
SmartDialog.showToast('出错了:${err.toString()}');
}
},
minLeadingWidth: 0,
// dense: true,
leading: const Icon(Icons.watch_later_outlined, size: 19),
title: Text( title: Text(
'稍后再看', '分享动态',
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
leading: const Icon(Icons.share_outlined, size: 19),
onTap: () async {
var result = await Share.share(
'${HttpString.baseUrl}/dynamic/${item.idStr} UP主: ${item.modules.moduleAuthor.name}')
.whenComplete(() {});
return result;
},
minLeadingWidth: 0,
),
ListTile(
title: Text(
'临时屏蔽:${item.modules.moduleAuthor.name}',
style: Theme.of(context).textTheme.titleSmall,
),
leading: const Icon(Icons.visibility_off_outlined, size: 19),
onTap: () {
Get.back();
DynamicsController dynamicsController =
Get.find<DynamicsController>();
dynamicsController.tempBannedList
.add(item.modules.moduleAuthor.mid);
SmartDialog.showToast(
'已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复');
},
minLeadingWidth: 0,
), ),
const Divider(thickness: 0.1, height: 1), const Divider(thickness: 0.1, height: 1),
ListTile( ListTile(

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/models/dynamics/result.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPalaX/pages/dynamics/index.dart'; import 'package:PiliPalaX/pages/dynamics/index.dart';
@@ -9,7 +10,7 @@ import 'forward_panel.dart';
class DynamicPanel extends StatelessWidget { class DynamicPanel extends StatelessWidget {
final dynamic item; final dynamic item;
final String? source; final String? source;
DynamicPanel({this.item, this.source, Key? key}) : super(key: key); DynamicPanel({required this.item, this.source, Key? key}) : super(key: key);
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());
@override @override
@@ -18,19 +19,20 @@ class DynamicPanel extends StatelessWidget {
padding: source == 'detail' padding: source == 'detail'
? const EdgeInsets.only(bottom: 12) ? const EdgeInsets.only(bottom: 12)
: EdgeInsets.zero, : EdgeInsets.zero,
decoration: BoxDecoration( // decoration: BoxDecoration(
border: Border( // border: Border(
bottom: BorderSide( // bottom: BorderSide(
width: 8, // width: 8,
color: Theme.of(context).dividerColor.withOpacity(0.05), // color: Theme.of(context).dividerColor.withOpacity(0.05),
), // ),
), // ),
), // ),
child: Material( child: Material(
elevation: 0, elevation: 0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
color: Theme.of(context).cardColor.withOpacity(0.5),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(0), borderRadius: BorderRadius.circular(5),
), ),
child: InkWell( child: InkWell(
onTap: () => _dynamicsController.pushDetail(item, 1), onTap: () => _dynamicsController.pushDetail(item, 1),
@@ -38,7 +40,7 @@ class DynamicPanel extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8), padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
child: AuthorPanel(item: item), child: AuthorPanel(item: item),
), ),
if (item!.modules!.moduleDynamic!.desc != null || if (item!.modules!.moduleDynamic!.desc != null ||

View File

@@ -11,26 +11,27 @@ import 'package:PiliPalaX/utils/utils.dart';
class UpPanel extends StatefulWidget { class UpPanel extends StatefulWidget {
final FollowUpModel? upData; final FollowUpModel? upData;
const UpPanel(this.upData, {Key? key}) : super(key: key); final ScrollController scrollController;
const UpPanel(this.upData, this.scrollController, {Key? key}) : super(key: key);
@override @override
State<UpPanel> createState() => _UpPanelState(); State<UpPanel> createState() => _UpPanelState();
} }
class _UpPanelState extends State<UpPanel> { class _UpPanelState extends State<UpPanel> {
final ScrollController scrollController = ScrollController();
int currentMid = -1; int currentMid = -1;
late double contentWidth = 56;
List<UpItem> upList = []; List<UpItem> upList = [];
List<LiveUserItem> liveList = []; List<LiveUserItem> liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
var userInfo; var userInfo;
bool _showLiveItems = false;
late DynamicsController dynamicsController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
dynamicsController = Get.find<DynamicsController>();
} }
@override @override
@@ -39,95 +40,81 @@ class _UpPanelState extends State<UpPanel> {
if (widget.upData!.liveUsers != null) { if (widget.upData!.liveUsers != null) {
liveList = widget.upData!.liveUsers!.items!; liveList = widget.upData!.liveUsers!.items!;
} }
return SliverPersistentHeader( // return const SizedBox();
floating: true, return CustomScrollView(
pinned: false, physics: const AlwaysScrollableScrollPhysics(),
delegate: _SliverHeaderDelegate( controller: widget.scrollController,
height: 126, slivers: [
child: Column( SliverToBoxAdapter(
mainAxisAlignment: MainAxisAlignment.start, child: SizedBox(
crossAxisAlignment: CrossAxisAlignment.start, height: 45,
children: [ child: TextButton(
Container( style: ButtonStyle(
color: Theme.of(context).colorScheme.background, padding: MaterialStateProperty.all(const EdgeInsets.only()),
padding: const EdgeInsets.only(left: 16, right: 16), ),
child: Row( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text('最新关注'), const Spacer(),
GestureDetector( const SizedBox(height: 12),
onTap: () { Text(
feedBack(); 'Live(${liveList.length})',
Get.toNamed('/follow?mid=${userInfo.mid}'); style: const TextStyle(
}, fontSize: 13,
child: Container(
padding: const EdgeInsets.only(top: 5, bottom: 5),
child: Text(
'查看全部',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
), ),
semanticsLabel:
'${_showLiveItems ? '展开' : '收起'}直播中的${liveList.length}个Up',
), ),
Icon(_showLiveItems ? Icons.expand_less : Icons.expand_more,
size: 12),
const Spacer(),
], ],
), ),
onPressed: () {
setState(() {
_showLiveItems = !_showLiveItems;
});
},
), ),
Container( ),
height: 90, ),
color: Theme.of(context).colorScheme.background, const SliverToBoxAdapter(
child: Row( child: SizedBox(
children: [ height: 10,
Expanded( ),
child: ListView( ),
physics: const BouncingScrollPhysics(), SliverGrid(
scrollDirection: Axis.horizontal, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
controller: scrollController, crossAxisCount: 1,
children: [ mainAxisExtent: 76,
const SizedBox(width: 10), crossAxisSpacing: 0,
if (liveList.isNotEmpty) ...[ mainAxisSpacing: 0,
for (int i = 0; i < liveList.length; i++) ...[ ),
upItemBuild(liveList[i], i) delegate: SliverChildListDelegate(
], [
VerticalDivider( if (_showLiveItems && liveList.isNotEmpty) ...[
indent: 20, for (int i = 0; i < liveList.length; i++) ...[
endIndent: 40, upItemBuild(liveList[i], i)
width: 26, ],
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.5),
),
],
upItemBuild(
UpItem(face: '', uname: '全部动态', mid: -1), 0),
upItemBuild(
UpItem(
face: userInfo.face,
uname: '',
mid: userInfo.mid,
),
1),
for (int i = 0; i < upList.length; i++) ...[
upItemBuild(upList[i], i + 2)
],
const SizedBox(width: 10),
],
),
),
], ],
), upItemBuild(UpItem(face: '', uname: '全部动态', mid: -1), 0),
), upItemBuild(
Container( UpItem(
height: 6, face: userInfo.face,
color: Theme.of(context) uname: '',
.colorScheme mid: userInfo.mid,
.onInverseSurface ),
.withOpacity(0.5), 1),
), for (int i = 0; i < upList.length; i++) ...[
], upItemBuild(upList[i], i + 2)
)), ],
); ],
)),
const SliverToBoxAdapter(
child: SizedBox(
height: 200,
),
),
]);
} }
Widget upItemBuild(data, i) { Widget upItemBuild(data, i) {
@@ -137,28 +124,27 @@ class _UpPanelState extends State<UpPanel> {
feedBack(); feedBack();
if (data.type == 'up') { if (data.type == 'up') {
currentMid = data.mid; currentMid = data.mid;
Get.find<DynamicsController>().mid.value = data.mid; // dynamicsController.mid.value = data.mid;
Get.find<DynamicsController>().upInfo.value = data; dynamicsController.upInfo.value = data;
Get.find<DynamicsController>().onSelectUp(data.mid); dynamicsController.onSelectUp(data.mid);
int liveLen = liveList.length; // int liveLen = liveList.length;
int upLen = upList.length; // int upLen = upList.length;
double itemWidth = contentWidth + itemPadding.horizontal; // double itemWidth = contentWidth + itemPadding.horizontal;
double screenWidth = MediaQuery.sizeOf(context).width; // double screenWidth = MediaQuery.sizeOf(context).width;
double moveDistance = 0.0; // double moveDistance = 0.0;
if (itemWidth * (upList.length + liveList.length) <= screenWidth) { // if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
} else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) { // } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
moveDistance = // moveDistance =
(i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2; // (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
} else { // } else {
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth; // moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
} // }
data.hasUpdate = false; data.hasUpdate = false;
scrollController.animateTo( // scrollController.animateTo(
moveDistance, // moveDistance,
duration: const Duration(milliseconds: 500), // duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut, // curve: Curves.easeInOut,
); // );
setState(() {}); setState(() {});
} else if (data.type == 'live') { } else if (data.type == 'live') {
LiveItemModel liveItem = LiveItemModel.fromJson({ LiveItemModel liveItem = LiveItemModel.fromJson({
@@ -182,63 +168,59 @@ class _UpPanelState extends State<UpPanel> {
Get.toNamed('/member?mid=${data.mid}', Get.toNamed('/member?mid=${data.mid}',
arguments: {'face': data.face, 'heroTag': heroTag}); arguments: {'face': data.face, 'heroTag': heroTag});
}, },
child: Padding( child: AnimatedOpacity(
padding: itemPadding, opacity: isCurrent ? 1 : 0.6,
child: AnimatedOpacity( duration: const Duration(milliseconds: 200),
opacity: isCurrent ? 1 : 0.3, curve: Curves.easeInOut,
duration: const Duration(milliseconds: 200), child: Column(
curve: Curves.easeInOut, mainAxisSize: MainAxisSize.min,
child: Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, children: [
mainAxisAlignment: MainAxisAlignment.center, Badge(
children: [ smallSize: 8,
Badge( label: data.type == 'live' ? const Text('Live') : null,
smallSize: 8, textColor: Theme.of(context).colorScheme.onSecondaryContainer,
label: data.type == 'live' ? const Text('Live') : null, alignment: data.type == 'live'
textColor: Theme.of(context).colorScheme.onSecondaryContainer, ? AlignmentDirectional.topCenter
alignment: data.type == 'live' : AlignmentDirectional.topEnd,
? AlignmentDirectional.topCenter padding: const EdgeInsets.only(left: 6, right: 6),
: AlignmentDirectional.topEnd, isLabelVisible: data.type == 'live' ||
padding: const EdgeInsets.only(left: 6, right: 6), (data.type == 'up' && (data.hasUpdate ?? false)),
isLabelVisible: data.type == 'live' || backgroundColor: data.type == 'live'
(data.type == 'up' && (data.hasUpdate ?? false)), ? Theme.of(context)
backgroundColor: data.type == 'live' .colorScheme
? Theme.of(context).colorScheme.secondaryContainer .secondaryContainer
: Theme.of(context).colorScheme.primary, .withOpacity(0.7)
child: data.face != '' : Theme.of(context).colorScheme.primary,
? NetworkImgLayer( child: data.face != ''
width: 50, ? NetworkImgLayer(
height: 50, width: 38,
src: data.face, height: 38,
type: 'avatar', src: data.face,
) type: 'avatar',
: const CircleAvatar( )
radius: 25, : const CircleAvatar(
backgroundImage: AssetImage( radius: 19,
'assets/images/noface.jpeg', backgroundImage: AssetImage(
), 'assets/images/logo/logo_android_2.png',
), ),
), ),
Padding( ),
padding: const EdgeInsets.only(top: 4), const SizedBox(height: 3),
child: SizedBox( Text(
width: contentWidth, data.uname,
child: Text( overflow: TextOverflow.clip,
data.uname, maxLines: 2,
overflow: TextOverflow.ellipsis, softWrap: true,
softWrap: false, textAlign: TextAlign.center,
textAlign: TextAlign.center, style: TextStyle(
style: TextStyle( color: currentMid == data.mid
color: currentMid == data.mid ? Theme.of(context).colorScheme.primary
? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.outline,
: Theme.of(context).colorScheme.outline, height: 1.1,
fontSize: fontSize: 12.5),
Theme.of(context).textTheme.labelMedium!.fontSize), ),
), ],
),
),
],
),
), ),
), ),
); );
@@ -273,35 +255,25 @@ class UpPanelSkeleton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.builder( return Column(
scrollDirection: Axis.horizontal, mainAxisSize: MainAxisSize.min,
physics: const NeverScrollableScrollPhysics(), mainAxisAlignment: MainAxisAlignment.center,
itemCount: 10, children: [
itemBuilder: ((context, index) { Container(
return Padding( width: 50,
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0), height: 50,
child: Column( decoration: BoxDecoration(
mainAxisSize: MainAxisSize.min, color: Theme.of(context).colorScheme.onInverseSurface,
mainAxisAlignment: MainAxisAlignment.center, borderRadius: BorderRadius.circular(50),
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(50),
),
),
Container(
margin: const EdgeInsets.only(top: 6),
width: 45,
height: 12,
color: Theme.of(context).colorScheme.onInverseSurface,
),
],
), ),
); ),
}), Container(
margin: const EdgeInsets.only(top: 6),
width: 45,
height: 12,
color: Theme.of(context).colorScheme.onInverseSurface,
),
],
); );
} }
} }

View File

@@ -105,8 +105,8 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
right: 0, right: 0,
bottom: 0, bottom: 0,
child: Container( child: Container(
height: 80, height: 70,
padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), padding: const EdgeInsets.fromLTRB(10, 0, 8, 8),
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: const LinearGradient( gradient: const LinearGradient(
@@ -139,17 +139,17 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
'时长${Utils.durationReadFormat(content.durationText)}', '时长${Utils.durationReadFormat(content.durationText)}',
), ),
if (content.durationText != null) if (content.durationText != null)
const SizedBox(width: 10), const SizedBox(width: 6),
Text(content.stat.play + '次围观'), Text(content.stat.play + '次围观'),
const SizedBox(width: 10), const SizedBox(width: 6),
Text(content.stat.danmu + '条弹幕') Text(content.stat.danmu + '条弹幕')
], ],
), ),
), ),
Image.asset( Image.asset(
'assets/images/play.png', 'assets/images/play.png',
width: 60, width: 50,
height: 60, height: 50,
), ),
], ],
), ),

View File

@@ -29,7 +29,7 @@ class FansController extends GetxController {
} }
Future queryFans(type) async { Future queryFans(type) async {
if (type == 'init') { if (type == 'init' || type == 'refresh') {
pn = 1; pn = 1;
loadingText.value == '加载中...'; loadingText.value == '加载中...';
} }
@@ -49,11 +49,14 @@ class FansController extends GetxController {
} else if (type == 'onLoad') { } else if (type == 'onLoad') {
fansList.addAll(res['data'].list); fansList.addAll(res['data'].list);
} }
print(total); print('fansList: ${fansList.length}, total: $total');
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) { if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
loadingText.value = '没有更多了'; loadingText.value = '没有更多了';
} }
pn += 1; pn += 1;
if (total > ps && pn == 2) {
queryFans('onLoad');
}
} else { } else {
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
} }

View File

@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/common/widgets/no_data.dart';
import 'package:PiliPalaX/models/fans/result.dart'; import 'package:PiliPalaX/models/fans/result.dart';
import '../../common/constants.dart';
import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/fan_item.dart'; import 'widgets/fan_item.dart';
@@ -60,60 +62,56 @@ class _FansPageState extends State<FansPage> {
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async => await _fansController.queryFans('init'), onRefresh: () async => await _fansController.queryFans('init'),
child: FutureBuilder( child: CustomScrollView(
future: _futureBuilderFuture, physics: const AlwaysScrollableScrollPhysics(),
builder: (context, snapshot) { controller: scrollController,
if (snapshot.connectionState == ConnectionState.done) { slivers: [
var data = snapshot.data; FutureBuilder(
if (data['status']) { future: _futureBuilderFuture,
List<FansItemModel> list = _fansController.fansList; builder: (context, snapshot) {
return Obx( if (snapshot.connectionState == ConnectionState.done &&
() => list.isNotEmpty snapshot.data != null) {
? ListView.builder( var data = snapshot.data;
controller: scrollController, if (data['status']) {
itemCount: list.length + 1, return Obx(() {
itemBuilder: (BuildContext context, int index) { List<FansItemModel> list = _fansController.fansList;
if (index == list.length) { return list.isNotEmpty
return Container( ? SliverGrid(
height: gridDelegate:
MediaQuery.of(context).padding.bottom + 60, SliverGridDelegateWithMaxCrossAxisExtent(
padding: EdgeInsets.only( mainAxisSpacing: StyleString.cardSpace,
bottom: crossAxisSpacing: StyleString.safeSpace,
MediaQuery.of(context).padding.bottom), maxCrossAxisExtent:
child: Center( Grid.maxRowWidth * 2,
child: Obx( mainAxisExtent: 56),
() => Text( delegate: SliverChildBuilderDelegate(
_fansController.loadingText.value, (BuildContext context, int index) {
style: TextStyle( return fanItem(item: list[index]);
color: Theme.of(context) },
.colorScheme childCount: list.length,
.outline, ))
fontSize: 13), : const NoData();
), });
), } else {
), return HttpError(
); errMsg: data['msg'],
} else { fn: () => _fansController.queryFans('init'),
return fanItem(item: list[index]); );
} }
}, } else {
) // 骨架屏
: const CustomScrollView( return const SliverToBoxAdapter(
slivers: [NoData()], child: SizedBox(
height: 200,
child: Center(
child: CircularProgressIndicator(),
), ),
); ),
} else { );
return HttpError( }
errMsg: data['msg'], },
fn: () => _fansController.queryFans('init'), ),
); ]),
}
} else {
// 骨架屏
return const SizedBox();
}
},
),
), ),
); );
} }

View File

@@ -1,3 +1,5 @@
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
import 'package:PiliPalaX/common/skeleton/video_card_v.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -68,17 +70,12 @@ class _FavPageState extends State<FavPage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverGrid( SliverGrid(
gridDelegate: gridDelegate: SliverGridDelegateWithExtentAndRatio(
SliverGridDelegateWithMaxCrossAxisExtent( mainAxisSpacing: StyleString.cardSpace,
mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2,
maxCrossAxisExtent: Grid.maxRowWidth * 2, childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: Grid.calculateActualWidth( mainAxisExtent: 0),
context,
Grid.maxRowWidth * 2,
StyleString.safeSpace) /
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: childCount:
_favController.favFolderData.value.list!.length, _favController.favFolderData.value.list!.length,
@@ -103,7 +100,25 @@ class _FavPageState extends State<FavPage> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return const Text('请求中'); return CustomScrollView(
physics: const NeverScrollableScrollPhysics(),
slivers: [
SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return const VideoCardHSkeleton();
},
childCount: 10,
),
),
],
);
} }
}, },
), ),

View File

@@ -215,16 +215,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
? const SliverToBoxAdapter(child: SizedBox()) ? const SliverToBoxAdapter(child: SizedBox())
: SliverGrid( : SliverGrid(
gridDelegate: gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent( SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio:
context, StyleString.aspectRatio * 2.3,
Grid.maxRowWidth * 2, mainAxisExtent: 0),
StyleString.safeSpace) /
2.1 /
StyleString.aspectRatio),
delegate: delegate:
SliverChildBuilderDelegate((context, index) { SliverChildBuilderDelegate((context, index) {
return FavVideoCardH( return FavVideoCardH(
@@ -244,7 +241,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SliverList( return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, childCount: 10), }, childCount: 10),

View File

@@ -51,18 +51,17 @@ class HistoryController extends GetxController {
} }
// 暂停观看历史 // 暂停观看历史
Future onPauseHistory() async { Future onPauseHistory(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: content:
Text(!pauseStatus.value ? '啊叻?你要暂停历史记录功能吗?' : '啊叻?要恢复历史记录功能吗?'), Text(!pauseStatus.value ? '啊叻?你要暂停历史记录功能吗?' : '啊叻?要恢复历史记录功能吗?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: const Text('取消')), child: const Text('取消')),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@@ -75,7 +74,7 @@ class HistoryController extends GetxController {
pauseStatus.value = !pauseStatus.value; pauseStatus.value = !pauseStatus.value;
localCache.put(LocalCacheKey.historyPause, pauseStatus.value); localCache.put(LocalCacheKey.historyPause, pauseStatus.value);
} }
SmartDialog.dismiss(); Get.back();
}, },
child: Text(!pauseStatus.value ? '确认暂停' : '确认恢复'), child: Text(!pauseStatus.value ? '确认暂停' : '确认恢复'),
) )
@@ -97,17 +96,16 @@ class HistoryController extends GetxController {
} }
// 清空观看历史 // 清空观看历史
Future onClearHistory() async { Future onClearHistory(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('啊叻?你要清空历史记录功能吗?'), content: const Text('啊叻?你要清空历史记录功能吗?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: const Text('取消')), child: const Text('取消')),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@@ -117,7 +115,7 @@ class HistoryController extends GetxController {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
SmartDialog.showToast('清空观看历史'); SmartDialog.showToast('清空观看历史');
} }
SmartDialog.dismiss(); Get.back();
historyList.clear(); historyList.clear();
}, },
child: const Text('确认清空'), child: const Text('确认清空'),
@@ -158,17 +156,16 @@ class HistoryController extends GetxController {
} }
// 删除选中的记录 // 删除选中的记录
Future onDelCheckedHistory() async { Future onDelCheckedHistory(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('确认删除所选历史记录吗?'), content: const Text('确认删除所选历史记录吗?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'取消', '取消',
style: TextStyle( style: TextStyle(
@@ -179,7 +176,7 @@ class HistoryController extends GetxController {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
/// TODO 优化 /// TODO 优化
await SmartDialog.dismiss(); Get.back();
SmartDialog.showLoading(msg: '请求中'); SmartDialog.showLoading(msg: '请求中');
List<HisListItem> result = List<HisListItem> result =
historyList.where((e) => e.checked!).toList(); historyList.where((e) => e.checked!).toList();

View File

@@ -87,10 +87,10 @@ class _HistoryPageState extends State<HistoryPage> {
// 处理菜单项选择的逻辑 // 处理菜单项选择的逻辑
switch (type) { switch (type) {
case 'pause': case 'pause':
_historyController.onPauseHistory(); _historyController.onPauseHistory(context);
break; break;
case 'clear': case 'clear':
_historyController.onClearHistory(); _historyController.onClearHistory(context);
break; break;
case 'del': case 'del':
_historyController.onDelHistory(); _historyController.onDelHistory();
@@ -162,7 +162,7 @@ class _HistoryPageState extends State<HistoryPage> {
child: const Text('全选'), child: const Text('全选'),
), ),
TextButton( TextButton(
onPressed: () => _historyController.onDelCheckedHistory(), onPressed: () => _historyController.onDelCheckedHistory(context),
child: Text( child: Text(
'删除', '删除',
style: TextStyle(color: Theme.of(context).colorScheme.error), style: TextStyle(color: Theme.of(context).colorScheme.error),
@@ -194,16 +194,13 @@ class _HistoryPageState extends State<HistoryPage> {
() => _historyController.historyList.isNotEmpty () => _historyController.historyList.isNotEmpty
? SliverGrid( ? SliverGrid(
gridDelegate: gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent( SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio:
context, StyleString.aspectRatio * 2.3,
Grid.maxRowWidth * 2, mainAxisExtent: 0),
StyleString.safeSpace) /
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return HistoryItem( return HistoryItem(
@@ -231,7 +228,13 @@ class _HistoryPageState extends State<HistoryPage> {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SliverList( return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, childCount: 10), }, childCount: 10),

View File

@@ -86,14 +86,12 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
Obx(() => SliverGrid( Obx(() => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth(context, childAspectRatio: StyleString.aspectRatio * 2.3,
Grid.maxRowWidth * 2, StyleString.safeSpace) / mainAxisExtent: 0),
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
return HistoryItem( return HistoryItem(

View File

@@ -67,8 +67,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
void setTabConfig() async { void setTabConfig() async {
defaultTabs = [...tabsConfig]; defaultTabs = [...tabsConfig];
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, tabbarSort = settingStorage
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']).map<String>((i) => i.toString()).toList(); .get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi'])
.map<String>((i) => i.toString())
.toList();
defaultTabs.retainWhere( defaultTabs.retainWhere(
(item) => tabbarSort.contains((item['type'] as TabType).id)); (item) => tabbarSort.contains((item['type'] as TabType).id));
defaultTabs.sort((a, b) => tabbarSort defaultTabs.sort((a, b) => tabbarSort

View File

@@ -46,104 +46,78 @@ class _HomePageState extends State<HomePage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
Brightness currentBrightness = MediaQuery.of(context).platformBrightness; // Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
// 设置状态栏图标的亮度 // // 设置状态栏图标的亮度
if (_homeController.enableGradientBg) { // if (_homeController.enableGradientBg) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( // SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarIconBrightness: currentBrightness == Brightness.light // statusBarIconBrightness: currentBrightness == Brightness.light
? Brightness.dark // ? Brightness.dark
: Brightness.light, // : Brightness.light,
)); // ));
} // }
return Scaffold( return Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: _homeController.enableGradientBg backgroundColor: Colors.transparent,
? null appBar: AppBar(
: AppBar(toolbarHeight: 0, elevation: 0), toolbarHeight: 0,
body: Stack( elevation: 0,
backgroundColor: Colors.transparent,
systemOverlayStyle: SystemUiOverlayStyle(
statusBarIconBrightness:
MediaQuery.of(context).platformBrightness == Brightness.dark
? Brightness.light
: Brightness.dark,
),
),
body: Column(
children: [ children: [
// gradient background CustomAppBar(
if (_homeController.enableGradientBg) ...[ stream: _homeController.hideSearchBar
Align( ? stream
alignment: Alignment.topLeft, : StreamController<bool>.broadcast().stream,
child: Opacity( ctr: _homeController,
opacity: 0.6, callback: showUserBottomSheet,
child: Container( ),
width: MediaQuery.of(context).size.width, if (_homeController.tabs.length > 1) ...[
height: MediaQuery.of(context).size.height, if (_homeController.enableGradientBg) ...[
decoration: BoxDecoration( const CustomTabs(),
gradient: LinearGradient( ] else ...[
colors: [ const SizedBox(height: 4),
Theme.of(context) SizedBox(
.colorScheme width: double.infinity,
.primary height: 42,
.withOpacity(0.9), child: Align(
Theme.of(context) alignment: Alignment.center,
.colorScheme child: TabBar(
.primary controller: _homeController.tabController,
.withOpacity(0.5), tabs: [
Theme.of(context).colorScheme.surface for (var i in _homeController.tabs) Tab(text: i['label'])
], ],
begin: Alignment.topLeft, isScrollable: true,
end: Alignment.bottomRight, dividerColor: Colors.transparent,
stops: const [0, 0.0034, 0.34]), enableFeedback: true,
splashBorderRadius: BorderRadius.circular(10),
tabAlignment: TabAlignment.center,
onTap: (value) {
feedBack();
if (_homeController.initialIndex.value == value) {
_homeController.tabsCtrList[value]().animateToTop();
}
_homeController.initialIndex.value = value;
},
), ),
), ),
), ),
),
],
Column(
children: [
CustomAppBar(
stream: _homeController.hideSearchBar
? stream
: StreamController<bool>.broadcast().stream,
ctr: _homeController,
callback: showUserBottomSheet,
),
if (_homeController.tabs.length > 1) ...[
if (_homeController.enableGradientBg) ...[
const CustomTabs(),
] else ...[
const SizedBox(height: 4),
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),
tabAlignment: TabAlignment.center,
onTap: (value) {
feedBack();
if (_homeController.initialIndex.value == value) {
_homeController.tabsCtrList[value]().animateToTop();
}
_homeController.initialIndex.value = value;
},
),
),
),
],
] else ...[
const SizedBox(height: 6),
],
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: _homeController.tabsPageList,
),
),
], ],
] else ...[
const SizedBox(height: 6),
],
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: _homeController.tabsPageList,
),
), ),
], ],
), ),

View File

@@ -31,6 +31,7 @@ class HotController extends GetxController {
videoList.addAll(res['data']); videoList.addAll(res['data']);
} }
_currentPage += 1; _currentPage += 1;
if (_currentPage == 2) queryHotFeed('onLoad');
} }
isLoadingMore = false; isLoadingMore = false;
return res; return res;

View File

@@ -82,8 +82,8 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
slivers: [ slivers: [
SliverPadding( SliverPadding(
// 单列布局 EdgeInsets.zero // 单列布局 EdgeInsets.zero
padding: padding: const EdgeInsets.fromLTRB(
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), StyleString.safeSpace, StyleString.safeSpace - 5, 0, 0),
sliver: FutureBuilder( sliver: FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
@@ -92,19 +92,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() => SliverGrid( () => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距 mainAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio: StyleString.aspectRatio * 2.3,
context, mainAxisExtent: 0),
Grid.maxRowWidth * 2,
StyleString.safeSpace) /
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return VideoCardH( return VideoCardH(
videoItem: _hotController.videoList[index], videoItem: _hotController.videoList[index],
@@ -135,7 +128,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
} }
} else { } else {
// 骨架屏 // 骨架屏
return SliverList( return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.cardSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, childCount: 10), }, childCount: 10),

View File

@@ -23,18 +23,17 @@ class LaterController extends GetxController {
return res; return res;
} }
Future toViewDel({int? aid}) async { Future toViewDel(BuildContext context, {int? aid}) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text( content: Text(
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'), aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'取消', '取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -51,7 +50,7 @@ class LaterController extends GetxController {
queryLaterList(); queryLaterList();
} }
} }
SmartDialog.dismiss(); Get.back();
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
}, },
child: Text(aid != null ? '确认移除' : '确认删除'), child: Text(aid != null ? '确认移除' : '确认删除'),
@@ -63,17 +62,16 @@ class LaterController extends GetxController {
} }
// 一键清空 // 一键清空
Future toViewClear() async { Future toViewClear(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('清空确认'), title: const Text('清空确认'),
content: const Text('确定要清空你的稍后再看列表吗?'), content: const Text('确定要清空你的稍后再看列表吗?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'取消', '取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -85,7 +83,7 @@ class LaterController extends GetxController {
if (res['status']) { if (res['status']) {
laterList.clear(); laterList.clear();
} }
SmartDialog.dismiss(); Get.back();
SmartDialog.showToast(res['msg']); SmartDialog.showToast(res['msg']);
}, },
child: const Text('确认'), child: const Text('确认'),

View File

@@ -47,7 +47,7 @@ class _LaterPageState extends State<LaterPage> {
Obx( Obx(
() => _laterController.laterList.isNotEmpty () => _laterController.laterList.isNotEmpty
? TextButton( ? TextButton(
onPressed: () => _laterController.toViewDel(), onPressed: () => _laterController.toViewDel(context),
child: const Text('移除已看'), child: const Text('移除已看'),
) )
: const SizedBox(), : const SizedBox(),
@@ -56,7 +56,7 @@ class _LaterPageState extends State<LaterPage> {
() => _laterController.laterList.isNotEmpty () => _laterController.laterList.isNotEmpty
? IconButton( ? IconButton(
tooltip: '一键清空', tooltip: '一键清空',
onPressed: () => _laterController.toViewClear(), onPressed: () => _laterController.toViewClear(context),
icon: Icon( icon: Icon(
Icons.clear_all_outlined, Icons.clear_all_outlined,
size: 21, size: 21,
@@ -72,61 +72,73 @@ class _LaterPageState extends State<LaterPage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _laterController.scrollController, controller: _laterController.scrollController,
slivers: [ slivers: [
FutureBuilder( SliverPadding(
future: _futureBuilderFuture, padding:
builder: (context, snapshot) { const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
if (snapshot.connectionState == ConnectionState.done) { sliver: FutureBuilder(
Map data = snapshot.data as Map; future: _futureBuilderFuture,
if (data['status']) { builder: (context, snapshot) {
return Obx( if (snapshot.connectionState == ConnectionState.done) {
() => _laterController.laterList.isNotEmpty && Map data = snapshot.data as Map;
!_laterController.isLoading.value if (data['status']) {
? SliverGrid( return Obx(
gridDelegate: () => _laterController.laterList.isNotEmpty &&
SliverGridDelegateWithMaxCrossAxisExtent( !_laterController.isLoading.value
mainAxisSpacing: StyleString.cardSpace, ? SliverGrid(
crossAxisSpacing: StyleString.safeSpace, gridDelegate:
maxCrossAxisExtent: Grid.maxRowWidth * 2, SliverGridDelegateWithExtentAndRatio(
mainAxisExtent: Grid.calculateActualWidth( mainAxisSpacing: StyleString.safeSpace,
context, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent:
Grid.maxRowWidth * 2, Grid.maxRowWidth * 2,
StyleString.safeSpace) / childAspectRatio:
2.1 / StyleString.aspectRatio * 2.3,
StyleString.aspectRatio), mainAxisExtent: 0),
delegate: delegate: SliverChildBuilderDelegate(
SliverChildBuilderDelegate((context, index) { (context, index) {
var videoItem = _laterController.laterList[index]; var videoItem =
return VideoCardH( _laterController.laterList[index];
videoItem: videoItem, return VideoCardH(
source: 'later', videoItem: videoItem,
longPress: () => _laterController.toViewDel( source: 'later',
aid: videoItem.aid)); longPress: () =>
}, childCount: _laterController.laterList.length), _laterController.toViewDel(context,
) aid: videoItem.aid));
: _laterController.isLoading.value },
? const SliverToBoxAdapter( childCount:
child: Center(child: Text('加载中')), _laterController.laterList.length),
) )
: const NoData(), : _laterController.isLoading.value
); ? const SliverToBoxAdapter(
} else { child: Center(child: Text('加载中')),
return HttpError( )
errMsg: data['msg'], : const NoData(),
fn: () => setState(() { );
_futureBuilderFuture = _laterController.queryLaterList(); } else {
}), return HttpError(
); errMsg: data['msg'],
} fn: () => setState(() {
} else { _futureBuilderFuture =
// 骨架屏 _laterController.queryLaterList();
return SliverList( }),
delegate: SliverChildBuilderDelegate((context, index) { );
return const VideoCardHSkeleton(); }
}, childCount: 10), } else {
); // 骨架屏
} return SliverGrid(
}, gridDelegate: SliverGridDelegateWithExtentAndRatio(
), mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 10),
);
}
},
)),
SliverToBoxAdapter( SliverToBoxAdapter(
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).padding.bottom + 10, height: MediaQuery.of(context).padding.bottom + 10,

View File

@@ -141,15 +141,12 @@ class _LivePageState extends State<LivePage>
Widget contentGrid(ctr, liveList) { Widget contentGrid(ctr, liveList) {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.maxRowWidth, maxCrossAxisExtent: Grid.maxRowWidth,
mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth, StyleString.safeSpace) / StyleString.aspectRatio+ childAspectRatio: StyleString.aspectRatio,
MediaQuery.textScalerOf(context).scale(80), mainAxisExtent: MediaQuery.textScalerOf(context).scale(80),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -111,6 +111,7 @@ class LoginPageController extends GetxController {
Future getCaptcha(oncall) async { Future getCaptcha(oncall) async {
SmartDialog.showLoading(msg: '请求中...'); SmartDialog.showLoading(msg: '请求中...');
var result = await LoginHttp.queryCaptcha(); var result = await LoginHttp.queryCaptcha();
SmartDialog.dismiss();
if (result['status']) { if (result['status']) {
CaptchaDataModel captchaData = result['data']; CaptchaDataModel captchaData = result['data'];
var registerData = Gt3RegisterData( var registerData = Gt3RegisterData(
@@ -119,7 +120,6 @@ class LoginPageController extends GetxController {
success: true, success: true,
); );
captcha.addEventHandler(onShow: (Map<String, dynamic> message) async { captcha.addEventHandler(onShow: (Map<String, dynamic> message) async {
SmartDialog.dismiss();
}, onClose: (Map<String, dynamic> message) async { }, onClose: (Map<String, dynamic> message) async {
SmartDialog.showToast('关闭验证'); SmartDialog.showToast('关闭验证');
}, onResult: (Map<String, dynamic> message) async { }, onResult: (Map<String, dynamic> message) async {

View File

@@ -12,13 +12,14 @@ import 'package:PiliPalaX/pages/media/index.dart';
import 'package:PiliPalaX/pages/rank/index.dart'; import 'package:PiliPalaX/pages/rank/index.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:path/path.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart'; import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController { class MainController extends GetxController {
List<Widget> pages = <Widget>[ List<Widget> pages = <Widget>[
const HomePage(), const HomePage(),
const RankPage(), // const RankPage(),
const DynamicsPage(), const DynamicsPage(),
const MediaPage(), const MediaPage(),
]; ];

View File

@@ -1,14 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:PiliPalaX/common/constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart'; import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart';
import 'package:PiliPalaX/pages/dynamics/index.dart'; import 'package:PiliPalaX/pages/dynamics/index.dart';
import 'package:PiliPalaX/pages/home/index.dart'; import 'package:PiliPalaX/pages/home/index.dart';
import 'package:PiliPalaX/pages/media/index.dart'; import 'package:PiliPalaX/pages/media/index.dart';
import 'package:PiliPalaX/pages/rank/index.dart';
import 'package:PiliPalaX/utils/event_bus.dart'; import 'package:PiliPalaX/utils/event_bus.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
@@ -24,7 +23,6 @@ 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 HomeController _homeController = Get.put(HomeController());
final RankController _rankController = Get.put(RankController());
final DynamicsController _dynamicController = Get.put(DynamicsController()); final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController()); final MediaController _mediaController = Get.put(MediaController());
@@ -32,6 +30,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
late bool enableMYBar; late bool enableMYBar;
late bool adaptiveNavBar; late bool adaptiveNavBar;
late bool enableGradientBg;
@override @override
void initState() { void initState() {
@@ -42,6 +41,8 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
adaptiveNavBar = adaptiveNavBar =
setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false); setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false);
enableGradientBg = setting.get(SettingBoxKey.enableGradientBg,
defaultValue: true);
} }
void setIndex(int value) async { void setIndex(int value) async {
@@ -63,20 +64,20 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_homeController.flag = false; _homeController.flag = false;
} }
if (currentPage is RankPage) { // if (currentPage is RankPage) {
if (_rankController.flag) { // if (_rankController.flag) {
// 单击返回顶部 双击并刷新 // // 单击返回顶部 双击并刷新
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { // if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_rankController.onRefresh(); // _rankController.onRefresh();
} else { // } else {
_rankController.animateToTop(); // _rankController.animateToTop();
} // }
_lastSelectTime = DateTime.now().millisecondsSinceEpoch; // _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
} // }
_rankController.flag = true; // _rankController.flag = true;
} else { // } else {
_rankController.flag = false; // _rankController.flag = false;
} // }
if (currentPage is DynamicsPage) { if (currentPage is DynamicsPage) {
if (_dynamicController.flag) { if (_dynamicController.flag) {
@@ -113,55 +114,100 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
onPopInvoked: (bool didPop) async { onPopInvoked: (bool didPop) async {
_mainController.onBackPressed(context); _mainController.onBackPressed(context);
}, },
child: adaptiveNavBar child: Scaffold(
? AdaptiveScaffold( extendBody: true,
body: (_) => PageView( body: Stack(children: [
physics: const NeverScrollableScrollPhysics(), // gradient background
controller: _mainController.pageController, if (enableGradientBg) ...[
onPageChanged: (index) { Align(
_mainController.selectedIndex = index; alignment: Alignment.topLeft,
setState(() {}); child: Opacity(
}, opacity: 0.6,
children: _mainController.pages, child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.9),
Theme.of(context)
.colorScheme
.primary
.withOpacity(0.5),
Theme.of(context).colorScheme.surface
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: const [0, 0.0034, 0.34]),
), ),
destinations: _mainController.navigationBars ),
.map((e) => NavigationDestination(
icon: Badge(
label: _mainController.dynamicBadgeType ==
DynamicBadgeMode.number
? Text(e['count'].toString())
: null,
padding: const EdgeInsets.fromLTRB(2, 0, 2, 0),
isLabelVisible: _mainController.dynamicBadgeType !=
DynamicBadgeMode.hidden &&
e['count'] > 0,
child: e['icon'],
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor:
Theme.of(context).colorScheme.onInverseSurface,
),
selectedIcon: e['selectIcon'],
label: e['label'],
))
.toList(),
onSelectedIndexChange: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
extendedNavigationRailWidth: 180,
transitionDuration: const Duration(milliseconds: 500),
useDrawer: true)
: Scaffold(
extendBody: true,
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.pageController,
onPageChanged: (index) {
_mainController.selectedIndex = index;
setState(() {});
},
children: _mainController.pages,
), ),
bottomNavigationBar: StreamBuilder( ),
],
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (adaptiveNavBar) ...[
SizedBox(
width: 55,
child: NavigationRail(
groupAlignment: 0.0,
minWidth: 40.0,
backgroundColor: Theme.of(context)
.colorScheme
.surface
.withOpacity(0.2),
selectedIndex: _mainController.selectedIndex,
onDestinationSelected: (value) => setIndex(value),
labelType: NavigationRailLabelType.none,
destinations: _mainController.navigationBars
.map((e) => NavigationRailDestination(
icon: Badge(
label: _mainController.dynamicBadgeType ==
DynamicBadgeMode.number
? Text(e['count'].toString())
: null,
padding:
const EdgeInsets.fromLTRB(2, 0, 2, 0),
isLabelVisible:
_mainController.dynamicBadgeType !=
DynamicBadgeMode.hidden &&
e['count'] > 0,
child: e['icon'],
backgroundColor:
Theme.of(context).colorScheme.primary,
textColor: Theme.of(context)
.colorScheme
.onInverseSurface,
),
selectedIcon: e['selectIcon'],
label: Text(e['label']),
padding:
const EdgeInsets.symmetric(vertical: 6),
))
.toList(),
)),
],
Expanded(
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.pageController,
onPageChanged: (index) {
_mainController.selectedIndex = index;
setState(() {});
},
children: _mainController.pages,
),
),
],
)
]),
bottomNavigationBar: adaptiveNavBar
? null
: StreamBuilder(
stream: _mainController.hideTabBar stream: _mainController.hideTabBar
? _mainController.bottomBarStream.stream ? _mainController.bottomBarStream.stream
: StreamController<bool>.broadcast().stream, : StreamController<bool>.broadcast().stream,
@@ -243,7 +289,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
); );
}, },
), ),
), ),
); );
} }
} }

View File

@@ -62,34 +62,35 @@ 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), backgroundColor: Colors.transparent,
appBar: AppBar(toolbarHeight: 30, backgroundColor: Colors.transparent),
body: SingleChildScrollView( body: SingleChildScrollView(
controller: mediaController.scrollController, controller: mediaController.scrollController,
child: Column( child: Column(
children: [ children: [
ListTile( ListTile(
leading: null, leading: null,
title: Padding( title: Padding(
padding: const EdgeInsets.only(left: 20), padding: const EdgeInsets.only(left: 20),
child: Text( child: Text(
'媒体库', '媒体库',
style: TextStyle( style: TextStyle(
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize, fontSize:
fontWeight: FontWeight.bold, Theme.of(context).textTheme.titleLarge!.fontSize,
fontWeight: FontWeight.bold,
),
), ),
), ),
), trailing: IconButton(
trailing: IconButton( tooltip: '设置',
tooltip: '设置', onPressed: () {
onPressed: () { Get.toNamed('/setting');
Get.toNamed('/setting'); },
}, icon: const Icon(
icon: const Icon( Icons.settings_outlined,
Icons.settings_outlined, size: 20,
size: 20, ),
), )),
)
),
for (var i in mediaController.list) ...[ for (var i in mediaController.list) ...[
ListTile( ListTile(
onTap: () => i['onTap'](), onTap: () => i['onTap'](),
@@ -145,14 +146,19 @@ class _MediaPageState extends State<MediaPage>
), ),
if (mediaController.favFolderData.value.count != null) if (mediaController.favFolderData.value.count != null)
TextSpan( TextSpan(
text: mediaController.favFolderData.value.count text: "${mediaController.favFolderData.value.count} ",
.toString(),
style: TextStyle( style: TextStyle(
fontSize: fontSize:
Theme.of(context).textTheme.titleSmall!.fontSize, Theme.of(context).textTheme.titleSmall!.fontSize,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),
WidgetSpan(
child: Icon(
Icons.arrow_forward_ios,
size: 18,
color: Theme.of(context).colorScheme.primary,
)),
], ],
), ),
), ),
@@ -275,10 +281,16 @@ class FavFolderItem extends StatelessWidget {
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.onInverseSurface, color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.4),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Theme.of(context).colorScheme.onInverseSurface, color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.4),
offset: const Offset(4, -12), // 阴影与容器的距离 offset: const Offset(4, -12), // 阴影与容器的距离
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。 blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。 spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。

View File

@@ -68,7 +68,7 @@ class MemberController extends GetxController {
} }
// 关注/取关up // 关注/取关up
Future actionRelationMod() async { Future actionRelationMod(BuildContext context) async {
if (userInfo == null) { if (userInfo == null) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
@@ -78,19 +78,18 @@ class MemberController extends GetxController {
return; return;
} }
if (attribute.value == 128) { if (attribute.value == 128) {
blockUser(); blockUser(context);
return; return;
} }
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (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! ? '取消关注UP主?' : '关注UP主?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -98,6 +97,7 @@ class MemberController extends GetxController {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Get.back();
await VideoHttp.relationMod( await VideoHttp.relationMod(
mid: mid, mid: mid,
act: memberInfo.value.isFollowed! ? 2 : 1, act: memberInfo.value.isFollowed! ? 2 : 1,
@@ -105,7 +105,6 @@ class MemberController extends GetxController {
); );
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
relationSearch(); relationSearch();
SmartDialog.dismiss();
memberInfo.update((val) {}); memberInfo.update((val) {});
}, },
child: const Text('确认'), child: const Text('确认'),
@@ -146,21 +145,20 @@ class MemberController extends GetxController {
} }
// 拉黑用户 // 拉黑用户
Future blockUser() async { Future blockUser(BuildContext context) async {
if (userInfo == null) { if (userInfo == null) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'), content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -168,12 +166,12 @@ class MemberController extends GetxController {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Get.back();
var res = await VideoHttp.relationMod( var res = await VideoHttp.relationMod(
mid: mid, mid: mid,
act: attribute.value != 128 ? 5 : 6, act: attribute.value != 128 ? 5 : 6,
reSrc: 11, reSrc: 11,
); );
SmartDialog.dismiss();
if (res['status']) { if (res['status']) {
attribute.value = attribute.value != 128 ? 128 : 0; attribute.value = attribute.value != 128 ? 128 : 0;
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';

View File

@@ -63,6 +63,7 @@ class _MemberPageState extends State<MemberPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isHorizontal = context.width > context.height;
return Scaffold( return Scaffold(
primary: true, primary: true,
body: Column( body: Column(
@@ -118,7 +119,7 @@ class _MemberPageState extends State<MemberPage>
itemBuilder: (BuildContext context) => <PopupMenuEntry>[ itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_memberController.ownerMid != _memberController.mid) ...[ if (_memberController.ownerMid != _memberController.mid) ...[
PopupMenuItem( PopupMenuItem(
onTap: () => _memberController.blockUser(), onTap: () => _memberController.blockUser(context),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -158,29 +159,42 @@ class _MemberPageState extends State<MemberPage>
), ),
child: Column( child: Column(
children: [ children: [
profileWidget(), profileWidget(isHorizontal),
Row(children: [
/// 动态链接 const Spacer(),
ListTile( InkWell(
onTap: _memberController.pushDynamicsPage, onTap: _memberController.pushDynamicsPage,
title: const Text('Ta的动态'), child: const Row(
trailing: children: [
const Icon(Icons.arrow_forward_outlined, size: 19), Text('Ta的动态', style: TextStyle(height: 2)),
), SizedBox(width: 5),
Icon(Icons.arrow_forward_ios, size: 19),
/// 视频 ],
ListTile( ),
onTap: _memberController.pushArchivesPage, ),
title: const Text('Ta的投稿'), const Spacer(),
trailing: InkWell(
const Icon(Icons.arrow_forward_outlined, size: 19), onTap: _memberController.pushArchivesPage,
), child: const Row(
children: [
/// 专栏 Text('Ta的投稿', style: TextStyle(height: 2)),
ListTile( SizedBox(width: 5),
onTap: () {}, Icon(Icons.arrow_forward_ios, size: 19),
title: const Text('Ta的专栏'), ],
), ),
),
const Spacer(),
InkWell(
onTap: () {},
child: const Row(
children: [
Text('Ta的专栏', style: TextStyle(height: 2)),
SizedBox(width: 5),
],
),
),
const Spacer(),
]),
MediaQuery.removePadding( MediaQuery.removePadding(
removeTop: true, removeTop: true,
removeBottom: true, removeBottom: true,
@@ -279,153 +293,163 @@ class _MemberPageState extends State<MemberPage>
); );
} }
Widget profileWidget() { Widget profileWidget(bool isHorizontal) {
return Padding( return Padding(
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20), padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
child: FutureBuilder( child: FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
Map data = snapshot.data!; Map data = snapshot.data!;
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() => Stack( () => Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
children: [ children: [profilePanelAndDetailInfo(isHorizontal, false)]),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePanel(ctr: _memberController),
const SizedBox(height: 20),
Row(
children: [
Flexible(
child: Text(
_memberController.memberInfo.value.name!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
)),
const SizedBox(width: 2),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.venus,
size: 14,
color: Colors.pink,
semanticLabel: '',
),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.mars,
size: 14,
color: Colors.blue,
semanticLabel: '',
),
const SizedBox(width: 4),
Image.asset(
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
height: 11,
semanticLabel:
'等级${_memberController.memberInfo.value.level}',
),
const SizedBox(width: 6),
if (_memberController
.memberInfo.value.vip!.status ==
1 &&
_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans'] !=
'') ...[
Image.network(
_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans'],
height: 20,
semanticLabel: _memberController
.memberInfo.value.vip!.label!['text'],
),
] else if (_memberController
.memberInfo.value.vip!.status ==
1 &&
_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans_static'] !=
'') ...[
Image.network(
_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans_static'],
height: 20,
semanticLabel: _memberController
.memberInfo.value.vip!.label!['text'],
),
],
TextButton(
child: Text("UID ${_memberController.mid}",
style: TextStyle(
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.5),
fontSize: 12,
// fontWeight: FontWeight.w200,
)),
onPressed: () {
Clipboard.setData(
ClipboardData(
text: _memberController.mid.toString()),
);
SmartDialog.showToast(
'已复制${_memberController.mid}至剪贴板');
}),
],
),
if (_memberController
.memberInfo.value.official!['title'] !=
'') ...[
const SizedBox(height: 6),
Text.rich(
maxLines: 2,
TextSpan(
text: _memberController
.memberInfo.value.official!['role'] ==
1
? '个人认证:'
: '企业认证:',
style: TextStyle(
color: Theme.of(context).primaryColor,
),
children: [
TextSpan(
text: _memberController
.memberInfo.value.official!['title'],
),
],
),
softWrap: true,
),
],
const SizedBox(height: 6),
if (_memberController.memberInfo.value.sign != '')
SelectableText(
_memberController.memberInfo.value.sign!,
),
],
),
],
),
); );
} else { } else {
return const SizedBox(); return const SizedBox();
} }
} else { } else {
// 骨架屏 // 骨架屏
return ProfilePanel(ctr: _memberController, loadingStatus: true); return profilePanelAndDetailInfo(isHorizontal, true);
} }
}, },
), ),
); );
} }
Widget profilePanelAndDetailInfo(bool isHorizontal, bool loadingStatus) {
if (isHorizontal) {
return Row(
children: [
Expanded(
child: ProfilePanel(
ctr: _memberController, loadingStatus: loadingStatus)),
const SizedBox(width: 20),
Expanded(child: profileDetailInfo()),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ProfilePanel(ctr: _memberController, loadingStatus: loadingStatus),
const SizedBox(height: 20),
profileDetailInfo(),
],
);
}
Widget profileDetailInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
_memberController.memberInfo.value.name ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
)),
const SizedBox(width: 2),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.venus,
size: 14,
color: Colors.pink,
semanticLabel: '',
),
if (_memberController.memberInfo.value.sex == '')
const Icon(
FontAwesomeIcons.mars,
size: 14,
color: Colors.blue,
semanticLabel: '',
),
const SizedBox(width: 4),
if (_memberController.memberInfo.value.level != null)
Image.asset(
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
height: 11,
semanticLabel: '等级${_memberController.memberInfo.value.level}',
),
const SizedBox(width: 6),
if (_memberController.memberInfo.value.vip?.status == 1) ...[
if (_memberController
.memberInfo.value.vip?.label?['img_label_uri_hans'] !=
'')
Image.network(
_memberController
.memberInfo.value.vip!.label!['img_label_uri_hans'],
height: 20,
semanticLabel:
_memberController.memberInfo.value.vip!.label!['text'],
),
if (_memberController.memberInfo.value.vip
?.label?['img_label_uri_hans_static'] !=
'')
Image.network(
_memberController.memberInfo.value.vip!
.label!['img_label_uri_hans_static'],
height: 20,
semanticLabel:
_memberController.memberInfo.value.vip!.label!['text'],
),
],
TextButton(
child: Text("UID ${_memberController.mid}",
style: TextStyle(
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.5),
fontSize: 12,
// fontWeight: FontWeight.w200,
)),
onPressed: () {
Clipboard.setData(
ClipboardData(text: _memberController.mid.toString()),
);
SmartDialog.showToast('已复制${_memberController.mid}至剪贴板');
}),
],
),
if (_memberController.memberInfo.value.official != null &&
_memberController.memberInfo.value.official!['title'] != '') ...[
const SizedBox(height: 6),
Text.rich(
maxLines: 2,
TextSpan(
text: _memberController.memberInfo.value.official!['role'] == 1
? '个人认证:'
: '企业认证:',
style: TextStyle(
color: Theme.of(context).primaryColor,
),
children: [
TextSpan(
text: _memberController.memberInfo.value.official!['title'],
),
],
),
softWrap: true,
),
],
const SizedBox(height: 6),
SelectableText(
_memberController.memberInfo.value.sign ?? '',
),
],
);
}
Widget commenWidget(msg) { Widget commenWidget(msg) {
return Padding( return Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(

View File

@@ -19,125 +19,147 @@ class ProfilePanel extends StatelessWidget {
MemberInfoModel memberInfo = ctr.memberInfo.value; MemberInfoModel memberInfo = ctr.memberInfo.value;
return Builder( return Builder(
builder: ((context) { builder: ((context) {
return Padding( return Row(
padding: children: [
EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20), Hero(
child: Row( tag: ctr.heroTag!,
children: [ child: Stack(
Hero( children: [
tag: ctr.heroTag!, NetworkImgLayer(
child: Stack( width: 90,
children: [ height: 90,
NetworkImgLayer( type: 'avatar',
width: 90, src: !loadingStatus ? memberInfo.face : ctr.face.value,
height: 90, ),
type: 'avatar', if (!loadingStatus &&
src: !loadingStatus ? memberInfo.face : ctr.face.value, memberInfo.liveRoom != null &&
), memberInfo.liveRoom!.liveStatus == 1)
if (!loadingStatus && Positioned(
memberInfo.liveRoom != null && bottom: 0,
memberInfo.liveRoom!.liveStatus == 1) left: 14,
Positioned( child: GestureDetector(
bottom: 0, onTap: () {
left: 14, LiveItemModel liveItem = LiveItemModel.fromJson({
child: GestureDetector( 'title': memberInfo.liveRoom!.title,
onTap: () { 'uname': memberInfo.name,
LiveItemModel liveItem = LiveItemModel.fromJson({ 'face': memberInfo.face,
'title': memberInfo.liveRoom!.title, 'roomid': memberInfo.liveRoom!.roomId,
'uname': memberInfo.name, 'watched_show': memberInfo.liveRoom!.watchedShow,
'face': memberInfo.face, });
'roomid': memberInfo.liveRoom!.roomId, Get.toNamed(
'watched_show': memberInfo.liveRoom!.watchedShow, '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}',
}); arguments: {'liveItem': liveItem},
Get.toNamed( );
'/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', },
arguments: {'liveItem': liveItem}, child: Container(
); padding: const EdgeInsets.fromLTRB(6, 2, 6, 2),
}, decoration: BoxDecoration(
child: Container( color: Theme.of(context).colorScheme.primary,
padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), borderRadius:
decoration: BoxDecoration( const BorderRadius.all(Radius.circular(10)),
color: Theme.of(context).colorScheme.primary, ),
borderRadius: child: Row(children: [
const BorderRadius.all(Radius.circular(10)), Image.asset(
'assets/images/live.gif',
height: 10,
), ),
child: Row(children: [ Text(
Image.asset( ' 直播中',
'assets/images/live.gif', style: TextStyle(
height: 10, color: Colors.white,
fontSize: Theme.of(context)
.textTheme
.labelSmall!
.fontSize),
)
]),
),
),
)
],
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding:
const EdgeInsets.only(top: 10, left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Get.toNamed(
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['following'].toString()
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold),
), ),
Text( Text(
' 直播中', '关注',
style: TextStyle( style: TextStyle(
color: Colors.white,
fontSize: Theme.of(context) fontSize: Theme.of(context)
.textTheme .textTheme
.labelSmall! .labelMedium!
.fontSize), .fontSize),
) )
]), ],
), ),
), ),
) InkWell(
], onTap: () {
), Get.toNamed(
), '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
const SizedBox(width: 12), },
Expanded( child: Column(
child: Column( children: [
mainAxisSize: MainAxisSize.min, Text(
children: [
Padding(
padding:
const EdgeInsets.only(top: 10, left: 10, right: 10),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
InkWell(
onTap: () {
Get.toNamed(
'/follow?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column(
children: [
Text(
!loadingStatus !loadingStatus
? ctr.userStat!['following'].toString() ? ctr.userStat!['follower'] != null
? Utils.numFormat(
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), )
) ],
],
),
), ),
InkWell( ),
onTap: () { InkWell(
Get.toNamed( onTap: null,
'/fan?mid=${memberInfo.mid}&name=${memberInfo.name}');
},
child: Column( child: Column(
children: [ children: [
Text( Text(
!loadingStatus !loadingStatus
? ctr.userStat!['follower'] != null ? ctr.userStat!['likes'] != null
? Utils.numFormat( ? Utils.numFormat(
ctr.userStat!['follower'], ctr.userStat!['likes'],
) )
: '-' : '-'
: '-', : '-',
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
@@ -145,117 +167,87 @@ class ProfilePanel extends StatelessWidget {
.fontSize), .fontSize),
) )
], ],
), )),
), ],
InkWell(
onTap: null,
child: Column(
children: [
Text(
!loadingStatus
? ctr.userStat!['likes'] != null
? Utils.numFormat(
ctr.userStat!['likes'],
)
: '-'
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text(
'获赞',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize),
)
],
)),
],
),
), ),
const SizedBox(height: 10), ),
if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[ const SizedBox(height: 10),
Row( if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[
children: [ Row(
Obx( children: [
() => Expanded( Obx(
child: TextButton( () => Expanded(
onPressed: () => ctr.actionRelationMod(), child: TextButton(
style: TextButton.styleFrom( onPressed: () => ctr.actionRelationMod(context),
foregroundColor: ctr.attribute.value == -1 style: TextButton.styleFrom(
? Colors.transparent foregroundColor: ctr.attribute.value == -1
: ctr.attribute.value != 0 ? Colors.transparent
? Theme.of(context) : ctr.attribute.value != 0
.colorScheme ? Theme.of(context).colorScheme.outline
.outline : Theme.of(context)
: Theme.of(context) .colorScheme
.colorScheme .onPrimary,
.onPrimary, backgroundColor: ctr.attribute.value != 0
backgroundColor: ctr.attribute.value != 0 ? Theme.of(context)
? Theme.of(context) .colorScheme
.colorScheme .onInverseSurface
.onInverseSurface : Theme.of(context)
: Theme.of(context) .colorScheme
.colorScheme .primary, // 设置按钮背景色
.primary, // 设置按钮背景色
),
child: Obx(() => Text(ctr.attributeText.value)),
), ),
child: Obx(() => Text(ctr.attributeText.value)),
), ),
), ),
const SizedBox(width: 8), ),
Expanded( const SizedBox(width: 8),
child: TextButton( Expanded(
onPressed: () {}, child: TextButton(
style: TextButton.styleFrom( onPressed: () {},
backgroundColor: Theme.of(context) style: TextButton.styleFrom(
.colorScheme backgroundColor: Theme.of(context)
.onInverseSurface, .colorScheme
), .onInverseSurface,
child: const Text('发消息'),
), ),
) child: const Text('发消息'),
], ),
) )
], ],
if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ )
TextButton(
onPressed: () {
Get.toNamed('/webview', parameters: {
'url': 'https://account.bilibili.com/account/home',
'pageTitle': '个人中心(建议浏览器打开)',
'type': 'url'
});
},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 80, right: 80),
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor:
Theme.of(context).colorScheme.primary,
),
child: const Text('个人中心(web)'),
)
],
if (ctr.ownerMid == -1) ...[
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 80, right: 80),
foregroundColor:
Theme.of(context).colorScheme.outline,
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text('未登录'),
)
]
], ],
), if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[
TextButton(
onPressed: () {
Get.toNamed('/webview', parameters: {
'url': 'https://account.bilibili.com/account/home',
'pageTitle': '个人中心(建议浏览器打开)',
'type': 'url'
});
},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 80, right: 80),
foregroundColor:
Theme.of(context).colorScheme.onPrimary,
backgroundColor: Theme.of(context).colorScheme.primary,
),
child: const Text('个人中心(web)'),
)
],
if (ctr.ownerMid == -1) ...[
TextButton(
onPressed: () {},
style: TextButton.styleFrom(
padding: const EdgeInsets.only(left: 80, right: 80),
foregroundColor: Theme.of(context).colorScheme.outline,
backgroundColor:
Theme.of(context).colorScheme.onInverseSurface,
),
child: const Text('未登录'),
)
]
],
), ),
], ),
), ],
); );
}), }),
); );

View File

@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/badge.dart';
import 'package:PiliPalaX/models/member/seasons.dart'; import 'package:PiliPalaX/models/member/seasons.dart';
import 'package:PiliPalaX/pages/member_seasons/widgets/item.dart'; import 'package:PiliPalaX/pages/member_seasons/widgets/item.dart';
import '../../../utils/grid.dart';
class MemberSeasonsPanel extends StatelessWidget { class MemberSeasonsPanel extends StatelessWidget {
final MemberSeasonsDataModel? data; final MemberSeasonsDataModel? data;
const MemberSeasonsPanel({super.key, this.data}); const MemberSeasonsPanel({super.key, this.data});
@@ -38,10 +40,9 @@ class MemberSeasonsPanel extends StatelessWidget {
size: 'small', size: 'small',
text: item.meta!.total.toString(), text: item.meta!.total.toString(),
), ),
const Spacer(),
SizedBox( SizedBox(
width: 35, width: 30,
height: 35, height: 30,
child: IconButton( child: IconButton(
tooltip: '前往', tooltip: '前往',
onPressed: () => Get.toNamed( onPressed: () => Get.toNamed(
@@ -50,7 +51,7 @@ class MemberSeasonsPanel extends StatelessWidget {
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: MaterialStateProperty.all(EdgeInsets.zero),
), ),
icon: const Icon( icon: const Icon(
Icons.arrow_forward, Icons.arrow_forward_ios,
size: 20, size: 20,
), ),
), ),
@@ -61,12 +62,12 @@ class MemberSeasonsPanel extends StatelessWidget {
LayoutBuilder( LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
return GridView.builder( return GridView.builder(
gridDelegate: gridDelegate: SliverGridDelegateWithExtentAndRatio(
const SliverGridDelegateWithFixedCrossAxisCount( mainAxisSpacing: StyleString.cardSpace,
crossAxisCount: 2, // Use a fixed count for GridView crossAxisSpacing: StyleString.cardSpace,
crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth,
mainAxisSpacing: StyleString.safeSpace,
childAspectRatio: 0.94, childAspectRatio: 0.94,
mainAxisExtent: 0,
), ),
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,

View File

@@ -65,58 +65,60 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: _memberArchivesController.scrollController, controller: _memberArchivesController.scrollController,
slivers: [ slivers: [
FutureBuilder( SliverPadding(
future: _futureBuilderFuture, padding:
builder: (BuildContext context, snapshot) { const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
if (snapshot.connectionState == ConnectionState.done) { sliver: FutureBuilder(
if (snapshot.data != null) { future: _futureBuilderFuture,
Map data = snapshot.data as Map; builder: (BuildContext context, snapshot) {
List list = _memberArchivesController.archivesList; if (snapshot.connectionState == ConnectionState.done) {
if (data['status']) { if (snapshot.data != null) {
return Obx( Map data = snapshot.data as Map;
() => list.isNotEmpty List list = _memberArchivesController.archivesList;
? SliverGrid( if (data['status']) {
gridDelegate: return Obx(
SliverGridDelegateWithMaxCrossAxisExtent( () => list.isNotEmpty
mainAxisSpacing: StyleString.cardSpace, ? SliverGrid(
crossAxisSpacing: StyleString.safeSpace, gridDelegate:
maxCrossAxisExtent: Grid.maxRowWidth * 2, SliverGridDelegateWithExtentAndRatio(
mainAxisExtent: Grid.calculateActualWidth( mainAxisSpacing: StyleString.safeSpace,
context, crossAxisSpacing: StyleString.safeSpace,
Grid.maxRowWidth * 2, maxCrossAxisExtent:
StyleString.safeSpace) / Grid.maxRowWidth * 2,
2.1 / childAspectRatio:
StyleString.aspectRatio), StyleString.aspectRatio * 2.3,
delegate: SliverChildBuilderDelegate( mainAxisExtent: 0),
(BuildContext context, index) { delegate: SliverChildBuilderDelegate(
return VideoCardH( (BuildContext context, index) {
videoItem: list[index], return VideoCardH(
showOwner: false, videoItem: list[index],
showPubdate: true, showOwner: false,
); showPubdate: true,
}, );
childCount: list.length, },
), childCount: list.length,
) ),
: const SliverToBoxAdapter(), )
); : const SliverToBoxAdapter(),
);
} else {
return HttpError(
errMsg: snapshot.data['msg'],
fn: () {},
);
}
} else { } else {
return HttpError( return HttpError(
errMsg: snapshot.data['msg'], errMsg: "投稿页出现错误",
fn: () {}, fn: () {},
); );
} }
} else { } else {
return HttpError( return const SliverToBoxAdapter();
errMsg: "投稿页出现错误",
fn: () {},
);
} }
} else { },
return const SliverToBoxAdapter(); ),
} )
},
),
], ],
), ),
); );

View File

@@ -7,6 +7,7 @@ import 'package:PiliPalaX/utils/utils.dart';
import '../../common/constants.dart'; import '../../common/constants.dart';
import '../../common/widgets/http_error.dart'; import '../../common/widgets/http_error.dart';
import '../../utils/grid.dart'; import '../../utils/grid.dart';
import '../../utils/storage.dart';
import '../dynamics/widgets/dynamic_panel.dart'; import '../dynamics/widgets/dynamic_panel.dart';
import 'package:waterfall_flow/waterfall_flow.dart'; import 'package:waterfall_flow/waterfall_flow.dart';
@@ -22,6 +23,7 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
late Future _futureBuilderFuture; late Future _futureBuilderFuture;
late ScrollController scrollController; late ScrollController scrollController;
late int mid; late int mid;
late bool dynamicsWaterfallFlow;
@override @override
void initState() { void initState() {
@@ -44,6 +46,8 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
} }
}, },
); );
dynamicsWaterfallFlow = GStrorage.setting
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
} }
@override @override
@@ -72,32 +76,51 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
Map data = snapshot.data as Map; Map data = snapshot.data as Map;
List list = _memberDynamicController.dynamicsList; List list = _memberDynamicController.dynamicsList;
if (data['status']) { if (data['status']) {
return Obx( return Obx(() {
() => list.isNotEmpty if (list.isEmpty) {
? SliverWaterfallFlow.extent( return const SliverToBoxAdapter();
maxCrossAxisExtent: Grid.maxRowWidth * 2, }
//cacheExtent: 0.0, if (!dynamicsWaterfallFlow) {
crossAxisSpacing: StyleString.safeSpace, return SliverCrossAxisGroup(
mainAxisSpacing: StyleString.safeSpace, slivers: [
const SliverFillRemaining(),
SliverConstrainedCrossAxis(
maxExtent: Grid.maxRowWidth * 2,
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return DynamicPanel(item: list[index]);
},
childCount: list.length,
),
)),
const SliverFillRemaining(),
],
);
}
return SliverWaterfallFlow.extent(
maxCrossAxisExtent: Grid.maxRowWidth * 2,
//cacheExtent: 0.0,
crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace,
/// follow max child trailing layout offset and layout with full cross axis extend /// follow max child trailing layout offset and layout with full cross axis extend
/// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow]
/// with full cross axis extend /// with full cross axis extend
// LastChildLayoutType.fullCrossAxisExtend, // LastChildLayoutType.fullCrossAxisExtend,
/// as foot at trailing and layout with full cross axis extend /// as foot at trailing and layout with full cross axis extend
/// show no more item at trailing when children are not full of viewport /// show no more item at trailing when children are not full of viewport
/// if children is full of viewport, it's the same as fullCrossAxisExtend /// if children is full of viewport, it's the same as fullCrossAxisExtend
// LastChildLayoutType.foot, // LastChildLayoutType.foot,
lastChildLayoutTypeBuilder: (index) => lastChildLayoutTypeBuilder: (index) =>
index == list.length index == list.length
? LastChildLayoutType.foot ? LastChildLayoutType.foot
: LastChildLayoutType.none, : LastChildLayoutType.none,
children: [ children: [
for (var i in list) DynamicPanel(item: i), for (var i in list) DynamicPanel(item: i),
]) ]);
: const SliverToBoxAdapter(), });
);
} else { } else {
return HttpError( return HttpError(
errMsg: snapshot.data['msg'], errMsg: snapshot.data['msg'],

View File

@@ -7,6 +7,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/common/widgets/no_data.dart';
import 'package:PiliPalaX/common/widgets/video_card_h.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart';
import '../../common/constants.dart';
import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
class MemberSearchPage extends StatefulWidget { class MemberSearchPage extends StatefulWidget {
@@ -48,150 +50,132 @@ class _MemberSearchPageState extends State<MemberSearchPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
titleSpacing: 0, titleSpacing: 0,
actions: [ actions: [
IconButton( IconButton(
tooltip: '搜索', tooltip: '搜索',
onPressed: () => _memberSearchCtr.submit(), onPressed: () => _memberSearchCtr.submit(),
icon: const Icon(CupertinoIcons.search, size: 22)), icon: const Icon(CupertinoIcons.search, size: 22)),
const SizedBox(width: 10) const SizedBox(width: 10)
], ],
title: Obx( title: Obx(
() => TextField( () => TextField(
autofocus: true, autofocus: true,
focusNode: _memberSearchCtr.searchFocusNode, focusNode: _memberSearchCtr.searchFocusNode,
controller: _memberSearchCtr.controller.value, controller: _memberSearchCtr.controller.value,
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onChanged: (value) => _memberSearchCtr.onChange(value), onChanged: (value) => _memberSearchCtr.onChange(value),
decoration: InputDecoration( decoration: InputDecoration(
hintText: _memberSearchCtr.hintText, hintText: _memberSearchCtr.hintText,
border: InputBorder.none, border: InputBorder.none,
suffixIcon: IconButton( suffixIcon: IconButton(
tooltip: '清空', tooltip: '清空',
icon: Icon( icon: Icon(
Icons.clear, Icons.clear,
size: 22, size: 22,
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _memberSearchCtr.onClear(),
), ),
onPressed: () => _memberSearchCtr.onClear(),
), ),
onSubmitted: (String value) => _memberSearchCtr.submit(),
), ),
onSubmitted: (String value) => _memberSearchCtr.submit(),
), ),
), ),
), body: Obx(
body: Obx( () {
() => Column( if (_memberSearchCtr.loadingStatus.value == 'init') {
children: _memberSearchCtr.loadingStatus.value == 'init' return Center(
? [ child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'),
Expanded( );
child: Center( }
child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'), return CustomScrollView(
), physics: const AlwaysScrollableScrollPhysics(),
), controller: scrollController,
] slivers: <Widget>[
: [ FutureBuilder(
// TabBar( future: _memberSearchCtr.searchArchives(),
// controller: _tabController, builder: (context, snapshot) {
// tabs: const [ if (snapshot.connectionState == ConnectionState.done &&
// Tab(text: "视频"), snapshot.hasData) {
// Tab(text: "动态"), Map data = snapshot.data as Map;
// ], if (data['status']) {
// ), return SliverPadding(
Expanded( padding:
child: const EdgeInsets.all(StyleString.safeSpace),
// TabBarView( sliver: Obx(
// 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 () => _memberSearchCtr.archiveList.isNotEmpty
? ListView.builder( ? SliverGrid(
controller: scrollController, gridDelegate:
itemCount: SliverGridDelegateWithExtentAndRatio(
_memberSearchCtr.archiveList.length + mainAxisSpacing: StyleString
1, .safeSpace,
itemBuilder: (context, index) { crossAxisSpacing: StyleString
if (index == .safeSpace,
_memberSearchCtr maxCrossAxisExtent: Grid.maxRowWidth *
.archiveList.length) { 2,
return Container( childAspectRatio:
height: MediaQuery.of(context) StyleString.aspectRatio * 2.3,
.padding mainAxisExtent: 0),
.bottom + delegate: SliverChildBuilderDelegate(
60, (context, index) {
padding: EdgeInsets.only( return VideoCardH(
bottom: MediaQuery.of(context) videoItem: _memberSearchCtr
.padding .archiveList[index]);
.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]);
}
}, },
) childCount: _memberSearchCtr.archiveList
.length))
: _memberSearchCtr.loadingStatus.value == : _memberSearchCtr.loadingStatus.value ==
'loading' 'loading'
? ListView.builder( ? SliverGrid(
itemCount: 10, gridDelegate:
itemBuilder: (context, index) { SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing:
StyleString.cardSpace,
crossAxisSpacing:
StyleString.safeSpace,
maxCrossAxisExtent:
Grid.maxRowWidth * 2,
childAspectRatio:
StyleString.aspectRatio *
2.1,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate(
(context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, }, childCount: 10))
) : const NoData(),
: const CustomScrollView( ));
slivers: <Widget>[ } else {
NoData(), return HttpError(
], errMsg: data['msg'],
), fn: () => setState(() {}),
); );
} else { }
return CustomScrollView( } else {
slivers: <Widget>[ // 骨架屏
HttpError( return SliverPadding(
errMsg: data['msg'], padding: const EdgeInsets.all(StyleString.safeSpace),
fn: () => setState(() {}), sliver: SliverGrid(
) gridDelegate:
], SliverGridDelegateWithExtentAndRatio(
); mainAxisSpacing: StyleString.cardSpace,
} crossAxisSpacing: StyleString.safeSpace,
} else { maxCrossAxisExtent: Grid.maxRowWidth * 2,
// 骨架屏 childAspectRatio:
return ListView.builder( StyleString.aspectRatio * 2.3,
itemCount: 10, mainAxisExtent: 0),
itemBuilder: (context, index) { delegate:
return const VideoCardHSkeleton(); SliverChildBuilderDelegate((context, index) {
}, return const VideoCardHSkeleton();
); }, childCount: 10)));
} }
}, },
), ),
// ], ],
// ), );
), },
], ));
),
),
);
} }
} }

View File

@@ -151,7 +151,10 @@ class _MinePageState extends State<MinePage> {
semanticsLabel: '头像', semanticsLabel: '头像',
width: 85, width: 85,
height: 85) height: 85)
: Image.asset('assets/images/noface.jpeg',semanticLabel: "默认头像",), : Image.asset(
'assets/images/noface.jpeg',
semanticLabel: "默认头像",
),
), ),
), ),
), ),
@@ -183,8 +186,8 @@ class _MinePageState extends State<MinePage> {
style: style:
TextStyle(color: Theme.of(context).colorScheme.outline)), TextStyle(color: Theme.of(context).colorScheme.outline)),
TextSpan( TextSpan(
text: (_mineController.userInfo.value.money ?? '-') text:
.toString(), (_mineController.userInfo.value.money ?? '-').toString(),
style: style:
TextStyle(color: Theme.of(context).colorScheme.primary)), TextStyle(color: Theme.of(context).colorScheme.primary)),
])) ]))
@@ -256,12 +259,12 @@ class _MinePageState extends State<MinePage> {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold); fontWeight: FontWeight.bold);
return SizedBox( return SizedBox(
height: constraints.maxWidth / 3 * 0.6, height: constraints.maxWidth * 0.33 * 0.6,
child: GridView.count( child: GridView.count(
primary: false, primary: false,
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
crossAxisCount: 3, crossAxisCount: 3,
childAspectRatio: 1.67, childAspectRatio: 1.66,
children: <Widget>[ children: <Widget>[
InkWell( InkWell(
onTap: () => _mineController.pushDynamic(), onTap: () => _mineController.pushDynamic(),

View File

@@ -90,7 +90,8 @@ class _ImagePreviewState extends State<ImagePreview>
ListTile( ListTile(
onTap: () { onTap: () {
Get.back(); Get.back();
DownloadUtils.downloadImg(_previewController.currentImgUrl); DownloadUtils.downloadImg(
context, _previewController.currentImgUrl);
}, },
dense: true, dense: true,
title: const Text('保存到手机', style: TextStyle(fontSize: 14)), title: const Text('保存到手机', style: TextStyle(fontSize: 14)),

View File

@@ -34,7 +34,7 @@ class RankController extends GetxController with GetTickerProviderStateMixin {
void animateToTop() { void animateToTop() {
int index = tabController.index; int index = tabController.index;
var ctr = tabsCtrList[index]; var ctr = tabsCtrList[index];
ctr().animateToTop(); ctr.animateToTop();
} }
void setTabConfig() async { void setTabConfig() async {

View File

@@ -35,72 +35,94 @@ class _RankPageState extends State<RankPage>
} }
}); });
} }
@override @override
void dispose() { void dispose() {
_rankController.tabController.removeListener(() {}); _rankController.tabController.removeListener(() {});
_rankController.tabController.dispose(); _rankController.tabController.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Row(
appBar: AppBar( children: [
toolbarHeight: 0, const SizedBox(
elevation: 0, width: StyleString.cardSpace,
systemOverlayStyle: SystemUiOverlayStyle(
// Customize the status bar here
statusBarIconBrightness:
MediaQuery.of(context).platformBrightness == Brightness.dark
? Brightness.light
: Brightness.dark,
), ),
), // SizedBox(
body: Row( // width: 55,
children: [ // child: NavigationRail(
const SizedBox( //
width: StyleString.cardSpace, // backgroundColor: Colors.transparent,
// minWidth: 50.0,
// // elevation: 0,
// selectedIndex: _selectedTabIndex,
// onDestinationSelected: (int index) {
// feedBack();
// if (_selectedTabIndex == index) {
// _rankController.tabsCtrList[index]().animateToTop();
// } else {
// setState(() {
// _rankController.tabController.index = index;
// _selectedTabIndex = index;
// });
// }
// },
// labelType: NavigationRailLabelType.none,
// destinations: [
// for (var tab in _rankController.tabs)
// NavigationRailDestination(
// padding: EdgeInsets.zero,
// icon: Text(tab['label']),
// // selectedIcon: Text(tab['label']),
// label: const SizedBox.shrink(),
// ),
// ],
// trailing: const SizedBox(height: 100),
// )),
LayoutBuilder(builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: NavigationRail(
backgroundColor: Colors.transparent,
minWidth: 50.0,
// elevation: 0,
selectedIndex: _selectedTabIndex,
onDestinationSelected: (int index) {
feedBack();
if (_selectedTabIndex == index) {
_rankController.tabsCtrList[index]().animateToTop();
} else {
setState(() {
_rankController.tabController.index = index;
_selectedTabIndex = index;
});
}
},
labelType: NavigationRailLabelType.none,
destinations: [
for (var tab in _rankController.tabs)
NavigationRailDestination(
icon: Text(tab['label']),
// selectedIcon: Text(tab['label']),
label: const SizedBox.shrink(),
),
],
trailing: const SizedBox(height: 100),
))));
}),
Expanded(
child: TabBarView(
controller: _rankController.tabController,
children: _rankController.tabsPageList,
), ),
LayoutBuilder(builder: (context, constraint) { ),
return SingleChildScrollView( ],
child: ConstrainedBox(
constraints:
BoxConstraints(minHeight: constraint.maxHeight + 100),
child: IntrinsicHeight(
child: NavigationRail(
minWidth: 55.0,
selectedIndex: _selectedTabIndex,
onDestinationSelected: (int index) {
feedBack();
if (_selectedTabIndex == index) {
_rankController.tabsCtrList[index]().animateToTop();
} else {
setState(() {
_rankController.tabController.index = index;
_selectedTabIndex = index;
});
}
},
labelType: NavigationRailLabelType.none,
destinations: [
for (var tab in _rankController.tabs)
NavigationRailDestination(
icon: Text(tab['label']),
// selectedIcon: Text(tab['label']),
label: const SizedBox.shrink(),
),
],
))));
}),
Expanded(
child: TabBarView(
controller: _rankController.tabController,
children: _rankController.tabsPageList,
),
),
],
),
); );
} }
} }

View File

@@ -27,7 +27,6 @@ class ZonePage extends StatefulWidget {
class _ZonePageState extends State<ZonePage> class _ZonePageState extends State<ZonePage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
late ZoneController _zoneController; late ZoneController _zoneController;
List videoList = [];
Future? _futureBuilderFuture; Future? _futureBuilderFuture;
late ScrollController scrollController; late ScrollController scrollController;
@@ -81,12 +80,12 @@ class _ZonePageState extends State<ZonePage>
return await _zoneController.onRefresh(); return await _zoneController.onRefresh();
}, },
child: CustomScrollView( child: CustomScrollView(
controller: _zoneController.scrollController, controller: scrollController,
slivers: [ slivers: [
SliverPadding( SliverPadding(
// 单列布局 EdgeInsets.zero // 单列布局 EdgeInsets.zero
padding: padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0), const EdgeInsets.fromLTRB(StyleString.safeSpace, StyleString.safeSpace, 0, 0),
sliver: FutureBuilder( sliver: FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
@@ -95,19 +94,12 @@ class _ZonePageState extends State<ZonePage>
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() => SliverGrid( () => SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距 mainAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace,
// 列间距
crossAxisSpacing: StyleString.cardSpace,
// 最大宽度
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth(context, childAspectRatio: StyleString.aspectRatio * 2.3,
Grid.maxRowWidth * 2, StyleString.cardSpace, mainAxisExtent: 0),
screenWidthOffset:
StyleString.cardSpace + 55) /
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return VideoCardH( return VideoCardH(
videoItem: _zoneController.videoList[index], videoItem: _zoneController.videoList[index],
@@ -138,7 +130,13 @@ class _ZonePageState extends State<ZonePage>
} }
} else { } else {
// 骨架屏 // 骨架屏
return SliverList( return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
}, childCount: 10), }, childCount: 10),

View File

@@ -146,17 +146,15 @@ class _RcmdPageState extends State<RcmdPage>
Widget contentGrid(ctr, videoList) { Widget contentGrid(ctr, videoList) {
return SliverGrid( return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
// 行间距 // 行间距
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.cardSpace,
// 列间距 // 列间距
crossAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.cardSpace,
// 最大宽度 // 最大宽度
maxCrossAxisExtent: Grid.maxRowWidth, maxCrossAxisExtent: Grid.maxRowWidth,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio: StyleString.aspectRatio,
context, Grid.maxRowWidth, StyleString.safeSpace) / mainAxisExtent: MediaQuery.textScalerOf(context).scale(90),
StyleString.aspectRatio +
MediaQuery.textScalerOf(context).scale(90),
), ),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {

View File

@@ -6,6 +6,8 @@ import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/models/common/search_type.dart'; import 'package:PiliPalaX/models/common/search_type.dart';
import '../../common/constants.dart';
import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/article_panel.dart'; import 'widgets/article_panel.dart';
import 'widgets/live_panel.dart'; import 'widgets/live_panel.dart';
@@ -132,25 +134,34 @@ class _SearchPanelState extends State<SearchPanel>
} }
} else { } else {
// 骨架屏 // 骨架屏
return ListView.builder( return CustomScrollView(
addAutomaticKeepAlives: false, physics: const AlwaysScrollableScrollPhysics(),
addRepaintBoundaries: false, slivers: [
itemCount: 15, SliverGrid(
itemBuilder: (context, index) { gridDelegate: SliverGridDelegateWithExtentAndRatio(
switch (widget.searchType) { mainAxisSpacing: StyleString.safeSpace,
case SearchType.video: crossAxisSpacing: StyleString.safeSpace,
return const VideoCardHSkeleton(); maxCrossAxisExtent: Grid.maxRowWidth * 2,
case SearchType.media_bangumi: childAspectRatio: StyleString.aspectRatio * 2.3,
return const MediaBangumiSkeleton(); mainAxisExtent: 0),
case SearchType.bili_user: delegate: SliverChildBuilderDelegate(
return const VideoCardHSkeleton(); (context, index) {
case SearchType.live_room: switch (widget.searchType) {
return const VideoCardHSkeleton(); case SearchType.video:
default: return const VideoCardHSkeleton();
return const VideoCardHSkeleton(); case SearchType.media_bangumi:
} return const MediaBangumiSkeleton();
}, case SearchType.bili_user:
); return const VideoCardHSkeleton();
case SearchType.live_room:
return const VideoCardHSkeleton();
default:
return const VideoCardHSkeleton();
}
},
childCount: 15,
))
]);
} }
}, },
), ),

View File

@@ -12,14 +12,12 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
color: Theme.of(context).colorScheme.outline); color: Theme.of(context).colorScheme.outline);
return CustomScrollView(controller: ctr.scrollController, slivers: [ return CustomScrollView(controller: ctr.scrollController, slivers: [
SliverGrid( SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio: StyleString.aspectRatio * 2.3,
context, Grid.maxRowWidth * 2, StyleString.safeSpace) / mainAxisExtent: 0),
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return InkWell( return InkWell(
@@ -32,8 +30,8 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
}); });
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.symmetric(
StyleString.safeSpace, 5, StyleString.safeSpace, 5), horizontal: StyleString.safeSpace),
child: LayoutBuilder(builder: (context, boxConstraints) { child: LayoutBuilder(builder: (context, boxConstraints) {
final double width = (boxConstraints.maxWidth - final double width = (boxConstraints.maxWidth -
StyleString.cardSpace * StyleString.cardSpace *

View File

@@ -13,14 +13,12 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
child: GridView.builder( child: GridView.builder(
primary: false, primary: false,
controller: ctr!.scrollController, controller: ctr!.scrollController,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithExtentAndRatio(
maxCrossAxisExtent: Grid.maxRowWidth, maxCrossAxisExtent: Grid.maxRowWidth,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
mainAxisSpacing: StyleString.safeSpace, mainAxisSpacing: StyleString.safeSpace,
mainAxisExtent: Grid.calculateActualWidth( childAspectRatio: StyleString.aspectRatio,
context, Grid.maxRowWidth, StyleString.safeSpace) / mainAxisExtent: MediaQuery.textScalerOf(context).scale(80),
StyleString.aspectRatio +
MediaQuery.textScalerOf(context).scale(80),
), ),
itemCount: list.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {

View File

@@ -19,10 +19,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
slivers: [ slivers: [
SliverGrid( SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisSpacing: StyleString.cardSpace, mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace, crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2, maxCrossAxisExtent: Grid.maxRowWidth * 2,
mainAxisExtent: 157, mainAxisExtent: 160,
), ),
delegate: SliverChildBuilderDelegate((BuildContext context, int index) { delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
var i = list![index]; var i = list![index];
@@ -36,8 +36,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
// }); // });
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(StyleString.safeSpace,
StyleString.safeSpace, 7, StyleString.safeSpace, 2), StyleString.safeSpace, StyleString.safeSpace, 2),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -125,7 +125,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
if (res['status']) { if (res['status']) {
EpisodeItem episode = EpisodeItem episode =
res['data'].episodes.first; res['data'].episodes.first;
int? epId = res['data'].userStatus?.progress?.lastEpId; int? epId = res['data']
.userStatus
?.progress
?.lastEpId;
if (epId == null) { if (epId == null) {
epId = episode.epId; epId = episode.epId;
} else { } else {

View File

@@ -28,7 +28,8 @@ class SearchVideoPanel extends StatelessWidget {
Container( Container(
width: context.width, width: context.width,
height: 34, height: 34,
padding: const EdgeInsets.only(left: 8, top: 0, right: 12), padding: const EdgeInsets.only(
left: StyleString.safeSpace, top: 0, right: 12),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@@ -69,7 +70,7 @@ class SearchVideoPanel extends StatelessWidget {
style: ButtonStyle( style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: MaterialStateProperty.all(EdgeInsets.zero),
), ),
onPressed: () => controller.onShowFilterDialog(ctr), onPressed: () => controller.onShowFilterDialog(context, ctr),
icon: Icon( icon: Icon(
Icons.filter_list_outlined, Icons.filter_list_outlined,
size: 18, size: 18,
@@ -84,22 +85,23 @@ class SearchVideoPanel extends StatelessWidget {
child: CustomScrollView( child: CustomScrollView(
controller: ctr.scrollController, controller: ctr.scrollController,
slivers: [ slivers: [
SliverGrid( SliverPadding(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( padding: const EdgeInsets.all(StyleString.safeSpace),
mainAxisSpacing: StyleString.cardSpace, sliver: SliverGrid(
crossAxisSpacing: StyleString.safeSpace, gridDelegate: SliverGridDelegateWithExtentAndRatio(
maxCrossAxisExtent: Grid.maxRowWidth * 2, mainAxisSpacing: StyleString.safeSpace,
mainAxisExtent: Grid.calculateActualWidth(context, crossAxisSpacing: StyleString.safeSpace,
Grid.maxRowWidth * 2, StyleString.safeSpace) / maxCrossAxisExtent: Grid.maxRowWidth * 2,
2.1 / childAspectRatio: StyleString.aspectRatio * 2.3,
StyleString.aspectRatio), mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
return VideoCardH(videoItem: list[index], showPubdate: true); return VideoCardH(
}, videoItem: list[index], showPubdate: true);
childCount: list.length, },
), childCount: list.length,
), ),
)),
], ],
)), )),
], ],
@@ -176,10 +178,10 @@ class VideoPanelController extends GetxController {
super.onInit(); super.onInit();
} }
onShowFilterDialog(searchPanelCtr) { onShowFilterDialog(BuildContext context, SearchPanelController searchPanelCtr) {
SmartDialog.show( showDialog(
animationType: SmartAnimationType.centerFade_otherSlide, context: context,
builder: (BuildContext context) { builder: (context) {
TextStyle textStyle = Theme.of(context).textTheme.titleMedium!; TextStyle textStyle = Theme.of(context).textTheme.titleMedium!;
return AlertDialog( return AlertDialog(
title: const Text('时长筛选'), title: const Text('时长筛选'),

View File

@@ -48,17 +48,16 @@ class SettingController extends GetxController {
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0); setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0);
} }
loginOut() async { loginOut(BuildContext context) async {
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('确认要退出登录吗'), content: const Text('确认要退出登录吗'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: const Text('点错了'), child: const Text('点错了'),
), ),
TextButton( TextButton(
@@ -73,7 +72,7 @@ class SettingController extends GetxController {
.put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''}); .put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''});
await LoginUtils.refreshLoginStatus(false); await LoginUtils.refreshLoginStatus(false);
SmartDialog.dismiss().then((value) => Get.back()); Get.back();
}, },
child: const Text('确认'), child: const Text('确认'),
) )

View File

@@ -54,10 +54,9 @@ class _ExtraSettingState extends State<ExtraSetting> {
var systemProxyHost = ''; var systemProxyHost = '';
var systemProxyPort = ''; var systemProxyPort = '';
SmartDialog.show( showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('设置代理'), title: const Text('设置代理'),
content: Column( content: Column(
@@ -101,7 +100,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Get.back();
}, },
child: Text( child: Text(
'取消', '取消',
@@ -112,7 +111,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
onPressed: () async { onPressed: () async {
setting.put(SettingBoxKey.systemProxyHost, systemProxyHost); setting.put(SettingBoxKey.systemProxyHost, systemProxyHost);
setting.put(SettingBoxKey.systemProxyPort, systemProxyPort); setting.put(SettingBoxKey.systemProxyPort, systemProxyPort);
SmartDialog.dismiss(); Get.back();
// Request.dio; // Request.dio;
}, },
child: const Text('确认'), child: const Text('确认'),
@@ -210,6 +209,13 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.disableLikeMsg, setKey: SettingBoxKey.disableLikeMsg,
defaultVal: false, defaultVal: false,
), ),
const SetSwitchItem(
title: '默认展示评论区',
subTitle: '在视频详情页默认切换至评论区页仅tab型布局',
leading: Icon(Icons.mode_comment_outlined),
setKey: SettingBoxKey.defaultShowComment,
defaultVal: false,
),
ListTile( ListTile(
dense: false, dense: false,
title: Text('评论展示', style: titleStyle), title: Text('评论展示', style: titleStyle),

View File

@@ -12,7 +12,8 @@ class FontSizeSelectPage extends StatefulWidget {
class _FontSizeSelectPageState extends State<FontSizeSelectPage> { class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
List<double> list = [0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3]; List<double> list = List.generate(12, (index) => 0.85 + index * 0.05);
//[0.85, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35];
late double minsize; late double minsize;
late double maxSize; late double maxSize;
late double currentSize; late double currentSize;

View File

@@ -21,7 +21,7 @@ class _TabbarSetPageState extends State<TabbarSetPage> {
super.initState(); super.initState();
defaultTabs = tabsConfig; defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']); defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi']);
// 对 tabData 进行排序 // 对 tabData 进行排序
defaultTabs.sort((a, b) { defaultTabs.sort((a, b) {
int indexA = tabbarSort.indexOf((a['type'] as TabType).id); int indexA = tabbarSort.indexOf((a['type'] as TabType).id);

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart'; import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart';
import 'package:PiliPalaX/plugin/pl_player/index.dart'; import 'package:PiliPalaX/plugin/pl_player/index.dart';
@@ -76,10 +77,9 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
// 添加自定义倍速 // 添加自定义倍速
void onAddSpeed() { void onAddSpeed() {
double customSpeed = 1.0; double customSpeed = 1.0;
SmartDialog.show( showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('添加倍速'), title: const Text('添加倍速'),
content: Column( content: Column(
@@ -103,7 +103,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
@@ -112,7 +112,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
await videoStorage.put( await videoStorage.put(
VideoBoxKey.customSpeedsList, customSpeedsList); VideoBoxKey.customSpeedsList, customSpeedsList);
setState(() {}); setState(() {});
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('确认添加'), child: const Text('确认添加'),
) )

View File

@@ -120,7 +120,7 @@ class _PlaySettingState extends State<PlaySetting> {
), ),
const SetSwitchItem( const SetSwitchItem(
title: '竖屏扩大展示', title: '竖屏扩大展示',
subTitle: '小屏竖屏视频宽高比由16:9扩大至4:5不支持临时收起)', subTitle: '小屏竖屏视频宽高比由16:9扩大至1:1不支持收起)横屏适配时扩大至9:16',
leading: Icon(Icons.expand_outlined), leading: Icon(Icons.expand_outlined),
setKey: SettingBoxKey.enableVerticalExpand, setKey: SettingBoxKey.enableVerticalExpand,
defaultVal: false, defaultVal: false,
@@ -151,7 +151,7 @@ class _PlaySettingState extends State<PlaySetting> {
subTitle: '进入后台时继续播放', subTitle: '进入后台时继续播放',
leading: Icon(Icons.motion_photos_pause_outlined), leading: Icon(Icons.motion_photos_pause_outlined),
setKey: SettingBoxKey.continuePlayInBackground, setKey: SettingBoxKey.continuePlayInBackground,
defaultVal: true, defaultVal: false,
), ),
if (Platform.isAndroid) if (Platform.isAndroid)
SetSwitchItem( SetSwitchItem(

View File

@@ -121,7 +121,8 @@ class _PrivacySettingState extends State<PrivacySetting> {
)), )),
ListTile( ListTile(
onTap: () { onTap: () {
SmartDialog.show( showDialog(
context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('查看详情'), title: const Text('查看详情'),
@@ -130,7 +131,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('确认'), child: const Text('确认'),
) )
@@ -150,9 +151,9 @@ class _PrivacySettingState extends State<PrivacySetting> {
} }
void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) { void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) {
SmartDialog.show( showDialog(
useSystem: true, context: context,
builder: (BuildContext context) { builder: (context) {
return SimpleDialog( return SimpleDialog(
title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)), title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)),
children: [ children: [
@@ -175,13 +176,14 @@ class _PrivacySettingState extends State<PrivacySetting> {
), ),
dense: false, dense: false,
onTap: () async { onTap: () async {
await SmartDialog.dismiss(); Navigator.of(context).pop();
if (!userLogin) { if (!userLogin) {
SmartDialog.showToast('请先登录'); SmartDialog.showToast('请先登录');
return; return;
} }
final String cookie = await CookieTool.exportCookie(); final String cookie = await CookieTool.exportCookie();
await SmartDialog.show( await showDialog(
context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('导出cookie危险', title: const Text('导出cookie危险',
@@ -190,7 +192,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await SmartDialog.dismiss(); Navigator.of(context).pop();
await Clipboard.setData( await Clipboard.setData(
ClipboardData(text: cookie)); ClipboardData(text: cookie));
}, },
@@ -199,7 +201,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await SmartDialog.dismiss(); Navigator.of(context).pop();
}, },
child: const Text('取消'), child: const Text('取消'),
), ),
@@ -223,13 +225,14 @@ class _PrivacySettingState extends State<PrivacySetting> {
), ),
dense: false, dense: false,
onTap: () async { onTap: () async {
await SmartDialog.dismiss();
ClipboardData? data = await Clipboard.getData('text/plain'); ClipboardData? data = await Clipboard.getData('text/plain');
if (data == null || data.text == null || data.text == '') { if (data == null || data.text == null || data.text == '') {
SmartDialog.showToast('未检测到剪贴板内容'); SmartDialog.showToast('未检测到剪贴板内容');
return; return;
} }
await SmartDialog.show( if (!context.mounted) return;
await showDialog(
context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('导入剪贴板中的cookie'), title: const Text('导入剪贴板中的cookie'),
@@ -237,13 +240,13 @@ class _PrivacySettingState extends State<PrivacySetting> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await SmartDialog.dismiss(); Get.back();
}, },
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
await SmartDialog.dismiss(); Get.back();
final String cookie = data.text!; final String cookie = data.text!;
try { try {
await CookieTool.importCookie(cookie); await CookieTool.importCookie(cookie);

View File

@@ -6,6 +6,7 @@ import 'package:PiliPalaX/models/common/rcmd_type.dart';
import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart'; import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart';
import 'package:PiliPalaX/utils/recommend_filter.dart'; import 'package:PiliPalaX/utils/recommend_filter.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:get/get.dart';
import 'widgets/switch_item.dart'; import 'widgets/switch_item.dart';
@@ -93,8 +94,9 @@ class _RecommendSettingState extends State<RecommendSetting> {
return; return;
} }
// 显示一个确认框,告知用户可能会导致账号被风控 // 显示一个确认框,告知用户可能会导致账号被风控
SmartDialog.show( if (!context.mounted) return;
animationType: SmartAnimationType.centerFade_otherSlide, await showDialog(
context: context,
builder: (context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
@@ -104,14 +106,20 @@ class _RecommendSettingState extends State<RecommendSetting> {
TextButton( TextButton(
onPressed: () { onPressed: () {
result = null; result = null;
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('取消'), child: const Text('取消'),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Get.back();
await MemberHttp.cookieToKey(); var res = await MemberHttp.cookieToKey();
if (res['status']) {
SmartDialog.showToast(res['msg']);
} else {
SmartDialog.showToast(
'获取access_key失败${res['msg']}');
}
}, },
child: const Text('确定'), child: const Text('确定'),
), ),
@@ -254,10 +262,9 @@ class _RecommendSettingState extends State<RecommendSetting> {
'* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n' '* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n'
'* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n' '* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n'
'* 后续可能会增加更多过滤条件,敬请期待。', '* 后续可能会增加更多过滤条件,敬请期待。',
style: Theme.of(context) style: Theme.of(context).textTheme.labelSmall!.copyWith(
.textTheme color:
.labelSmall! Theme.of(context).colorScheme.outline.withOpacity(0.7)),
.copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)),
), ),
) )
], ],

View File

@@ -13,6 +13,7 @@ import 'package:PiliPalaX/utils/global_data.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/up_panel_position.dart';
import '../../plugin/pl_player/utils/fullscreen.dart'; import '../../plugin/pl_player/utils/fullscreen.dart';
import '../../models/common/nav_bar_config.dart'; import '../../models/common/nav_bar_config.dart';
import 'controller.dart'; import 'controller.dart';
@@ -34,13 +35,18 @@ class _StyleSettingState extends State<StyleSetting> {
late int picQuality; late int picQuality;
late ThemeType _tempThemeValue; late ThemeType _tempThemeValue;
late double maxRowWidth; late double maxRowWidth;
late UpPanelPosition upPanelPosition;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
_tempThemeValue = settingController.themeType.value; _tempThemeValue = settingController.themeType.value;
maxRowWidth = setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; maxRowWidth =
setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double;
upPanelPosition = UpPanelPosition.values[setting.get(
SettingBoxKey.upPanelPosition,
defaultValue: UpPanelPosition.leftFixed.code)];
} }
@override @override
@@ -70,15 +76,15 @@ class _StyleSettingState extends State<StyleSetting> {
callFn: (value) { callFn: (value) {
if (value) { if (value) {
autoScreen(); autoScreen();
SmartDialog.showToast('已开启横屏适配'); SmartDialog.showToast('已开启横屏适配,推荐将全屏方式设为【不改变当前方向】');
} else { } else {
AutoOrientation.portraitUpMode(); AutoOrientation.portraitUpMode();
SmartDialog.showToast('已关闭横屏适配'); SmartDialog.showToast('已关闭横屏适配');
} }
}), }),
const SetSwitchItem( const SetSwitchItem(
title: '自适应底栏/侧边栏', title: '改用侧边栏',
subTitle: '横竖屏自动切换(其它底栏设置失效', subTitle: '开启后底栏被替换,且底栏相关设置失效',
leading: Icon(Icons.chrome_reader_mode_outlined), leading: Icon(Icons.chrome_reader_mode_outlined),
setKey: SettingBoxKey.adaptiveNavBar, setKey: SettingBoxKey.adaptiveNavBar,
defaultVal: false, defaultVal: false,
@@ -90,6 +96,94 @@ class _StyleSettingState extends State<StyleSetting> {
setKey: SettingBoxKey.enableMYBar, setKey: SettingBoxKey.enableMYBar,
defaultVal: true, defaultVal: true,
), ),
const SetSwitchItem(
title: '首页背景渐变',
setKey: SettingBoxKey.enableGradientBg,
leading: Icon(Icons.gradient_outlined),
defaultVal: true,
needReboot: true,
),
ListTile(
onTap: () async {
double? result = await showDialog(
context: context,
builder: (context) {
return SlideDialog<double>(
title: '最大列宽度默认240dp',
value: maxRowWidth,
min: 150.0,
max: 500.0,
divisions: 35,
suffix: 'dp',
);
});
if (result != null) {
maxRowWidth = result;
setting.put(SettingBoxKey.maxRowWidth, result);
SmartDialog.showToast('重启生效');
setState(() {});
}
},
leading: const Icon(Icons.calendar_view_week_outlined),
dense: false,
title: Text('列表宽度dp限制', style: titleStyle),
subtitle: Text(
'当前:${maxRowWidth.toInt()}dp屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。'
'宽度越小列数越多横条、大卡会2倍折算',
style: subTitleStyle,
),
),
const SetSwitchItem(
title: '播放页移除安全边距',
subTitle: '隐藏状态栏、撑满屏幕,但播放控件仍处于安全域内',
leading: Icon(Icons.crop_outlined),
setKey: SettingBoxKey.videoPlayerRemoveSafeArea,
defaultVal: false,
needReboot: true,
),
const SetSwitchItem(
title: '动态页启用瀑布流',
subTitle: '关闭会显示为单列',
leading: Icon(Icons.view_array_outlined),
setKey: SettingBoxKey.dynamicsWaterfallFlow,
defaultVal: true,
needReboot: true,
),
ListTile(
dense: false,
title: Text('动态页Up主显示位置', style: titleStyle),
leading: const Icon(Icons.person_outlined),
subtitle: Text('当前:${upPanelPosition.labels}', style: subTitleStyle),
onTap: () async {
UpPanelPosition? result = await showDialog(
context: context,
builder: (context) {
return SelectDialog<UpPanelPosition>(
title: '动态页Up主显示位置',
value: upPanelPosition,
values: UpPanelPosition.values.map((e) {
return {'title': e.labels, 'value': e};
}).toList(),
);
},
);
if (result != null) {
upPanelPosition = result;
setting.put(SettingBoxKey.upPanelPosition, result.code);
SmartDialog.showToast('重启生效');
setState(() {});
}
},
),
ListTile(
dense: false,
onTap: () => settingController.setDynamicBadgeMode(context),
title: Text('动态未读标记', style: titleStyle),
leading: const Icon(Icons.motion_photos_on_outlined),
subtitle: Obx(() => Text(
'当前标记样式:${settingController.dynamicBadgeType.value.description}',
style: subTitleStyle)),
),
const SetSwitchItem( const SetSwitchItem(
title: '首页顶栏收起', title: '首页顶栏收起',
subTitle: '首页列表滑动时,收起顶栏', subTitle: '首页列表滑动时,收起顶栏',
@@ -106,44 +200,6 @@ class _StyleSettingState extends State<StyleSetting> {
defaultVal: false, defaultVal: false,
needReboot: true, needReboot: true,
), ),
const SetSwitchItem(
title: '首页背景渐变',
setKey: SettingBoxKey.enableGradientBg,
leading: Icon(Icons.gradient_outlined),
defaultVal: true,
needReboot: true,
),
ListTile(
onTap: () async {
double? result = await showDialog(
context: context,
builder: (context) {
return SlideDialog<double>(
title: '最大列宽度默认240dp',
value: maxRowWidth,
min: 150.0,
max: 500.0,
divisions: 35,
suffix: 'dp',
);
}
);
if (result != null) {
maxRowWidth = result;
setting.put(SettingBoxKey.maxRowWidth, result);
SmartDialog.showToast('重启生效');
setState(() {});
}
},
leading: const Icon(Icons.calendar_view_week_outlined),
dense: false,
title: Text('列表宽度dp上限', style: titleStyle),
subtitle: Text(
'当前:${maxRowWidth.toInt()}dp屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。'
'宽度越小列数越多横条、大卡会2倍折算',
style: subTitleStyle,
),
),
ListTile( ListTile(
dense: false, dense: false,
onTap: () { onTap: () {
@@ -235,6 +291,9 @@ class _StyleSettingState extends State<StyleSetting> {
leading: const Icon(Icons.opacity_outlined), leading: const Icon(Icons.opacity_outlined),
title: Text('气泡提示不透明度', style: titleStyle), title: Text('气泡提示不透明度', style: titleStyle),
subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle), subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle),
trailing: Obx(() => Text(
settingController.toastOpacity.value.toStringAsFixed(1),
style: Theme.of(context).textTheme.titleSmall)),
), ),
ListTile( ListTile(
dense: false, dense: false,
@@ -263,15 +322,6 @@ class _StyleSettingState extends State<StyleSetting> {
'当前模式:${settingController.themeType.value.description}', '当前模式:${settingController.themeType.value.description}',
style: subTitleStyle)), style: subTitleStyle)),
), ),
ListTile(
dense: false,
onTap: () => settingController.setDynamicBadgeMode(context),
title: Text('动态未读标记', style: titleStyle),
leading: const Icon(Icons.motion_photos_on_outlined),
subtitle: Obx(() => Text(
'当前标记样式:${settingController.dynamicBadgeType.value.description}',
style: subTitleStyle)),
),
ListTile( ListTile(
dense: false, dense: false,
onTap: () => Get.toNamed('/colorSetting'), onTap: () => Get.toNamed('/colorSetting'),
@@ -281,13 +331,6 @@ class _StyleSettingState extends State<StyleSetting> {
'当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}', '当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}',
style: subTitleStyle)), style: subTitleStyle)),
), ),
const SetSwitchItem(
title: '默认展示评论区',
subTitle: '在视频详情页默认切换至评论区页',
leading: Icon(Icons.mode_comment_outlined),
setKey: SettingBoxKey.defaultShowComment,
defaultVal: false,
),
ListTile( ListTile(
dense: false, dense: false,
onTap: () => settingController.seteDefaultHomePage(context), onTap: () => settingController.seteDefaultHomePage(context),

View File

@@ -56,7 +56,7 @@ class SettingPage extends StatelessWidget {
leading: const Icon(Icons.style_outlined), leading: const Icon(Icons.style_outlined),
dense: false, dense: false,
title: const Text('外观设置'), title: const Text('外观设置'),
subtitle: Text('横屏适配(平板)、列宽、首页、主题、字号、图片、动态红点、帧率等', style: subTitleStyle), subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等', style: subTitleStyle),
), ),
ListTile( ListTile(
onTap: () => Get.toNamed('/extraSetting'), onTap: () => Get.toNamed('/extraSetting'),
@@ -81,7 +81,7 @@ class SettingPage extends StatelessWidget {
visible: settingController.userLogin.value, visible: settingController.userLogin.value,
child: ListTile( child: ListTile(
leading: const Icon(Icons.logout_outlined), leading: const Icon(Icons.logout_outlined),
onTap: () => settingController.loginOut(), onTap: () => settingController.loginOut(context),
dense: false, dense: false,
title: const Text('退出登录'), title: const Text('退出登录'),
), ),

View File

@@ -58,17 +58,12 @@ class _SubPageState extends State<SubPage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
SliverGrid( SliverGrid(
gridDelegate: gridDelegate: SliverGridDelegateWithExtentAndRatio(
SliverGridDelegateWithMaxCrossAxisExtent( mainAxisSpacing: StyleString.cardSpace,
mainAxisSpacing: StyleString.cardSpace, crossAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace, maxCrossAxisExtent: Grid.maxRowWidth * 2,
maxCrossAxisExtent: Grid.maxRowWidth * 2, childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: Grid.calculateActualWidth( mainAxisExtent: 0),
context,
Grid.maxRowWidth * 2,
StyleString.safeSpace) /
2.1 /
StyleString.aspectRatio),
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
childCount: childCount:
_subController.subFolderData.value.list!.length, _subController.subFolderData.value.list!.length,

View File

@@ -266,7 +266,7 @@ class VideoDetailController extends GetxController
type: DataSourceType.network, type: DataSourceType.network,
httpHeaders: { httpHeaders: {
'user-agent': 'user-agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'referer': HttpString.baseUrl 'referer': HttpString.baseUrl
}, },
), ),
@@ -329,7 +329,7 @@ class VideoDetailController extends GetxController
return result; return result;
} }
final List<VideoItem> allVideosList = data.dash!.video!; final List<VideoItem> allVideosList = data.dash!.video!;
print("allVideosList:${allVideosList}"); // print("allVideosList:${allVideosList}");
// 当前可播放的最高质量视频 // 当前可播放的最高质量视频
int currentHighVideoQa = allVideosList.first.quality!.code; int currentHighVideoQa = allVideosList.first.quality!.code;
// 预设的画质为null则当前可用的最高质量 // 预设的画质为null则当前可用的最高质量
@@ -426,6 +426,7 @@ class VideoDetailController extends GetxController
} else { } else {
if (result['code'] == -404) { if (result['code'] == -404) {
isShowCover.value = false; isShowCover.value = false;
SmartDialog.showToast('视频不存在或已被删除');
} }
if (result['code'] == 87008) { if (result['code'] == 87008) {
SmartDialog.showToast("当前视频可能是专属视频,可能需包月充电观看(${result['msg']})"); SmartDialog.showToast("当前视频可能是专属视频,可能需包月充电观看(${result['msg']})");

View File

@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@@ -42,6 +43,8 @@ class VideoIntroController extends GetxController {
// 是否点赞 // 是否点赞
RxBool hasLike = false.obs; RxBool hasLike = false.obs;
// 是否点踩
RxBool hasDislike = false.obs;
// 是否投币 // 是否投币
RxBool hasCoin = false.obs; RxBool hasCoin = false.obs;
// 是否收藏 // 是否收藏
@@ -145,15 +148,16 @@ class VideoIntroController extends GetxController {
// 获取点赞状态 // 获取点赞状态
Future queryHasLikeVideo() async { Future queryHasLikeVideo() async {
var result = await VideoHttp.hasLikeVideo(bvid: bvid); var result = await VideoHttp.hasLikeVideo(bvid: bvid);
// data num 被点赞标志 0未点赞 1已点赞 // data num 被点赞标志 0未点赞 1已点赞 2已点踩
hasLike.value = result["data"] == 1 ? true : false; hasLike.value = result["data"] == 1;
hasDislike.value = result["data"] == 2;
} }
// 获取投币状态 // 获取投币状态
Future queryHasCoinVideo() async { Future queryHasCoinVideo() async {
var result = await VideoHttp.hasCoinVideo(bvid: bvid); var result = await VideoHttp.hasCoinVideo(bvid: bvid);
if (result['status']) { if (result['status']) {
hasCoin.value = result["data"]['multiply'] == 0 ? false : true; hasCoin.value = result["data"]['multiply'] != 0;
} }
} }
@@ -170,7 +174,7 @@ class VideoIntroController extends GetxController {
} }
// 一键三连 // 一键三连
Future actionOneThree() async { Future actionOneThree(BuildContext context) async {
if (userInfo == null) { if (userInfo == null) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
@@ -180,19 +184,19 @@ class VideoIntroController extends GetxController {
SmartDialog.showToast('🙏 UP已经收到了'); SmartDialog.showToast('🙏 UP已经收到了');
return false; return false;
} }
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('一键三连 给UP送温暖'), content: const Text('一键三连 给UP送温暖'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: const Text('点错了')), child: const Text('点错了')),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
Get.back();
var result = await VideoHttp.oneThree(bvid: bvid); var result = await VideoHttp.oneThree(bvid: bvid);
if (result['status']) { if (result['status']) {
hasLike.value = result["data"]["like"]; hasLike.value = result["data"]["like"];
@@ -202,7 +206,6 @@ class VideoIntroController extends GetxController {
} else { } else {
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
SmartDialog.dismiss();
}, },
child: const Text('确认'), child: const Text('确认'),
) )
@@ -224,6 +227,7 @@ class VideoIntroController extends GetxController {
if (!hasLike.value) { if (!hasLike.value) {
SmartDialog.showToast('点赞成功'); SmartDialog.showToast('点赞成功');
hasLike.value = true; hasLike.value = true;
hasDislike.value = false;
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1; videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
} else if (hasLike.value) { } else if (hasLike.value) {
SmartDialog.showToast('取消赞'); SmartDialog.showToast('取消赞');
@@ -236,6 +240,29 @@ class VideoIntroController extends GetxController {
} }
} }
Future actionDislikeVideo() async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
var result =
await VideoHttp.dislikeVideo(bvid: bvid, type: !hasDislike.value);
if (result['status']) {
// hasLike.value = result["data"] == 1 ? true : false;
if (!hasDislike.value) {
SmartDialog.showToast('点踩成功');
hasDislike.value = true;
hasLike.value = false;
} else {
SmartDialog.showToast('取消踩');
hasDislike.value = false;
}
// hasDislike.refresh();
} else {
SmartDialog.showToast(result['msg']);
}
}
// 投币 // 投币
Future actionCoinVideo() async { Future actionCoinVideo() async {
if (userInfo == null) { if (userInfo == null) {
@@ -350,10 +377,33 @@ class VideoIntroController extends GetxController {
// 分享视频 // 分享视频
Future actionShareVideo() async { Future actionShareVideo() async {
var result = await Share.share( showDialog(
'${videoDetail.value.title} UP主: ${videoDetail.value.owner!.name!} - ${HttpString.baseUrl}/video/$bvid') context: Get.context!,
.whenComplete(() {}); builder: (context) {
return result; String videoUrl = '${HttpString.baseUrl}/video/$bvid';
return AlertDialog(
title: const Text('分享方式'),
actions: [
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: videoUrl));
SmartDialog.showToast('已复制');
Get.back();
},
child: const Text('复制链接')),
TextButton(
onPressed: () async {
var result = await Share.share('${videoDetail.value.title} '
'UP主: ${videoDetail.value.owner!.name!}'
' - $videoUrl')
.whenComplete(() {});
Get.back();
return result;
},
child: const Text('分享视频')),
],
);
});
} }
Future queryVideoInFolder() async { Future queryVideoInFolder() async {
@@ -394,7 +444,7 @@ class VideoIntroController extends GetxController {
} }
// 关注/取关up // 关注/取关up
Future actionRelationMod() async { Future actionRelationMod(BuildContext context) async {
feedBack(); feedBack();
if (userInfo == null) { if (userInfo == null) {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
@@ -413,16 +463,15 @@ class VideoIntroController extends GetxController {
actionStatus = 0; actionStatus = 0;
break; break;
} }
SmartDialog.show( await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'), content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'点错了', '点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -465,7 +514,7 @@ class VideoIntroController extends GetxController {
} }
} }
} }
SmartDialog.dismiss(); Get.back();
}, },
child: const Text('确认'), child: const Text('确认'),
) )

View File

@@ -244,6 +244,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData t = Theme.of(context); final ThemeData t = Theme.of(context);
final Color outline = t.colorScheme.outline; final Color outline = t.colorScheme.outline;
bool isHorizontal = context.width > context.height * 1.25;
return SliverPadding( return SliverPadding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10), left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
@@ -252,20 +253,132 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row(children: [
Expanded(
child: GestureDetector(
onTap: onPushMember,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 1, horizontal: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NetworkImgLayer(
type: 'avatar',
src: loadingStatus
? owner.face
: widget.videoDetail!.owner!.face,
width: 30,
height: 30,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
owner.name,
style: TextStyle(
fontSize: 12,
color: t.colorScheme.primary),
// semanticsLabel: "Up主${owner.name}",
),
const SizedBox(height: 0),
Text(
follower,
semanticsLabel: "$follower粉丝",
style: TextStyle(
fontSize: 12,
color: outline,
),
),
]),
const Spacer(),
Obx(() => AnimatedOpacity(
opacity: loadingStatus ||
videoIntroController
.followStatus.isEmpty
? 0
: 1,
duration: const Duration(milliseconds: 50),
child: SizedBox(
height: 32,
child: Obx(
() => videoIntroController
.followStatus.isNotEmpty
? TextButton(
onPressed: () =>
videoIntroController
.actionRelationMod(
context),
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
left: 8, right: 8),
foregroundColor:
followStatus['attribute'] !=
0
? outline
: t.colorScheme
.onPrimary,
backgroundColor:
followStatus['attribute'] !=
0
? t.colorScheme
.onInverseSurface
: t.colorScheme
.primary, // 设置按钮背景色
),
child: Text(
followStatus['attribute'] != 0
? '已关注'
: '关注',
style: TextStyle(
fontSize: t.textTheme
.labelMedium!.fontSize),
),
)
: ElevatedButton(
onPressed: () =>
videoIntroController
.actionRelationMod(
context),
child: const Text('关注'),
),
),
),
)),
],
),
),
)),
if (isHorizontal)
Expanded(
child: actionGrid(context, videoIntroController)),
]),
const SizedBox(height: 8),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(), onTap: () => showIntroDetail(),
child: Text( child: Row(children: [
!loadingStatus Expanded(
? widget.videoDetail!.title child: Text(
: videoItem['title'], !loadingStatus
style: const TextStyle( ? widget.videoDetail!.title
fontSize: 18, : videoItem['title'],
fontWeight: FontWeight.bold, style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: t.colorScheme.outline,
), ),
maxLines: 2, ]),
overflow: TextOverflow.ellipsis,
),
), ),
Stack( Stack(
children: [ children: [
@@ -363,7 +476,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// ), // ),
// ), // ),
// 点赞收藏转发 布局样式2 // 点赞收藏转发 布局样式2
actionGrid(context, videoIntroController), if (!isHorizontal) actionGrid(context, videoIntroController),
// 合集 // 合集
if (!loadingStatus && if (!loadingStatus &&
widget.videoDetail!.ugcSeason != null) ...[ widget.videoDetail!.ugcSeason != null) ...[
@@ -387,93 +500,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
changeFuc: videoIntroController.changeSeasonOrbangu, changeFuc: videoIntroController.changeSeasonOrbangu,
)) ))
], ],
GestureDetector(
onTap: onPushMember,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 4),
child: Row(
children: [
NetworkImgLayer(
type: 'avatar',
src: loadingStatus
? owner.face
: widget.videoDetail!.owner!.face,
width: 34,
height: 34,
fadeInDuration: Duration.zero,
fadeOutDuration: Duration.zero,
),
const SizedBox(width: 10),
Text(
owner.name,
style: const TextStyle(fontSize: 13),
// semanticsLabel: "Up主${owner.name}",
),
const SizedBox(width: 6),
Text(
follower,
semanticsLabel: "粉丝数:$follower",
style: TextStyle(
fontSize: t.textTheme.labelSmall!.fontSize,
color: outline,
),
),
const Spacer(),
Obx(() => AnimatedOpacity(
opacity: loadingStatus ||
videoIntroController
.followStatus.isEmpty
? 0
: 1,
duration: const Duration(milliseconds: 50),
child: SizedBox(
height: 32,
child: Obx(
() => videoIntroController
.followStatus.isNotEmpty
? TextButton(
onPressed: videoIntroController
.actionRelationMod,
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
left: 8, right: 8),
foregroundColor:
followStatus['attribute'] != 0
? outline
: t.colorScheme.onPrimary,
backgroundColor:
followStatus['attribute'] != 0
? t.colorScheme
.onInverseSurface
: t.colorScheme
.primary, // 设置按钮背景色
),
child: Text(
followStatus['attribute'] != 0
? '已关注'
: '关注',
style: TextStyle(
fontSize: t.textTheme
.labelMedium!.fontSize),
),
)
: ElevatedButton(
onPressed: videoIntroController
.actionRelationMod,
child: const Text('关注'),
),
),
),
)),
],
),
),
),
], ],
) )
: const SizedBox( : const SizedBox(
height: 100, height: 130,
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
@@ -503,6 +533,16 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
? Utils.numFormat(widget.videoDetail!.stat!.like!) ? Utils.numFormat(widget.videoDetail!.stat!.like!)
: '-'), : '-'),
), ),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsDown),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: handleState(videoIntroController.actionDislikeVideo),
selectStatus: videoIntroController.hasDislike.value,
loadingStatus: loadingStatus,
semanticsLabel: '点踩',
text: "点踩"),
),
// ActionItem( // ActionItem(
// icon: const Icon(FontAwesomeIcons.clock), // icon: const Icon(FontAwesomeIcons.clock),
// onTap: () => videoIntroController.actionShareVideo(), // onTap: () => videoIntroController.actionShareVideo(),
@@ -536,7 +576,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.comment), icon: const Icon(FontAwesomeIcons.comment),
onTap: () => videoDetailCtr.tabCtr.animateTo(1), onTap: () => videoDetailCtr.tabCtr
.animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1),
selectStatus: false, selectStatus: false,
loadingStatus: loadingStatus, loadingStatus: loadingStatus,
semanticsLabel: '评论', semanticsLabel: '评论',

View File

@@ -104,6 +104,7 @@ class IntroDetail extends StatelessWidget {
), ),
), ),
), ),
const SizedBox(height: 100),
], ],
), ),
), ),

View File

@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/overlay_pop.dart'; import 'package:PiliPalaX/common/widgets/overlay_pop.dart';
import 'package:PiliPalaX/common/widgets/video_card_h.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart';
import '../../../../common/constants.dart';
import '../../../../utils/grid.dart';
import './controller.dart'; import './controller.dart';
class RelatedVideoPanel extends StatefulWidget { class RelatedVideoPanel extends StatefulWidget {
@@ -33,61 +35,75 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return FutureBuilder( return SliverPadding(
future: _futureBuilder, padding: const EdgeInsets.all(StyleString.safeSpace),
builder: (BuildContext context, AsyncSnapshot snapshot) { sliver: FutureBuilder(
if (snapshot.connectionState == ConnectionState.done) { future: _futureBuilder,
if (snapshot.data == null) { builder: (BuildContext context, AsyncSnapshot snapshot) {
return const SliverToBoxAdapter(child: SizedBox()); if (snapshot.connectionState == ConnectionState.done) {
} if (snapshot.data == null) {
if (snapshot.data!['status'] && snapshot.data != null) { return const SliverToBoxAdapter(child: SizedBox());
RxList relatedVideoList = _releatedController.relatedVideoList; }
// 请求成功 if (snapshot.data!['status'] && snapshot.hasData) {
return Obx( RxList relatedVideoList = _releatedController.relatedVideoList;
() => SliverList( // 请求成功
return Obx(
() => SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) {
if (index == relatedVideoList.length) {
return SizedBox(
height: MediaQuery.of(context).padding.bottom);
} else {
return Material(
child: VideoCardH(
videoItem: relatedVideoList[index],
showPubdate: true,
longPress: () {
try {
_releatedController.popupDialog =
_createPopupDialog(_releatedController
.relatedVideoList[index]);
Overlay.of(context)
.insert(_releatedController.popupDialog!);
} catch (err) {
return {};
}
},
longPressEnd: () {
_releatedController.popupDialog?.remove();
},
),
);
}
}, childCount: relatedVideoList.length + 1),
),
);
} else {
// 请求错误
return HttpError(errMsg: '出错了', fn: () {});
}
} else {
// 骨架屏
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.3,
mainAxisExtent: 0),
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
if (index == relatedVideoList.length) { return const VideoCardHSkeleton();
return SizedBox( }, childCount: 5),
height: MediaQuery.of(context).padding.bottom); );
} else { }
return Material( },
child: VideoCardH( ));
videoItem: relatedVideoList[index],
showPubdate: true,
longPress: () {
try {
_releatedController.popupDialog =
_createPopupDialog(_releatedController
.relatedVideoList[index]);
Overlay.of(context)
.insert(_releatedController.popupDialog!);
} catch (err) {
return {};
}
},
longPressEnd: () {
_releatedController.popupDialog?.remove();
},
),
);
}
}, childCount: relatedVideoList.length + 1),
),
);
} else {
// 请求错误
return HttpError(errMsg: '出错了', fn: () {});
}
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 5),
);
}
},
);
} }
OverlayEntry _createPopupDialog(videoItem) { OverlayEntry _createPopupDialog(videoItem) {

View File

@@ -59,6 +59,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late bool enableVerticalExpand; late bool enableVerticalExpand;
late bool autoPiP; late bool autoPiP;
late bool pipNoDanmaku; late bool pipNoDanmaku;
late bool removeSafeArea;
final Floating floating = Floating(); final Floating floating = Floating();
// 生命周期监听 // 生命周期监听
// late final AppLifecycleListener _lifecycleListener; // late final AppLifecycleListener _lifecycleListener;
@@ -66,7 +67,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
RxBool isFullScreen = false.obs; RxBool isFullScreen = false.obs;
late StreamSubscription<bool> fullScreenStatusListener; late StreamSubscription<bool> fullScreenStatusListener;
late final MethodChannel onUserLeaveHintListener; late final MethodChannel onUserLeaveHintListener;
StreamSubscription<Duration>? _bufferedListener; // StreamSubscription<Duration>? _bufferedListener;
@override @override
void initState() { void initState() {
@@ -97,6 +98,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
pipNoDanmaku = setting.get(SettingBoxKey.pipNoDanmaku, defaultValue: true); pipNoDanmaku = setting.get(SettingBoxKey.pipNoDanmaku, defaultValue: true);
enableVerticalExpand = enableVerticalExpand =
setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false); setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false);
removeSafeArea = setting.get(SettingBoxKey.videoPlayerRemoveSafeArea,
defaultValue: false);
if (removeSafeArea) hideStatusBar();
videoSourceInit(); videoSourceInit();
appbarStreamListen(); appbarStreamListen();
// lifecycleListener(); // lifecycleListener();
@@ -146,6 +150,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
if (status == PlayerStatus.completed) { if (status == PlayerStatus.completed) {
shutdownTimerService.handleWaitingFinished(); shutdownTimerService.handleWaitingFinished();
bool notExitFlag = false; bool notExitFlag = false;
/// 顺序播放 列表循环 /// 顺序播放 列表循环
if (plPlayerController!.playRepeat != PlayRepeat.pause && if (plPlayerController!.playRepeat != PlayRepeat.pause &&
plPlayerController!.playRepeat != PlayRepeat.singleCycle) { plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
@@ -246,7 +251,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
AutoOrientation.portraitUpMode(); AutoOrientation.portraitUpMode();
} }
shutdownTimerService.handleWaitingFinished(); shutdownTimerService.handleWaitingFinished();
_bufferedListener?.cancel(); // _bufferedListener?.cancel();
if (plPlayerController != null) { if (plPlayerController != null) {
plPlayerController!.removeStatusLister(playerListener); plPlayerController!.removeStatusLister(playerListener);
fullScreenStatusListener.cancel(); fullScreenStatusListener.cancel();
@@ -262,7 +267,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
@override @override
// 离开当前页面时 // 离开当前页面时
void didPushNext() async { void didPushNext() async {
_bufferedListener?.cancel(); // _bufferedListener?.cancel();
/// 开启 /// 开启
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false) if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)
@@ -392,10 +397,57 @@ class _VideoDetailPageState extends State<VideoDetailPage>
return const SizedBox(); return const SizedBox();
} }
}); });
Widget manualPlayerWidget = Obx(
() => Visibility(
visible: videoDetailController.isShowCover.value &&
videoDetailController.isEffective.value,
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: AppBar(
primary: false,
foregroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: Colors.transparent,
actions: [
IconButton(
tooltip: '稍后再看',
onPressed: () async {
var res = await UserHttp.toViewLater(
bvid: videoDetailController.bvid);
SmartDialog.showToast(res['msg']);
},
icon: const Icon(Icons.history_outlined),
),
const SizedBox(width: 14)
],
),
),
Positioned(
right: 12,
bottom: 10,
child: IconButton(
tooltip: '播放',
onPressed: () => handlePlay(),
icon: Image.asset(
'assets/images/play.png',
width: 60,
height: 60,
)),
),
],
)),
);
Widget childWhenDisabled = SafeArea( Widget childWhenDisabled = SafeArea(
top: MediaQuery.of(context).orientation == Orientation.portrait && top: !removeSafeArea &&
MediaQuery.of(context).orientation == Orientation.portrait &&
isFullScreen.value == true, isFullScreen.value == true,
bottom: MediaQuery.of(context).orientation == Orientation.portrait && bottom: !removeSafeArea &&
MediaQuery.of(context).orientation == Orientation.portrait &&
isFullScreen.value == true, isFullScreen.value == true,
left: false, //isFullScreen != true, left: false, //isFullScreen != true,
right: false, //isFullScreen != true, right: false, //isFullScreen != true,
@@ -404,17 +456,26 @@ class _VideoDetailPageState extends State<VideoDetailPage>
Scaffold( Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey, key: videoDetailController.scaffoldKey,
backgroundColor: Colors.black, // backgroundColor: Colors.black,
appBar: PreferredSize( appBar: removeSafeArea
preferredSize: const Size.fromHeight(0), ? null
child: AppBar( : AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.black,
elevation: 0, elevation: 0,
// systemOverlayStyle: const SystemUiOverlayStyle( toolbarHeight: 0,
// statusBarColor: Colors.transparent, systemOverlayStyle: const SystemUiOverlayStyle(
// statusBarIconBrightness: Brightness.light), statusBarIconBrightness: Brightness.light),
), ),
), // appBar: PreferredSize(
// preferredSize: const Size.fromHeight(0),
// child: AppBar(
// backgroundColor: Colors.transparent,
// elevation: 0,
// systemOverlayStyle: const SystemUiOverlayStyle(
// statusBarColor: Colors.transparent,
// statusBarIconBrightness: Brightness.light),
// ),
// ),
body: Column( body: Column(
children: [ children: [
Obx( Obx(
@@ -424,7 +485,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// print(videoDetailController.tabCtr.index); // print(videoDetailController.tabCtr.index);
if (enableVerticalExpand && if (enableVerticalExpand &&
plPlayerController?.direction.value == 'vertical') { plPlayerController?.direction.value == 'vertical') {
videoheight = context.width * 5 / 4; videoheight = context.width;
} }
if (MediaQuery.of(context).orientation == if (MediaQuery.of(context).orientation ==
Orientation.landscape && Orientation.landscape &&
@@ -439,15 +500,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
!isFullScreen.value && !isFullScreen.value &&
isShowing && isShowing &&
mounted) { mounted) {
showStatusBar(); if (!removeSafeArea) showStatusBar();
} }
return SizedBox( return Container(
color: Colors.black,
height: MediaQuery.of(context).orientation == height: MediaQuery.of(context).orientation ==
Orientation.landscape || Orientation.landscape ||
isFullScreen.value == true isFullScreen.value == true
? MediaQuery.sizeOf(context).height - ? MediaQuery.sizeOf(context).height -
(MediaQuery.of(context).orientation == (MediaQuery.of(context).orientation ==
Orientation.landscape Orientation.landscape ||
removeSafeArea
? 0 ? 0
: MediaQuery.of(context).padding.top) : MediaQuery.of(context).padding.top)
: videoheight, : videoheight,
@@ -498,59 +561,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
), ),
), ),
Obx( manualPlayerWidget,
() => Visibility(
visible: videoDetailController
.isShowCover.value &&
videoDetailController
.isEffective.value,
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: AppBar(
primary: false,
foregroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor:
Colors.transparent,
actions: [
IconButton(
tooltip: '稍后再看',
onPressed: () async {
var res = await UserHttp
.toViewLater(
bvid:
videoDetailController
.bvid);
SmartDialog.showToast(
res['msg']);
},
icon: const Icon(
Icons.history_outlined),
),
const SizedBox(width: 14)
],
),
),
Positioned(
right: 12,
bottom: 10,
child: IconButton(
tooltip: '播放',
onPressed: () => handlePlay(),
icon: Image.asset(
'assets/images/play.png',
width: 60,
height: 60,
)),
),
],
)),
),
] ]
], ],
)), )),
@@ -629,15 +640,308 @@ class _VideoDetailPageState extends State<VideoDetailPage>
], ],
), ),
); );
Widget childWhenDisabledAlmostSquareInner = Obx(() {
if (enableVerticalExpand &&
plPlayerController?.direction.value == 'vertical') {
final double videoheight = context.height -
(removeSafeArea
? 0
: (MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom));
final double videowidth = videoheight * 9 / 16;
return Row(children: [
SizedBox(
height: videoheight,
width: isFullScreen.value == true ? context.width : videowidth,
child: PopScope(
canPop: isFullScreen.value != true,
onPopInvoked: (bool didPop) {
if (isFullScreen.value == true) {
plPlayerController!.triggerFullScreen(status: false);
}
if (MediaQuery.of(context).orientation ==
Orientation.landscape &&
!horizontalScreen) {
verticalScreenForTwoSeconds();
}
},
child: Stack(children: <Widget>[
if (isShowing) plPlayer,
/// 关闭自动播放时 手动播放
if (!videoDetailController.autoPlay.value) ...<Widget>[
Obx(
() => Visibility(
visible: videoDetailController.isShowCover.value,
child: Positioned(
top: 0,
left: 0,
right: 0,
child: GestureDetector(
onTap: () {
handlePlay();
},
child: NetworkImgLayer(
type: 'emote',
src: videoDetailController.videoItem['pic'],
width: videowidth,
height: videoheight,
),
),
),
),
),
manualPlayerWidget,
]
]),
),
),
Expanded(
child: TabBarView(
physics: const BouncingScrollPhysics(),
controller: videoDetailController.tabCtr,
children: <Widget>[
CustomScrollView(
key: const PageStorageKey<String>('简介'),
slivers: <Widget>[
if (videoDetailController.videoType ==
SearchType.video) ...[
const VideoIntroPanel(),
] else if (videoDetailController.videoType ==
SearchType.media_bangumi) ...[
Obx(() => BangumiIntroPanel(
cid: videoDetailController.cid.value)),
],
SliverToBoxAdapter(
child: Divider(
indent: 12,
endIndent: 12,
color: Theme.of(context).dividerColor.withOpacity(0.06),
),
),
const RelatedVideoPanel(),
],
),
Obx(
() => VideoReplyPanel(
bvid: videoDetailController.bvid,
oid: videoDetailController.oid.value,
),
)
],
),
),
]);
}
final double videoheight = context.height / 2.5;
final double videowidth = context.width;
return Column(
children: [
SizedBox(
width: videowidth,
height: isFullScreen.value == true
? context.height -
(removeSafeArea
? 0
: (MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom))
: videoheight,
child: PopScope(
canPop: isFullScreen.value != true,
onPopInvoked: (bool didPop) {
if (isFullScreen.value == true) {
plPlayerController!.triggerFullScreen(status: false);
}
if (MediaQuery.of(context).orientation ==
Orientation.landscape &&
!horizontalScreen) {
verticalScreenForTwoSeconds();
}
},
child: Stack(children: <Widget>[
if (isShowing) plPlayer,
/// 关闭自动播放时 手动播放
if (!videoDetailController.autoPlay.value) ...<Widget>[
Obx(
() => Visibility(
visible: videoDetailController.isShowCover.value,
child: Positioned(
top: 0,
left: 0,
right: 0,
child: GestureDetector(
onTap: () {
handlePlay();
},
child: NetworkImgLayer(
type: 'emote',
src: videoDetailController.videoItem['pic'],
width: videowidth,
height: videoheight,
),
),
),
),
),
manualPlayerWidget,
]
]),
),
),
Expanded(
child: Row(children: [
Expanded(
child: CustomScrollView(
key: PageStorageKey<String>('简介${videoDetailController.bvid}'),
slivers: <Widget>[
if (videoDetailController.videoType == SearchType.video) ...[
const VideoIntroPanel(),
const RelatedVideoPanel(),
] else if (videoDetailController.videoType ==
SearchType.media_bangumi) ...[
Obx(() =>
BangumiIntroPanel(cid: videoDetailController.cid.value)),
]
],
)),
Expanded(
child: Obx(
() => VideoReplyPanel(
bvid: videoDetailController.bvid,
oid: videoDetailController.oid.value,
),
),
)
]))
],
);
});
Widget childWhenDisabledLandscapeInner = Obx(() { Widget childWhenDisabledLandscapeInner = Obx(() {
// 系数是以下三个方程(分别代表特定平板、折叠屏内屏、普通手机横屏尺寸)的近似解 if (enableVerticalExpand &&
// 820x+1180y+983.67z=450 plPlayerController?.direction.value == 'vertical') {
// 1812x+2176y+1985.68z=680 final double videoheight = context.height -
// 1080x+2340y+1589.72z=560 (removeSafeArea
final double videoheight = sqrt(context.height * context.width) * 12.555 - ? 0
context.height * 7.690 - : (MediaQuery.of(context).padding.top +
context.width * 4.741; MediaQuery.of(context).padding.bottom));
final double videowidth = videoheight * 16 / 9; final double videowidth = videoheight * 9 / 16;
return Row(
children: [
SizedBox(
height: videoheight,
width: isFullScreen.value == true ? context.width : videowidth,
child: PopScope(
canPop: isFullScreen.value != true,
onPopInvoked: (bool didPop) {
if (isFullScreen.value == true) {
plPlayerController!.triggerFullScreen(status: false);
}
if (MediaQuery.of(context).orientation ==
Orientation.landscape &&
!horizontalScreen) {
verticalScreenForTwoSeconds();
}
},
child: Stack(
children: <Widget>[
if (isShowing) plPlayer,
/// 关闭自动播放时 手动播放
if (!videoDetailController.autoPlay.value) ...<Widget>[
Obx(
() => Visibility(
visible: videoDetailController.isShowCover.value,
child: Positioned(
top: 0,
left: 0,
right: 0,
child: GestureDetector(
onTap: () {
handlePlay();
},
child: NetworkImgLayer(
type: 'emote',
src: videoDetailController.videoItem['pic'],
width: videowidth,
height: videoheight,
),
),
),
),
),
manualPlayerWidget,
]
],
),
),
),
Expanded(
child: Row(children: [
Expanded(
child: CustomScrollView(
key: PageStorageKey<String>('简介${videoDetailController.bvid}'),
slivers: <Widget>[
if (videoDetailController.videoType == SearchType.video) ...[
const VideoIntroPanel(),
const RelatedVideoPanel(),
] else if (videoDetailController.videoType ==
SearchType.media_bangumi) ...[
Obx(() => BangumiIntroPanel(
cid: videoDetailController.cid.value)),
]
],
)),
Expanded(
child: Obx(
() => VideoReplyPanel(
bvid: videoDetailController.bvid,
oid: videoDetailController.oid.value,
),
),
)
]))
// Expanded(
// child: TabBarView(
// physics: const BouncingScrollPhysics(),
// controller: videoDetailController.tabCtr,
// children: <Widget>[
// CustomScrollView(
// key: const PageStorageKey<String>('简介'),
// slivers: <Widget>[
// if (videoDetailController.videoType ==
// SearchType.video) ...[
// const VideoIntroPanel(),
// ] else if (videoDetailController.videoType ==
// SearchType.media_bangumi) ...[
// Obx(() => BangumiIntroPanel(
// cid: videoDetailController.cid.value)),
// ],
// SliverToBoxAdapter(
// child: Divider(
// indent: 12,
// endIndent: 12,
// color: Theme.of(context).dividerColor.withOpacity(0.06),
// ),
// ),
// const RelatedVideoPanel(),
// ],
// ),
// Obx(
// () => VideoReplyPanel(
// bvid: videoDetailController.bvid,
// oid: videoDetailController.oid.value,
// ),
// )
// ],
// ),
// ),
],
);
}
final double videowidth =
max(context.height / context.width * 1.04, 1 / 2) * context.width;
final double videoheight = videowidth * 9 / 16;
return Row( return Row(
children: [ children: [
Column( Column(
@@ -689,57 +993,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
), ),
), ),
Obx( manualPlayerWidget,
() => Visibility(
visible: videoDetailController
.isShowCover.value &&
videoDetailController.isEffective.value,
child: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: AppBar(
primary: false,
foregroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor: Colors.transparent,
actions: [
IconButton(
tooltip: '稍后再看',
onPressed: () async {
var res =
await UserHttp.toViewLater(
bvid:
videoDetailController
.bvid);
SmartDialog.showToast(
res['msg']);
},
icon: const Icon(
Icons.history_outlined),
),
const SizedBox(width: 14)
],
),
),
Positioned(
right: 12,
bottom: 10,
child: IconButton(
tooltip: '播放',
onPressed: () => handlePlay(),
icon: Image.asset(
'assets/images/play.png',
width: 60,
height: 60,
)),
),
],
)),
),
] ]
], ],
))), ))),
@@ -750,8 +1004,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
? 0 ? 0
: context.height - : context.height -
videoheight - videoheight -
MediaQuery.of(context).padding.top - (removeSafeArea
MediaQuery.of(context).padding.bottom, ? 0
: (MediaQuery.of(context).padding.top +
MediaQuery.of(context).padding.bottom)),
child: CustomScrollView( child: CustomScrollView(
key: PageStorageKey<String>( key: PageStorageKey<String>(
'简介${videoDetailController.bvid}'), '简介${videoDetailController.bvid}'),
@@ -759,6 +1015,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
if (videoDetailController.videoType == if (videoDetailController.videoType ==
SearchType.video) ...[ SearchType.video) ...[
const VideoIntroPanel(), const VideoIntroPanel(),
const RelatedVideoPanel(),
] else if (videoDetailController.videoType == ] else if (videoDetailController.videoType ==
SearchType.media_bangumi) ...[ SearchType.media_bangumi) ...[
Obx(() => BangumiIntroPanel( Obx(() => BangumiIntroPanel(
@@ -769,59 +1026,88 @@ class _VideoDetailPageState extends State<VideoDetailPage>
], ],
), ),
SizedBox( SizedBox(
width: isFullScreen.value == true width: isFullScreen.value == true
? 0 ? 0
: (context.width - : (context.width -
MediaQuery.of(context).padding.left - videowidth -
MediaQuery.of(context).padding.right - (removeSafeArea
videowidth), ? 0
height: context.height - : (MediaQuery.of(context).padding.left +
MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.right))),
MediaQuery.of(context).padding.bottom, height: context.height -
child: TabBarView( (removeSafeArea
physics: const BouncingScrollPhysics(), ? 0
controller: videoDetailController.tabCtr, : (MediaQuery.of(context).padding.top +
children: <Widget>[ MediaQuery.of(context).padding.bottom)),
if (videoDetailController.videoType == SearchType.video) child:
const CustomScrollView( // TabBarView(
slivers: [ // physics: const BouncingScrollPhysics(),
RelatedVideoPanel(), // controller: videoDetailController.tabCtr,
], // children: <Widget>[
), // if (videoDetailController.videoType == SearchType.video)
Obx( // const CustomScrollView(
() => VideoReplyPanel( // slivers: [
bvid: videoDetailController.bvid, // RelatedVideoPanel(),
oid: videoDetailController.oid.value, // ],
), // ),
) Obx(
], () => VideoReplyPanel(
), bvid: videoDetailController.bvid,
) oid: videoDetailController.oid.value,
),
)
// ],
// ),
)
], ],
); );
}); });
Widget childWhenDisabledLandscape = Container( Widget childWhenDisabledLandscape = SafeArea(
color: Theme.of(context).colorScheme.background, left: !removeSafeArea && isFullScreen.value != true,
child: SafeArea( right: !removeSafeArea && isFullScreen.value != true,
left: isFullScreen.value != true, top: !removeSafeArea,
right: isFullScreen.value != true, bottom: !removeSafeArea,
child: Stack(children: [ child: Stack(children: [
Scaffold( Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey, key: videoDetailController.scaffoldKey,
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Colors.black,
appBar: PreferredSize( appBar: removeSafeArea
preferredSize: const Size.fromHeight(0), ? null
child: AppBar( : AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.black,
elevation: 0, elevation: 0,
// systemOverlayStyle: const SystemUiOverlayStyle( toolbarHeight: 0,
// statusBarColor: Colors.transparent, systemOverlayStyle: const SystemUiOverlayStyle(
// statusBarIconBrightness: Brightness.dark), statusBarIconBrightness: Brightness.light),
), ),
), body: Container(
body: childWhenDisabledLandscapeInner) color: Theme.of(context).colorScheme.background,
]))); child: childWhenDisabledLandscapeInner))
]));
Widget childWhenDisabledAlmostSquare = SafeArea(
left: !removeSafeArea && isFullScreen.value != true,
right: !removeSafeArea && isFullScreen.value != true,
top: !removeSafeArea,
bottom: !removeSafeArea,
child: Stack(children: [
Scaffold(
resizeToAvoidBottomInset: false,
key: videoDetailController.scaffoldKey,
backgroundColor: Colors.black,
appBar: removeSafeArea
? null
: AppBar(
backgroundColor: Colors.black,
elevation: 0,
toolbarHeight: 0,
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarIconBrightness: Brightness.light),
),
body: Container(
color: Theme.of(context).colorScheme.background,
child: childWhenDisabledAlmostSquareInner))
]));
Widget childWhenEnabled = Obx( Widget childWhenEnabled = Obx(
() => !videoDetailController.autoPlay.value () => !videoDetailController.autoPlay.value
? const SizedBox() ? const SizedBox()
@@ -851,7 +1137,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
), ),
); );
if (!horizontalScreen) { Widget autoChoose(Widget childWhenDisabled) {
if (Platform.isAndroid) { if (Platform.isAndroid) {
return PiPSwitcher( return PiPSwitcher(
childWhenDisabled: childWhenDisabled, childWhenDisabled: childWhenDisabled,
@@ -862,34 +1148,59 @@ class _VideoDetailPageState extends State<VideoDetailPage>
return childWhenDisabled; return childWhenDisabled;
} }
return OrientationBuilder( if (!horizontalScreen) {
builder: (BuildContext context, Orientation orientation) { return autoChoose(childWhenDisabled);
}
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
if (!isShowing) { if (!isShowing) {
return ColoredBox(color: Theme.of(context).colorScheme.background); return ColoredBox(color: Theme.of(context).colorScheme.background);
} }
if (orientation == Orientation.landscape) { if (constraints.maxWidth > constraints.maxHeight * 1.25) {
if (!horizontalScreen) { // hideStatusBar();
hideStatusBar(); // videoDetailController.hiddenReplyReplyPanel();
videoDetailController.hiddenReplyReplyPanel(); return autoChoose(childWhenDisabledLandscape);
} else if (constraints.maxWidth * (9 / 16) <
(2 / 5) * constraints.maxHeight) {
if (!isFullScreen.value) {
if (!removeSafeArea) showStatusBar();
} }
return autoChoose(childWhenDisabled);
} else { } else {
if (!isFullScreen.value) { if (!isFullScreen.value) {
showStatusBar(); if (!removeSafeArea) showStatusBar();
} }
return autoChoose(childWhenDisabledAlmostSquare);
} }
if (Platform.isAndroid) { //
return PiPSwitcher( // final Orientation orientation =
childWhenDisabled: // constraints.maxWidth > constraints.maxHeight * 1.25
!horizontalScreen || orientation == Orientation.portrait // ? Orientation.landscape
? childWhenDisabled // : Orientation.portrait;
: childWhenDisabledLandscape, // if (orientation == Orientation.landscape) {
childWhenEnabled: childWhenEnabled, // if (!horizontalScreen) {
floating: floating, // hideStatusBar();
); // videoDetailController.hiddenReplyReplyPanel();
} // }
return !horizontalScreen || orientation == Orientation.portrait // } else {
? childWhenDisabled // if (!isFullScreen.value) {
: childWhenDisabledLandscape; // showStatusBar();
// }
// }
// if (Platform.isAndroid) {
// return PiPSwitcher(
// childWhenDisabled:
// !horizontalScreen || orientation == Orientation.portrait
// ? childWhenDisabled
// : childWhenDisabledLandscape,
// childWhenEnabled: childWhenEnabled,
// floating: floating,
// );
// }
// return !horizontalScreen || orientation == Orientation.portrait
// ? childWhenDisabled
// : childWhenDisabledLandscape;
}); });
} }
} }

View File

@@ -237,7 +237,7 @@ class _HeaderControlState extends State<HeaderControl> {
builder: (BuildContext context) { builder: (BuildContext context) {
// TODO: 支持更多类型和颜色的弹幕 // TODO: 支持更多类型和颜色的弹幕
return AlertDialog( return AlertDialog(
title: const Text('发送弹幕(测试)'), title: const Text('发送弹幕'),
content: StatefulBuilder( content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) { builder: (BuildContext context, StateSetter setState) {
return TextField( return TextField(
@@ -733,6 +733,8 @@ class _HeaderControlState extends State<HeaderControl> {
double danmakuDurationVal = widget.controller!.danmakuDurationVal; double danmakuDurationVal = widget.controller!.danmakuDurationVal;
// 弹幕描边 // 弹幕描边
double strokeWidth = widget.controller!.strokeWidth; double strokeWidth = widget.controller!.strokeWidth;
// 字体粗细
int fontWeight = widget.controller!.fontWeight;
final DanmakuController danmakuController = final DanmakuController danmakuController =
widget.controller!.danmakuController!; widget.controller!.danmakuController!;
@@ -762,7 +764,20 @@ class _HeaderControlState extends State<HeaderControl> {
child: Center(child: Text('弹幕设置', style: titleStyle)), child: Center(child: Text('弹幕设置', style: titleStyle)),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text('智能云屏蔽 $danmakuWeight'), Row(
children: [
Text('智能云屏蔽 $danmakuWeight'),
const Spacer(),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
minimumSize: Size.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () => Get.toNamed('/danmakuBlock'),
child: const Text("屏蔽管理"))
],
),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 0, top: 0,
@@ -909,6 +924,45 @@ class _HeaderControlState extends State<HeaderControl> {
), ),
), ),
), ),
Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'),
Padding(
padding: const EdgeInsets.only(
top: 0,
bottom: 6,
left: 10,
right: 10,
),
child: SliderTheme(
data: SliderThemeData(
trackShape: MSliderTrackShape(),
thumbColor: Theme.of(context).colorScheme.primary,
activeTrackColor: Theme.of(context).colorScheme.primary,
trackHeight: 10,
thumbShape: const RoundSliderThumbShape(
enabledThumbRadius: 6.0),
),
child: Slider(
min: 0,
max: 8,
value: fontWeight.toDouble(),
divisions: 9,
label: '${fontWeight + 1}',
onChanged: (double val) {
fontWeight = val.toInt();
widget.controller!.fontWeight = fontWeight;
widget.controller?.putDanmakuSettings();
setState(() {});
try {
final DanmakuOption currentOption =
danmakuController.option;
final DanmakuOption updatedOption =
currentOption.copyWith(fontWeight: fontWeight);
danmakuController.updateOption(updatedOption);
} catch (_) {}
},
),
),
),
Text('描边粗细 $strokeWidth'), Text('描边粗细 $strokeWidth'),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(

View File

@@ -203,17 +203,19 @@ class _WhisperPageState extends State<WhisperPage> {
.content != .content !=
'' ''
? (sessionList[i] ? (sessionList[i]
.lastMsg .lastMsg
.content['text'] ?? .content['text'] ??
sessionList[i] sessionList[i]
.lastMsg .lastMsg
.content['content'] ?? .content['content'] ??
sessionList[i] sessionList[i]
.lastMsg .lastMsg
.content['title'] ?? .content['title'] ??
sessionList[i] sessionList[i]
.lastMsg .lastMsg
.content['reply_content']) .content[
'reply_content']) ??
sessionList[i].lastMsg.content
: '不支持的消息类型', : '不支持的消息类型',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@@ -243,6 +243,7 @@ class PlPlayerController {
late double opacityVal; late double opacityVal;
late double fontSizeVal; late double fontSizeVal;
late double strokeWidth; late double strokeWidth;
late int fontWeight;
late double danmakuDurationVal; late double danmakuDurationVal;
late List<double> speedsList; late List<double> speedsList;
double? defaultDuration; double? defaultDuration;
@@ -286,12 +287,10 @@ class PlPlayerController {
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false); setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
danmakuWeight.value = danmakuWeight.value =
setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0); setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0);
blockTypes = blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []);
setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []);
showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5); showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5);
// 不透明度 // 不透明度
opacityVal = opacityVal = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0);
setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0);
// 字体大小 // 字体大小
fontSizeVal = fontSizeVal =
setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0); setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0);
@@ -300,6 +299,8 @@ class PlPlayerController {
setting.get(SettingBoxKey.danmakuDuration, defaultValue: 4.0); setting.get(SettingBoxKey.danmakuDuration, defaultValue: 4.0);
// 描边粗细 // 描边粗细
strokeWidth = setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5); strokeWidth = setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5);
// 弹幕字体粗细
fontWeight = setting.get(SettingBoxKey.fontWeight, defaultValue: 5);
playRepeat = PlayRepeat.values.toList().firstWhere( playRepeat = PlayRepeat.values.toList().firstWhere(
(e) => (e) =>
e.value == e.value ==
@@ -312,7 +313,7 @@ class PlPlayerController {
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
// 后台播放 // 后台播放
_continuePlayInBackground.value = _continuePlayInBackground.value =
setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: true); setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: false);
if (!enableAutoLongPressSpeed) { if (!enableAutoLongPressSpeed) {
_longPressSpeed.value = videoStorage _longPressSpeed.value = videoStorage
.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0); .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0);
@@ -356,7 +357,7 @@ class PlPlayerController {
double speed = 1.0, double speed = 1.0,
// 硬件加速 // 硬件加速
bool enableHA = true, bool enableHA = true,
String? hwdec, String? hwdec,
double? width, double? width,
double? height, double? height,
Duration? duration, Duration? duration,
@@ -458,7 +459,6 @@ class PlPlayerController {
bufferSize: bufferSize, bufferSize: bufferSize,
), ),
); );
var pp = player.platform as NativePlayer; var pp = player.platform as NativePlayer;
// 解除倍速限制 // 解除倍速限制
await pp.setProperty("af", "scaletempo2=max-speed=8"); await pp.setProperty("af", "scaletempo2=max-speed=8");
@@ -515,7 +515,7 @@ class PlPlayerController {
configuration: VideoControllerConfiguration( configuration: VideoControllerConfiguration(
enableHardwareAcceleration: enableHA, enableHardwareAcceleration: enableHA,
androidAttachSurfaceAfterVideoParameters: false, androidAttachSurfaceAfterVideoParameters: false,
hwdec: hwdec, hwdec: enableHA ? hwdec: null,
), ),
); );
@@ -991,8 +991,7 @@ class PlPlayerController {
/// 设置后台播放 /// 设置后台播放
Future<void> setBackgroundPlay(bool val) async { Future<void> setBackgroundPlay(bool val) async {
_continuePlayInBackground.value = val; setting.put(SettingBoxKey.enableBackgroundPlay, val);
setting.put(SettingBoxKey.continuePlayInBackground, val);
videoPlayerServiceHandler.revalidateSetting(); videoPlayerServiceHandler.revalidateSetting();
} }
@@ -1150,6 +1149,7 @@ class PlPlayerController {
setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal); setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal);
setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal); setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal);
setting.put(SettingBoxKey.strokeWidth, strokeWidth); setting.put(SettingBoxKey.strokeWidth, strokeWidth);
setting.put(SettingBoxKey.fontWeight, fontWeight);
} }
Future<void> dispose({String type = 'single'}) async { Future<void> dispose({String type = 'single'}) async {
@@ -1161,6 +1161,7 @@ class PlPlayerController {
return; return;
} }
_playerCount.value = 0; _playerCount.value = 0;
pause();
try { try {
_timer?.cancel(); _timer?.cancel();
_timerForVolume?.cancel(); _timerForVolume?.cancel();

View File

@@ -5,6 +5,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -17,6 +18,7 @@ import 'package:PiliPalaX/plugin/pl_player/models/fullscreen_mode.dart';
import 'package:PiliPalaX/plugin/pl_player/utils.dart'; import 'package:PiliPalaX/plugin/pl_player/utils.dart';
import 'package:PiliPalaX/utils/feed_back.dart'; import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:saver_gallery/saver_gallery.dart';
import 'package:screen_brightness/screen_brightness.dart'; import 'package:screen_brightness/screen_brightness.dart';
import '../../common/widgets/audio_video_progress_bar.dart'; import '../../common/widgets/audio_video_progress_bar.dart';
@@ -330,8 +332,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
episodes.addAll(pages); episodes.addAll(pages);
changeFucCall = videoIntroController!.changeSeasonOrbangu; changeFucCall = videoIntroController!.changeSeasonOrbangu;
} else if (isBangumi) { } else if (isBangumi) {
episodes.addAll(bangumiIntroController!.bangumiDetail.value.episodes!); episodes.addAll(bangumiIntroController!
changeFucCall = bangumiIntroController!.changeSeasonOrbangu; .bangumiDetail.value.episodes!);
changeFucCall =
bangumiIntroController!.changeSeasonOrbangu;
} }
ListSheet( ListSheet(
episodes: episodes, episodes: episodes,
@@ -500,11 +504,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
controls: NoVideoControls, controls: NoVideoControls,
pauseUponEnteringBackgroundMode: !_.continuePlayInBackground.value, pauseUponEnteringBackgroundMode: !_.continuePlayInBackground.value,
resumeUponEnteringForegroundMode: true, resumeUponEnteringForegroundMode: true,
subtitleViewConfiguration: const SubtitleViewConfiguration( // 字幕尺寸调节
style: subTitleStyle, subtitleViewConfiguration: SubtitleViewConfiguration(
padding: EdgeInsets.all(24.0), style: subTitleStyle,
textScaleFactor: 1.0, padding: const EdgeInsets.all(24.0),
), textScaleFactor: MediaQuery.textScaleFactorOf(context)),
fit: _.videoFit.value, fit: _.videoFit.value,
), ),
), ),
@@ -848,36 +852,32 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
), ),
// 头部、底部控制条 // 头部、底部控制条
SafeArea( Obx(
top: false, () => Column(
bottom: false, children: [
child: Obx( if (widget.headerControl != null || _.headerControl != null)
() => Column(
children: [
if (widget.headerControl != null || _.headerControl != null)
ClipRect(
child: AppBarAni(
controller: animationController,
visible: !_.controlsLock.value && _.showControls.value,
position: 'top',
child: widget.headerControl ?? _.headerControl!,
),
),
const Spacer(),
ClipRect( ClipRect(
child: AppBarAni( child: AppBarAni(
controller: animationController, controller: animationController,
visible: !_.controlsLock.value && _.showControls.value, visible: !_.controlsLock.value && _.showControls.value,
position: 'bottom', position: 'top',
child: widget.bottomControl ?? child: widget.headerControl ?? _.headerControl!,
BottomControl(
controller: widget.controller,
buildBottomControl: buildBottomControl(),
),
), ),
), ),
], const Spacer(),
), ClipRect(
child: AppBarAni(
controller: animationController,
visible: !_.controlsLock.value && _.showControls.value,
position: 'bottom',
child: widget.bottomControl ??
BottomControl(
controller: widget.controller,
buildBottomControl: buildBottomControl(),
),
),
),
],
), ),
), ),
@@ -981,7 +981,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FractionalTranslation( child: FractionalTranslation(
translation: const Offset(1, 0.0), translation: const Offset(1, -0.4),
child: Visibility( child: Visibility(
visible: _.showControls.value, visible: _.showControls.value,
child: ComBtn( child: ComBtn(
@@ -1000,6 +1000,42 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
), ),
), ),
), ),
// 截图
Obx(
() => Align(
alignment: Alignment.centerRight,
child: FractionalTranslation(
translation: const Offset(-1, -0.4),
child: Visibility(
visible: _.showControls.value && _.isFullScreen.value,
child: ComBtn(
tooltip: '截图',
icon: const Icon(
Icons.photo_camera,
size: 20,
color: Colors.white,
),
fuc: () => {
_.videoPlayerController
?.screenshot(format: 'image/png')
.then((value) {
if (value != null) {
SmartDialog.showToast('截图成功');
String _name = DateTime.now().toString();
SaverGallery.saveImage(value,
name: _name,
androidRelativePath: "Pictures/Screenshots",
androidExistNotSave: false);
SmartDialog.showToast('$_name.png已保存到相册/截图');
}
})
},
),
),
),
),
),
// //
Obx(() { Obx(() {
if (_.dataStatus.loading || _.isBuffering.value) { if (_.dataStatus.loading || _.isBuffering.value) {

View File

@@ -50,7 +50,7 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
), ),
), ),
child: child, child: SafeArea(child: child),
), ),
); );
} }

View File

@@ -11,6 +11,7 @@ import 'package:PiliPalaX/pages/setting/pages/logs.dart';
import '../pages/about/index.dart'; import '../pages/about/index.dart';
import '../pages/blacklist/index.dart'; import '../pages/blacklist/index.dart';
import '../pages/danmaku_block/index.dart';
import '../pages/dynamics/detail/index.dart'; import '../pages/dynamics/detail/index.dart';
import '../pages/dynamics/index.dart'; import '../pages/dynamics/index.dart';
import '../pages/fan/index.dart'; import '../pages/fan/index.dart';
@@ -184,6 +185,8 @@ class Routes {
CustomGetPage(name: '/subscription', page: () => const SubPage()), CustomGetPage(name: '/subscription', page: () => const SubPage()),
// 订阅详情 // 订阅详情
CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()), CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()),
// 弹幕屏蔽管理
CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()),
]; ];
} }

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class CacheManage { class CacheManage {
CacheManage._internal(); CacheManage._internal();
@@ -76,17 +77,16 @@ class CacheManage {
} }
// 清除缓存 // 清除缓存
Future<bool> clearCacheAll() async { Future<bool> clearCacheAll(BuildContext context) async {
bool cleanStatus = await SmartDialog.show( bool cleanStatus = await showDialog(
useSystem: true, context: context,
animationType: SmartAnimationType.centerFade_otherSlide, builder: (context) {
builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: (() => {SmartDialog.dismiss()}), onPressed: () => Get.back(),
child: Text( child: Text(
'取消', '取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@@ -94,7 +94,7 @@ class CacheManage {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Get.back();
SmartDialog.showLoading(msg: '正在清除...'); SmartDialog.showLoading(msg: '正在清除...');
try { try {
// 清除缓存 图片缓存 // 清除缓存 图片缓存

View File

@@ -10,15 +10,15 @@ import 'dart:io';
class DownloadUtils { class DownloadUtils {
// 获取存储权限 // 获取存储权限
static Future<bool> requestStoragePer() async { static Future<bool> requestStoragePer(BuildContext context) async {
await Permission.storage.request(); await Permission.storage.request();
PermissionStatus status = await Permission.storage.status; PermissionStatus status = await Permission.storage.status;
if (status == PermissionStatus.denied || if (status == PermissionStatus.denied ||
status == PermissionStatus.permanentlyDenied) { status == PermissionStatus.permanentlyDenied) {
SmartDialog.show( if (!context.mounted) return false;
useSystem: true, await showDialog(
animationType: SmartAnimationType.centerFade_otherSlide, context: context,
builder: (BuildContext context) { builder: (context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('存储权限未授权'), content: const Text('存储权限未授权'),
@@ -69,21 +69,22 @@ class DownloadUtils {
} }
} }
static Future<bool> checkPermissionDependOnSdkInt() async { static Future<bool> checkPermissionDependOnSdkInt(BuildContext context) async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo; final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt <= 32) { if (androidInfo.version.sdkInt <= 32) {
return await requestStoragePer(); if (!context.mounted) return false;
return await requestStoragePer(context);
} else { } else {
return await requestPhotoPer(); return await requestPhotoPer();
} }
} }
return await requestStoragePer(); return await requestStoragePer(context);
} }
static Future<bool> downloadImg(String imgUrl, static Future<bool> downloadImg(BuildContext context, String imgUrl,
{String imgType = 'cover'}) async { {String imgType = 'cover'}) async {
try { try {
if (!await checkPermissionDependOnSdkInt()) { if (!await checkPermissionDependOnSdkInt(context)) {
// // return false; // // return false;
} }
SmartDialog.showLoading(msg: '正在下载原图'); SmartDialog.showLoading(msg: '正在下载原图');

View File

@@ -1,18 +1,112 @@
import 'dart:math';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/rendering.dart';
import 'storage.dart'; import 'storage.dart';
class Grid { class Grid {
static double maxRowWidth = GStrorage.setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double; static double maxRowWidth = GStrorage.setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double;
//
// static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) {
// double screenWidth = MediaQuery.of(context).size.width;
// if (screenWidthOffset != null) {
// screenWidth -= screenWidthOffset;
// }
// if (GStrorage.setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false) as bool) {
// screenWidth -= 55;
// }
// int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
// if (columnCount < 1){
// columnCount = 1;
// }
// double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing;
// return columnWidth;
// }
}
class SliverGridDelegateWithExtentAndRatio extends SliverGridDelegate {
/// Creates a delegate that makes grid layouts with tiles that have a maximum
/// cross-axis extent.
///
/// The [maxCrossAxisExtent], [mainAxisExtent], [mainAxisSpacing],
/// and [crossAxisSpacing] arguments must not be negative.
/// The [childAspectRatio] argument must be greater than zero.
const SliverGridDelegateWithExtentAndRatio({
required this.maxCrossAxisExtent,
this.mainAxisSpacing = 0.0,
this.crossAxisSpacing = 0.0,
this.childAspectRatio = 1.0,
this.mainAxisExtent = 0.0,
}) : assert(maxCrossAxisExtent > 0),
assert(mainAxisSpacing >= 0),
assert(crossAxisSpacing >= 0),
assert(childAspectRatio > 0);
static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) { /// The maximum extent of tiles in the cross axis.
double screenWidth = MediaQuery.of(context).size.width; ///
if (screenWidthOffset != null) { /// This delegate will select a cross-axis extent for the tiles that is as
screenWidth -= screenWidthOffset; /// large as possible subject to the following conditions:
} ///
int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil(); /// - The extent evenly divides the cross-axis extent of the grid.
if (columnCount < 1){ /// - The extent is at most [maxCrossAxisExtent].
columnCount = 1; ///
} /// For example, if the grid is vertical, the grid is 500.0 pixels wide, and
double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing; /// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4
return columnWidth; /// columns that are 125.0 pixels wide.
final double maxCrossAxisExtent;
/// The number of logical pixels between each child along the main axis.
final double mainAxisSpacing;
/// The number of logical pixels between each child along the cross axis.
final double crossAxisSpacing;
/// The ratio of the cross-axis to the main-axis extent of each child.
final double childAspectRatio;
/// The extent of each tile in the main axis. If provided, it would add
/// after [childAspectRatio] is used.
final double mainAxisExtent;
bool _debugAssertIsValid(double crossAxisExtent) {
assert(crossAxisExtent > 0.0);
assert(maxCrossAxisExtent > 0.0);
assert(mainAxisSpacing >= 0.0);
assert(crossAxisSpacing >= 0.0);
assert(childAspectRatio > 0.0);
return true;
}
@override
SliverGridLayout getLayout(SliverConstraints constraints) {
assert(_debugAssertIsValid(constraints.crossAxisExtent));
int crossAxisCount = ((constraints.crossAxisExtent - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
// Ensure a minimum count of 1, can be zero and result in an infinite extent
// below when the window size is 0.
crossAxisCount = max(1, crossAxisCount);
final double usableCrossAxisExtent = max(
0.0,
constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1),
);
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio + mainAxisExtent;
return SliverGridRegularTileLayout(
crossAxisCount: crossAxisCount,
mainAxisStride: childMainAxisExtent + mainAxisSpacing,
crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
childMainAxisExtent: childMainAxisExtent,
childCrossAxisExtent: childCrossAxisExtent,
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
);
}
@override
bool shouldRelayout(SliverGridDelegateWithExtentAndRatio oldDelegate) {
return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent
|| oldDelegate.mainAxisSpacing != mainAxisSpacing
|| oldDelegate.crossAxisSpacing != crossAxisSpacing
|| oldDelegate.childAspectRatio != childAspectRatio
|| oldDelegate.mainAxisExtent != mainAxisExtent;
} }
} }

View File

@@ -145,6 +145,7 @@ class SettingBoxKey {
/// 其他 /// 其他
autoUpdate = 'autoUpdate', autoUpdate = 'autoUpdate',
autoClearCache = 'autoClearCache', autoClearCache = 'autoClearCache',
defaultShowComment = 'defaultShowComment',
replySortType = 'replySortType', replySortType = 'replySortType',
defaultDynamicType = 'defaultDynamicType', defaultDynamicType = 'defaultDynamicType',
enableHotKey = 'enableHotKey', enableHotKey = 'enableHotKey',
@@ -156,7 +157,7 @@ class SettingBoxKey {
disableLikeMsg = 'disableLikeMsg', disableLikeMsg = 'disableLikeMsg',
defaultHomePage = 'defaultHomePage', defaultHomePage = 'defaultHomePage',
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 // 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细
danmakuWeight = 'danmakuWeight', danmakuWeight = 'danmakuWeight',
danmakuBlockType = 'danmakuBlockType', danmakuBlockType = 'danmakuBlockType',
danmakuShowArea = 'danmakuShowArea', danmakuShowArea = 'danmakuShowArea',
@@ -164,6 +165,8 @@ class SettingBoxKey {
danmakuFontScale = 'danmakuFontScale', danmakuFontScale = 'danmakuFontScale',
danmakuDuration = 'danmakuDuration', danmakuDuration = 'danmakuDuration',
strokeWidth = 'strokeWidth', strokeWidth = 'strokeWidth',
fontWeight = 'fontWeight',
danmakuFilterRule = 'danmakuFilterRule',
// 代理host port // 代理host port
systemProxyHost = 'systemProxyHost', systemProxyHost = 'systemProxyHost',
@@ -177,6 +180,9 @@ class SettingBoxKey {
enableSingleRow = 'enableSingleRow', // 首页单列 enableSingleRow = 'enableSingleRow', // 首页单列
displayMode = 'displayMode', displayMode = 'displayMode',
maxRowWidth = 'maxRowWidth', // 首页列最大宽度dp maxRowWidth = 'maxRowWidth', // 首页列最大宽度dp
videoPlayerRemoveSafeArea = 'videoPlayerHideStatusBar',
dynamicsWaterfallFlow = 'dynamicsWaterfallFlow', // 动态瀑布流
upPanelPosition = 'upPanelPosition', // up主面板位置
adaptiveNavBar = 'adaptiveNavBar', adaptiveNavBar = 'adaptiveNavBar',
enableMYBar = 'enableMYBar', enableMYBar = 'enableMYBar',
hideSearchBar = 'hideSearchBar', // 收起顶栏 hideSearchBar = 'hideSearchBar', // 收起顶栏
@@ -184,8 +190,7 @@ class SettingBoxKey {
tabbarSort = 'tabbarSort', // 首页tabbar tabbarSort = 'tabbarSort', // 首页tabbar
dynamicBadgeMode = 'dynamicBadgeMode', dynamicBadgeMode = 'dynamicBadgeMode',
hiddenSettingUnlocked = 'hiddenSettingUnlocked', hiddenSettingUnlocked = 'hiddenSettingUnlocked',
enableGradientBg = 'enableGradientBg', enableGradientBg = 'enableGradientBg';
defaultShowComment = 'defaultShowComment';
} }
class LocalCacheKey { class LocalCacheKey {

View File

@@ -351,7 +351,7 @@ class Utils {
child: Text( child: Text(
"点此查看完整更新即commit内容", "点此查看完整更新即commit内容",
style: style:
TextStyle(color: Theme.of(context).primaryColor), TextStyle(color: Theme.of(context).colorScheme.primary),
)), )),
], ],
), ),
@@ -361,7 +361,7 @@ class Utils {
TextButton( TextButton(
onPressed: () { onPressed: () {
setting.put(SettingBoxKey.autoUpdate, false); setting.put(SettingBoxKey.autoUpdate, false);
SmartDialog.dismiss(); Get.back();
}, },
child: Text( child: Text(
'不再提醒', '不再提醒',
@@ -370,7 +370,7 @@ class Utils {
), ),
), ),
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: () => Get.back(),
child: Text( child: Text(
'取消', '取消',
style: style:

View File

@@ -37,26 +37,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.4.10" version: "3.5.1"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.4.2" version: "2.5.0"
asn1lib: asn1lib:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.5.2" version: "1.5.3"
async: async:
dependency: transitive dependency: transitive
description: description:
@@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: audio_service name: audio_service
sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4 sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.18.12" version: "0.18.13"
audio_service_platform_interface: audio_service_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -85,18 +85,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: audio_service_web name: audio_service_web
sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7" sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.1.1" version: "0.1.2"
audio_session: audio_session:
dependency: "direct main" dependency: "direct main"
description: description:
name: audio_session name: audio_session
sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f" sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.1.18" version: "0.1.19"
auto_orientation: auto_orientation:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -149,10 +149,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.4.8" version: "2.4.9"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
@@ -173,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: built_value name: built_value
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "8.9.0" version: "8.9.2"
cached_network_image: cached_network_image:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -197,18 +197,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cached_network_image_web name: cached_network_image_web
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
catcher_2: catcher_2:
dependency: "direct main" dependency: "direct main"
description: description:
name: catcher_2 name: catcher_2
sha256: "0691d0a5a2b7ccbe434ff67071218bbff86d264d215c6afc10f404cd6d6e7a50" sha256: "2c2c6f8cf8c817730cd1dbb010d55292396930e7a3d42c04c3039e3fd411a2f8"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.2.0" version: "1.2.6"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -317,26 +317,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cupertino_icons name: cupertino_icons
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.0.6" version: "1.0.8"
custom_sliding_segmented_control: custom_sliding_segmented_control:
dependency: "direct main" dependency: "direct main"
description: description:
name: custom_sliding_segmented_control name: custom_sliding_segmented_control
sha256: "05b73fa48d57218bfdf806bad68a859812b216cd81fe81c6cbefde89f39eb257" sha256: "8b29c39e053136cdb899cf1b049d11e5ac0f4622f6444ae4f80b6ba72a640763"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.3.4" version: "2.3.6"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@@ -365,10 +365,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "5.4.0" version: "5.4.3+1"
dio_cookie_manager: dio_cookie_manager:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -381,10 +381,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio_http2_adapter name: dio_http2_adapter
sha256: "3bb35e81eb8a688eb1cb15beb97f46823698b44037e7b55227aa1060f5593adc" sha256: ea2f5e7906a157cb049abce95f89a693459f82e5b376be4636086368e3a350f1
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.4.0" version: "2.5.2"
dismissible_page: dismissible_page:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -397,10 +397,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.6.9" version: "1.7.0"
easy_debounce: easy_debounce:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -510,14 +510,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_adaptive_scaffold:
dependency: "direct main"
description:
name: flutter_adaptive_scaffold
sha256: "600bbe237530a249f957f7d0f36273c20bd38d137e28e098c5231c30cadbe927"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted
version: "0.1.10+1"
flutter_cache_manager: flutter_cache_manager:
dependency: transitive dependency: transitive
description: description:
@@ -575,26 +567,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.0.17" version: "2.0.19"
flutter_smart_dialog: flutter_smart_dialog:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_smart_dialog name: flutter_smart_dialog
sha256: e9ee69eeac16165d142f1974b4db05ca9846cffafb7c94674a38ec07d7e6cda1 sha256: "9b23a0b23b52a259f2901997eaf0b169bf5c61ff2178204872709610e9f6c0be"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.9.6" version: "4.9.6+1"
flutter_svg: flutter_svg:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_svg name: flutter_svg
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.0.9" version: "2.0.10+1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -604,10 +596,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_volume_controller name: flutter_volume_controller
sha256: "0f10cc759499cb6c3e152a8f6ff8e5ce385b99db7e1f586d1a29d8e6c11f4082" sha256: fa4c36dfe7ef7f423704f34ab8e64e00b4a30a90aa6e56f251e9dba649efcd7f
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@@ -617,10 +609,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fluttertoast name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "8.2.4" version: "8.2.5"
font_awesome_flutter: font_awesome_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -633,10 +625,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: frontend_server_client name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.2.0" version: "4.0.0"
get: get:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -745,10 +737,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: image name: image
sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.1.6" version: "4.1.7"
intl: intl:
dependency: transitive dependency: transitive
description: description:
@@ -777,10 +769,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.8.1" version: "4.9.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -817,10 +809,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: logger name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac" sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.0.2+1" version: "2.3.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -833,10 +825,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: mailer name: mailer
sha256: "57f6dd1496699999a7bfd0aa6be0645384f477f4823e16d4321c40a434346382" sha256: d25d89555c1031abacb448f07b801d7c01b4c21d4558e944b12b64394c84a3cb
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "6.0.1" version: "6.1.0"
marquee: marquee:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -971,8 +963,8 @@ packages:
description: description:
path: "." path: "."
ref: master ref: master
resolved-ref: d1cb3f0190ca67ec4d7fd372dac96f4f17a81a1a resolved-ref: e12184de6f4ab4647d098c561b0508c47ce359e8
url: "https://github.com/guozhigq/flutter_ns_danmaku.git" url: "https://github.com/orz12/flutter_ns_danmaku.git"
source: git source: git
version: "0.0.5" version: "0.0.5"
octo_image: octo_image:
@@ -1027,18 +1019,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.2.2" version: "2.2.4"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
@@ -1075,26 +1067,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: permission_handler name: permission_handler
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6" sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "11.2.0" version: "11.3.1"
permission_handler_android: permission_handler_android:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb" sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "12.0.3" version: "12.0.6"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_apple name: permission_handler_apple
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830 sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "9.3.0" version: "9.4.4"
permission_handler_html: permission_handler_html:
dependency: transitive dependency: transitive
description: description:
@@ -1107,10 +1099,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_platform_interface name: permission_handler_platform_interface
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c" sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.1.0" version: "4.2.1"
permission_handler_windows: permission_handler_windows:
dependency: transitive dependency: transitive
description: description:
@@ -1147,10 +1139,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.7.4" version: "3.9.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@@ -1211,10 +1203,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: saver_gallery name: saver_gallery
sha256: cceebad1f792adad4eb5015b415dcd521779a772ea574f0d7fc534b128deac83 sha256: "0f740608072053a0da3b19cc5812a87e36f5c3c0b959d2475c4eb3d697f4a782"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.0.2" version: "3.0.3"
screen_brightness: screen_brightness:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1275,10 +1267,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sentry name: sentry
sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25 sha256: fd1fbfe860c05f5c52820ec4dbf2b6473789e83ead26cfc18bca4fe80bf3f008
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "7.16.0" version: "8.2.0"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -1291,10 +1283,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: share_plus_platform_interface name: share_plus_platform_interface
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.3.1" version: "3.4.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
@@ -1472,26 +1464,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "6.2.4" version: "6.2.6"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "6.2.2" version: "6.3.1"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "6.2.4" version: "6.2.5"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -1504,18 +1496,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.1.0" version: "3.2.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "2.3.1" version: "2.3.2"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
@@ -1544,26 +1536,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics name: vector_graphics
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_graphics_codec: vector_graphics_codec:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_codec name: vector_graphics_codec
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_graphics_compiler: vector_graphics_compiler:
dependency: transitive dependency: transitive
description: description:
name: vector_graphics_compiler name: vector_graphics_compiler
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.1.9+2" version: "1.1.11+1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@@ -1648,18 +1640,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413 sha256: "4445dea609bcdf4bcf6b0521e8c936abb34711d9e9b73c5ff8a6fd4eecb0db3c"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "4.5.0" version: "4.6.0"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af" sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.14.0" version: "3.16.1"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -1672,10 +1664,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c" sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.10.2" version: "3.13.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@@ -1717,5 +1709,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.2.0 <4.0.0" dart: ">=3.2.3 <4.0.0"
flutter: ">=3.16.0" flutter: ">=3.16.0"

View File

@@ -37,8 +37,6 @@ dependencies:
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
# 动态取色 # 动态取色
dynamic_color: ^1.6.8 dynamic_color: ^1.6.8
# Adaptive scaffold
flutter_adaptive_scaffold: ^0.1.10+1
get: ^4.6.5 get: ^4.6.5
@@ -117,7 +115,7 @@ dependencies:
# 弹幕 # 弹幕
ns_danmaku: ns_danmaku:
git: git:
url: https://github.com/guozhigq/flutter_ns_danmaku.git url: https://github.com/orz12/flutter_ns_danmaku.git
ref: master ref: master
# 状态栏图标控制 # 状态栏图标控制
status_bar_control: ^3.2.1 status_bar_control: ^3.2.1