feat: live photo

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-01-28 14:19:59 +08:00
parent b761c35d10
commit f5d7dc6b6a
15 changed files with 227 additions and 73 deletions

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
@@ -68,7 +70,8 @@ Widget articleContent({
} else { } else {
context.imageView( context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!), initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList, imgList:
imgList.map((url) => SourceModel(url: url)).toList(),
); );
} }
}, },

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
@@ -54,7 +56,7 @@ Widget htmlRender({
callback([imgUrl], 0); callback([imgUrl], 0);
} else { } else {
context.imageView( context.imageView(
imgList: [imgUrl], imgList: [SourceModel(url: imgUrl)],
); );
} }
}, },

View File

@@ -2,9 +2,12 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/nine_grid_view.dart'; import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ImageModel { class ImageModel {
@@ -12,16 +15,20 @@ class ImageModel {
required this.width, required this.width,
required this.height, required this.height,
required this.url, required this.url,
this.liveUrl,
}); });
dynamic width; dynamic width;
dynamic height; dynamic height;
String url; String url;
String? liveUrl;
bool? _isLongPic; bool? _isLongPic;
bool? _isLivePhoto;
dynamic get safeWidth => width ?? 1; dynamic get safeWidth => width ?? 1;
dynamic get safeHeight => height ?? 1; dynamic get safeHeight => height ?? 1;
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9); bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
bool get isLivePhoto => _isLivePhoto ??= liveUrl?.isNotEmpty == true;
} }
Widget imageview( Widget imageview(
@@ -83,6 +90,8 @@ Widget imageview(
); );
} }
late final enableLivePhoto = GStorage.enableLivePhoto;
return NineGridView( return NineGridView(
type: NineGridType.weiBo, type: NineGridType.weiBo,
margin: const EdgeInsets.only(top: 6), margin: const EdgeInsets.only(top: 6),
@@ -102,7 +111,17 @@ Widget imageview(
onViewImage?.call(); onViewImage?.call();
context.imageView( context.imageView(
initialPage: index, initialPage: index,
imgList: picArr.map((item) => item.url).toList(), imgList: picArr
.map(
(item) => SourceModel(
sourceType: item.isLivePhoto && enableLivePhoto
? SourceType.livePhoto
: SourceType.networkImage,
url: item.url,
liveUrl: item.liveUrl,
),
)
.toList(),
onDismissed: onDismissed, onDismissed: onDismissed,
); );
} }
@@ -143,7 +162,14 @@ Widget imageview(
}, },
), ),
), ),
if (picArr[index].isLongPic) if (picArr[index].liveUrl?.isNotEmpty == true)
const PBadge(
text: 'Live',
right: 8,
bottom: 8,
type: 'gray',
)
else if (picArr[index].isLongPic)
const PBadge( const PBadge(
text: '长图', text: '长图',
right: 8, right: 8,

View File

@@ -10,6 +10,8 @@ import 'package:dio/dio.dart';
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:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:status_bar_control/status_bar_control.dart'; import 'package:status_bar_control/status_bar_control.dart';
@@ -33,6 +35,20 @@ typedef IndexedFocusedWidgetBuilder = Widget Function(
typedef IndexedTagStringBuilder = String Function(int index); typedef IndexedTagStringBuilder = String Function(int index);
enum SourceType { fileImage, networkImage, livePhoto }
class SourceModel {
final SourceType sourceType;
final String url;
final String? liveUrl;
const SourceModel({
this.sourceType = SourceType.networkImage,
required this.url,
this.liveUrl,
});
}
class InteractiveviewerGallery<T> extends StatefulWidget { class InteractiveviewerGallery<T> extends StatefulWidget {
const InteractiveviewerGallery({ const InteractiveviewerGallery({
super.key, super.key,
@@ -45,17 +61,14 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.onDismissed, this.onDismissed,
this.setStatusBar, this.setStatusBar,
this.onClose, this.onClose,
this.isFile,
}); });
final bool? isFile;
final VoidCallback? onClose; final VoidCallback? onClose;
final bool? setStatusBar; final bool? setStatusBar;
/// The sources to show. /// The sources to show.
final List<String> sources; final List<SourceModel> sources;
/// The index of the first source in [sources] to show. /// The index of the first source in [sources] to show.
final int initIndex; final int initIndex;
@@ -92,7 +105,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
late Offset _doubleTapLocalPosition; late Offset _doubleTapLocalPosition;
int? currentIndex; late final RxInt currentIndex = widget.initIndex.obs;
late List<bool> _thumbList; late List<bool> _thumbList;
late final int _quality = GStorage.previewQ; late final int _quality = GStorage.previewQ;
@@ -115,10 +128,13 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_animation?.value ?? Matrix4.identity(); _animation?.value ?? Matrix4.identity();
}); });
currentIndex = widget.initIndex;
if (widget.setStatusBar != false) { if (widget.setStatusBar != false) {
setStatusBar(); setStatusBar();
} }
if (widget.sources[currentIndex.value].sourceType == SourceType.livePhoto) {
_onPlay(currentIndex.value);
}
} }
setStatusBar() async { setStatusBar() async {
@@ -132,6 +148,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
@override @override
void dispose() async { void dispose() async {
_player?.dispose();
_pageController?.dispose(); _pageController?.dispose();
_animationController.removeListener(() {}); _animationController.removeListener(() {});
_animationController.dispose(); _animationController.dispose();
@@ -140,8 +157,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE); StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
} }
} }
if (widget.isFile != true) { for (int index = 0; index < widget.sources.length; index++) {
for (int index = 0; index < widget.sources.length; index++) { if (widget.sources[index].sourceType == SourceType.networkImage) {
CachedNetworkImageProvider(_getActualUrl(index)).evict(); CachedNetworkImageProvider(_getActualUrl(index)).evict();
} }
} }
@@ -201,14 +218,22 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
} }
} }
void _onPlay(int index) {
_player ??= Player();
_videoController ??= VideoController(_player!);
_player!.open(Media(widget.sources[index].liveUrl!));
}
/// When the page view changed its page, the source will animate back into the /// When the page view changed its page, the source will animate back into the
/// original scale if it was scaled up. /// original scale if it was scaled up.
/// ///
/// Additionally the swipe up / down to dismiss gets enabled. /// Additionally the swipe up / down to dismiss gets enabled.
void _onPageChanged(int page) { void _onPageChanged(int page) {
setState(() { _player?.pause();
currentIndex = page; currentIndex.value = page;
}); if (widget.sources[page].sourceType == SourceType.livePhoto) {
_onPlay(page);
}
widget.onPageChanged?.call(page); widget.onPageChanged?.call(page);
if (_transformationController!.value != Matrix4.identity()) { if (_transformationController!.value != Matrix4.identity()) {
// animate the reset for the transformation of the interactive viewer // animate the reset for the transformation of the interactive viewer
@@ -226,7 +251,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
String _getActualUrl(int index) => _thumbList[index] && _quality != 100 String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index]}@${_quality}q.webp'.http2https ? '${widget.sources[index]}@${_quality}q.webp'.http2https
: widget.sources[index].http2https; : widget.sources[index].url.http2https;
void onClose() { void onClose() {
if (widget.onClose != null) { if (widget.onClose != null) {
@@ -237,6 +262,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
} }
} }
Player? _player;
VideoController? _videoController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Stack(
@@ -272,12 +300,15 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_doubleTapLocalPosition = details.localPosition; _doubleTapLocalPosition = details.localPosition;
}, },
onDoubleTap: onDoubleTap, onDoubleTap: onDoubleTap,
onLongPress: widget.isFile == true ? null : onLongPress, onLongPress:
widget.sources[index].sourceType == SourceType.fileImage
? null
: onLongPress,
child: widget.itemBuilder != null child: widget.itemBuilder != null
? widget.itemBuilder!( ? widget.itemBuilder!(
context, context,
index, index,
index == currentIndex, index == currentIndex.value,
_enablePageView, _enablePageView,
) )
: _itemBuilder(index), : _itemBuilder(index),
@@ -321,47 +352,60 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
if (widget.sources.length > 1) if (widget.sources.length > 1)
Align( Align(
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Obx(
"${currentIndex! + 1}/${widget.sources.length}", () => Text(
style: const TextStyle(color: Colors.white), "${currentIndex.value + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
),
), ),
), ),
if (widget.isFile != true) if (widget.sources[currentIndex.value].sourceType !=
SourceType.fileImage)
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: PopupMenuButton( child: PopupMenuButton(
itemBuilder: (context) { itemBuilder: (context) {
return [ return [
PopupMenuItem( PopupMenuItem(
value: 0, onTap: () => onShareImg(
onTap: () => widget.sources[currentIndex.value].url),
onShareImg(widget.sources[currentIndex!]),
child: const Text("分享图片"), child: const Text("分享图片"),
), ),
PopupMenuItem( PopupMenuItem(
value: 1,
onTap: () { onTap: () {
Utils.copyText(widget.sources[currentIndex!]); Utils.copyText(
widget.sources[currentIndex.value].url);
}, },
child: const Text("复制链接"), child: const Text("复制链接"),
), ),
PopupMenuItem( PopupMenuItem(
value: 2,
onTap: () { onTap: () {
DownloadUtils.downloadImg( DownloadUtils.downloadImg(
context, context,
[widget.sources[currentIndex!]], [widget.sources[currentIndex.value].url],
); );
}, },
child: const Text("保存图片"), child: const Text("保存图片"),
), ),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadVideo(
context,
widget.sources[currentIndex.value].liveUrl!,
);
},
child: const Text("保存 Live"),
),
if (widget.sources.length > 1) if (widget.sources.length > 1)
PopupMenuItem( PopupMenuItem(
value: 3,
onTap: () { onTap: () {
DownloadUtils.downloadImg( DownloadUtils.downloadImg(
context, context,
widget.sources, widget.sources
.map((item) => item.url)
.toList(),
); );
}, },
child: const Text("保存全部图片"), child: const Text("保存全部图片"),
@@ -396,34 +440,37 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
Widget _itemBuilder(index) { Widget _itemBuilder(index) {
return Center( return Center(
child: Hero( child: Hero(
tag: widget.sources[index], tag: widget.sources[index].url,
child: widget.isFile == true child: switch (widget.sources[index].sourceType) {
? Image( SourceType.fileImage => Image(
filterQuality: FilterQuality.low, filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index])), image: FileImage(File(widget.sources[index].url)),
) ),
: CachedNetworkImage( SourceType.networkImage => CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0), fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 0), fadeOutDuration: const Duration(milliseconds: 0),
imageUrl: _getActualUrl(index), imageUrl: _getActualUrl(index),
// fit: BoxFit.contain, // fit: BoxFit.contain,
progressIndicatorBuilder: (context, url, progress) { progressIndicatorBuilder: (context, url, progress) {
return Center( return Center(
child: SizedBox( child: SizedBox(
width: 150.0, width: 150.0,
child: LinearProgressIndicator( child:
value: progress.progress ?? 0), LinearProgressIndicator(value: progress.progress ?? 0),
), ),
); );
}, },
// errorListener: (value) { // errorListener: (value) {
// WidgetsBinding.instance.addPostFrameCallback((_) { // WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() { // setState(() {
// _thumbList[index] = false; // _thumbList[index] = false;
// }); // });
// }); // });
// }, // },
), ),
SourceType.livePhoto =>
IgnorePointer(child: Video(controller: _videoController!)),
},
), ),
); );
} }
@@ -487,7 +534,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
children: [ children: [
ListTile( ListTile(
onTap: () { onTap: () {
onShareImg(widget.sources[currentIndex!]); onShareImg(widget.sources[currentIndex.value].url);
Get.back(); Get.back();
}, },
dense: true, dense: true,
@@ -496,7 +543,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
ListTile( ListTile(
onTap: () { onTap: () {
Get.back(); Get.back();
Utils.copyText(widget.sources[currentIndex!]); Utils.copyText(widget.sources[currentIndex.value].url);
}, },
dense: true, dense: true,
title: const Text('复制链接', style: TextStyle(fontSize: 14)), title: const Text('复制链接', style: TextStyle(fontSize: 14)),
@@ -506,19 +553,35 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
Get.back(); Get.back();
DownloadUtils.downloadImg( DownloadUtils.downloadImg(
context, context,
[widget.sources[currentIndex!]], [widget.sources[currentIndex.value].url],
); );
}, },
dense: true, dense: true,
title: const Text('保存图片', style: TextStyle(fontSize: 14)), title: const Text('保存图片', style: TextStyle(fontSize: 14)),
), ),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadVideo(
context,
widget.sources[currentIndex.value].liveUrl!,
);
},
dense: true,
title: const Text(
'保存 Live',
style: TextStyle(fontSize: 14),
),
),
if (widget.sources.length > 1) if (widget.sources.length > 1)
ListTile( ListTile(
onTap: () { onTap: () {
Get.back(); Get.back();
DownloadUtils.downloadImg( DownloadUtils.downloadImg(
context, context,
widget.sources, widget.sources.map((item) => item.url).toList(),
); );
}, },
dense: true, dense: true,

View File

@@ -673,6 +673,7 @@ class OpusPicsModel {
int? size; int? size;
String? src; String? src;
String? url; String? url;
String? liveUrl;
OpusPicsModel.fromJson(Map<String, dynamic> json) { OpusPicsModel.fromJson(Map<String, dynamic> json) {
width = json['width']; width = json['width'];
@@ -680,6 +681,7 @@ class OpusPicsModel {
size = json['size'] != null ? json['size'].toInt() : 0; size = json['size'] != null ? json['size'].toInt() : 0;
src = json['src']; src = json['src'];
url = json['url']; url = json['url'];
liveUrl = json['live_url'];
} }
} }

View File

@@ -1,6 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'package:PiliPlus/common/widgets/http_error.dart'; import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -224,9 +226,11 @@ class _BangumiInfoState extends State<BangumiInfo>
videoDetailCtr.onViewImage(); videoDetailCtr.onViewImage();
context.imageView( context.imageView(
imgList: [ imgList: [
!widget.isLoading SourceModel(
? widget.bangumiDetail!.cover! url: !widget.isLoading
: bangumiItem!.cover! ? widget.bangumiDetail!.cover!
: bangumiItem!.cover!,
)
], ],
onDismissed: videoDetailCtr.onDismissed, onDismissed: videoDetailCtr.onDismissed,
); );

View File

@@ -4,6 +4,8 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart'; import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:chat_bottom_container/chat_bottom_container.dart'; import 'package:chat_bottom_container/chat_bottom_container.dart';
@@ -274,8 +276,12 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
onTap: () { onTap: () {
controller.keepChatPanel(); controller.keepChatPanel();
context.imageView( context.imageView(
isFile: true, imgList: pathList
imgList: pathList, .map((path) => SourceModel(
url: path,
sourceType: SourceType.fileImage,
))
.toList(),
initialPage: index, initialPage: index,
); );
}, },

View File

@@ -16,6 +16,7 @@ Widget content(context, item, source, callback) {
width: item.width, width: item.width,
height: item.height, height: item.height,
url: item.url ?? '', url: item.url ?? '',
liveUrl: item.liveUrl,
), ),
) )
.toList(), .toList(),

