merge design

This commit is contained in:
guozhigq
2023-09-04 15:17:19 +08:00
7 changed files with 332 additions and 237 deletions

View File

@ -14,7 +14,7 @@ class FavDetailController extends GetxController {
int currentPage = 1;
bool isLoadingMore = false;
RxMap favInfo = {}.obs;
RxList<FavDetailItemData> favList = [FavDetailItemData()].obs;
RxList favList = [].obs;
RxString loadingText = '加载中...'.obs;
int mediaCount = 0;
@ -61,15 +61,13 @@ class FavDetailController extends GetxController {
aid: id, addIds: '', delIds: mediaId.toString());
if (result['status']) {
if (result['data']['prompt']) {
List<FavDetailItemData> dataList = favDetailData.value.medias!;
List dataList = favList;
for (var i in dataList) {
if (i.id == id) {
dataList.remove(i);
break;
}
}
favDetailData.value.medias = dataList;
favDetailData.refresh();
SmartDialog.showToast('取消收藏');
}
}

View File

@ -168,7 +168,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
child: Obx(
() => Text(
'${_favDetailController.favInfo['media_count'] ?? '-'}条视频',
'${_favDetailController.favList.length}条视频',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
@ -187,14 +187,20 @@ class _FavDetailPageState extends State<FavDetailPage> {
if (_favDetailController.item!.mediaCount == 0) {
return const NoData();
} else {
List favList = _favDetailController.favList;
return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return FavVideoCardH(
videoItem: _favDetailController.favList[index],
);
}, childCount: _favDetailController.favList.length),
),
() => favList.isEmpty
? const SliverToBoxAdapter(child: SizedBox())
: SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return FavVideoCardH(
videoItem: favList[index],
callFn: () => _favDetailController
.onCancelFav(favList[index].id),
);
}, childCount: favList.length),
),
);
}
} else {

View File

@ -10,134 +10,109 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../controller.dart';
// 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget {
final dynamic videoItem;
final FavDetailController _favDetailController =
Get.put(FavDetailController());
final Function? callFn;
FavVideoCardH({Key? key, required this.videoItem}) : super(key: key);
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
: super(key: key);
@override
Widget build(BuildContext context) {
int id = videoItem.id;
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
String heroTag = Utils.makeHeroTag(id);
return Dismissible(
movementDuration: const Duration(milliseconds: 300),
background: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.errorContainer,
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.clear_all_rounded),
SizedBox(width: 6),
Text('取消收藏')
],
)),
direction: DismissDirection.endToStart,
key: ValueKey<int>(videoItem.id),
onDismissed: (DismissDirection direction) {
_favDetailController.onCancelFav(videoItem.id);
// widget.onDeleteNotice();
},
child: InkWell(
onTap: () async {
// int? seasonId;
String? epId;
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
// seasonId = videoItem.ogv['season_id'];
epId = videoItem.epId;
} else if (videoItem.page == 0 || videoItem.page > 1) {
var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) {
epId = result['data'].epId;
}
return InkWell(
onTap: () async {
// int? seasonId;
String? epId;
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
// seasonId = videoItem.ogv['season_id'];
epId = videoItem.epId;
} else if (videoItem.page == 0 || videoItem.page > 1) {
var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) {
epId = result['data'].epId;
}
}
Map<String, String> parameters = {
'bvid': bvid,
'cid': videoItem.cid.toString(),
'epId': epId ?? '',
};
// if (seasonId != null) {
// parameters['seasonId'] = seasonId.toString();
// }
Get.toNamed('/video', parameters: parameters, arguments: {
'videoItem': videoItem,
'heroTag': heroTag,
'videoType':
epId != null ? SearchType.media_bangumi : SearchType.video,
});
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
Map<String, String> parameters = {
'bvid': bvid,
'cid': videoItem.cid.toString(),
'epId': epId ?? '',
};
// if (seasonId != null) {
// parameters['seasonId'] = seasonId.toString();
// }
Get.toNamed('/video', parameters: parameters, arguments: {
'videoItem': videoItem,
'heroTag': heroTag,
'videoType':
epId != null ? SearchType.media_bangumi : SearchType.video,
});
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
),
Positioned(
right: 4,
bottom: 4,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 1, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.black54.withOpacity(0.4)),
child: Text(
Utils.timeFormat(videoItem.duration!),
style: const TextStyle(
fontSize: 11, color: Colors.white),
),
),
Positioned(
right: 4,
bottom: 4,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 1, horizontal: 6),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(4),
color:
Colors.black54.withOpacity(0.4)),
child: Text(
Utils.timeFormat(videoItem.duration!),
style: const TextStyle(
fontSize: 11, color: Colors.white),
),
),
)
],
);
},
),
)
],
);
},
),
VideoContent(videoItem: videoItem)
],
),
);
},
),
),
VideoContent(videoItem: videoItem, callFn: callFn)
],
),
);
},
),
],
),
),
],
),
);
}
@ -145,7 +120,8 @@ class FavVideoCardH extends StatelessWidget {
class VideoContent extends StatelessWidget {
final dynamic videoItem;
const VideoContent({super.key, required this.videoItem});
final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn});
@override
Widget build(BuildContext context) {
@ -173,7 +149,6 @@ class VideoContent extends StatelessWidget {
color: Theme.of(context).colorScheme.outline,
),
),
const SizedBox(height: 2),
Row(
children: [
StatView(
@ -181,7 +156,51 @@ class VideoContent extends StatelessWidget {
view: videoItem.cntInfo['play'],
),
const SizedBox(width: 8),
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku'])
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
const Spacer(),
SizedBox(
width: 26,
height: 26,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('要取消收藏吗?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
)),
TextButton(
onPressed: () async {
await callFn!();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
icon: Icon(
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
),
),
],
),
],

View File

@ -4,6 +4,7 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'controller.dart';
import 'widgets/bottom_control.dart';
class LiveRoomPage extends StatefulWidget {
const LiveRoomPage({super.key});
@ -87,96 +88,38 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
),
body: Column(
children: [
Hero(
tag: _liveRoomController.heroTag,
child: Stack(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: plPlayerController!.videoPlayerController != null
? PLVideoPlayer(controller: plPlayerController!)
: const SizedBox(),
),
// if (_liveRoomController.liveItem != null &&
// _liveRoomController.liveItem.cover != null)
// Visibility(
// visible: isShowCover,
// child: Positioned(
// top: 0,
// left: 0,
// right: 0,
// child: NetworkImgLayer(
// type: 'emote',
// src: _liveRoomController.liveItem.cover,
// width: Get.size.width,
// height: videoHeight,
// ),
// ),
// ),
],
),
Stack(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: plPlayerController!.videoPlayerController != null
? PLVideoPlayer(
controller: plPlayerController!,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
),
)
: const SizedBox(),
),
// if (_liveRoomController.liveItem != null &&
// _liveRoomController.liveItem.cover != null)
// Visibility(
// visible: isShowCover,
// child: Positioned(
// top: 0,
// left: 0,
// right: 0,
// child: NetworkImgLayer(
// type: 'emote',
// src: _liveRoomController.liveItem.cover,
// width: Get.size.width,
// height: videoHeight,
// ),
// ),
// ),
],
),
// Container(
// height: 45,
// padding: const EdgeInsets.only(left: 12, right: 12),
// decoration: BoxDecoration(
// color: Theme.of(context).colorScheme.background,
// border: Border(
// bottom: BorderSide(
// color: Theme.of(context).dividerColor.withOpacity(0.1)),
// ),
// ),
// child: Row(children: <Widget>[
// SizedBox(
// width: 38,
// height: 38,
// child: IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.subtitles_outlined,
// size: 21,
// ),
// ),
// ),
// const Spacer(),
// SizedBox(
// width: 38,
// height: 38,
// child: IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.hd_outlined,
// size: 20,
// ),
// ),
// ),
// SizedBox(
// width: 38,
// height: 38,
// child: IconButton(
// onPressed: () => _liveRoomController
// .setVolumn(plPlayerController!.volume.value),
// icon: Obx(() => Icon(
// _liveRoomController.volumeOff.value
// ? Icons.volume_off_outlined
// : Icons.volume_up_outlined,
// size: 21,
// )),
// ),
// ),
// SizedBox(
// width: 38,
// height: 38,
// child: IconButton(
// onPressed: () => {},
// // plPlayerController!.goToFullscreen(context),
// icon: const Icon(
// Icons.fullscreen,
// ),
// ),
// ),
// ]),
// ),
],
),
);

