diff --git a/change_log/1.0.22.0430.md b/change_log/1.0.22.0430.md
new file mode 100644
index 00000000..29f8aecf
--- /dev/null
+++ b/change_log/1.0.22.0430.md
@@ -0,0 +1,27 @@
+## 1.0.22
+
+### 功能
++ 字幕
++ 全屏时选集
++ 动态转发
++ 评论视频并转发
++ 收藏夹删除
++ 合集显示封面
++ 底部导航栏编辑、排序功能
++ 历史记录进度条展示
++ 直播画质切换
++ 排行榜功能
++ 视频详情页推荐视频开关
++ 显示联合投稿up
+
+### 修复
++ 收藏夹个数错误
++ 封面保存权限问题
++ 合集最后1p未展示
++ up主页关注按钮触发灰屏
+
+### 优化
++ 视频简介查看逻辑
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
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/http/search.dart b/lib/http/search.dart
index 18481ea8..cf1a1b49 100644
--- a/lib/http/search.dart
+++ b/lib/http/search.dart
@@ -163,4 +163,20 @@ class SearchHttp {
};
}
}
+
+ static Future