From 25447e96679e14767d29e6ed59d65ba31b92b6e6 Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 9 Sep 2023 09:56:45 +0800 Subject: [PATCH] Impl saveFile on Windows platform --- lib/components/thumbnail.dart | 14 +++++++ lib/l10n/app_en.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/platform/path.dart | 9 +++-- windows/runner/CMakeLists.txt | 1 + windows/runner/flutter_window.cpp | 65 +++++++++++++++++++++++++++++++ 6 files changed, 90 insertions(+), 5 deletions(-) diff --git a/lib/components/thumbnail.dart b/lib/components/thumbnail.dart index 38c0b0e..f7c908e 100644 --- a/lib/components/thumbnail.dart +++ b/lib/components/thumbnail.dart @@ -4,6 +4,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:logging/logging.dart'; +import 'package:path/path.dart'; import '../api/client.dart'; import '../api/gallery.dart'; import '../globals.dart'; @@ -45,6 +46,7 @@ class Thumbnail extends StatefulWidget { enum _ThumbnailMenu { copyImage, copyImgUrl, + saveAs, } class _Thumbnail extends State { @@ -55,6 +57,7 @@ class _Thumbnail extends State { bool _showNsfw = false; String? _uri; CancelToken? _cancel; + String? _fileName; Future _fetchData() async { try { _cancel = CancelToken(); @@ -106,6 +109,7 @@ class _Thumbnail extends State { _fileId = widget._fileId; _showNsfw = false; _uri = null; + _fileName = "${basenameWithoutExtension(widget._pMeta.name)}_thumb"; super.initState(); } @@ -133,6 +137,13 @@ class _Thumbnail extends State { _log.warning("Failed to copy image url to clipboard:", err); } break; + case _ThumbnailMenu.saveAs: + try { + await platformPath.saveFile(_fileName!, "image/jpeg", _data!); + } catch (err) { + _log.warning("Failed to save image:", err); + } + break; } } @@ -160,6 +171,9 @@ class _Thumbnail extends State { PopupMenuItem( value: _ThumbnailMenu.copyImgUrl, child: Text(AppLocalizations.of(context)!.copyImgUrl)), + PopupMenuItem( + value: _ThumbnailMenu.saveAs, + child: Text(AppLocalizations.of(context)!.saveAs)), ]; return list; })); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index e313588..e2b6016 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -35,5 +35,6 @@ "none": "none", "sortByGid": "Sort by gallery id", "asc": "Ascending", - "desc": "Descending" + "desc": "Descending", + "saveAs": "Save As" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9997bcc..b2d697b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -35,5 +35,6 @@ "none": "无", "sortByGid": "按画廊ID排序", "asc": "升序", - "desc": "降序" + "desc": "降序", + "saveAs": "另存为" } diff --git a/lib/platform/path.dart b/lib/platform/path.dart index d38a00c..bc7770a 100644 --- a/lib/platform/path.dart +++ b/lib/platform/path.dart @@ -7,7 +7,7 @@ final Logger _log = Logger("platformPath"); class Path { static const platform = MethodChannel("lifegpc.eh_downloader_flutter/path"); - static const _safChannel=MethodChannel("lifegpc.eh_downloader_flutter/saf"); + static const _safChannel = MethodChannel("lifegpc.eh_downloader_flutter/saf"); String? _currentExe; bool _currentExeLoaded = false; @@ -28,7 +28,10 @@ class Path { } /// 保存文件 - static Future saveFile(String filenameWithoutExtension,String mimeType,Uint8List bytes,{String dir=""}) async{ - return _safChannel.invokeMethod("saveFile",[filenameWithoutExtension,dir,mimeType,bytes]); + Future saveFile( + String filenameWithoutExtension, String mimeType, Uint8List bytes, + {String dir = ""}) async { + return _safChannel.invokeMethod( + "saveFile", [filenameWithoutExtension, dir, mimeType, bytes]); } } diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt index 24483a2..ece282c 100644 --- a/windows/runner/CMakeLists.txt +++ b/windows/runner/CMakeLists.txt @@ -39,6 +39,7 @@ target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") target_link_libraries(${BINARY_NAME} PRIVATE utils) +target_link_libraries(${BINARY_NAME} PRIVATE "Comdlg32.lib") target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") # Run the Flutter tool portions of the build. This must not be removed. diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 3c5fce3..d28352b 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -58,6 +58,71 @@ bool FlutterWindow::OnCreate() { result->NotImplemented(); } }); + flutter::MethodChannel<> saf(flutter_controller_->engine()->messenger(), "lifegpc.eh_downloader_flutter/saf", + &flutter::StandardMethodCodec::GetInstance()); + saf.SetMethodCallHandler([&](const flutter::MethodCall<>& call, std::unique_ptr> result) { + if (call.method_name() == "saveFile") { + auto args = std::get_if(call.arguments()); + auto fileName = std::get_if(&args->at(0)); + auto dir = std::get_if(&args->at(1)); + auto mimeType = std::get_if(&args->at(2)); + auto data = std::get_if>(&args->at(3)); + if (!fileName || !dir || !mimeType || !data) { + result->Error("INVALID_ARGUMENT", "Invalid arguments."); + return; + } + std::wstring wFileName; + if (!wchar_util::str_to_wstr(wFileName, *fileName, CP_UTF8)) { + result->Error("ERROR", "Failed to convert file name to wstring."); + return; + } + std::wstring wDir; + if (!dir->empty() && !wchar_util::str_to_wstr(wDir, *dir, CP_UTF8)) { + result->Error("ERROR", "Failed to convert dir to wstring."); + return; + } + OPENFILENAMEW ofn; + ZeroMemory(&ofn, sizeof(ofn)); + ofn.lStructSize = sizeof(ofn); + ofn.hwndOwner = Win32Window::GetHandle(); + std::wstring filter; + std::wstring defExt; + if (*mimeType == "image/jpeg") { + filter.append(std::wstring(L"JPEG File(*.jpg)\0*.jpg\0", 23)); + defExt = L"jpg"; + } else if (*mimeType == "image/png") { + filter.append(std::wstring(L"PNG File(*.png)\0*.png\0", 22)); + defExt = L"png"; + } else if (*mimeType == "image/gif") { + filter.append(std::wstring(L"GIF File(*.gif)\0*.gif\0", 22)); + defExt = L"gif"; + }; + filter.append(std::wstring(L"All Files\0*.*\0\0", 15)); + ofn.lpstrFilter = filter.c_str(); + ofn.lpstrDefExt = defExt.empty() ? nullptr : defExt.c_str(); + wchar_t wFileNameBuf[MAX_PATH_SIZE]; + memcpy(wFileNameBuf, wFileName.c_str(), (wFileName.size() + 1) * sizeof(wchar_t)); + ofn.lpstrFile = wFileNameBuf; + ofn.nMaxFile = MAX_PATH_SIZE; + ofn.lpstrInitialDir = wDir.empty() ? nullptr : wDir.c_str(); + ofn.Flags = OFN_DONTADDTORECENT | OFN_NONETWORKBUTTON | OFN_NOREADONLYRETURN | OFN_OVERWRITEPROMPT; + if (!GetSaveFileNameW(&ofn)) { + result->Error("ERROR", "Failed to get file name."); + return; + } + FILE* f = nullptr; + _wfopen_s(&f, wFileNameBuf, L"wb"); + if (!f) { + result->Error("ERROR", "Failed to open file."); + return; + } + fwrite(data->data(), sizeof(uint8_t), data->size(), f); + fclose(f); + result->Success(); + } else { + result->NotImplemented(); + } + }); SetChildContent(flutter_controller_->view()->GetNativeWindow());