diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 65906625..1e7d9fed 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -49,6 +49,8 @@
NSPhotoLibraryAddUsageDescription
请允许APP保存图片到相册
+ NSPhotoLibraryUsageDescription
+ 请允许APP保存图片到相册
NSCameraUsageDescription
App需要您的同意,才能访问相册
NSAppleMusicUsageDescription
diff --git a/lib/common/widgets/overlay_pop.dart b/lib/common/widgets/overlay_pop.dart
deleted file mode 100644
index 4f0a3899..00000000
--- a/lib/common/widgets/overlay_pop.dart
+++ /dev/null
@@ -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),
- )
- ],
- )),
- ],
- ),
- );
- }
-}
diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart
index 25e701ac..df0c29b7 100644
--- a/lib/common/widgets/video_card_h.dart
+++ b/lib/common/widgets/video_card_h.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
+import 'package:pilipala/utils/image_save.dart';
import '../../http/search.dart';
import '../../http/user.dart';
import '../../http/video.dart';
@@ -16,8 +17,7 @@ class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
- this.longPress,
- this.longPressEnd,
+ this.onPressedFn,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
@@ -27,8 +27,8 @@ class VideoCardH extends StatelessWidget {
});
// ignore: prefer_typing_uninitialized_variables
final videoItem;
- final Function()? longPress;
- final Function()? longPressEnd;
+ final Function()? onPressedFn;
+ // normal 推荐, later 稍后再看, search 搜索
final String source;
final bool showOwner;
final bool showView;
@@ -45,109 +45,103 @@ class VideoCardH extends StatelessWidget {
type = videoItem.type;
} catch (_) {}
final String heroTag = Utils.makeHeroTag(aid);
- return GestureDetector(
- onLongPress: () {
- if (longPress != null) {
- longPress!();
+ return InkWell(
+ onTap: () async {
+ try {
+ 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) {
- // if (longPressEnd != null) {
- // longPressEnd!();
- // }
- // },
- child: InkWell(
- onTap: () async {
- try {
- 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());
- }
- },
- child: Padding(
- padding: const EdgeInsets.fromLTRB(
- StyleString.safeSpace, 5, StyleString.safeSpace, 5),
- child: LayoutBuilder(
- builder: (BuildContext context, BoxConstraints boxConstraints) {
- final double width = (boxConstraints.maxWidth -
- StyleString.cardSpace *
- 6 /
- MediaQuery.textScalerOf(context).scale(1.0)) /
- 2;
- return Container(
- constraints: const BoxConstraints(minHeight: 88),
- height: width / StyleString.aspectRatio,
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- 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,
- ),
+ onLongPress: () => imageSaveDialog(
+ context,
+ videoItem,
+ SmartDialog.dismiss,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(
+ StyleString.safeSpace, 5, StyleString.safeSpace, 5),
+ child: LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints boxConstraints) {
+ final double width = (boxConstraints.maxWidth -
+ StyleString.cardSpace *
+ 6 /
+ MediaQuery.textScalerOf(context).scale(1.0)) /
+ 2;
+ return Container(
+ constraints: const BoxConstraints(minHeight: 88),
+ height: width / StyleString.aspectRatio,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ 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(
- text: Utils.timeFormat(videoItem.duration!),
- right: 6.0,
- bottom: 6.0,
- type: 'gray',
- ),
- if (type != 'video')
- PBadge(
- text: type,
- left: 6.0,
- bottom: 6.0,
- type: 'primary',
- ),
- // if (videoItem.rcmdReason != null &&
- // videoItem.rcmdReason.content != '')
- // pBadge(videoItem.rcmdReason.content, context,
- // 6.0, 6.0, null, null),
- if (showCharge && videoItem?.isChargingSrc)
- const PBadge(
- text: '充电专属',
- right: 6.0,
- top: 6.0,
- type: 'primary',
- ),
- ],
- );
- },
- ),
+ ),
+ if (videoItem.duration != 0)
+ PBadge(
+ text: Utils.timeFormat(videoItem.duration!),
+ right: 6.0,
+ bottom: 6.0,
+ type: 'gray',
+ ),
+ if (type != 'video')
+ PBadge(
+ text: type,
+ left: 6.0,
+ bottom: 6.0,
+ type: 'primary',
+ ),
+ // if (videoItem.rcmdReason != null &&
+ // videoItem.rcmdReason.content != '')
+ // pBadge(videoItem.rcmdReason.content, context,
+ // 6.0, 6.0, null, null),
+ if (showCharge && videoItem?.isChargingSrc)
+ const PBadge(
+ text: '充电专属',
+ right: 6.0,
+ top: 6.0,
+ type: 'primary',
+ ),
+ ],
+ );
+ },
),
- VideoContent(
- videoItem: videoItem,
- source: source,
- showOwner: showOwner,
- showView: showView,
- showDanmaku: showDanmaku,
- showPubdate: showPubdate,
- )
- ],
- ),
- );
- },
- ),
+ ),
+ VideoContent(
+ videoItem: videoItem,
+ source: source,
+ showOwner: showOwner,
+ showView: showView,
+ showDanmaku: showDanmaku,
+ showPubdate: showPubdate,
+ onPressedFn: onPressedFn,
+ )
+ ],
+ ),
+ );
+ },
),
),
);
@@ -162,6 +156,7 @@ class VideoContent extends StatelessWidget {
final bool showView;
final bool showDanmaku;
final bool showPubdate;
+ final Function()? onPressedFn;
const VideoContent({
super.key,
@@ -171,6 +166,7 @@ class VideoContent extends StatelessWidget {
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
+ this.onPressedFn,
});
@override
@@ -181,7 +177,7 @@ class VideoContent extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (videoItem.title is String) ...[
+ if (source == 'normal' || source == 'later') ...[
Text(
videoItem.title as String,
textAlign: TextAlign.start,
@@ -196,7 +192,7 @@ class VideoContent extends StatelessWidget {
maxLines: 2,
text: TextSpan(
children: [
- for (final i in videoItem.title) ...[
+ for (final i in videoItem.titleList) ...[
TextSpan(
text: i['text'] as String,
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,
+ ),
+ )
+ ],
],
),
],
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index 9916aa7a..6a97d7e7 100644
--- a/lib/common/widgets/video_card_v.dart
+++ b/lib/common/widgets/video_card_v.dart
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/utils/feed_back.dart';
+import 'package:pilipala/utils/image_save.dart';
import '../../models/model_rec_video_item.dart';
-import 'overlay_pop.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import '../../http/dynamics.dart';
@@ -127,14 +127,11 @@ class VideoCardV extends StatelessWidget {
String heroTag = Utils.makeHeroTag(videoItem.id);
return InkWell(
onTap: () async => onPushDetail(heroTag),
- onLongPress: () {
- SmartDialog.show(
- builder: (context) => OverlayPop(
- videoItem: videoItem,
- closeFn: () => SmartDialog.dismiss(),
- ),
- );
- },
+ onLongPress: () => imageSaveDialog(
+ context,
+ videoItem,
+ SmartDialog.dismiss,
+ ),
borderRadius: BorderRadius.circular(16),
child: Column(
children: [
diff --git a/lib/models/bangumi/list.dart b/lib/models/bangumi/list.dart
index c15014d0..fe71bb61 100644
--- a/lib/models/bangumi/list.dart
+++ b/lib/models/bangumi/list.dart
@@ -30,6 +30,7 @@ class BangumiListItemModel {
BangumiListItemModel({
this.badge,
this.badgeType,
+ this.pic,
this.cover,
// this.firstEp,
this.indexShow,
@@ -50,6 +51,7 @@ class BangumiListItemModel {
String? badge;
int? badgeType;
+ String? pic;
String? cover;
String? indexShow;
int? isFinish;
@@ -70,6 +72,7 @@ class BangumiListItemModel {
BangumiListItemModel.fromJson(Map json) {
badge = json['badge'] == '' ? null : json['badge'];
badgeType = json['badge_type'];
+ pic = json['cover'];
cover = json['cover'];
indexShow = json['index_show'];
isFinish = json['is_finish'];
diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart
index 418fb99d..81917b72 100644
--- a/lib/models/search/result.dart
+++ b/lib/models/search/result.dart
@@ -25,6 +25,7 @@ class SearchVideoItemModel {
this.aid,
this.bvid,
this.title,
+ this.titleList,
this.description,
this.pic,
// this.play,
@@ -54,8 +55,8 @@ class SearchVideoItemModel {
String? arcurl;
int? aid;
String? bvid;
- List? title;
- // List? titleList;
+ String? title;
+ List? titleList;
String? description;
String? pic;
// String? play;
@@ -82,8 +83,9 @@ class SearchVideoItemModel {
aid = json['aid'];
bvid = json['bvid'];
mid = json['mid'];
- // title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
- title = Em.regTitle(json['title']);
+ title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
+ // title = Em.regTitle(json['title']);
+ titleList = Em.regTitle(json['title']);
description = json['description'];
pic = json['pic'] != null && json['pic'].startsWith('//')
? 'https:${json['pic']}'
@@ -232,6 +234,7 @@ class SearchLiveItemModel {
this.userCover,
this.type,
this.title,
+ this.titleList,
this.cover,
this.pic,
this.online,
@@ -251,7 +254,8 @@ class SearchLiveItemModel {
String? face;
String? userCover;
String? type;
- List? title;
+ String? title;
+ List? titleList;
String? cover;
String? pic;
int? online;
@@ -272,7 +276,8 @@ class SearchLiveItemModel {
face = json['uface'];
userCover = json['user_cover'];
type = json['type'];
- title = Em.regTitle(json['title']);
+ title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
+ titleList = Em.regTitle(json['title']);
cover = json['cover'];
pic = json['cover'];
online = json['online'];
@@ -302,6 +307,7 @@ class SearchMBangumiItemModel {
this.type,
this.mediaId,
this.title,
+ this.titleList,
this.orgTitle,
this.mediaType,
this.cv,
@@ -328,7 +334,8 @@ class SearchMBangumiItemModel {
String? type;
int? mediaId;
- List? title;
+ String? title;
+ List? titleList;
String? orgTitle;
int? mediaType;
String? cv;
@@ -355,7 +362,8 @@ class SearchMBangumiItemModel {
SearchMBangumiItemModel.fromJson(Map json) {
type = json['type'];
mediaId = json['media_id'];
- title = Em.regTitle(json['title']);
+ title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
+ titleList = Em.regTitle(json['title']);
orgTitle = json['org_title'];
mediaType = json['media_type'];
cv = json['cv'];
diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart
index c1233ddf..3c8f6d2a 100644
--- a/lib/pages/bangumi/widgets/bangumu_card_v.dart
+++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart
@@ -5,7 +5,9 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/http/search.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/utils/image_save.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@@ -14,109 +16,87 @@ class BangumiCardV extends StatelessWidget {
const BangumiCardV({
super.key,
required this.bangumiItem,
- this.longPress,
- this.longPressEnd,
});
- final bangumiItem;
- final Function()? longPress;
- final Function()? longPressEnd;
+ final BangumiListItemModel bangumiItem;
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(bangumiItem.mediaId);
- return Card(
- elevation: 0,
- clipBehavior: Clip.hardEdge,
- margin: EdgeInsets.zero,
- child: GestureDetector(
- // onLongPress: () {
- // if (longPress != null) {
- // longPress!();
- // }
- // },
- // onLongPressEnd: (details) {
- // if (longPressEnd != null) {
- // longPressEnd!();
- // }
- // },
- child: InkWell(
- onTap: () async {
- final int seasonId = bangumiItem.seasonId;
- SmartDialog.showLoading(msg: '获取中...');
- final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
- SmartDialog.dismiss().then((value) {
- if (res['status']) {
- if (res['data'].episodes.isEmpty) {
- SmartDialog.showToast('资源加载失败');
- return;
- }
- EpisodeItem episode = res['data'].episodes.first;
- String bvid = episode.bvid!;
- int cid = episode.cid!;
- String pic = episode.cover!;
- String heroTag = Utils.makeHeroTag(cid);
- Get.toNamed(
- '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
- arguments: {
- 'pic': pic,
- 'heroTag': heroTag,
- 'videoType': SearchType.media_bangumi,
- 'bangumiItem': res['data'],
- },
+ return InkWell(
+ onTap: () async {
+ final int seasonId = bangumiItem.seasonId!;
+ SmartDialog.showLoading(msg: '获取中...');
+ final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
+ SmartDialog.dismiss().then((value) {
+ if (res['status']) {
+ if (res['data'].episodes.isEmpty) {
+ SmartDialog.showToast('资源加载失败');
+ return;
+ }
+ EpisodeItem episode = res['data'].episodes.first;
+ String bvid = episode.bvid!;
+ int cid = episode.cid!;
+ String pic = episode.cover!;
+ String heroTag = Utils.makeHeroTag(cid);
+ Get.toNamed(
+ '/video?bvid=$bvid&cid=$cid&seasonId=$seasonId',
+ arguments: {
+ 'pic': pic,
+ 'heroTag': heroTag,
+ 'videoType': SearchType.media_bangumi,
+ 'bangumiItem': res['data'],
+ },
+ );
+ }
+ });
+ },
+ onLongPress: () =>
+ imageSaveDialog(context, bangumiItem, SmartDialog.dismiss),
+ child: Column(
+ children: [
+ ClipRRect(
+ borderRadius: const BorderRadius.all(
+ 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',
+ ),
+ ],
);
- }
- });
- },
- 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)
+ ],
),
);
}
diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart
index 1c4008ff..79e5c073 100644
--- a/lib/pages/fav_detail/widget/fav_video_card.dart
+++ b/lib/pages/fav_detail/widget/fav_video_card.dart
@@ -1,3 +1,4 @@
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.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/models/common/search_type.dart';
import 'package:pilipala/utils/id_utils.dart';
+import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../../../common/widgets/badge.dart';
@@ -61,6 +63,11 @@ class FavVideoCardH extends StatelessWidget {
epId != null ? SearchType.media_bangumi : SearchType.video,
});
},
+ onLongPress: () => imageSaveDialog(
+ context,
+ videoItem,
+ SmartDialog.dismiss,
+ ),
child: Column(
children: [
Padding(
diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart
index e2e20e73..80d08e7b 100644
--- a/lib/pages/hot/view.dart
+++ b/lib/pages/hot/view.dart
@@ -3,8 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.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/widgets/http_error.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
@@ -78,15 +76,6 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin {
return VideoCardH(
videoItem: _hotController.videoList[index],
showPubdate: true,
- longPress: () {
- _hotController.popupDialog = _createPopupDialog(
- _hotController.videoList[index]);
- Overlay.of(context)
- .insert(_hotController.popupDialog!);
- },
- longPressEnd: () {
- _hotController.popupDialog?.remove();
- },
);
}, childCount: _hotController.videoList.length),
),
@@ -122,14 +111,4 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin {
),
);
}
-
- OverlayEntry _createPopupDialog(videoItem) {
- return OverlayEntry(
- builder: (context) => AnimatedDialog(
- closeFn: _hotController.popupDialog?.remove,
- child: OverlayPop(
- videoItem: videoItem, closeFn: _hotController.popupDialog?.remove),
- ),
- );
- }
}
diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart
index d4695154..7c6e158d 100644
--- a/lib/pages/later/view.dart
+++ b/lib/pages/later/view.dart
@@ -84,7 +84,7 @@ class _LaterPageState extends State {
return VideoCardH(
videoItem: videoItem,
source: 'later',
- longPress: () => _laterController.toViewDel(
+ onPressedFn: () => _laterController.toViewDel(
aid: videoItem.aid));
}, childCount: _laterController.laterList.length),
)
diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart
index c61d20b3..83605495 100644
--- a/lib/pages/live/view.dart
+++ b/lib/pages/live/view.dart
@@ -5,9 +5,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.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/overlay_pop.dart';
import 'package:pilipala/utils/main_stream.dart';
import 'controller.dart';
@@ -112,16 +110,6 @@ class _LivePageState extends State
);
}
- OverlayEntry _createPopupDialog(liveItem) {
- return OverlayEntry(
- builder: (context) => AnimatedDialog(
- closeFn: _liveController.popupDialog?.remove,
- child: OverlayPop(
- videoItem: liveItem, closeFn: _liveController.popupDialog?.remove),
- ),
- );
- }
-
Widget contentGrid(ctr, liveList) {
// double maxWidth = Get.size.width;
// int baseWidth = 500;
@@ -152,14 +140,6 @@ class _LivePageState extends State
? LiveCardV(
liveItem: liveList[index],
crossAxisCount: crossAxisCount,
- longPress: () {
- _liveController.popupDialog =
- _createPopupDialog(liveList[index]);
- Overlay.of(context).insert(_liveController.popupDialog!);
- },
- longPressEnd: () {
- _liveController.popupDialog?.remove();
- },
)
: const VideoCardVSkeleton();
},
diff --git a/lib/pages/live/widgets/live_item.dart b/lib/pages/live/widgets/live_item.dart
index 9218d4fb..f70ba82b 100644
--- a/lib/pages/live/widgets/live_item.dart
+++ b/lib/pages/live/widgets/live_item.dart
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/models/live/item.dart';
+import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/utils.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 {
final LiveItemModel liveItem;
final int crossAxisCount;
- final Function()? longPress;
- final Function()? longPressEnd;
const LiveCardV({
Key? key,
required this.liveItem,
required this.crossAxisCount,
- this.longPress,
- this.longPressEnd,
}) : super(key: key);
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(liveItem.roomId);
- return Card(
- elevation: 0,
- clipBehavior: Clip.hardEdge,
- margin: EdgeInsets.zero,
- child: GestureDetector(
- onLongPress: () {
- if (longPress != null) {
- longPress!();
- }
- },
- // onLongPressEnd: (details) {
- // if (longPressEnd != null) {
- // longPressEnd!();
- // }
- // },
- child: InkWell(
- onTap: () async {
- Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
- arguments: {'liveItem': liveItem, 'heroTag': heroTag});
- },
- child: Column(
- children: [
- ClipRRect(
- borderRadius: const BorderRadius.all(StyleString.imgRadius),
- child: 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: liveItem.cover!,
- width: maxWidth,
- height: maxHeight,
+ return InkWell(
+ onLongPress: () => imageSaveDialog(
+ context,
+ liveItem,
+ SmartDialog.dismiss,
+ ),
+ borderRadius: BorderRadius.circular(16),
+ onTap: () async {
+ Get.toNamed('/liveRoom?roomid=${liveItem.roomId}',
+ arguments: {'liveItem': liveItem, 'heroTag': heroTag});
+ },
+ child: Column(
+ children: [
+ ClipRRect(
+ borderRadius: const BorderRadius.all(StyleString.imgRadius),
+ child: 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: liveItem.cover!,
+ width: maxWidth,
+ height: maxHeight,
+ ),
+ ),
+ if (crossAxisCount != 1)
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 0,
+ child: AnimatedOpacity(
+ 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)
+ ],
),
);
}
diff --git a/lib/pages/member_seasons/widgets/item.dart b/lib/pages/member_seasons/widgets/item.dart
index 4df74b70..85b763b7 100644
--- a/lib/pages/member_seasons/widgets/item.dart
+++ b/lib/pages/member_seasons/widgets/item.dart
@@ -1,10 +1,12 @@
import 'package:flutter/material.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/search.dart';
+import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/utils.dart';
class MemberSeasonsItem extends StatelessWidget {
@@ -29,6 +31,11 @@ class MemberSeasonsItem extends StatelessWidget {
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
},
+ onLongPress: () => imageSaveDialog(
+ context,
+ seasonItem,
+ SmartDialog.dismiss,
+ ),
child: Column(
children: [
AspectRatio(
diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart
index 04631a8c..72d81f95 100644
--- a/lib/pages/rank/zone/view.dart
+++ b/lib/pages/rank/zone/view.dart
@@ -3,8 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.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/widgets/http_error.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
@@ -82,15 +80,6 @@ class _ZonePageState extends State
return VideoCardH(
videoItem: _zoneController.videoList[index],
showPubdate: true,
- longPress: () {
- _zoneController.popupDialog = _createPopupDialog(
- _zoneController.videoList[index]);
- Overlay.of(context)
- .insert(_zoneController.popupDialog!);
- },
- longPressEnd: () {
- _zoneController.popupDialog?.remove();
- },
);
}, childCount: _zoneController.videoList.length),
),
@@ -126,14 +115,4 @@ class _ZonePageState extends State
),
);
}
-
- OverlayEntry _createPopupDialog(videoItem) {
- return OverlayEntry(
- builder: (context) => AnimatedDialog(
- closeFn: _zoneController.popupDialog?.remove,
- child: OverlayPop(
- videoItem: videoItem, closeFn: _zoneController.popupDialog?.remove),
- ),
- );
- }
}
diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart
index 6fb5f5b8..5f797f09 100644
--- a/lib/pages/search_panel/widgets/live_panel.dart
+++ b/lib/pages/search_panel/widgets/live_panel.dart
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
+import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/utils.dart';
Widget searchLivePanel(BuildContext context, ctr, list) {
@@ -42,15 +44,15 @@ class LiveItem extends StatelessWidget {
Get.toNamed('/liveRoom?roomid=${liveItem.roomid}',
arguments: {'liveItem': liveItem, 'heroTag': heroTag});
},
+ onLongPress: () => imageSaveDialog(
+ context,
+ liveItem,
+ SmartDialog.dismiss,
+ ),
child: Column(
children: [
ClipRRect(
- borderRadius: const BorderRadius.only(
- topLeft: StyleString.imgRadius,
- topRight: StyleString.imgRadius,
- bottomLeft: StyleString.imgRadius,
- bottomRight: StyleString.imgRadius,
- ),
+ borderRadius: const BorderRadius.all(StyleString.imgRadius),
child: AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
@@ -108,7 +110,7 @@ class LiveContent extends StatelessWidget {
RichText(
text: TextSpan(
children: [
- for (var i in liveItem.title) ...[
+ for (var i in liveItem.titleList) ...[
TextSpan(
text: i['text'],
style: TextStyle(
diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart
index 18799d3a..7d88b183 100644
--- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart
+++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart
@@ -63,7 +63,7 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
style: TextStyle(
color: Theme.of(context).colorScheme.onSurface),
children: [
- for (var i in i.title) ...[
+ for (var i in i.titleList) ...[
TextSpan(
text: i['text'],
style: TextStyle(
diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart
index b96ff004..c24a007c 100644
--- a/lib/pages/search_panel/widgets/video_panel.dart
+++ b/lib/pages/search_panel/widgets/video_panel.dart
@@ -35,7 +35,11 @@ class SearchVideoPanel extends StatelessWidget {
padding: index == 0
? const EdgeInsets.only(top: 2)
: EdgeInsets.zero,
- child: VideoCardH(videoItem: i, showPubdate: true),
+ child: VideoCardH(
+ videoItem: i,
+ showPubdate: true,
+ source: 'search',
+ ),
);
},
),
diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart
index 11aebc39..dcdee4ef 100644
--- a/lib/pages/subscription_detail/widget/sub_video_card.dart
+++ b/lib/pages/subscription_detail/widget/sub_video_card.dart
@@ -1,3 +1,4 @@
+import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.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/http/search.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/common/widgets/network_img_layer.dart';
import '../../../common/widgets/badge.dart';
@@ -40,6 +42,11 @@ class SubVideoCardH extends StatelessWidget {
'videoType': SearchType.video,
});
},
+ onLongPress: () => imageSaveDialog(
+ context,
+ videoItem,
+ SmartDialog.dismiss,
+ ),
child: Column(
children: [
Padding(
diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart
index 0912724e..fe3b0dca 100644
--- a/lib/pages/video/detail/related/view.dart
+++ b/lib/pages/video/detail/related/view.dart
@@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.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/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import './controller.dart';
@@ -54,20 +52,6 @@ class _RelatedVideoPanelState extends State
child: VideoCardH(
videoItem: relatedVideoList[index],
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
},
);
}
-
- OverlayEntry _createPopupDialog(videoItem) {
- return OverlayEntry(
- builder: (BuildContext context) => AnimatedDialog(
- closeFn: _releatedController.popupDialog?.remove,
- child: OverlayPop(
- videoItem: videoItem,
- closeFn: _releatedController.popupDialog?.remove),
- ),
- );
- }
}
diff --git a/lib/utils/download.dart b/lib/utils/download.dart
index 2aff8999..42dbbecf 100644
--- a/lib/utils/download.dart
+++ b/lib/utils/download.dart
@@ -15,24 +15,7 @@ class DownloadUtils {
PermissionStatus status = await Permission.storage.status;
if (status == PermissionStatus.denied ||
status == PermissionStatus.permanentlyDenied) {
- SmartDialog.show(
- 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('去授权'),
- )
- ],
- );
- },
- );
+ await permissionDialog('提示', '存储权限未授权');
return false;
} else {
return true;
@@ -45,24 +28,7 @@ class DownloadUtils {
PermissionStatus status = await Permission.photos.status;
if (status == PermissionStatus.denied ||
status == PermissionStatus.permanentlyDenied) {
- SmartDialog.show(
- 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('去授权'),
- )
- ],
- );
- },
- );
+ await permissionDialog('提示', '相册权限未授权');
return false;
} else {
return true;
@@ -72,17 +38,16 @@ class DownloadUtils {
static Future downloadImg(String imgUrl,
{String imgType = 'cover'}) async {
try {
- if (!Platform.isAndroid || !await requestPhotoPer()) {
- return false;
- }
- final androidInfo = await DeviceInfoPlugin().androidInfo;
- if (androidInfo.version.sdkInt <= 32) {
- if (!await requestStoragePer()) {
- return false;
- }
- } else {
- if (!await requestPhotoPer()) {
- return false;
+ if (Platform.isAndroid) {
+ final androidInfo = await DeviceInfoPlugin().androidInfo;
+ if (androidInfo.version.sdkInt <= 32) {
+ if (!await requestStoragePer()) {
+ return false;
+ }
+ } else {
+ if (!await requestPhotoPer()) {
+ return false;
+ }
}
}
@@ -101,13 +66,38 @@ class DownloadUtils {
);
SmartDialog.dismiss();
if (result.isSuccess) {
- await SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 ');
+ SmartDialog.showToast('「${'$picName.$imgSuffix'}」已保存 ');
+ return true;
+ } else {
+ await permissionDialog('保存失败', '相册权限未授权');
+ return false;
}
- return true;
} catch (err) {
SmartDialog.dismiss();
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('去授权'),
+ )
+ ],
+ );
+ },
+ );
+ }
}
diff --git a/lib/utils/image_save.dart b/lib/utils/image_save.dart
new file mode 100644
index 00000000..0cd6915c
--- /dev/null
+++ b/lib/utils/image_save.dart
@@ -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),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+}