View File

@@ -27,6 +27,7 @@ InlineSpan picsNodes(List<OpusPicsModel> pics, callback) {
width: item.width, width: item.width,
height: item.height, height: item.height,
url: item.url ?? '', url: item.url ?? '',
liveUrl: item.liveUrl,
), ),
) )
.toList(), .toList(),

View File

@@ -17,6 +17,7 @@ Widget picWidget(item, context, callback) {
width: item.width, width: item.width,
height: item.height, height: item.height,
url: item.url ?? '', url: item.url ?? '',
liveUrl: item.liveUrl,
), ),
) )
.toList(), .toList(),

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/models/space/card.dart' as space; import 'package:PiliPlus/models/space/card.dart' as space;
@@ -81,7 +83,7 @@ class UserInfoCard extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
context.imageView( context.imageView(
imgList: [imgUrl ?? 'bgTag'], imgList: [SourceModel(url: imgUrl ?? 'bgTag')],
); );
}, },
child: CachedNetworkImage( child: CachedNetworkImage(
@@ -458,7 +460,7 @@ class UserInfoCard extends StatelessWidget {
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
context.imageView( context.imageView(
imgList: [card.face ?? 'avatarTag'], imgList: [SourceModel(url: card.face ?? 'avatarTag')],
); );
}, },
child: NetworkImgLayer( child: NetworkImgLayer(

View File

@@ -1914,6 +1914,14 @@ List<SettingsModel> get extraSettings => [
defaultVal: true, defaultVal: true,
onChanged: (value) => ModuleAuthorModel.showDynDecorate = value, onChanged: (value) => ModuleAuthorModel.showDynDecorate = value,
), ),
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '预览 Live Photo',
subtitle: '开启则以视频形式预览Live Photo否则预览静态图片',
leading: Icon(Icons.image_outlined),
setKey: SettingBoxKey.enableLivePhoto,
defaultVal: true,
),
SettingsModel( SettingsModel(
settingsType: SettingsType.sw1tch, settingsType: SettingsType.sw1tch,
enableFeedback: true, enableFeedback: true,

View File

@@ -6,6 +6,7 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
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:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:saver_gallery/saver_gallery.dart'; import 'package:saver_gallery/saver_gallery.dart';
import 'dart:io'; import 'dart:io';
@@ -85,6 +86,38 @@ class DownloadUtils {
return await requestStoragePer(context); return await requestStoragePer(context);
} }
static Future downloadVideo(BuildContext context, String url) async {
try {
if (!await checkPermissionDependOnSdkInt(context)) {
return;
}
SmartDialog.showLoading(msg: '正在下载');
String videoName =
"video_${DateTime.now().toString().replaceAll(' ', '_').replaceAll(':', '-').split('.').first}.${url.split('.').lastOrNull ?? 'mp4'}";
String savePath = '${(await getTemporaryDirectory()).path}/$videoName';
await Request.dio.download(url, savePath);
SmartDialog.showLoading(msg: '正在保存');
final SaveResult result = await SaverGallery.saveFile(
filePath: savePath,
fileName: videoName,
androidRelativePath: "Pictures/PiliPlus",
skipIfExists: false,
);
SmartDialog.dismiss();
if (result.isSuccess) {
SmartDialog.showToast('$videoName」已保存 ');
} else {
SmartDialog.showToast('保存失败,${result.errorMessage}');
}
return true;
} catch (err) {
SmartDialog.dismiss();
SmartDialog.showToast(err.toString());
return false;
}
}
static Future downloadImg( static Future downloadImg(
BuildContext context, BuildContext context,
List<String> imgList, { List<String> imgList, {

View File

@@ -73,15 +73,13 @@ extension BuildContextExt on BuildContext {
void imageView({ void imageView({
int? initialPage, int? initialPage,
required List<String> imgList, required List<SourceModel> imgList,
ValueChanged<int>? onDismissed, ValueChanged<int>? onDismissed,
bool? isFile,
}) { }) {
Navigator.of(this).push( Navigator.of(this).push(
HeroDialogRoute( HeroDialogRoute(
builder: (context) => InteractiveviewerGallery( builder: (context) => InteractiveviewerGallery(
sources: imgList, sources: imgList,
isFile: isFile,
initIndex: initialPage ?? 0, initIndex: initialPage ?? 0,
onPageChanged: (int pageIndex) {}, onPageChanged: (int pageIndex) {},
onDismissed: onDismissed, onDismissed: onDismissed,

View File

@@ -354,6 +354,9 @@ class GStorage {
static bool get showDynDecorate => static bool get showDynDecorate =>
GStorage.setting.get(SettingBoxKey.showDynDecorate, defaultValue: true); GStorage.setting.get(SettingBoxKey.showDynDecorate, defaultValue: true);
static bool get enableLivePhoto =>
GStorage.setting.get(SettingBoxKey.enableLivePhoto, defaultValue: true);
static List<double> get dynamicDetailRatio => List<double>.from(setting static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0])); .get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -581,6 +584,7 @@ class SettingBoxKey {
mainTabBarView = 'mainTabBarView', mainTabBarView = 'mainTabBarView',
searchSuggestion = 'searchSuggestion', searchSuggestion = 'searchSuggestion',
showDynDecorate = 'showDynDecorate', showDynDecorate = 'showDynDecorate',
enableLivePhoto = 'enableLivePhoto',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',