From 0f134b8dca24538d33af76e42da1d09cac386da3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 4 Sep 2023 12:41:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?mod:=20=E4=BF=AE=E6=94=B9=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E6=94=B6=E8=97=8F=E7=9A=84=E9=80=BB=E8=BE=91=20issues#60?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/favDetail/controller.dart | 6 +- lib/pages/favDetail/view.dart | 22 +- .../favDetail/widget/fav_video_card.dart | 249 ++++++++++-------- 3 files changed, 150 insertions(+), 127 deletions(-) diff --git a/lib/pages/favDetail/controller.dart b/lib/pages/favDetail/controller.dart index 8b772716..c2c63dd5 100644 --- a/lib/pages/favDetail/controller.dart +++ b/lib/pages/favDetail/controller.dart @@ -14,7 +14,7 @@ class FavDetailController extends GetxController { int currentPage = 1; bool isLoadingMore = false; RxMap favInfo = {}.obs; - RxList 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 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('取消收藏'); } } diff --git a/lib/pages/favDetail/view.dart b/lib/pages/favDetail/view.dart index 426bfa8f..cda6c2b7 100644 --- a/lib/pages/favDetail/view.dart +++ b/lib/pages/favDetail/view.dart @@ -168,7 +168,7 @@ class _FavDetailPageState extends State { 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 { 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 { diff --git a/lib/pages/favDetail/widget/fav_video_card.dart b/lib/pages/favDetail/widget/fav_video_card.dart index 61ac06f1..471f19bc 100644 --- a/lib/pages/favDetail/widget/fav_video_card.dart +++ b/lib/pages/favDetail/widget/fav_video_card.dart @@ -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(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 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 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, + ), + ), + ), ], ), ], From 1bebb32a0d4f6b53f23d09904c9d5b244c12399b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 4 Sep 2023 15:01:02 +0800 Subject: [PATCH 2/2] =?UTF-8?q?mod:=20=E7=9B=B4=E6=92=AD=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/liveRoom/view.dart | 121 +++++------------- .../liveRoom/widgets/bottom_control.dart | 118 +++++++++++++++++ lib/plugin/pl_player/controller.dart | 5 + lib/plugin/pl_player/view.dart | 48 ++++--- 4 files changed, 182 insertions(+), 110 deletions(-) create mode 100644 lib/pages/liveRoom/widgets/bottom_control.dart diff --git a/lib/pages/liveRoom/view.dart b/lib/pages/liveRoom/view.dart index fa881cb8..01718bd2 100644 --- a/lib/pages/liveRoom/view.dart +++ b/lib/pages/liveRoom/view.dart @@ -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 { ), 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: [ - // 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, - // ), - // ), - // ), - // ]), - // ), ], ), ); diff --git a/lib/pages/liveRoom/widgets/bottom_control.dart b/lib/pages/liveRoom/widgets/bottom_control.dart new file mode 100644 index 00000000..f538acad --- /dev/null +++ b/lib/pages/liveRoom/widgets/bottom_control.dart @@ -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 createState() => _BottomControlState(); + + @override + Size get preferredSize => throw UnimplementedError(); +} + +class _BottomControlState extends State { + late PlayUrlModel videoInfo; + List 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); + } +} diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 386524b9..81c20d99 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -105,6 +105,7 @@ class PlPlayerController { ]; PreferredSizeWidget? headerControl; + PreferredSizeWidget? bottomControl; Widget? danmuWidget; /// 数据加载监听 @@ -819,6 +820,7 @@ class PlPlayerController { child: PLVideoPlayer( controller: this, headerControl: headerControl, + bottomControl: bottomControl, danmuWidget: danmuWidget, ), ), @@ -867,6 +869,9 @@ class PlPlayerController { if (!_enableHeart) { return false; } + if (videoType.value == 'live') { + return; + } // 播放状态变化时,更新 if (type == 'status') { await VideoHttp.heartBeat( diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 1b3703d4..5e7416bf 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -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 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); @@ -562,34 +565,33 @@ class _PLVideoPlayerState extends State // 头部、底部控制条 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), + ), + ), + ], ), ), @@ -608,6 +610,10 @@ class _PLVideoPlayerState extends State !_.isFullScreen.value) { return Container(); } + + if (_.videoType.value == 'live') { + return Container(); + } if (value > max || max <= 0) { return Container(); }