feat: 长按保存封面
This commit is contained in:
@ -49,6 +49,8 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>请允许APP保存图片到相册</string>
|
<string>请允许APP保存图片到相册</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>请允许APP保存图片到相册</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>NSCameraUsageDescription</key>
|
||||||
<string>App需要您的同意,才能访问相册</string>
|
<string>App需要您的同意,才能访问相册</string>
|
||||||
<key>NSAppleMusicUsageDescription</key>
|
<key>NSAppleMusicUsageDescription</key>
|
||||||
|
|||||||
@ -1,87 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import '../../utils/download.dart';
|
|
||||||
import '../constants.dart';
|
|
||||||
import 'network_img_layer.dart';
|
|
||||||
|
|
||||||
class OverlayPop extends StatelessWidget {
|
|
||||||
const OverlayPop({super.key, this.videoItem, this.closeFn});
|
|
||||||
|
|
||||||
final dynamic videoItem;
|
|
||||||
final Function? closeFn;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2;
|
|
||||||
return Container(
|
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.background,
|
|
||||||
borderRadius: BorderRadius.circular(10.0),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
NetworkImgLayer(
|
|
||||||
width: imgWidth,
|
|
||||||
height: imgWidth / StyleString.aspectRatio,
|
|
||||||
src: videoItem.pic! as String,
|
|
||||||
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! as String,
|
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
IconButton(
|
|
||||||
tooltip: '保存封面图',
|
|
||||||
onPressed: () async {
|
|
||||||
await DownloadUtils.downloadImg(
|
|
||||||
videoItem.pic != null
|
|
||||||
? videoItem.pic as String
|
|
||||||
: videoItem.cover as String,
|
|
||||||
);
|
|
||||||
// closeFn!();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.download, size: 20),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.dart';
|
||||||
import '../../http/search.dart';
|
import '../../http/search.dart';
|
||||||
import '../../http/user.dart';
|
import '../../http/user.dart';
|
||||||
import '../../http/video.dart';
|
import '../../http/video.dart';
|
||||||
@ -16,8 +17,7 @@ class VideoCardH extends StatelessWidget {
|
|||||||
const VideoCardH({
|
const VideoCardH({
|
||||||
super.key,
|
super.key,
|
||||||
required this.videoItem,
|
required this.videoItem,
|
||||||
this.longPress,
|
this.onPressedFn,
|
||||||
this.longPressEnd,
|
|
||||||
this.source = 'normal',
|
this.source = 'normal',
|
||||||
this.showOwner = true,
|
this.showOwner = true,
|
||||||
this.showView = true,
|
this.showView = true,
|
||||||
@ -27,8 +27,8 @@ class VideoCardH extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
final videoItem;
|
final videoItem;
|
||||||
final Function()? longPress;
|
final Function()? onPressedFn;
|
||||||
final Function()? longPressEnd;
|
// normal 推荐, later 稍后再看, search 搜索
|
||||||
final String source;
|
final String source;
|
||||||
final bool showOwner;
|
final bool showOwner;
|
||||||
final bool showView;
|
final bool showView;
|
||||||
@ -45,109 +45,103 @@ class VideoCardH extends StatelessWidget {
|
|||||||
type = videoItem.type;
|
type = videoItem.type;
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
final String heroTag = Utils.makeHeroTag(aid);
|
final String heroTag = Utils.makeHeroTag(aid);
|
||||||
return GestureDetector(
|
return InkWell(
|
||||||
onLongPress: () {
|
onTap: () async {
|
||||||
if (longPress != null) {
|
try {
|
||||||
longPress!();
|
if (type == 'ketang') {
|
||||||
|
SmartDialog.showToast('课堂视频暂不支持播放');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final int cid =
|
||||||
|
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
||||||
|
} catch (err) {
|
||||||
|
SmartDialog.showToast(err.toString());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// onLongPressEnd: (details) {
|
onLongPress: () => imageSaveDialog(
|
||||||
// if (longPressEnd != null) {
|
context,
|
||||||
// longPressEnd!();
|
videoItem,
|
||||||
// }
|
SmartDialog.dismiss,
|
||||||
// },
|
),
|
||||||
child: InkWell(
|
child: Padding(
|
||||||
onTap: () async {
|
padding: const EdgeInsets.fromLTRB(
|
||||||
try {
|
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||||
if (type == 'ketang') {
|
child: LayoutBuilder(
|
||||||
SmartDialog.showToast('课堂视频暂不支持播放');
|
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||||
return;
|
final double width = (boxConstraints.maxWidth -
|
||||||
}
|
StyleString.cardSpace *
|
||||||
final int cid =
|
6 /
|
||||||
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||||
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
2;
|
||||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
return Container(
|
||||||
} catch (err) {
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
SmartDialog.showToast(err.toString());
|
height: width / StyleString.aspectRatio,
|
||||||
}
|
child: Row(
|
||||||
},
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
child: Padding(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
padding: const EdgeInsets.fromLTRB(
|
children: <Widget>[
|
||||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
AspectRatio(
|
||||||
child: LayoutBuilder(
|
aspectRatio: StyleString.aspectRatio,
|
||||||
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
child: LayoutBuilder(
|
||||||
final double width = (boxConstraints.maxWidth -
|
builder: (BuildContext context,
|
||||||
StyleString.cardSpace *
|
BoxConstraints boxConstraints) {
|
||||||
6 /
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
MediaQuery.textScalerOf(context).scale(1.0)) /
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
2;
|
return Stack(
|
||||||
return Container(
|
children: [
|
||||||
constraints: const BoxConstraints(minHeight: 88),
|
Hero(
|
||||||
height: width / StyleString.aspectRatio,
|
tag: heroTag,
|
||||||
child: Row(
|
child: NetworkImgLayer(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
src: videoItem.pic as String,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
width: maxWidth,
|
||||||
children: <Widget>[
|
height: maxHeight,
|
||||||
AspectRatio(
|
|
||||||
aspectRatio: StyleString.aspectRatio,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (BuildContext context,
|
|
||||||
BoxConstraints boxConstraints) {
|
|
||||||
final double maxWidth = boxConstraints.maxWidth;
|
|
||||||
final double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: videoItem.pic as String,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (videoItem.duration != 0)
|
),
|
||||||
PBadge(
|
if (videoItem.duration != 0)
|
||||||
text: Utils.timeFormat(videoItem.duration!),
|
PBadge(
|
||||||
right: 6.0,
|
text: Utils.timeFormat(videoItem.duration!),
|
||||||
bottom: 6.0,
|
right: 6.0,
|
||||||
type: 'gray',
|
bottom: 6.0,
|
||||||
),
|
type: 'gray',
|
||||||
if (type != 'video')
|
),
|
||||||
PBadge(
|
if (type != 'video')
|
||||||
text: type,
|
PBadge(
|
||||||
left: 6.0,
|
text: type,
|
||||||
bottom: 6.0,
|
left: 6.0,
|
||||||
type: 'primary',
|
bottom: 6.0,
|
||||||
),
|
type: 'primary',
|
||||||
// if (videoItem.rcmdReason != null &&
|
),
|
||||||
// videoItem.rcmdReason.content != '')
|
// if (videoItem.rcmdReason != null &&
|
||||||
// pBadge(videoItem.rcmdReason.content, context,
|
// videoItem.rcmdReason.content != '')
|
||||||
// 6.0, 6.0, null, null),
|
// pBadge(videoItem.rcmdReason.content, context,
|
||||||
if (showCharge && videoItem?.isChargingSrc)
|
// 6.0, 6.0, null, null),
|
||||||
const PBadge(
|
if (showCharge && videoItem?.isChargingSrc)
|
||||||
text: '充电专属',
|
const PBadge(
|
||||||
right: 6.0,
|
text: '充电专属',
|
||||||
top: 6.0,
|
right: 6.0,
|
||||||
type: 'primary',
|
top: 6.0,
|
||||||
),
|
type: 'primary',
|
||||||
],
|
),
|
||||||
);
|
],
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
VideoContent(
|
),
|
||||||
videoItem: videoItem,
|
VideoContent(
|
||||||
source: source,
|
videoItem: videoItem,
|
||||||
showOwner: showOwner,
|
source: source,
|
||||||
showView: showView,
|
showOwner: showOwner,
|
||||||
showDanmaku: showDanmaku,
|
showView: showView,
|
||||||
showPubdate: showPubdate,
|
showDanmaku: showDanmaku,
|
||||||
)
|
showPubdate: showPubdate,
|
||||||
],
|
onPressedFn: onPressedFn,
|
||||||
),
|
)
|
||||||
);
|
],
|
||||||
},
|
),
|
||||||
),
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -162,6 +156,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
final bool showView;
|
final bool showView;
|
||||||
final bool showDanmaku;
|
final bool showDanmaku;
|
||||||
final bool showPubdate;
|
final bool showPubdate;
|
||||||
|
final Function()? onPressedFn;
|
||||||
|
|
||||||
const VideoContent({
|
const VideoContent({
|
||||||
super.key,
|
super.key,
|
||||||
@ -171,6 +166,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
this.showView = true,
|
this.showView = true,
|
||||||
this.showDanmaku = true,
|
this.showDanmaku = true,
|
||||||
this.showPubdate = false,
|
this.showPubdate = false,
|
||||||
|
this.onPressedFn,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -181,7 +177,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (videoItem.title is String) ...[
|
if (source == 'normal' || source == 'later') ...[
|
||||||
Text(
|
Text(
|
||||||
videoItem.title as String,
|
videoItem.title as String,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
@ -196,7 +192,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
for (final i in videoItem.title) ...[
|
for (final i in videoItem.titleList) ...[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'] as String,
|
text: i['text'] as String,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@ -374,6 +370,19 @@ class VideoContent extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (source == 'later') ...[
|
||||||
|
IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => onPressedFn?.call(),
|
||||||
|
icon: Icon(
|
||||||
|
Icons.clear_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.dart';
|
||||||
import '../../models/model_rec_video_item.dart';
|
import '../../models/model_rec_video_item.dart';
|
||||||
import 'overlay_pop.dart';
|
|
||||||
import 'stat/danmu.dart';
|
import 'stat/danmu.dart';
|
||||||
import 'stat/view.dart';
|
import 'stat/view.dart';
|
||||||
import '../../http/dynamics.dart';
|
import '../../http/dynamics.dart';
|
||||||
@ -127,14 +127,11 @@ class VideoCardV extends StatelessWidget {
|
|||||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () async => onPushDetail(heroTag),
|
onTap: () async => onPushDetail(heroTag),
|
||||||
onLongPress: () {
|
onLongPress: () => imageSaveDialog(
|
||||||
SmartDialog.show(
|
context,
|
||||||
builder: (context) => OverlayPop(
|
videoItem,
|
||||||
videoItem: videoItem,
|
SmartDialog.dismiss,
|
||||||
closeFn: () => SmartDialog.dismiss(),
|
),
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@ -30,6 +30,7 @@ class BangumiListItemModel {
|
|||||||
BangumiListItemModel({
|
BangumiListItemModel({
|
||||||
this.badge,
|
this.badge,
|
||||||
this.badgeType,
|
this.badgeType,
|
||||||
|
this.pic,
|
||||||
this.cover,
|
this.cover,
|
||||||
// this.firstEp,
|
// this.firstEp,
|
||||||
this.indexShow,
|
this.indexShow,
|
||||||
@ -50,6 +51,7 @@ class BangumiListItemModel {
|
|||||||
|
|
||||||
String? badge;
|
String? badge;
|
||||||
int? badgeType;
|
int? badgeType;
|
||||||
|
String? pic;
|
||||||
String? cover;
|
String? cover;
|
||||||
String? indexShow;
|
String? indexShow;
|
||||||
int? isFinish;
|
int? isFinish;
|
||||||
@ -70,6 +72,7 @@ class BangumiListItemModel {
|
|||||||
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
badge = json['badge'] == '' ? null : json['badge'];
|
badge = json['badge'] == '' ? null : json['badge'];
|
||||||
badgeType = json['badge_type'];
|
badgeType = json['badge_type'];
|
||||||
|
pic = json['cover'];
|
||||||
cover = json['cover'];
|
cover = json['cover'];
|
||||||
indexShow = json['index_show'];
|
indexShow = json['index_show'];
|
||||||
isFinish = json['is_finish'];
|
isFinish = json['is_finish'];
|
||||||
|
|||||||
@ -25,6 +25,7 @@ class SearchVideoItemModel {
|
|||||||
this.aid,
|
this.aid,
|
||||||
this.bvid,
|
this.bvid,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.titleList,
|
||||||
this.description,
|
this.description,
|
||||||
this.pic,
|
this.pic,
|
||||||
// this.play,
|
// this.play,
|
||||||
@ -54,8 +55,8 @@ class SearchVideoItemModel {
|
|||||||
String? arcurl;
|
String? arcurl;
|
||||||
int? aid;
|
int? aid;
|
||||||
String? bvid;
|
String? bvid;
|
||||||
List? title;
|
String? title;
|
||||||
// List? titleList;
|
List? titleList;
|
||||||
String? description;
|
String? description;
|
||||||
String? pic;
|
String? pic;
|
||||||
// String? play;
|
// String? play;
|
||||||
@ -82,8 +83,9 @@ class SearchVideoItemModel {
|
|||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
title = Em.regTitle(json['title']);
|
// title = Em.regTitle(json['title']);
|
||||||
|
titleList = Em.regTitle(json['title']);
|
||||||
description = json['description'];
|
description = json['description'];
|
||||||
pic = json['pic'] != null && json['pic'].startsWith('//')
|
pic = json['pic'] != null && json['pic'].startsWith('//')
|
||||||
? 'https:${json['pic']}'
|
? 'https:${json['pic']}'
|
||||||
@ -232,6 +234,7 @@ class SearchLiveItemModel {
|
|||||||
this.userCover,
|
this.userCover,
|
||||||
this.type,
|
this.type,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.titleList,
|
||||||
this.cover,
|
this.cover,
|
||||||
this.pic,
|
this.pic,
|
||||||
this.online,
|
this.online,
|
||||||
@ -251,7 +254,8 @@ class SearchLiveItemModel {
|
|||||||
String? face;
|
String? face;
|
||||||
String? userCover;
|
String? userCover;
|
||||||
String? type;
|
String? type;
|
||||||
List? title;
|
String? title;
|
||||||
|
List? titleList;
|
||||||
String? cover;
|
String? cover;
|
||||||
String? pic;
|
String? pic;
|
||||||
int? online;
|
int? online;
|
||||||
@ -272,7 +276,8 @@ class SearchLiveItemModel {
|
|||||||
face = json['uface'];
|
face = json['uface'];
|
||||||
userCover = json['user_cover'];
|
userCover = json['user_cover'];
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
title = Em.regTitle(json['title']);
|
title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
|
titleList = Em.regTitle(json['title']);
|
||||||
cover = json['cover'];
|
cover = json['cover'];
|
||||||
pic = json['cover'];
|
pic = json['cover'];
|
||||||
online = json['online'];
|
online = json['online'];
|
||||||
@ -302,6 +307,7 @@ class SearchMBangumiItemModel {
|
|||||||
this.type,
|
this.type,
|
||||||
this.mediaId,
|
this.mediaId,
|
||||||
this.title,
|
this.title,
|
||||||
|
this.titleList,
|
||||||
this.orgTitle,
|
this.orgTitle,
|
||||||
this.mediaType,
|
this.mediaType,
|
||||||
this.cv,
|
this.cv,
|
||||||
@ -328,7 +334,8 @@ class SearchMBangumiItemModel {
|
|||||||
|
|
||||||
String? type;
|
String? type;
|
||||||
int? mediaId;
|
int? mediaId;
|
||||||
List? title;
|
String? title;
|
||||||
|
List? titleList;
|
||||||
String? orgTitle;
|
String? orgTitle;
|
||||||
int? mediaType;
|
int? mediaType;
|
||||||
String? cv;
|
String? cv;
|
||||||
@ -355,7 +362,8 @@ class SearchMBangumiItemModel {
|
|||||||
SearchMBangumiItemModel.fromJson(Map<String, dynamic> json) {
|
SearchMBangumiItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
mediaId = json['media_id'];
|
mediaId = json['media_id'];
|
||||||
title = Em.regTitle(json['title']);
|
title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
|
||||||
|
titleList = Em.regTitle(json['title']);
|
||||||
orgTitle = json['org_title'];
|
orgTitle = json['org_title'];
|
||||||
mediaType = json['media_type'];
|
mediaType = json['media_type'];
|
||||||
cv = json['cv'];
|
cv = json['cv'];
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import 'package:pilipala/common/constants.dart';
|
|||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/list.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.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';
|
||||||
|
|
||||||
@ -14,109 +16,87 @@ class BangumiCardV extends StatelessWidget {
|
|||||||
const BangumiCardV({
|
const BangumiCardV({
|
||||||
super.key,
|
super.key,
|
||||||
required this.bangumiItem,
|
required this.bangumiItem,
|
||||||
this.longPress,
|
|
||||||
this.longPressEnd,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final bangumiItem;
|
final BangumiListItemModel bangumiItem;
|
||||||
final Function()? longPress;
|
|
||||||
final Function()? longPressEnd;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
|
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
|
||||||
return Card(
|
return InkWell(
|
||||||
elevation: 0,
|
onTap: () async {
|
||||||
clipBehavior: Clip.hardEdge,
|
final int seasonId = bangumiItem.seasonId!;
|
||||||
margin: EdgeInsets.zero,
|
SmartDialog.showLoading(msg: '获取中...');
|
||||||
child: GestureDetector(
|
final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
||||||
// onLongPress: () {
|
SmartDialog.dismiss().then((value) {
|
||||||
// if (longPress != null) {
|
if (res['status']) {
|
||||||
// longPress!();
|
if (res['data'].episodes.isEmpty) {
|
||||||
// }
|
SmartDialog.showToast('资源加载失败');
|
||||||
// },
|
return;
|
||||||
// onLongPressEnd: (details) {
|
}
|
||||||
// if (longPressEnd != null) {
|
EpisodeItem episode = res['data'].episodes.first;
|
||||||
// longPressEnd!();
|
String bvid = episode.bvid!;
|
||||||
// }
|
int cid = episode.cid!;
|
||||||
// },
|
String pic = episode.cover!;
|
||||||
child: InkWell(
|
String heroTag = Utils.makeHeroTag(cid);
|
||||||
onTap: () async {
|
Get.toNamed(
|
||||||
final int seasonId = bangumiItem.seasonId;
|
'/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
|
||||||
SmartDialog.showLoading(msg: '获取中...');
|
arguments: {
|
||||||
final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
|
'pic': pic,
|
||||||
SmartDialog.dismiss().then((value) {
|
'heroTag': heroTag,
|
||||||
if (res['status']) {
|
'videoType': SearchType.media_bangumi,
|
||||||
if (res['data'].episodes.isEmpty) {
|
'bangumiItem': res['data'],
|
||||||
SmartDialog.showToast('资源加载失败');
|
},
|
||||||
return;
|
);
|
||||||
}
|
}
|
||||||
EpisodeItem episode = res['data'].episodes.first;
|
});
|
||||||
String bvid = episode.bvid!;
|
},
|
||||||
int cid = episode.cid!;
|
onLongPress: () =>
|
||||||
String pic = episode.cover!;
|
imageSaveDialog(context, bangumiItem, SmartDialog.dismiss),
|
||||||
String heroTag = Utils.makeHeroTag(cid);
|
child: Column(
|
||||||
Get.toNamed(
|
children: [
|
||||||
'/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
|
ClipRRect(
|
||||||
arguments: {
|
borderRadius: const BorderRadius.all(
|
||||||
'pic': pic,
|
StyleString.imgRadius,
|
||||||
'heroTag': heroTag,
|
),
|
||||||
'videoType': SearchType.media_bangumi,
|
child: AspectRatio(
|
||||||
'bangumiItem': res['data'],
|
aspectRatio: 0.65,
|
||||||
},
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
|
final double maxWidth = boxConstraints.maxWidth;
|
||||||
|
final double maxHeight = boxConstraints.maxHeight;
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
src: bangumiItem.cover,
|
||||||
|
width: maxWidth,
|
||||||
|
height: maxHeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (bangumiItem.badge != null)
|
||||||
|
PBadge(
|
||||||
|
text: bangumiItem.badge,
|
||||||
|
top: 6,
|
||||||
|
right: 6,
|
||||||
|
bottom: null,
|
||||||
|
left: null),
|
||||||
|
if (bangumiItem.order != null)
|
||||||
|
PBadge(
|
||||||
|
text: bangumiItem.order,
|
||||||
|
top: null,
|
||||||
|
right: null,
|
||||||
|
bottom: 6,
|
||||||
|
left: 6,
|
||||||
|
type: 'gray',
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}),
|
||||||
});
|
),
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
ClipRRect(
|
|
||||||
borderRadius: const BorderRadius.only(
|
|
||||||
topLeft: StyleString.imgRadius,
|
|
||||||
topRight: StyleString.imgRadius,
|
|
||||||
bottomLeft: StyleString.imgRadius,
|
|
||||||
bottomRight: StyleString.imgRadius,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 0.65,
|
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
|
||||||
final double maxWidth = boxConstraints.maxWidth;
|
|
||||||
final double maxHeight = boxConstraints.maxHeight;
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: bangumiItem.cover,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (bangumiItem.badge != null)
|
|
||||||
PBadge(
|
|
||||||
text: bangumiItem.badge,
|
|
||||||
top: 6,
|
|
||||||
right: 6,
|
|
||||||
bottom: null,
|
|
||||||
left: null),
|
|
||||||
if (bangumiItem.order != null)
|
|
||||||
PBadge(
|
|
||||||
text: bangumiItem.order,
|
|
||||||
top: null,
|
|
||||||
right: null,
|
|
||||||
bottom: 6,
|
|
||||||
left: 6,
|
|
||||||
type: 'gray',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BangumiContent(bangumiItem: bangumiItem)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
BangumiContent(bangumiItem: bangumiItem)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:pilipala/http/search.dart';
|
|||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.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 '../../../common/widgets/badge.dart';
|
import '../../../common/widgets/badge.dart';
|
||||||
@ -61,6 +63,11 @@ class FavVideoCardH extends StatelessWidget {
|
|||||||
epId != null ? SearchType.media_bangumi : SearchType.video,
|
epId != null ? SearchType.media_bangumi : SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onLongPress: () => imageSaveDialog(
|
||||||
|
context,
|
||||||
|
videoItem,
|
||||||
|
SmartDialog.dismiss,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
|
||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
@ -78,15 +76,6 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
return VideoCardH(
|
return VideoCardH(
|
||||||
videoItem: _hotController.videoList[index],
|
videoItem: _hotController.videoList[index],
|
||||||
showPubdate: true,
|
showPubdate: true,
|
||||||
longPress: () {
|
|
||||||
_hotController.popupDialog = _createPopupDialog(
|
|
||||||
_hotController.videoList[index]);
|
|
||||||
Overlay.of(context)
|
|
||||||
.insert(_hotController.popupDialog!);
|
|
||||||
},
|
|
||||||
longPressEnd: () {
|
|
||||||
_hotController.popupDialog?.remove();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}, childCount: _hotController.videoList.length),
|
}, childCount: _hotController.videoList.length),
|
||||||
),
|
),
|
||||||
@ -122,14 +111,4 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayEntry _createPopupDialog(videoItem) {
|
|
||||||
return OverlayEntry(
|
|
||||||
builder: (context) => AnimatedDialog(
|
|
||||||
closeFn: _hotController.popupDialog?.remove,
|
|
||||||
child: OverlayPop(
|
|
||||||
videoItem: videoItem, closeFn: _hotController.popupDialog?.remove),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,7 +84,7 @@ class _LaterPageState extends State<LaterPage> {
|
|||||||
return VideoCardH(
|
return VideoCardH(
|
||||||
videoItem: videoItem,
|
videoItem: videoItem,
|
||||||
source: 'later',
|
source: 'later',
|
||||||
longPress: () => _laterController.toViewDel(
|
onPressedFn: () => _laterController.toViewDel(
|
||||||
aid: videoItem.aid));
|
aid: videoItem.aid));
|
||||||
}, childCount: _laterController.laterList.length),
|
}, childCount: _laterController.laterList.length),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,9 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
import 'package:pilipala/common/skeleton/video_card_v.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
|
||||||
import 'package:pilipala/utils/main_stream.dart';
|
import 'package:pilipala/utils/main_stream.dart';
|
||||||
|
|
||||||
import 'controller.dart';
|
import 'controller.dart';
|
||||||
@ -112,16 +110,6 @@ class _LivePageState extends State<LivePage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayEntry _createPopupDialog(liveItem) {
|
|
||||||
return OverlayEntry(
|
|
||||||
builder: (context) => AnimatedDialog(
|
|
||||||
closeFn: _liveController.popupDialog?.remove,
|
|
||||||
child: OverlayPop(
|
|
||||||
videoItem: liveItem, closeFn: _liveController.popupDialog?.remove),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget contentGrid(ctr, liveList) {
|
Widget contentGrid(ctr, liveList) {
|
||||||
// double maxWidth = Get.size.width;
|
// double maxWidth = Get.size.width;
|
||||||
// int baseWidth = 500;
|
// int baseWidth = 500;
|
||||||
@ -152,14 +140,6 @@ class _LivePageState extends State<LivePage>
|
|||||||
? LiveCardV(
|
? LiveCardV(
|
||||||
liveItem: liveList[index],
|
liveItem: liveList[index],
|
||||||
crossAxisCount: crossAxisCount,
|
crossAxisCount: crossAxisCount,
|
||||||
longPress: () {
|
|
||||||
_liveController.popupDialog =
|
|
||||||
_createPopupDialog(liveList[index]);
|
|
||||||
Overlay.of(context).insert(_liveController.popupDialog!);
|
|
||||||
},
|
|
||||||
longPressEnd: () {
|
|
||||||
_liveController.popupDialog?.remove();
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
: const VideoCardVSkeleton();
|
: const VideoCardVSkeleton();
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.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';
|
||||||
|
|
||||||
@ -9,81 +11,66 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
class LiveCardV extends StatelessWidget {
|
class LiveCardV extends StatelessWidget {
|
||||||
final LiveItemModel liveItem;
|
final LiveItemModel liveItem;
|
||||||
final int crossAxisCount;
|
final int crossAxisCount;
|
||||||
final Function()? longPress;
|
|
||||||
final Function()? longPressEnd;
|
|
||||||
|
|
||||||
const LiveCardV({
|
const LiveCardV({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.liveItem,
|
required this.liveItem,
|
||||||
required this.crossAxisCount,
|
required this.crossAxisCount,
|
||||||
this.longPress,
|
|
||||||
this.longPressEnd,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
String heroTag = Utils.makeHeroTag(liveItem.roomId);
|
||||||
return Card(
|
return InkWell(
|
||||||
elevation: 0,
|
onLongPress: () => imageSaveDialog(
|
||||||
clipBehavior: Clip.hardEdge,
|
context,
|
||||||
margin: EdgeInsets.zero,
|
liveItem,
|
||||||
child: GestureDetector(
|
SmartDialog.dismiss,
|
||||||
onLongPress: () {
|
),
|
||||||
if (longPress != null) {
|
borderRadius: BorderRadius.circular(16),
|
||||||
longPress!();
|
onTap: () async {
|
||||||
}
|
Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
|
||||||
},
|
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
|
||||||
// onLongPressEnd: (details) {
|
},
|
||||||
// if (longPressEnd != null) {
|
child: Column(
|
||||||
// longPressEnd!();
|
children: [
|
||||||
// }
|
ClipRRect(
|
||||||
// },
|
borderRadius: const BorderRadius.all(StyleString.imgRadius),
|
||||||
child: InkWell(
|
child: AspectRatio(
|
||||||
onTap: () async {
|
aspectRatio: StyleString.aspectRatio,
|
||||||
Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
},
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
child: Column(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
Hero(
|
||||||
borderRadius: const BorderRadius.all(StyleString.imgRadius),
|
tag: heroTag,
|
||||||
child: AspectRatio(
|
child: NetworkImgLayer(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
src: liveItem.cover!,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
width: maxWidth,
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
height: maxHeight,
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
),
|
||||||
return Stack(
|
),
|
||||||
children: [
|
if (crossAxisCount != 1)
|
||||||
Hero(
|
Positioned(
|
||||||
tag: heroTag,
|
left: 0,
|
||||||
child: NetworkImgLayer(
|
right: 0,
|
||||||
src: liveItem.cover!,
|
bottom: 0,
|
||||||
width: maxWidth,
|
child: AnimatedOpacity(
|
||||||
height: maxHeight,
|
opacity: 1,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: VideoStat(
|
||||||
|
liveItem: liveItem,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (crossAxisCount != 1)
|
),
|
||||||
Positioned(
|
],
|
||||||
left: 0,
|
);
|
||||||
right: 0,
|
}),
|
||||||
bottom: 0,
|
),
|
||||||
child: AnimatedOpacity(
|
|
||||||
opacity: 1,
|
|
||||||
duration: const Duration(milliseconds: 200),
|
|
||||||
child: VideoStat(
|
|
||||||
liveItem: liveItem,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
LiveContent(liveItem: liveItem, crossAxisCount: crossAxisCount)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class MemberSeasonsItem extends StatelessWidget {
|
class MemberSeasonsItem extends StatelessWidget {
|
||||||
@ -29,6 +31,11 @@ class MemberSeasonsItem extends StatelessWidget {
|
|||||||
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
|
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
|
||||||
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
|
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
|
||||||
},
|
},
|
||||||
|
onLongPress: () => imageSaveDialog(
|
||||||
|
context,
|
||||||
|
seasonItem,
|
||||||
|
SmartDialog.dismiss,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
AspectRatio(
|
AspectRatio(
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
|
||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
@ -82,15 +80,6 @@ class _ZonePageState extends State<ZonePage>
|
|||||||
return VideoCardH(
|
return VideoCardH(
|
||||||
videoItem: _zoneController.videoList[index],
|
videoItem: _zoneController.videoList[index],
|
||||||
showPubdate: true,
|
showPubdate: true,
|
||||||
longPress: () {
|
|
||||||
_zoneController.popupDialog = _createPopupDialog(
|
|
||||||
_zoneController.videoList[index]);
|
|
||||||
Overlay.of(context)
|
|
||||||
.insert(_zoneController.popupDialog!);
|
|
||||||
},
|
|
||||||
longPressEnd: () {
|
|
||||||
_zoneController.popupDialog?.remove();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}, childCount: _zoneController.videoList.length),
|
}, childCount: _zoneController.videoList.length),
|
||||||
),
|
),
|
||||||
@ -126,14 +115,4 @@ class _ZonePageState extends State<ZonePage>
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayEntry _createPopupDialog(videoItem) {
|
|
||||||
return OverlayEntry(
|
|
||||||
builder: (context) => AnimatedDialog(
|
|
||||||
closeFn: _zoneController.popupDialog?.remove,
|
|
||||||
child: OverlayPop(
|
|
||||||
videoItem: videoItem, closeFn: _zoneController.popupDialog?.remove),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget searchLivePanel(BuildContext context, ctr, list) {
|
Widget searchLivePanel(BuildContext context, ctr, list) {
|
||||||
@ -42,15 +44,15 @@ class LiveItem extends StatelessWidget {
|
|||||||
Get.toNamed('/liveRoom?roomid=${liveItem.roomid}',
|
Get.toNamed('/liveRoom?roomid=${liveItem.roomid}',
|
||||||
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
|
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
|
||||||
},
|
},
|
||||||
|
onLongPress: () => imageSaveDialog(
|
||||||
|
context,
|
||||||
|
liveItem,
|
||||||
|
SmartDialog.dismiss,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.all(StyleString.imgRadius),
|
||||||
topLeft: StyleString.imgRadius,
|
|
||||||
topRight: StyleString.imgRadius,
|
|
||||||
bottomLeft: StyleString.imgRadius,
|
|
||||||
bottomRight: StyleString.imgRadius,
|
|
||||||
),
|
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
aspectRatio: StyleString.aspectRatio,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
@ -108,7 +110,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
children: [
|
children: [
|
||||||
for (var i in liveItem.title) ...[
|
for (var i in liveItem.titleList) ...[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@ -63,7 +63,7 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onSurface),
|
color: Theme.of(context).colorScheme.onSurface),
|
||||||
children: [
|
children: [
|
||||||
for (var i in i.title) ...[
|
for (var i in i.titleList) ...[
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: i['text'],
|
text: i['text'],
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
|||||||
@ -35,7 +35,11 @@ class SearchVideoPanel extends StatelessWidget {
|
|||||||
padding: index == 0
|
padding: index == 0
|
||||||
? const EdgeInsets.only(top: 2)
|
? const EdgeInsets.only(top: 2)
|
||||||
: EdgeInsets.zero,
|
: EdgeInsets.zero,
|
||||||
child: VideoCardH(videoItem: i, showPubdate: true),
|
child: VideoCardH(
|
||||||
|
videoItem: i,
|
||||||
|
showPubdate: true,
|
||||||
|
source: 'search',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
@ -5,6 +6,7 @@ import 'package:pilipala/common/widgets/stat/danmu.dart';
|
|||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/models/common/search_type.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
import 'package:pilipala/utils/image_save.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 '../../../common/widgets/badge.dart';
|
import '../../../common/widgets/badge.dart';
|
||||||
@ -40,6 +42,11 @@ class SubVideoCardH extends StatelessWidget {
|
|||||||
'videoType': SearchType.video,
|
'videoType': SearchType.video,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
onLongPress: () => imageSaveDialog(
|
||||||
|
context,
|
||||||
|
videoItem,
|
||||||
|
SmartDialog.dismiss,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/widgets/animated_dialog.dart';
|
|
||||||
import 'package:pilipala/common/widgets/http_error.dart';
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
import 'package:pilipala/common/widgets/overlay_pop.dart';
|
|
||||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||||
import './controller.dart';
|
import './controller.dart';
|
||||||
|
|
||||||
@ -54,20 +52,6 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
|
|||||||
child: VideoCardH(
|
child: VideoCardH(
|
||||||
videoItem: relatedVideoList[index],
|
videoItem: relatedVideoList[index],
|
||||||
showPubdate: true,
|
showPubdate: true,
|
||||||
longPress: () {
|
|
||||||
try {
|
|
||||||
_releatedController.popupDialog =
|
|
||||||
_createPopupDialog(_releatedController
|
|
||||||
.relatedVideoList[index]);
|
|
||||||
Overlay.of(context)
|
|
||||||
.insert(_releatedController.popupDialog!);
|
|
||||||
} catch (err) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
longPressEnd: () {
|
|
||||||
_releatedController.popupDialog?.remove();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -89,15 +73,4 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OverlayEntry _createPopupDialog(videoItem) {
|
|
||||||
return OverlayEntry(
|
|
||||||
builder: (BuildContext context) => AnimatedDialog(
|
|
||||||
closeFn: _releatedController.popupDialog?.remove,
|
|
||||||
child: OverlayPop(
|
|
||||||
videoItem: videoItem,
|
|
||||||
closeFn: _releatedController.popupDialog?.remove),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,24 +15,7 @@ class DownloadUtils {
|
|||||||
PermissionStatus status = await Permission.storage.status;
|
PermissionStatus status = await Permission.storage.status;
|
||||||
if (status == PermissionStatus.denied ||
|
if (status == PermissionStatus.denied ||
|
||||||
status == PermissionStatus.permanentlyDenied) {
|
status == PermissionStatus.permanentlyDenied) {
|
||||||
SmartDialog.show(
|
await permissionDialog('提示', '存储权限未授权');
|
||||||
useSystem: true,
|
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('提示'),
|
|
||||||
content: const Text('存储权限未授权'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
openAppSettings();
|
|
||||||
},
|
|
||||||
child: const Text('去授权'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@ -45,24 +28,7 @@ class DownloadUtils {
|
|||||||
PermissionStatus status = await Permission.photos.status;
|
PermissionStatus status = await Permission.photos.status;
|
||||||
if (status == PermissionStatus.denied ||
|
if (status == PermissionStatus.denied ||
|
||||||
status == PermissionStatus.permanentlyDenied) {
|
status == PermissionStatus.permanentlyDenied) {
|
||||||
SmartDialog.show(
|
await permissionDialog('提示', '相册权限未授权');
|
||||||
useSystem: true,
|
|
||||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('提示'),
|
|
||||||
content: const Text('相册权限未授权'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
openAppSettings();
|
|
||||||
},
|
|
||||||
child: const Text('去授权'),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
@ -72,17 +38,16 @@ class DownloadUtils {
|
|||||||
static Future<bool> downloadImg(String imgUrl,
|
static Future<bool> downloadImg(String imgUrl,
|
||||||
{String imgType = 'cover'}) async {
|
{String imgType = 'cover'}) async {
|
||||||
try {
|
try {
|
||||||
if (!Platform.isAndroid || !await requestPhotoPer()) {
|
if (Platform.isAndroid) {
|
||||||
return false;
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
}
|
if (androidInfo.version.sdkInt <= 32) {
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
if (!await requestStoragePer()) {
|
||||||
if (androidInfo.version.sdkInt <= 32) {
|
return false;
|
||||||
if (!await requestStoragePer()) {
|
}
|
||||||
return false;
|
} else {
|
||||||
}
|
if (!await requestPhotoPer()) {
|
||||||
} else {
|
return false;
|
||||||
if (!await requestPhotoPer()) {
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +66,38 @@ class DownloadUtils {
|
|||||||
);
|
);
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
await SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 ');
|
SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 ');
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
await permissionDialog('保存失败', '相册权限未授权');
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
SmartDialog.dismiss();
|
SmartDialog.dismiss();
|
||||||
SmartDialog.showToast(err.toString());
|
SmartDialog.showToast(err.toString());
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future permissionDialog(String title, String content,
|
||||||
|
{Function? onGranted}) async {
|
||||||
|
await SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: Text(content),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
openAppSettings();
|
||||||
|
},
|
||||||
|
child: const Text('去授权'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
88
lib/utils/image_save.dart
Normal file
88
lib/utils/image_save.dart
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/download.dart';
|
||||||
|
|
||||||
|
Future imageSaveDialog(context, videoItem, closeFn) {
|
||||||
|
final double imgWidth =
|
||||||
|
MediaQuery.sizeOf(context).width - StyleString.safeSpace * 2;
|
||||||
|
return SmartDialog.show(
|
||||||
|
animationType: SmartAnimationType.centerScale_otherSlide,
|
||||||
|
builder: (context) => Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: imgWidth,
|
||||||
|
height: imgWidth / StyleString.aspectRatio,
|
||||||
|
src: videoItem.pic! as String,
|
||||||
|
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! as String,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
IconButton(
|
||||||
|
tooltip: '保存封面图',
|
||||||
|
onPressed: () async {
|
||||||
|
bool saveStatus = await DownloadUtils.downloadImg(
|
||||||
|
videoItem.pic != null
|
||||||
|
? videoItem.pic as String
|
||||||
|
: videoItem.cover as String,
|
||||||
|
);
|
||||||
|
// 保存成功,自动关闭弹窗
|
||||||
|
if (saveStatus) {
|
||||||
|
closeFn?.call();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.download, size: 20),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user