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

@@ -5,10 +5,15 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/http/user.dart';
import 'package:PiliPalaX/utils/feed_back.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 {
final dynamic item;
const AuthorPanel({super.key, required this.item});
final Function? addBannedList;
const AuthorPanel({super.key, required this.item, this.addBannedList});
@override
Widget build(BuildContext context) {
@@ -77,28 +82,27 @@ class AuthorPanel extends StatelessWidget {
],
),
const Spacer(),
if (item.type == 'DYNAMIC_TYPE_AV')
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: item);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: item);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
),
],
);
}
@@ -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(
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,
),
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),
ListTile(

View File

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

View File

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

View File

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