diff --git a/lib/common/widgets/animated_dialog.dart b/lib/common/widgets/animated_dialog.dart index 4d35e3a0..7c7c4395 100644 --- a/lib/common/widgets/animated_dialog.dart +++ b/lib/common/widgets/animated_dialog.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; class AnimatedDialog extends StatefulWidget { - const AnimatedDialog({Key? key, required this.child}) : super(key: key); + const AnimatedDialog({Key? key, required this.child, this.closeFn}) + : super(key: key); final Widget child; + final Function? closeFn; @override State createState() => AnimatedDialogState(); @@ -39,12 +41,16 @@ class AnimatedDialogState extends State Widget build(BuildContext context) { return Material( color: Colors.black.withOpacity(opacityAnimation!.value), - child: Center( - child: FadeTransition( - opacity: scaleAnimation!, - child: ScaleTransition( - scale: scaleAnimation!, - child: widget.child, + child: InkWell( + splashColor: Colors.transparent, + onTap: () => widget.closeFn!(), + child: Center( + child: FadeTransition( + opacity: scaleAnimation!, + child: ScaleTransition( + scale: scaleAnimation!, + child: widget.child, + ), ), ), ), diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart index a3511402..53d4c9a1 100644 --- a/lib/common/widgets/overlay_pop.dart +++ b/lib/common/widgets/overlay_pop.dart @@ -1,42 +1,82 @@ import 'package:flutter/material.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/download.dart'; class OverlayPop extends StatelessWidget { final dynamic videoItem; - const OverlayPop({super.key, this.videoItem}); + final Function? closeFn; + const OverlayPop({super.key, this.videoItem, this.closeFn}); @override Widget build(BuildContext context) { + double imgWidth = MediaQuery.of(context).size.width - 8 * 2; return Container( - margin: const EdgeInsets.symmetric(horizontal: 8.0), + margin: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: Theme.of(context).colorScheme.background, - borderRadius: BorderRadius.circular(6.0), + borderRadius: BorderRadius.circular(10.0), ), - child: ClipRRect( - borderRadius: BorderRadius.circular(6.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - NetworkImgLayer( - width: (MediaQuery.of(context).size.width - 16), - height: (MediaQuery.of(context).size.width - 16) / - StyleString.aspectRatio, - src: videoItem.pic!, - ), - Padding( - padding: const EdgeInsets.fromLTRB(12, 15, 10, 15), - child: Text( - videoItem.title!, - // maxLines: 1, - // overflow: TextOverflow.ellipsis, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Stack( + children: [ + NetworkImgLayer( + width: imgWidth, + height: imgWidth / StyleString.aspectRatio, + src: videoItem.pic!, + quality: 100, ), - ), - ], - ), + Positioned( + right: 8, + top: 8, + child: Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: + const BorderRadius.all(Radius.circular(20))), + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => closeFn!(), + icon: const Icon( + Icons.close, + size: 18, + color: Colors.white, + ), + ), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 10, 8, 10), + child: Row( + children: [ + Expanded( + child: Text( + videoItem.title!, + ), + ), + const SizedBox(width: 4), + IconButton( + tooltip: '保存封面图', + onPressed: () async { + await DownloadUtils.downloadImg( + videoItem.pic ?? videoItem.cover); + // closeFn!(); + }, + icon: const Icon(Icons.download, size: 20), + ) + ], + )), + ], ), ); } diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index ae6edf4d..4cfce1ef 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -37,11 +37,11 @@ class VideoCardH extends StatelessWidget { longPress!(); } }, - onLongPressEnd: (details) { - if (longPressEnd != null) { - longPressEnd!(); - } - }, + // onLongPressEnd: (details) { + // if (longPressEnd != null) { + // longPressEnd!(); + // } + // }, child: InkWell( onTap: () async { try { diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 4477ad11..a9898c43 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -79,11 +79,11 @@ class VideoCardV extends StatelessWidget { longPress!(); } }, - onLongPressEnd: (details) { - if (longPressEnd != null) { - longPressEnd!(); - } - }, + // onLongPressEnd: (details) { + // if (longPressEnd != null) { + // longPressEnd!(); + // } + // }, child: InkWell( onTap: () async => onPushDetail(heroTag), child: Column( diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 1b76c079..191ebc0e 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -126,7 +126,9 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { OverlayEntry _createPopupDialog(videoItem) { return OverlayEntry( builder: (context) => AnimatedDialog( - child: OverlayPop(videoItem: videoItem), + closeFn: _hotController.popupDialog?.remove, + child: OverlayPop( + videoItem: videoItem, closeFn: _hotController.popupDialog?.remove), ), ); } diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 2e4dab02..e07950ae 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -113,7 +113,9 @@ class _LivePageState extends State { OverlayEntry _createPopupDialog(liveItem) { return OverlayEntry( builder: (context) => AnimatedDialog( - child: OverlayPop(videoItem: liveItem), + closeFn: _liveController.popupDialog?.remove, + child: OverlayPop( + videoItem: liveItem, closeFn: _liveController.popupDialog?.remove), ), ); } diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart index 3e6df115..f676a877 100644 --- a/lib/pages/live/widgets/live_item.dart +++ b/lib/pages/live/widgets/live_item.dart @@ -35,11 +35,11 @@ class LiveCardV extends StatelessWidget { longPress!(); } }, - onLongPressEnd: (details) { - if (longPressEnd != null) { - longPressEnd!(); - } - }, + // onLongPressEnd: (details) { + // if (longPressEnd != null) { + // longPressEnd!(); + // } + // }, child: InkWell( onTap: () async { Get.toNamed('/liveRoom?roomid=${liveItem.roomId}', diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 9f544648..6cead2df 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -128,7 +128,9 @@ class _RcmdPageState extends State OverlayEntry _createPopupDialog(videoItem) { return OverlayEntry( builder: (context) => AnimatedDialog( - child: OverlayPop(videoItem: videoItem), + closeFn: _rcmdController.popupDialog?.remove, + child: OverlayPop( + videoItem: videoItem, closeFn: _rcmdController.popupDialog?.remove), ), ); } diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index bc2c326c..73c6e289 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -6,71 +6,74 @@ import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_h.dart'; import './controller.dart'; -class RelatedVideoPanel extends GetView { +class RelatedVideoPanel extends StatefulWidget { const RelatedVideoPanel({super.key}); + @override + State createState() => _RelatedVideoPanelState(); +} + +class _RelatedVideoPanelState extends State { + final ReleatedController _releatedController = + Get.put(ReleatedController(), tag: Get.arguments['heroTag']); @override Widget build(BuildContext context) { - return GetBuilder( - init: ReleatedController(), - id: Get.arguments['heroTag'], - builder: (context) { - return FutureBuilder( - future: ReleatedController().queryRelatedVideo(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data!['status']) { - // 请求成功 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - if (index == snapshot.data['data'].length) { - return SizedBox( - height: MediaQuery.of(context).padding.bottom); - } else { - return Material( - child: VideoCardH( - videoItem: snapshot.data['data'][index], - longPress: () { - try { - ReleatedController().popupDialog = - _createPopupDialog( - snapshot.data['data'][index]); - Overlay.of(context) - .insert(ReleatedController().popupDialog!); - } catch (_) { - return {}; - } - }, - longPressEnd: () { - ReleatedController().popupDialog?.remove(); - }, - ), - ); - } - }, childCount: snapshot.data['data'].length + 1)); - } else { - // 请求错误 - return const Center( - child: Text('出错了'), - ); - } + return FutureBuilder( + future: _releatedController.queryRelatedVideo(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data!['status']) { + // 请求成功 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + if (index == snapshot.data['data'].length) { + return SizedBox(height: MediaQuery.of(context).padding.bottom); } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 5), + return Material( + child: VideoCardH( + videoItem: snapshot.data['data'][index], + longPress: () { + try { + _releatedController.popupDialog = + _createPopupDialog(snapshot.data['data'][index]); + Overlay.of(context) + .insert(_releatedController.popupDialog!); + } catch (err) { + return {}; + } + }, + longPressEnd: () { + _releatedController.popupDialog?.remove(); + }, + ), ); } - }, + }, childCount: snapshot.data['data'].length + 1)); + } else { + // 请求错误 + return const Center( + child: Text('出错了'), + ); + } + } else { + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 5), ); - }); + } + }, + ); } OverlayEntry _createPopupDialog(videoItem) { return OverlayEntry( builder: (context) => AnimatedDialog( - child: OverlayPop(videoItem: videoItem), + closeFn: _releatedController.popupDialog?.remove, + child: OverlayPop( + videoItem: videoItem, + closeFn: _releatedController.popupDialog?.remove), ), ); } diff --git a/lib/utils/download.dart b/lib/utils/download.dart new file mode 100644 index 00000000..830464b2 --- /dev/null +++ b/lib/utils/download.dart @@ -0,0 +1,39 @@ +import 'dart:typed_data'; + +import 'package:dio/dio.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class DownloadUtils { + // 获取存储全县 + static requestStoragePer() async { + Map statuses = await [ + Permission.storage, + Permission.photos, + ].request(); + statuses[Permission.storage].toString(); + } + + static Future downloadImg(String imgUrl) async { + await requestStoragePer(); + SmartDialog.showLoading(msg: '保存中'); + var response = await Dio() + .get(imgUrl, options: Options(responseType: ResponseType.bytes)); + String picName = + "plpl_cover_${DateTime.now().toString().split('-').join()}.png"; + final result = await ImageGallerySaver.saveImage( + Uint8List.fromList(response.data), + quality: 100, + name: picName, + ); + SmartDialog.dismiss(); + if (result != null) { + if (result['isSuccess']) { + // ignore: avoid_print + await SmartDialog.showToast('「$picName」已保存 '); + } + } + return true; + } +}