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

View File

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

View File

@ -10,134 +10,109 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../controller.dart';
// 收藏视频卡片 - 水平布局 // 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget { class FavVideoCardH extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final FavDetailController _favDetailController = final Function? callFn;
Get.put(FavDetailController());
FavVideoCardH({Key? key, required this.videoItem}) : super(key: key); const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
int id = videoItem.id; int id = videoItem.id;
String bvid = videoItem.bvid ?? IdUtils.av2bv(id); String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
String heroTag = Utils.makeHeroTag(id); String heroTag = Utils.makeHeroTag(id);
return Dismissible( return InkWell(
movementDuration: const Duration(milliseconds: 300), onTap: () async {
background: Container( // int? seasonId;
decoration: BoxDecoration( String? epId;
color: Theme.of(context).colorScheme.errorContainer, if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
), videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
child: const Row( // seasonId = videoItem.ogv['season_id'];
mainAxisAlignment: MainAxisAlignment.center, epId = videoItem.epId;
children: [ } else if (videoItem.page == 0 || videoItem.page > 1) {
Icon(Icons.clear_all_rounded), var result = await VideoHttp.videoIntro(bvid: bvid);
SizedBox(width: 6), if (result['status']) {
Text('取消收藏') epId = result['data'].epId;
],
)),
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;
}
} }
}
Map<String, String> parameters = { Map<String, String> parameters = {
'bvid': bvid, 'bvid': bvid,
'cid': videoItem.cid.toString(), 'cid': videoItem.cid.toString(),
'epId': epId ?? '', 'epId': epId ?? '',
}; };
// if (seasonId != null) { // if (seasonId != null) {
// parameters['seasonId'] = seasonId.toString(); // parameters['seasonId'] = seasonId.toString();
// } // }
Get.toNamed('/video', parameters: parameters, arguments: { Get.toNamed('/video', parameters: parameters, arguments: {
'videoItem': videoItem, 'videoItem': videoItem,
'heroTag': heroTag, 'heroTag': heroTag,
'videoType': 'videoType':
epId != null ? SearchType.media_bangumi : SearchType.video, epId != null ? SearchType.media_bangumi : SearchType.video,
}); });
}, },
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5), StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double width = double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2; (boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox( return SizedBox(
height: width / StyleString.aspectRatio, height: width / StyleString.aspectRatio,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: StyleString.aspectRatio, aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, boxConstraints) { builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth; double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight; double maxHeight = boxConstraints.maxHeight;
return Stack( return Stack(
children: [ children: [
Hero( Hero(
tag: heroTag, tag: heroTag,
child: NetworkImgLayer( child: NetworkImgLayer(
src: videoItem.pic, src: videoItem.pic,
width: maxWidth, width: maxWidth,
height: maxHeight, 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 { class VideoContent extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
const VideoContent({super.key, required this.videoItem}); final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -173,7 +149,6 @@ class VideoContent extends StatelessWidget {
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
const SizedBox(height: 2),
Row( Row(
children: [ children: [
StatView( StatView(
@ -181,7 +156,51 @@ class VideoContent extends StatelessWidget {
view: videoItem.cntInfo['play'], view: videoItem.cntInfo['play'],
), ),
const SizedBox(width: 8), 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 'package:pilipala/plugin/pl_player/index.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/bottom_control.dart';
class LiveRoomPage extends StatefulWidget { class LiveRoomPage extends StatefulWidget {
const LiveRoomPage({super.key}); const LiveRoomPage({super.key});
@ -87,96 +88,38 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
), ),
body: Column( body: Column(
children: [ children: [
Hero( Stack(
tag: _liveRoomController.heroTag, children: [
child: Stack( AspectRatio(
children: [ aspectRatio: 16 / 9,
AspectRatio( child: plPlayerController!.videoPlayerController != null
aspectRatio: 16 / 9, ? PLVideoPlayer(
child: plPlayerController!.videoPlayerController != null controller: plPlayerController!,
? PLVideoPlayer(controller: plPlayerController!) bottomControl: BottomControl(
: const SizedBox(), controller: plPlayerController,
), liveRoomCtr: _liveRoomController,
// if (_liveRoomController.liveItem != null && ),
// _liveRoomController.liveItem.cover != null) )
// Visibility( : const SizedBox(),
// visible: isShowCover, ),
// child: Positioned( // if (_liveRoomController.liveItem != null &&
// top: 0, // _liveRoomController.liveItem.cover != null)
// left: 0, // Visibility(
// right: 0, // visible: isShowCover,
// child: NetworkImgLayer( // child: Positioned(
// type: 'emote', // top: 0,
// src: _liveRoomController.liveItem.cover, // left: 0,
// width: Get.size.width, // right: 0,
// height: videoHeight, // 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? headerControl;
PreferredSizeWidget? bottomControl;
Widget? danmuWidget; Widget? danmuWidget;
/// 数据加载监听 /// 数据加载监听
@ -828,6 +829,7 @@ class PlPlayerController {
child: PLVideoPlayer( child: PLVideoPlayer(
controller: this, controller: this,
headerControl: headerControl, headerControl: headerControl,
bottomControl: bottomControl,
danmuWidget: danmuWidget, danmuWidget: danmuWidget,
), ),
), ),
@ -879,6 +881,9 @@ class PlPlayerController {
if (!_enableHeart) { if (!_enableHeart) {
return false; return false;
} }
if (videoType.value == 'live') {
return;
}
// 播放状态变化时,更新 // 播放状态变化时,更新
if (type == 'status') { if (type == 'status') {
await VideoHttp.heartBeat( await VideoHttp.heartBeat(

View File

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