diff --git a/lib/appdata.dart b/lib/appdata.dart index 82ad8df..4b1bf0a 100644 --- a/lib/appdata.dart +++ b/lib/appdata.dart @@ -1,6 +1,9 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/services.dart'; +import 'package:path_provider/path_provider.dart'; + import 'foundation/app.dart'; import 'network/models.dart'; @@ -9,9 +12,18 @@ class _Appdata { var searchOptions = SearchOptions(); + Map settings = { + "downloadPath": null, + "downloadSubPath": r"/${id}-p${index}.${ext}", + "tagsWeight": "", + "useTranslatedNameForDownload": false, + }; + void writeData() async { await File("${App.dataPath}/account.json") .writeAsString(jsonEncode(account)); + await File("${App.dataPath}/settings.json") + .writeAsString(jsonEncode(settings)); } Future readData() async { @@ -19,6 +31,39 @@ class _Appdata { if (file.existsSync()) { account = Account.fromJson(jsonDecode(await file.readAsString())); } + final settingsFile = File("${App.dataPath}/settings.json"); + if (settingsFile.existsSync()) { + var json = jsonDecode(await settingsFile.readAsString()); + for(var key in json.keys) { + settings[key] = json[key]; + } + } + if(settings["downloadPath"] == null) { + settings["downloadPath"] = await _defaultDownloadPath; + } + } + + String get downloadPath => settings["downloadPath"]; + + Future get _defaultDownloadPath async{ + if(App.isAndroid) { + var externalStoragePaths = await getExternalStorageDirectories(type: StorageDirectory.downloads); + var res = externalStoragePaths?.first.path; + res ??= (await getExternalStorageDirectory())!.path; + return "$res/pixes"; + } else if (App.isWindows){ + var res = await const MethodChannel("pixes/picture_folder").invokeMethod(""); + if(res != "error") { + return res + "/pixes"; + } + } else if (App.isMacOS || App.isLinux) { + var downloadPath = (await getDownloadsDirectory())?.path; + if(downloadPath != null) { + return "$downloadPath/pixes"; + } + } + + return "${App.dataPath}/download"; } } diff --git a/lib/foundation/image_provider.dart b/lib/foundation/image_provider.dart index 946fc63..19da40c 100644 --- a/lib/foundation/image_provider.dart +++ b/lib/foundation/image_provider.dart @@ -150,12 +150,16 @@ class CachedImageProvider extends BaseImageProvider { Future load(StreamController chunkEvents) async{ var cached = await CacheManager().findCache(key); if(cached != null) { + chunkEvents.add(const ImageChunkEvent( + cumulativeBytesLoaded: 1, + expectedTotalBytes: 1, + )); return await File(cached).readAsBytes(); } var dio = AppDio(); final time = DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now()); final hash = md5.convert(utf8.encode(time + Network.hashSalt)).toString(); - var res = await dio.get( + var res = await dio.get( url, options: Options( responseType: ResponseType.stream, @@ -174,12 +178,16 @@ class CachedImageProvider extends BaseImageProvider { } var data = []; var cachingFile = await CacheManager().openWrite(key); - await for (var chunk in res.data.stream) { + await for (var chunk in res.data!.stream) { + var length = res.data!.contentLength+1; + if(length < data.length) { + length = data.length + 1; + } data.addAll(chunk); await cachingFile.writeBytes(chunk); chunkEvents.add(ImageChunkEvent( cumulativeBytesLoaded: data.length, - expectedTotalBytes: res.data.contentLength+1, + expectedTotalBytes: length, )); } await cachingFile.close(); diff --git a/lib/foundation/widget_utils.dart b/lib/foundation/widget_utils.dart index 3e463ef..ae55b03 100644 --- a/lib/foundation/widget_utils.dart +++ b/lib/foundation/widget_utils.dart @@ -56,4 +56,12 @@ extension WidgetExtension on Widget{ Widget sliverPaddingHorizontal(double padding){ return SliverPadding(padding: EdgeInsets.symmetric(horizontal: padding), sliver: this); } + + Widget fixWidth(double width){ + return SizedBox(width: width, child: this); + } + + Widget fixHeight(double height){ + return SizedBox(height: height, child: this); + } } \ No newline at end of file diff --git a/lib/network/download.dart b/lib/network/download.dart index 66910f0..ea4314e 100644 --- a/lib/network/download.dart +++ b/lib/network/download.dart @@ -1,5 +1,268 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:crypto/crypto.dart'; +import 'package:intl/intl.dart'; +import 'package:pixes/appdata.dart'; +import 'package:pixes/foundation/app.dart'; +import 'package:pixes/foundation/log.dart'; +import 'package:pixes/network/app_dio.dart'; import 'package:pixes/network/network.dart'; +import 'package:pixes/utils/io.dart'; +import 'package:sqlite3/sqlite3.dart'; extension IllustExt on Illust { - bool get downloaded => false; + bool get downloaded => DownloadManager().checkDownloaded(id); + + bool get downloading => + DownloadManager().tasks.any((element) => element.illust.id == id); +} + +class DownloadedIllust { + final int illustId; + final String title; + final String author; + final int imageCount; + + DownloadedIllust({ + required this.illustId, + required this.title, + required this.author, + required this.imageCount, + }); +} + +class DownloadingTask { + final Illust illust; + + void Function(int)? receiveBytesCallback; + + void Function(DownloadingTask)? onCompleted; + + DownloadingTask(this.illust, {this.receiveBytesCallback, this.onCompleted}); + + int _downloadingIndex = 0; + + int get totalImages => illust.images.length; + + int get downloadedImages => _downloadingIndex; + + bool _stop = true; + + String? error; + + void start() { + _stop = false; + _download(); + } + + Dio get dio => Network().dio; + + void cancel() { + _stop = true; + DownloadManager().tasks.remove(this); + for(var path in imagePaths) { + File(path).deleteIfExists(); + } + } + + List imagePaths = []; + + void _download() async{ + try{ + while(_downloadingIndex < illust.images.length) { + if(_stop) return; + var url = illust.images[_downloadingIndex].original; + var ext = url.split('.').last; + if(!["jpg", "png", "gif", "webp", "jpeg", "avif"].contains(ext)) { + ext = "jpg"; + } + var path = _generateFilePath(illust, _downloadingIndex, ext); + final time = DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now()); + final hash = md5.convert(utf8.encode(time + Network.hashSalt)).toString(); + var res = await dio.get(url, options: Options( + responseType: ResponseType.stream, + headers: { + "referer": "https://app-api.pixiv.net/", + "user-agent": "PixivAndroidApp/5.0.234 (Android 14; Pixes)", + "x-client-time": time, + "x-client-hash": hash, + "accept-enconding": "gzip", + }, + )); + var file = File(path); + if(!file.existsSync()) { + file.createSync(recursive: true); + } + await for (var data in res.data!.stream) { + await file.writeAsBytes(data, mode: FileMode.append); + receiveBytesCallback?.call(data.length); + } + imagePaths.add(path); + _downloadingIndex++; + } + onCompleted?.call(this); + } + catch(e, s) { + error = e.toString(); + _stop = true; + Log.error("Download", "Download error: $e\n$s"); + } + } + + static String _generateFilePath(Illust illust, int index, String ext) { + final String downloadPath = appdata.settings["downloadPath"]; + final String subPathPatten = appdata.settings["downloadSubPath"]; + final tags = appdata.settings["useTranslatedNameForDownload"] == false + ? illust.tags.map((e) => e.name).toList() + : illust.tags.map((e) => e.translatedName ?? e.name).toList(); + final tagsWeight = (appdata.settings["tagsWeight"] as String).split(' '); + tags.sort((a, b) => tagsWeight.indexOf(a) - tagsWeight.indexOf(b)); + subPathPatten.replaceAll(r"${id}", illust.id.toString()); + subPathPatten.replaceAll(r"${title}", illust.title); + subPathPatten.replaceAll(r"${author}", illust.author.name); + subPathPatten.replaceAll(r"${ext}", ext); + for(int i=0; i instance ??= DownloadManager._(); + + static DownloadManager? instance; + + DownloadManager._(){ + init(); + } + + late Database _db; + + int _currentBytes = 0; + int _bytesPerSecond = 0; + + int get bytesPerSecond => _bytesPerSecond; + + Timer? _loop; + + var tasks = []; + + void Function()? uiUpdateCallback; + + void registerUiUpdater(void Function() callback) { + uiUpdateCallback = callback; + } + + void removeUiUpdater() { + uiUpdateCallback = null; + } + + void init() { + _db = sqlite3.open("${App.dataPath}/download.db"); + _db.execute(''' + create table if not exists download ( + illust_id integer primary key not null, + title text not null, + author text not null, + imageCount int not null + ); + '''); + _db.execute(''' + create table if not exists images ( + illust_id integer not null, + image_index integer not null, + path text not null, + primary key (illust_id, image_index) + ); + '''); + } + + void saveInfo(Illust illust, List imagePaths) { + _db.execute(''' + insert into download (illust_id, title, author, imageCount) + values (?, ?, ?, ?) + ''', [illust.id, illust.title, illust.author.name, imagePaths.length]); + for (var i = 0; i < imagePaths.length; i++) { + _db.execute(''' + insert into images (illust_id, image_index, path) + values (?, ?, ?) + ''', [illust.id, i, imagePaths[i]]); + } + } + + File? getImage(int illustId, int index) { + var res = _db.select(''' + select * from images + where illust_id = ? and image_index = ?; + '''); + if (res.isEmpty) return null; + var file = File(res.first["path"] as String); + if (!file.existsSync()) return null; + return file; + } + + bool checkDownloaded(int illustId) { + var res = _db.select(''' + select * from download + where illust_id = ?; + ''', [illustId]); + return res.isNotEmpty; + } + + List listAll() { + var res = _db.select(''' + select * from download; + '''); + return res.map((e) => + DownloadedIllust( + illustId: e["illust_id"] as int, + title: e["title"] as String, + author: e["author"] as String, + imageCount: e["imageCount"] as int, + )).toList(); + } + + void addDownloadingTask(Illust illust) { + var task = DownloadingTask(illust, receiveBytesCallback: receiveBytes, onCompleted: (task) { + saveInfo(illust, task.imagePaths); + tasks.remove(task); + }); + tasks.add(task); + run(); + } + + void receiveBytes(int bytes) { + _currentBytes += bytes; + } + + int get maxConcurrentTasks => 3; + + void run() { + _loop ??= Timer.periodic(const Duration(seconds: 1), (timer) { + _bytesPerSecond = _currentBytes; + _currentBytes = 0; + uiUpdateCallback?.call(); + for(int i=0; i get _headers { + Map get headers { final time = DateFormat("yyyy-MM-dd'T'HH:mm:ss'+00:00'").format(DateTime.now()); final hash = md5.convert(utf8.encode(time + hashSalt)).toString(); @@ -80,7 +80,7 @@ class Network { }, options: Options( contentType: Headers.formUrlEncodedContentType, - headers: _headers)); + headers: headers)); if (res.statusCode != 200) { throw "Invalid Status code ${res.statusCode}"; } @@ -106,7 +106,7 @@ class Network { }, options: Options( contentType: Headers.formUrlEncodedContentType, - headers: _headers)); + headers: headers)); var account = Account.fromJson(json.decode(res.data!)); appdata.account = account; appdata.writeData(); @@ -126,7 +126,7 @@ class Network { final res = await dio.get>(path, queryParameters: query, options: - Options(headers: _headers, validateStatus: (status) => true)); + Options(headers: headers, validateStatus: (status) => true)); if (res.statusCode == 200) { return Res(res.data!); } else if (res.statusCode == 400) { @@ -162,7 +162,7 @@ class Network { queryParameters: query, data: data, options: Options( - headers: _headers, + headers: headers, validateStatus: (status) => true, contentType: Headers.formUrlEncodedContentType)); if (res.statusCode == 200) { diff --git a/lib/pages/download_page.dart b/lib/pages/download_page.dart deleted file mode 100644 index 968b40f..0000000 --- a/lib/pages/download_page.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:fluent_ui/fluent_ui.dart'; - -class DownloadPage extends StatefulWidget { - const DownloadPage({super.key}); - - @override - State createState() => _DownloadPageState(); -} - -class _DownloadPageState extends State { - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} diff --git a/lib/pages/downloaded_page.dart b/lib/pages/downloaded_page.dart new file mode 100644 index 0000000..9b2a734 --- /dev/null +++ b/lib/pages/downloaded_page.dart @@ -0,0 +1,15 @@ +import 'package:fluent_ui/fluent_ui.dart'; + +class DownloadedPage extends StatefulWidget { + const DownloadedPage({super.key}); + + @override + State createState() => _DownloadedPageState(); +} + +class _DownloadedPageState extends State { + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/pages/downloading_page.dart b/lib/pages/downloading_page.dart new file mode 100644 index 0000000..9dda021 --- /dev/null +++ b/lib/pages/downloading_page.dart @@ -0,0 +1,166 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:pixes/components/md.dart'; +import 'package:pixes/foundation/app.dart'; +import 'package:pixes/foundation/image_provider.dart'; +import 'package:pixes/network/download.dart'; +import 'package:pixes/utils/translation.dart'; + +import '../utils/io.dart'; + +class DownloadingPage extends StatefulWidget { + const DownloadingPage({super.key}); + + @override + State createState() => _DownloadingPageState(); +} + +class _DownloadingPageState extends State { + @override + void initState() { + DownloadManager().registerUiUpdater(() => setState((){})); + super.initState(); + } + + @override + void dispose() { + DownloadManager().removeUiUpdater(); + super.dispose(); + } + + Map controller = {}; + + @override + Widget build(BuildContext context) { + return ScaffoldPage( + content: CustomScrollView( + slivers: [ + buildTop(), + const SliverPadding(padding: EdgeInsets.only(top: 16)), + buildContent() + ], + ), + ); + } + + Widget buildTop() { + int bytesPerSecond = DownloadManager().bytesPerSecond; + + return SliverToBoxAdapter( + child: SizedBox( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text("${"Speed".tl}: ${bytesToText(bytesPerSecond)}/s", + style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold)), + ), + ), + ); + } + + Widget buildContent() { + return SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + var task = DownloadManager().tasks[index]; + return buildItem(task); + }, + childCount: DownloadManager().tasks.length + ), + ).sliverPaddingHorizontal(12); + } + + Widget buildItem(DownloadingTask task) { + controller[task.illust.id.toString()] ??= FlyoutController(); + + return Card( + padding: const EdgeInsets.symmetric(vertical: 8), + child: SizedBox( + height: 96, + child: Row( + children: [ + const SizedBox(width: 12), + Container( + height: double.infinity, + width: 72, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + border: Border.all(color: ColorScheme.of(context).outlineVariant, width: 0.6), + ), + child: Image( + image: CachedImageProvider(task.illust.images.first.medium), + fit: BoxFit.cover, + filterQuality: FilterQuality.medium, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text(task.illust.title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 4), + Text(task.illust.author.name, style: const TextStyle(fontSize: 12, color: Colors.grey)), + const Spacer(), + if(task.error == null) + Text("${task.downloadedImages}/${task.totalImages} ${"Downloaded".tl}", style: const TextStyle(fontSize: 12, color: Colors.grey)) + else + Text("Error: ${task.error!.replaceAll("\n", " ")}", style: TextStyle(fontSize: 12, color: ColorScheme.of(context).error), maxLines: 2,), + ], + ), + ), + const SizedBox(width: 4), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if(task.error != null) + Button( + child: Text("Retry".tl).fixWidth(46), + onPressed: () { + task.retry(); + setState(() {}); + }, + ), + const SizedBox(height: 4), + FlyoutTarget( + controller: controller[task.illust.id.toString()]!, + child: Button( + child: Text("Cancel".tl, style: TextStyle(color: ColorScheme.of(context).error),).fixWidth(46), + onPressed: (){ + controller[task.illust.id.toString()]!.showFlyout( + navigatorKey: App.rootNavigatorKey.currentState, + builder: (context) { + return FlyoutContent( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Are you sure you want to cancel this download?'.tl, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12.0), + Button( + onPressed: () { + Flyout.of(context).close(); + task.cancel(); + setState(() {}); + }, + child: Text('Yes'.tl), + ), + ], + ), + ); + }, + ); + } + ), + ) + ], + ), + const SizedBox(width: 12), + ], + ), + ), + ); + } +} diff --git a/lib/pages/illust_page.dart b/lib/pages/illust_page.dart index 1b210f1..42a2ba4 100644 --- a/lib/pages/illust_page.dart +++ b/lib/pages/illust_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/material.dart' show Icons; import 'package:pixes/components/animated_image.dart'; @@ -69,6 +71,11 @@ class _IllustPageState extends State { } Widget buildImage(double width, double height, int index) { + File? downloadFile; + if(widget.illust.downloaded) { + downloadFile = DownloadManager().getImage(widget.illust.id, index); + } + if (index == 0) { return Text( widget.illust.title, @@ -93,9 +100,13 @@ class _IllustPageState extends State { width: imageWidth, height: imageHeight, child: GestureDetector( - onTap: () => ImagePage.show(widget.illust.images[index].original), + onTap: () => ImagePage.show(downloadFile == null + ? widget.illust.images[index].original + : "file://${downloadFile.path}"), child: Image( - image: CachedImageProvider(widget.illust.images[index].large), + image: downloadFile == null + ? CachedImageProvider(widget.illust.images[index].large) as ImageProvider + : FileImage(downloadFile) as ImageProvider, width: imageWidth, fit: BoxFit.cover, height: imageHeight, @@ -123,14 +134,9 @@ class _IllustPageState extends State { ), ); - if (index == 0) { - return Hero( - tag: "illust_${widget.illust.id}", - child: image, - ); - } else { - return image; - } + return Center( + child: image, + ); } } @@ -374,7 +380,10 @@ class _BottomBarState extends State<_BottomBar> { }); } - void download() {} + void download() { + DownloadManager().addDownloadingTask(widget.illust); + setState(() {}); + } bool showText = width > 640; @@ -416,24 +425,47 @@ class _BottomBarState extends State<_BottomBar> { yield const SizedBox(width: 8,); if (!widget.illust.downloaded) { - yield Button( - onPressed: download, - child: SizedBox( - height: 28, - child: Row( - children: [ - const Icon( - FluentIcons.download, - size: 18, - ), - if(showText) - const SizedBox(width: 8,), - if(showText) - Text("Download".tl), - ], + if(widget.illust.downloading) { + yield Button( + onPressed: () => {}, + child: SizedBox( + height: 28, + child: Row( + children: [ + Icon( + FluentIcons.download, + color: ColorScheme.of(context).outline, + size: 18, + ), + if(showText) + const SizedBox(width: 8,), + if(showText) + Text("Downloading".tl, + style: TextStyle(color: ColorScheme.of(context).outline),), + ], + ), ), - ), - ); + ); + } else { + yield Button( + onPressed: download, + child: SizedBox( + height: 28, + child: Row( + children: [ + const Icon( + FluentIcons.download, + size: 18, + ), + if(showText) + const SizedBox(width: 8,), + if(showText) + Text("Download".tl), + ], + ), + ), + ); + } } yield const SizedBox(width: 8,); diff --git a/lib/pages/image_page.dart b/lib/pages/image_page.dart index 31efe65..86b8238 100644 --- a/lib/pages/image_page.dart +++ b/lib/pages/image_page.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:fluent_ui/fluent_ui.dart'; import 'package:photo_view/photo_view.dart'; import 'package:pixes/components/page_route.dart'; @@ -60,7 +62,9 @@ class _ImagePageState extends State with WindowListener{ color: Colors.transparent ), filterQuality: FilterQuality.medium, - imageProvider: CachedImageProvider(widget.url), + imageProvider: widget.url.startsWith("file://") + ? FileImage(File(widget.url.replaceFirst("file://", ""))) + : CachedImageProvider(widget.url) as ImageProvider, )), Positioned( top: 0, diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index cd1ed08..f93cf46 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -8,6 +8,7 @@ import "package:pixes/components/md.dart"; import "package:pixes/foundation/app.dart"; import "package:pixes/network/network.dart"; import "package:pixes/pages/bookmarks.dart"; +import "package:pixes/pages/downloaded_page.dart"; import "package:pixes/pages/following_artworks.dart"; import "package:pixes/pages/ranking.dart"; import "package:pixes/pages/recommendation_page.dart"; @@ -20,7 +21,7 @@ import "package:pixes/utils/translation.dart"; import "package:window_manager/window_manager.dart"; import "../components/page_route.dart"; -import "download_page.dart"; +import "downloading_page.dart"; const _kAppBarHeight = 36.0; @@ -34,7 +35,7 @@ class MainPage extends StatefulWidget { class _MainPageState extends State with WindowListener { final navigatorKey = GlobalKey(); - int index = 3; + int index = 4; int windowButtonKey = 0; @@ -101,9 +102,14 @@ class _MainPageState extends State with WindowListener { title: Text('Search'.tl), body: const SizedBox.shrink(), ), + PaneItem( + icon: const Icon(MdIcons.downloading, size: 20,), + title: Text('Downloading'.tl), + body: const SizedBox.shrink(), + ), PaneItem( icon: const Icon(MdIcons.download, size: 20,), - title: Text('Download'.tl), + title: Text('Downloaded'.tl), body: const SizedBox.shrink(), ), PaneItemSeparator(), @@ -148,7 +154,8 @@ class _MainPageState extends State with WindowListener { static final pageBuilders = [ () => UserInfoPage(appdata.account!.user.id), () => const SearchPage(), - () => const DownloadPage(), + () => const DownloadingPage(), + () => const DownloadedPage(), () => const RecommendationPage(), () => const BookMarkedArtworkPage(), () => const FollowingArtworksPage(), diff --git a/lib/utils/io.dart b/lib/utils/io.dart index 4edfcb7..ca097e7 100644 --- a/lib/utils/io.dart +++ b/lib/utils/io.dart @@ -19,4 +19,16 @@ extension FSExt on FileSystemEntity { } return 0; } +} + +String bytesToText(int bytes) { + if(bytes < 1024) { + return "$bytes B"; + } else if(bytes < 1024 * 1024) { + return "${(bytes / 1024).toStringAsFixed(2)} KB"; + } else if(bytes < 1024 * 1024 * 1024) { + return "${(bytes / 1024 / 1024).toStringAsFixed(2)} MB"; + } else { + return "${(bytes / 1024 / 1024 / 1024).toStringAsFixed(2)} GB"; + } } \ No newline at end of file diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index 2206cfd..9f987f3 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -1,14 +1,46 @@ +#pragma comment(lib, "winhttp.lib") #include "flutter_window.h" +#include #include #include #include #include #include - +#include +#include #include "flutter/generated_plugin_registrant.h" +#include "utils.h" std::unique_ptr>&& mouseEvents = nullptr; +static std::string getProxy() { + _WINHTTP_CURRENT_USER_IE_PROXY_CONFIG net; + WinHttpGetIEProxyConfigForCurrentUser(&net); + if (net.lpszProxy == nullptr) { + GlobalFree(net.lpszAutoConfigUrl); + GlobalFree(net.lpszProxyBypass); + return "No Proxy"; + } + else { + GlobalFree(net.lpszAutoConfigUrl); + GlobalFree(net.lpszProxyBypass); + return Utf8FromUtf16(net.lpszProxy); + } +} + +static std::string getPicturePath() { + PWSTR picturesPath; + HRESULT result = SHGetKnownFolderPath(FOLDERID_Pictures, 0, NULL, &picturesPath); + if (SUCCEEDED(result)) { + auto res = Utf8FromUtf16(picturesPath); + CoTaskMemFree(picturesPath); + return res; + } + else { + return "error"; + } +} + FlutterWindow::FlutterWindow(const flutter::DartProject& project) : project_(project) {} @@ -31,15 +63,13 @@ bool FlutterWindow::OnCreate() { } RegisterPlugins(flutter_controller_->engine()); - //监听鼠标侧键的EventChannel - const auto channelName = "pixes/mouse"; - flutter::EventChannel<> channel2( + const auto channelName = "pixes/mouse"; + flutter::EventChannel<> channel2( flutter_controller_->engine()->messenger(), channelName, &flutter::StandardMethodCodec::GetInstance() - ); - - auto eventHandler = std::make_unique< - flutter::StreamHandlerFunctions>( + ); + auto eventHandler = std::make_unique< + flutter::StreamHandlerFunctions>( []( const flutter::EncodableValue* arguments, std::unique_ptr>&& events){ @@ -50,9 +80,27 @@ bool FlutterWindow::OnCreate() { -> std::unique_ptr> { mouseEvents = nullptr; return nullptr; - }); + }); + channel2.SetStreamHandler(std::move(eventHandler)); - channel2.SetStreamHandler(std::move(eventHandler)); + const auto pictureFolderChannel = "pixes/picture_folder"; + flutter::MethodChannel<> channel3( + flutter_controller_->engine()->messenger(), pictureFolderChannel, + &flutter::StandardMethodCodec::GetInstance() + ); + channel3.SetMethodCallHandler([]( + const flutter::MethodCall<>& call, const std::unique_ptr>& result) { + result->Success(getPicturePath()); + }); + + const flutter::MethodChannel<> channel( + flutter_controller_->engine()->messenger(), "pixes/proxy", + &flutter::StandardMethodCodec::GetInstance() + ); + channel.SetMethodCallHandler( + [](const flutter::MethodCall<>& call, const std::unique_ptr>& result) { + result->Success(getProxy()); + }); SetChildContent(flutter_controller_->view()->GetNativeWindow()); @@ -114,4 +162,4 @@ FlutterWindow::MessageHandler(HWND hwnd, UINT const message, } return Win32Window::MessageHandler(hwnd, message, wparam, lparam); -} +} \ No newline at end of file