From 523535ba1628ac8ecdc4b04debc40d88b770d2a5 Mon Sep 17 00:00:00 2001
From: ZhuJHua <1624109111@qq.com>
Date: Sat, 14 Dec 2024 23:55:40 +0800
Subject: [PATCH] feat(save_image): add save image to local
Closes #46
---
ios/Runner/Info.plist | 120 +++++++++---------
.../window_buttons/window_buttons.dart | 5 +-
lib/main.dart | 8 +-
lib/pages/home/calendar/calendar_view.dart | 14 +-
lib/pages/home/media/media_logic.dart | 47 +------
lib/pages/image/image_view.dart | 9 ++
lib/utils/media_util.dart | 22 +++-
macos/Runner/Info.plist | 12 +-
pubspec.lock | 13 +-
pubspec.yaml | 7 +-
10 files changed, 121 insertions(+), 136 deletions(-)
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 1683f6e..6f01274 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,63 +1,65 @@
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- 心绪日记
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- 心绪日记
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
- NSFaceIDUsageDescription
- Used to apply locks
- NSPhotoLibraryUsageDescription
- Used to apply locks
- NSCameraUsageDescription
- Used to apply locks
- NSMicrophoneUsageDescription
- Used to apply locks
- NSLocationWhenInUseUsageDescription
- This app needs access to location when open.
- UIStatusBarHidden
-
- UIViewControllerBasedStatusBarAppearance
-
-
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ 心绪日记
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ 心绪日记
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ NSCameraUsageDescription
+ Used to apply locks
+ NSFaceIDUsageDescription
+ Used to apply locks
+ NSLocationWhenInUseUsageDescription
+ This app needs access to location when open.
+ NSMicrophoneUsageDescription
+ Used to apply locks
+ NSPhotoLibraryAddUsageDescription
+
+ NSPhotoLibraryUsageDescription
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UIStatusBarHidden
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
diff --git a/lib/components/window_buttons/window_buttons.dart b/lib/components/window_buttons/window_buttons.dart
index 8696e94..425b928 100644
--- a/lib/components/window_buttons/window_buttons.dart
+++ b/lib/components/window_buttons/window_buttons.dart
@@ -7,8 +7,6 @@ import 'package:mood_diary/api/api.dart';
import 'package:mood_diary/utils/cache_util.dart';
class WindowButtons extends StatelessWidget {
- final ColorScheme colorScheme;
-
final RxString hitokoto = ''.obs;
//获取一言
@@ -21,10 +19,11 @@ class WindowButtons extends StatelessWidget {
}
}
- WindowButtons({super.key, required this.colorScheme});
+ WindowButtons({super.key});
@override
Widget build(BuildContext context) {
+ final colorScheme = Theme.of(context).colorScheme;
//getHitokoto();
final buttonColors = WindowButtonColors(
iconNormal: colorScheme.secondary,
diff --git a/lib/main.dart b/lib/main.dart
index e990033..9595f28 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -25,16 +25,18 @@ import 'package:video_player_media_kit/video_player_media_kit.dart';
import 'components/window_buttons/window_buttons.dart';
+late final AppLocalizations l10n;
+
Future initSystem() async {
WidgetsFlutterBinding.ensureInitialized();
await findSystemLocale();
+ await RustLib.init();
await PrefUtil.initPref();
await IsarUtil.initIsar();
await WebDavUtil().initWebDav();
VideoPlayerMediaKit.ensureInitialized(android: true, iOS: true, macOS: true, windows: true);
await FMTCObjectBoxBackend().initialise();
await const FMTCStore('mapStore').manage.create();
- await RustLib.init();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
@@ -81,13 +83,13 @@ void main() async {
onGenerateTitle: (context) => AppLocalizations.of(context)!.appName,
backButtonDispatcher: GetRootBackButtonDispatcher(),
builder: (context, child) {
- final colorScheme = Theme.of(context).colorScheme;
+ l10n = AppLocalizations.of(context)!;
final mediaQuery = MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(PrefUtil.getValue('fontScale')!)),
child: FToastBuilder()(context, child!),
);
final windowChild = (Platform.isWindows || Platform.isMacOS || Platform.isLinux)
- ? Column(children: [WindowButtons(colorScheme: colorScheme), Expanded(child: mediaQuery)])
+ ? Column(children: [WindowButtons(), Expanded(child: mediaQuery)])
: mediaQuery;
return windowChild;
},
diff --git a/lib/pages/home/calendar/calendar_view.dart b/lib/pages/home/calendar/calendar_view.dart
index 1f6e6b9..8d115b8 100644
--- a/lib/pages/home/calendar/calendar_view.dart
+++ b/lib/pages/home/calendar/calendar_view.dart
@@ -5,9 +5,9 @@ import 'package:get/get.dart';
import 'package:mood_diary/common/values/border.dart';
import 'package:mood_diary/common/values/colors.dart';
import 'package:mood_diary/components/diary_card/calendar_diary_card/calendar_diary_card_view.dart';
+import 'package:mood_diary/components/loading/loading.dart';
import 'package:mood_diary/components/time_line/time_line_view.dart';
import 'package:mood_diary/utils/array_util.dart';
-import 'package:rive_animated_icon/rive_animated_icon.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'calendar_logic.dart';
@@ -230,17 +230,7 @@ class CalendarPage extends StatelessWidget {
Expanded(child: Obx(() {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
- child: state.isFetching.value
- ? Center(
- child: RiveAnimatedIcon(
- riveIcon: RiveIcon.search,
- height: 80,
- width: 80,
- loopAnimation: true,
- strokeWidth: 4,
- color: colorScheme.onSurface,
- ))
- : buildCardList(),
+ child: state.isFetching.value ? const Center(child: Processing()) : buildCardList(),
);
})),
],
diff --git a/lib/pages/home/media/media_logic.dart b/lib/pages/home/media/media_logic.dart
index bb2b70d..6ae401b 100644
--- a/lib/pages/home/media/media_logic.dart
+++ b/lib/pages/home/media/media_logic.dart
@@ -1,10 +1,8 @@
import 'package:flutter/animation.dart';
import 'package:get/get.dart';
import 'package:mood_diary/common/values/media_type.dart';
-import 'package:mood_diary/components/audio_player/audio_player_logic.dart';
import 'package:mood_diary/router/app_routes.dart';
-import '../../../utils/data/isar.dart';
import '../../../utils/file_util.dart';
import '../../../utils/notice_util.dart';
import 'media_state.dart';
@@ -85,50 +83,7 @@ class MediaLogic extends GetxController with GetSingleTickerProviderStateMixin {
state.isCleaning = true;
update(['modal']);
- // 获取各类型的所有文件路径并转换为Set以提高查找效率
- final imageFiles = (await FileUtil.getDirFileName(MediaType.image.value)).toSet();
- final audioFiles = (await FileUtil.getDirFileName(MediaType.audio.value)).toSet();
- final videoFiles = (await FileUtil.getDirFileName(MediaType.video.value)).toSet();
-
- // 用于存储日记中引用的文件名的Set
- final usedImages = {};
- final usedAudios = {};
- final usedVideos = {};
-
- // 获取日记总数
- final count = IsarUtil.countAllDiary();
-
- // 分批获取日记并收集引用的文件名
- const batchSize = 50;
- for (int i = 0; i < count; i += batchSize) {
- final diaryList = await IsarUtil.getDiary(i, batchSize);
- for (var diary in diaryList) {
- usedImages.addAll(diary.imageName);
- usedAudios.addAll(diary.audioName);
- usedVideos.addAll(diary.videoName);
- for (var name in diary.videoName) {
- var thumbnailName = 'thumbnail-${name.substring(6, 42)}.jpeg';
- usedVideos.add(thumbnailName);
- }
- }
- }
-
- // 计算需要删除的文件
- final imagesToDelete = imageFiles.difference(usedImages);
- final audiosToDelete = audioFiles.difference(usedAudios);
- final videosToDelete = videoFiles.difference(usedVideos);
-
- // delete controller when need
- for (var path in audiosToDelete) {
- Bind.delete(tag: path);
- }
-
- // 并行删除文件
- await Future.wait([
- FileUtil.deleteMediaFiles(imagesToDelete, MediaType.image.value),
- FileUtil.deleteMediaFiles(audiosToDelete, MediaType.audio.value),
- FileUtil.deleteMediaFiles(videosToDelete, MediaType.video.value),
- ]);
+ await FileUtil.cleanFile();
await getFilePath(state.mediaType.value);
state.isCleaning = false;
update(['modal']);
diff --git a/lib/pages/image/image_view.dart b/lib/pages/image/image_view.dart
index 3573bd7..d0050ba 100644
--- a/lib/pages/image/image_view.dart
+++ b/lib/pages/image/image_view.dart
@@ -2,6 +2,8 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:mood_diary/common/values/media_type.dart';
+import 'package:mood_diary/utils/media_util.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
@@ -26,6 +28,13 @@ class ImagePage extends StatelessWidget {
style: const TextStyle(color: Colors.white),
),
iconTheme: const IconThemeData(color: Colors.white),
+ actions: [
+ IconButton(
+ onPressed: () {
+ MediaUtil.saveToGallery(path: state.imagePathList[state.imageIndex], type: MediaType.image);
+ },
+ icon: const Icon(Icons.save_alt)),
+ ],
),
body: PhotoViewGallery.builder(
scrollPhysics: const PageScrollPhysics(),
diff --git a/lib/utils/media_util.dart b/lib/utils/media_util.dart
index 7800679..8f0628b 100644
--- a/lib/utils/media_util.dart
+++ b/lib/utils/media_util.dart
@@ -4,13 +4,16 @@ import 'dart:io';
import 'package:fc_native_video_thumbnail/fc_native_video_thumbnail.dart';
import 'package:flutter/material.dart';
import 'package:flutter_image_compress/flutter_image_compress.dart';
+import 'package:gal/gal.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
import 'package:mood_diary/src/rust/api/compress.dart';
import 'package:mood_diary/utils/log_util.dart';
+import 'package:mood_diary/utils/notice_util.dart';
import 'package:path/path.dart';
import 'package:uuid/uuid.dart';
+import '../common/values/media_type.dart';
import '../src/rust/api/constants.dart' as r_type;
import 'data/pref.dart';
import 'file_util.dart';
@@ -113,9 +116,10 @@ class MediaUtil {
// 遍历视频文件
final videoFiles = videoDir.listSync().whereType();
for (final videoFile in videoFiles) {
+ //如果是缩略图则跳过
+ if (videoFile.path.contains('thumbnail')) continue;
final videoName = basename(videoFile.path);
final thumbnailPath = getThumbnailPath(videoName);
-
// 检查是否存在缩略图
if (!File(thumbnailPath).existsSync()) {
LogUtil.printInfo("Thumbnail missing for $videoName. Regenerating...");
@@ -268,4 +272,20 @@ class MediaUtil {
return await _thumbnail.getVideoThumbnail(
srcFile: xFile.path, destFile: destPath, width: height, height: height, format: 'jpeg', quality: 90);
}
+
+ // 保存视频或者图片到相册
+ static Future saveToGallery({required String path, required MediaType type}) async {
+ final hasAccess = await Gal.hasAccess(toAlbum: true);
+ if (!hasAccess) await Gal.requestAccess(toAlbum: true);
+ try {
+ if (type == MediaType.video) {
+ await Gal.putVideo(path, album: 'Moodiary');
+ } else {
+ await Gal.putImage(path, album: 'Moodiary');
+ }
+ NoticeUtil.showToast('已保存到相册');
+ } catch (e) {
+ NoticeUtil.showToast('保存失败');
+ }
+ }
}
diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist
index 606f2e3..fc2db67 100644
--- a/macos/Runner/Info.plist
+++ b/macos/Runner/Info.plist
@@ -24,13 +24,17 @@
$(MACOSX_DEPLOYMENT_TARGET)
NSHumanReadableCopyright
$(PRODUCT_COPYRIGHT)
+ NSLocationUsageDescription
+
NSMainNibFile
MainMenu
+ NSMicrophoneUsageDescription
+ Some message to describe why you need this permission
+ NSPhotoLibraryAddUsageDescription
+ This app needs access to location.
+ NSPhotoLibraryUsageDescription
+
NSPrincipalClass
NSApplication
- NSMicrophoneUsageDescription
- Some message to describe why you need this permission
- NSLocationUsageDescription
- This app needs access to location.
diff --git a/pubspec.lock b/pubspec.lock
index dd4beaf..39eb392 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -971,7 +971,7 @@ packages:
source: hosted
version: "2.4.0"
gal:
- dependency: transitive
+ dependency: "direct main"
description:
name: gal
sha256: "54c9b72528efce7c66234f3b6dd01cb0304fd8af8196de15571d7bdddb940977"
@@ -1965,11 +1965,12 @@ packages:
rive_animated_icon:
dependency: "direct main"
description:
- name: rive_animated_icon
- sha256: f789e9f026c3e5a3bcb369db9baef0637ddd15d9a7c4fe4749505202cdc11942
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.0.2"
+ path: "."
+ ref: HEAD
+ resolved-ref: "7ab479ea6747480038fc24914d6b642984fd5bc9"
+ url: "https://github.com/ZhuJHua/rive_animated_icons.git"
+ source: git
+ version: "2.0.3"
rive_common:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index e2b02d4..90f9f7e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,7 @@
name: mood_diary
description: "A new Flutter project."
publish_to: 'none'
-version: 2.6.2+62
+version: 2.6.3+63
environment:
sdk: '>=3.4.0 <4.0.0'
@@ -92,7 +92,10 @@ dependencies:
webdav_client: 1.2.2
confetti: 0.8.0
flutter_native_splash: 2.4.3
- rive_animated_icon: 2.0.2
+ gal: 2.3.0
+ rive_animated_icon:
+ git:
+ url: https://github.com/ZhuJHua/rive_animated_icons.git
network_info_plus: 6.1.2
scrollable_positioned_list: 0.3.8
flutter_localizations: