From 951bcae603ba1d20adc80fae5c4e89f3ff2f6b7a Mon Sep 17 00:00:00 2001 From: Selene29 Date: Sun, 13 Jul 2025 12:52:23 +0200 Subject: [PATCH] Local Comic: Add "Open Folder" button (#443) --- assets/translation.json | 2 ++ lib/pages/local_comics_page.dart | 59 ++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/assets/translation.json b/assets/translation.json index 4fbd451..2ec3f9a 100644 --- a/assets/translation.json +++ b/assets/translation.json @@ -398,6 +398,7 @@ "Clear Unfavorited": "清除未收藏", "Reverse": "反转", "Delete Chapters": "删除章节", + "Open Folder": "打开文件夹", "Path copied to clipboard": "路径已复制到剪贴板", "Reverse default chapter order": "反转默认章节顺序" }, @@ -800,6 +801,7 @@ "Clear Unfavorited": "清除未收藏", "Reverse": "反轉", "Delete Chapters": "刪除章節", + "Open Folder": "打開資料夾", "Path copied to clipboard": "路徑已複製到剪貼簿", "Reverse default chapter order": "反轉預設章節順序" } diff --git a/lib/pages/local_comics_page.dart b/lib/pages/local_comics_page.dart index 9095ae7..8c8ab24 100644 --- a/lib/pages/local_comics_page.dart +++ b/lib/pages/local_comics_page.dart @@ -14,6 +14,7 @@ import 'package:venera/utils/io.dart'; import 'package:venera/utils/pdf.dart'; import 'package:venera/utils/translations.dart'; import 'package:zip_flutter/zip_flutter.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class LocalComicsPage extends StatefulWidget { const LocalComicsPage({super.key}); @@ -143,6 +144,14 @@ class _LocalComicsPageState extends State { addFavorite(selectedComics.keys.toList()); }, ), + if (selectedComics.length == 1) + MenuEntry( + icon: Icons.folder_open, + text: "Open Folder".tl, + onClick: () { + openComicFolder(selectedComics.keys.first); + }, + ), if (selectedComics.length == 1) MenuEntry( icon: Icons.chrome_reader_mode_outlined, @@ -313,6 +322,13 @@ class _LocalComicsPageState extends State { }, menuBuilder: (c) { return [ + MenuEntry( + icon: Icons.folder_open, + text: "Open Folder".tl, + onClick: () { + openComicFolder(c as LocalComic); + }, + ), MenuEntry( icon: Icons.delete, text: "Delete".tl, @@ -519,6 +535,49 @@ class _LocalComicsPageState extends State { typedef ExportComicFunc = Future Function( LocalComic comic, String outFilePath); +/// Opens the folder containing the comic in the system file explorer +Future openComicFolder(LocalComic comic) async { + try { + final folderPath = comic.baseDir; + + if (App.isWindows) { + await Process.run('explorer', [folderPath]); + } else if (App.isMacOS) { + await Process.run('open', [folderPath]); + } else if (App.isLinux) { + // Try different file managers commonly found on Linux + try { + await Process.run('xdg-open', [folderPath]); + } catch (e) { + // Fallback to other common file managers + try { + await Process.run('nautilus', [folderPath]); + } catch (e) { + try { + await Process.run('dolphin', [folderPath]); + } catch (e) { + try { + await Process.run('thunar', [folderPath]); + } catch (e) { + // Last resort: use the URL launcher with file:// protocol + await launchUrlString('file://$folderPath'); + } + } + } + } + } else { + // For mobile platforms, use the URL launcher with file:// protocol + await launchUrlString('file://$folderPath'); + } + } catch (e, s) { + Log.error("Open Folder", "Failed to open comic folder: $e", s); + // Show error message to user + if (App.rootContext.mounted) { + App.rootContext.showMessage(message: "Failed to open folder: $e"); + } + } +} + void showDeleteChaptersPopWindow(BuildContext context, LocalComic comic) { var chapters = [];