mod: image save dialog

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-29 10:31:13 +08:00
parent eca69f3d6d
commit e9945ab63c
21 changed files with 160 additions and 141 deletions

View File

@@ -0,0 +1,91 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/utils/download.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
void imageSaveDialog({
required BuildContext context,
required String? title,
required String? cover,
}) {
final double imgWidth =
MediaQuery.sizeOf(context).width - StyleString.safeSpace * 2;
SmartDialog.show(
animationType: SmartAnimationType.centerScale_otherSlide,
builder: (context) => Container(
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(10.0),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: cover,
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: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: SmartDialog.dismiss,
icon: const Icon(
Icons.close,
size: 18,
color: Colors.white,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
child: Row(
children: [
Expanded(
child: SelectableText(
title ?? '',
style: Theme.of(context).textTheme.titleSmall,
),
),
const SizedBox(width: 4),
IconButton(
tooltip: '保存封面图',
onPressed: () async {
bool saveStatus = await DownloadUtils.downloadImg(
context,
[cover ?? ''],
);
// 保存成功,自动关闭弹窗
if (saveStatus) {
SmartDialog.dismiss();
}
},
icon: const Icon(Icons.download, size: 20),
)
],
),
),
],
),
),
);
}

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -16,24 +17,22 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
this.onTap,
this.onLongPress,
});
final dynamic videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source;
final bool showOwner;
final bool showView;
final bool showDanmaku;
final bool showPubdate;
final GestureTapCallback? onTap;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
@override
Widget build(BuildContext context) {
@@ -56,7 +55,23 @@ class VideoCardH extends StatelessWidget {
label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
},
child: InkWell(
onLongPress: longPress,
onLongPress: () {
if (onLongPress != null) {
onLongPress!();
} else {
imageSaveDialog(
context: context,
title: videoItem.title is String
? videoItem.title
: videoItem.title is List
? (videoItem.title as List)
.map((item) => item['text'])
.join()
: '',
cover: videoItem.pic,
);
}
},
onTap: () async {
if (onTap != null) {
onTap?.call();
@@ -154,7 +169,7 @@ class VideoCardH extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String) ...[
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title as String,
@@ -167,8 +182,8 @@ class VideoCardH extends StatelessWidget {
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
] else ...[
)
else
Expanded(
child: RichText(
overflow: TextOverflow.ellipsis,
@@ -194,7 +209,6 @@ class VideoCardH extends StatelessWidget {
),
),
),
],
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:PiliPalaX/grpc/app/card/v1/card.pb.dart' as card;
import 'package:PiliPalaX/utils/app_scheme.dart';
import 'package:flutter/material.dart';
@@ -12,8 +13,6 @@ class VideoCardHGrpc extends StatelessWidget {
const VideoCardHGrpc({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
@@ -21,8 +20,6 @@ class VideoCardHGrpc extends StatelessWidget {
this.showPubdate = false,
});
final card.Card videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final String source;
final bool showOwner;
final bool showView;
@@ -50,7 +47,11 @@ class VideoCardHGrpc extends StatelessWidget {
// },
child: InkWell(
borderRadius: BorderRadius.circular(12),
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.smallCoverV5.base.title,
cover: videoItem.smallCoverV5.base.cover,
),
onTap: () async {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:PiliPalaX/common/widgets/stat/danmu.dart';
import 'package:PiliPalaX/common/widgets/stat/view.dart';
import 'package:PiliPalaX/common/widgets/video_popup_menu.dart';
@@ -15,14 +16,10 @@ class VideoCardHMemberVideo extends StatelessWidget {
const VideoCardHMemberVideo({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.onTap,
this.bvid,
});
final Item videoItem;
final Function()? longPress;
final Function()? longPressEnd;
final VoidCallback? onTap;
final dynamic bvid;
@@ -34,7 +31,11 @@ class VideoCardHMemberVideo extends StatelessWidget {
return Stack(
children: [
InkWell(
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
onTap: () async {
if (onTap != null) {
onTap!();

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:PiliPalaX/http/search.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -18,14 +19,10 @@ import 'video_popup_menu.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
final dynamic videoItem;
final Function()? longPress;
final Function()? longPressEnd;
const VideoCardV({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
});
bool isStringNumeric(String str) {
@@ -163,7 +160,11 @@ class VideoCardV extends StatelessWidget {
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async => onPushDetail(heroTag),
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.pic,
),
child: Column(
children: [
AspectRatio(

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:PiliPalaX/models/space/item.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -10,14 +11,10 @@ import 'network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardVMemberHome extends StatelessWidget {
final Item videoItem;
final Function()? longPress;
final Function()? longPressEnd;
const VideoCardVMemberHome({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
});
void onPushDetail(heroTag) async {
@@ -147,7 +144,11 @@ class VideoCardVMemberHome extends StatelessWidget {
margin: EdgeInsets.zero,
child: InkWell(
onTap: () async => onPushDetail(heroTag),
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: videoItem.title,
cover: videoItem.cover,
),
child: Column(
children: [
AspectRatio(

View File

@@ -9,13 +9,9 @@ class BangumiCardV extends StatelessWidget {
const BangumiCardV({
super.key,
required this.bangumiItem,
this.longPress,
this.longPressEnd,
});
final dynamic bangumiItem;
final Function()? longPress;
final Function()? longPressEnd;
@override
Widget build(BuildContext context) {

View File

@@ -9,13 +9,9 @@ class BangumiCardVMemberHome extends StatelessWidget {
const BangumiCardVMemberHome({
super.key,
required this.bangumiItem,
this.longPress,
this.longPressEnd,
});
final Item bangumiItem;
final Function()? longPress;
final Function()? longPressEnd;
@override
Widget build(BuildContext context) {

View File

@@ -1,11 +0,0 @@
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:flutter/material.dart';
abstract class PopupController extends CommonController {
List<OverlayEntry?> popupDialog = <OverlayEntry?>[];
void removePopupDialog() {
popupDialog.last?.remove();
popupDialog.removeLast();
}
}

View File

@@ -1,8 +1,8 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/http/video.dart';
class HotController extends PopupController {
class HotController extends CommonController {
// int idx = 0;
@override

View File

@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/pages/home/index.dart';
@@ -82,15 +81,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
);
}
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
closeFn: _hotController.removePopupDialog,
videoItem: videoItem,
),
);
}
Widget _buildSkeleton() {
return SliverGrid(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
@@ -125,13 +115,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
return VideoCardH(
videoItem: loadingState.response[index],
showPubdate: true,
longPress: () {
_hotController.popupDialog.add(
_createPopupDialog(loadingState.response[index]));
Overlay.of(context)
.insert(_hotController.popupDialog.last!);
},
longPressEnd: _hotController.removePopupDialog,
);
},
childCount: loadingState.response.length,

View File

@@ -158,7 +158,7 @@ class _LaterPageState extends State<LaterPage> {
: () {
_laterController.onSelect(index);
},
longPress: () {
onLongPress: () {
if (_laterController.enableMultiSelect.value.not) {
_laterController.enableMultiSelect.value = true;
_laterController.onSelect(index);

View File

@@ -1,8 +1,8 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/http/live.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
class LiveController extends PopupController {
class LiveController extends CommonController {
@override
void onInit() {
super.onInit();

View File

@@ -1,3 +1,4 @@
import 'package:PiliPalaX/common/widgets/image_save.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/constants.dart';
@@ -8,14 +9,10 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class LiveCardV extends StatelessWidget {
final LiveItemModel liveItem;
final Function()? longPress;
final Function()? longPressEnd;
const LiveCardV({
super.key,
required this.liveItem,
this.longPress,
this.longPressEnd,
});
@override
@@ -29,7 +26,11 @@ class LiveCardV extends StatelessWidget {
Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
},
onLongPress: longPress,
onLongPress: () => imageSaveDialog(
context: context,
title: liveItem.title,
cover: liveItem.cover,
),
child: Column(
children: [
ClipRRect(

View File

@@ -1,8 +1,8 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
class ZoneController extends PopupController {
class ZoneController extends CommonController {
ZoneController({required this.zoneID});
int zoneID;

View File

@@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/video_card_h.dart';
@@ -98,15 +97,6 @@ class _ZonePageState extends State<ZonePage>
);
}
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
closeFn: _zoneController.removePopupDialog,
videoItem: videoItem,
),
);
}
Widget _buildSkeleton() {
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -135,12 +125,6 @@ class _ZonePageState extends State<ZonePage>
return VideoCardH(
videoItem: loadingState.response[index],
showPubdate: true,
longPress: () {
_zoneController.popupDialog
.add(_createPopupDialog(loadingState.response[index]));
Overlay.of(context).insert(_zoneController.popupDialog.last!);
},
longPressEnd: _zoneController.removePopupDialog,
);
},
childCount: loadingState.response.length,

View File

@@ -1,9 +1,9 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/utils/storage.dart';
class RcmdController extends PopupController {
class RcmdController extends CommonController {
late bool enableSaveLastData;
late String defaultRcmdType = 'app';

View File

@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/common/tab_type.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/pages/live/controller.dart';
import 'package:PiliPalaX/pages/live/widgets/live_item.dart';
import 'package:flutter/material.dart';
@@ -11,7 +11,6 @@ import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/skeleton/video_card_v.dart';
import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/video_card_v.dart';
import 'package:PiliPalaX/pages/home/index.dart';
@@ -31,7 +30,7 @@ class RcmdPage extends StatefulWidget {
class _RcmdPageState extends State<RcmdPage>
with AutomaticKeepAliveClientMixin {
late final PopupController _controller = widget.tabType == TabType.rcmd
late final CommonController _controller = widget.tabType == TabType.rcmd
? Get.put<RcmdController>(RcmdController())
: Get.put<LiveController>(LiveController());
@@ -104,15 +103,6 @@ class _RcmdPageState extends State<RcmdPage>
);
}
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
closeFn: _controller.removePopupDialog,
videoItem: videoItem,
),
);
}
Widget contentGrid(LoadingState loadingState) {
return SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
@@ -135,23 +125,9 @@ class _RcmdPageState extends State<RcmdPage>
? widget.tabType == TabType.rcmd
? VideoCardV(
videoItem: loadingState.response[index],
longPress: () {
_controller.popupDialog.add(
_createPopupDialog(loadingState.response[index]));
Overlay.of(context)
.insert(_controller.popupDialog.last!);
},
longPressEnd: _controller.removePopupDialog,
)
: LiveCardV(
liveItem: loadingState.response[index],
longPress: () {
_controller.popupDialog.add(
_createPopupDialog(loadingState.response[index]));
Overlay.of(context)
.insert(_controller.popupDialog.last!);
},
longPressEnd: _controller.removePopupDialog,
)
: const VideoCardVSkeleton();
},

View File

@@ -1,9 +1,9 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/http/video.dart';
class RelatedController extends PopupController {
class RelatedController extends CommonController {
// 视频aid
String bvid = Get.parameters['bvid'] ?? "";

View File

@@ -2,7 +2,6 @@ import 'package:PiliPalaX/http/loading_state.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'package:PiliPalaX/common/widgets/video_card_h.dart';
import '../../../../common/constants.dart';
@@ -33,15 +32,6 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
);
}
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (BuildContext context) => AnimatedDialog(
closeFn: _relatedController.removePopupDialog,
videoItem: videoItem,
),
);
}
Widget _buildBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => SliverGrid(
@@ -74,13 +64,6 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
child: VideoCardH(
videoItem: loadingState.response[index],
showPubdate: true,
longPress: () {
_relatedController.popupDialog.add(
_createPopupDialog(loadingState.response[index]));
Overlay.of(context)
.insert(_relatedController.popupDialog.last!);
},
longPressEnd: _relatedController.removePopupDialog,
),
);
}

View File

@@ -134,9 +134,11 @@ class DownloadUtils {
break;
}
}
return true;
} catch (err) {
SmartDialog.dismiss();
SmartDialog.showToast(err.toString());
return false;
}
}
}