View File

@ -0,0 +1,118 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/pages/liveRoom/index.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/storage.dart';
class BottomControl extends StatefulWidget implements PreferredSizeWidget {
final PlPlayerController? controller;
final LiveRoomController? liveRoomCtr;
const BottomControl({
this.controller,
this.liveRoomCtr,
Key? key,
}) : super(key: key);
@override
State<BottomControl> createState() => _BottomControlState();
@override
Size get preferredSize => throw UnimplementedError();
}
class _BottomControlState extends State<BottomControl> {
late PlayUrlModel videoInfo;
List<PlaySpeed> playSpeed = PlaySpeed.values;
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
TextStyle titleStyle = const TextStyle(fontSize: 14);
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
Box localCache = GStrorage.localCache;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
const textStyle = TextStyle(
color: Colors.white,
fontSize: 12,
);
return AppBar(
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
primary: false,
centerTitle: false,
automaticallyImplyLeading: false,
titleSpacing: 14,
title: Row(
children: [
ComBtn(
icon: const Icon(
Icons.subtitles_outlined,
size: 18,
color: Colors.white,
),
fuc: () => Get.back(),
),
const SizedBox(width: 4),
const Spacer(),
const SizedBox(width: 4),
ComBtn(
icon: const Icon(
Icons.hd_outlined,
size: 18,
color: Colors.white,
),
fuc: () => {},
),
const SizedBox(width: 4),
Obx(
() => ComBtn(
icon: Icon(
widget.liveRoomCtr!.volumeOff.value
? Icons.volume_off_outlined
: Icons.volume_up_outlined,
size: 18,
color: Colors.white,
),
fuc: () => {},
),
),
const SizedBox(width: 4),
ComBtn(
icon: const Icon(
Icons.fullscreen,
size: 20,
color: Colors.white,
),
fuc: () => widget.controller!.triggerFullScreen(),
),
],
),
);
}
}
class MSliderTrackShape extends RoundedRectSliderTrackShape {
@override
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
SliderThemeData? sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
const double trackHeight = 3;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2 + 4;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}

View File

@ -106,6 +106,7 @@ class PlPlayerController {
];
PreferredSizeWidget? headerControl;
PreferredSizeWidget? bottomControl;
Widget? danmuWidget;
/// 数据加载监听
@ -828,6 +829,7 @@ class PlPlayerController {
child: PLVideoPlayer(
controller: this,
headerControl: headerControl,
bottomControl: bottomControl,
danmuWidget: danmuWidget,
),
),
@ -879,6 +881,9 @@ class PlPlayerController {
if (!_enableHeart) {
return false;
}
if (videoType.value == 'live') {
return;
}
// 播放状态变化时,更新
if (type == 'status') {
await VideoHttp.heartBeat(

View File

@ -29,11 +29,13 @@ import 'widgets/forward_seek.dart';
class PLVideoPlayer extends StatefulWidget {
final PlPlayerController controller;
final PreferredSizeWidget? headerControl;
final PreferredSizeWidget? bottomControl;
final Widget? danmuWidget;
const PLVideoPlayer({
required this.controller,
this.headerControl,
this.bottomControl,
this.danmuWidget,
super.key,
});
@ -120,6 +122,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
vsync: this, duration: const Duration(milliseconds: 300));
videoController = widget.controller.videoController!;
widget.controller.headerControl = widget.headerControl;
widget.controller.bottomControl = widget.bottomControl;
widget.controller.danmuWidget = widget.danmuWidget;
defaultBtmProgressBehavior = setting.get(SettingBoxKey.btmProgressBehavior,
defaultValue: BtmProgresBehavior.values.first.code);
@ -552,34 +555,33 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
// 头部、底部控制条
Obx(
() => Visibility(
visible: _.videoType.value != 'live',
child: Column(
children: [
if (widget.headerControl != null)
ClipRect(
clipBehavior: Clip.hardEdge,
child: AppBarAni(
controller: animationController,
visible: !_.controlsLock.value && _.showControls.value,
position: 'top',
child: widget.headerControl!,
),
),
const Spacer(),
() => Column(
children: [
if (widget.headerControl != null)
ClipRect(
clipBehavior: Clip.hardEdge,
child: AppBarAni(
controller: animationController,
visible: !_.controlsLock.value && _.showControls.value,
position: 'bottom',
child: BottomControl(
controller: widget.controller,
triggerFullScreen: widget.controller.triggerFullScreen),
position: 'top',
child: widget.headerControl!,
),
),
],
),
const Spacer(),
ClipRect(
clipBehavior: Clip.hardEdge,
child: AppBarAni(
controller: animationController,
visible: !_.controlsLock.value && _.showControls.value,
position: 'bottom',
child: widget.bottomControl ??
BottomControl(
controller: widget.controller,
triggerFullScreen:
widget.controller.triggerFullScreen),
),
),
],
),
),
@ -598,6 +600,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
!_.isFullScreen.value) {
return Container();
}
if (_.videoType.value == 'live') {
return Container();
}
if (value > max || max <= 0) {
return Container();
}