From d3bb4bdce58d244c3cf02eb558d95cf71e70a920 Mon Sep 17 00:00:00 2001 From: liyafly Date: Sun, 7 Jan 2024 00:01:16 +0800 Subject: [PATCH 1/6] refactor: refactor the Settings page in SwiftUI --- Easydict/App/Localizable.xcstrings | 104 +++++++++++++++++++++ Easydict/NewApp/EasydictApp.swift | 2 +- Easydict/NewApp/View/MenuItemView.swift | 118 +++++++++++++++++++++++- 3 files changed, 220 insertions(+), 4 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0ee5bae52..cce887af9 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1044,6 +1044,16 @@ } } }, + "Export Log" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "导出日志" + } + } + } + }, "Failed to allocate memory" : { "comment" : "Error reason", "localizations" : { @@ -1055,6 +1065,16 @@ } } }, + "Feedback" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "反馈问题" + } + } + } + }, "first_language" : { "localizations" : { "en" : { @@ -1269,6 +1289,16 @@ } } }, + "Help" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "帮助" + } + } + } + }, "hide" : { "localizations" : { "en" : { @@ -1555,6 +1585,16 @@ } } }, + "Log Directory" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "日志目录" + } + } + } + }, "main_window" : { "localizations" : { "en" : { @@ -1598,6 +1638,70 @@ } } }, + "menu_input_translate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Input Translate" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "输入翻译" + } + } + } + }, + "menu_screenshot_Translate" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Screenshot Translate" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "截图翻译" + } + } + } + }, + "menu_selectWord_Translate" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "划词翻译" + } + } + } + }, + "menu_show_mini_window" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示迷你窗口" + } + } + } + }, + "menu_silent_screenshot_OCR" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "静默截图 OCR" + } + } + } + }, "mini_window" : { "localizations" : { "en" : { diff --git a/Easydict/NewApp/EasydictApp.swift b/Easydict/NewApp/EasydictApp.swift index ae385f4e7..be35f41c6 100644 --- a/Easydict/NewApp/EasydictApp.swift +++ b/Easydict/NewApp/EasydictApp.swift @@ -49,7 +49,7 @@ struct EasydictApp: App { .scaledToFit() } .help("Easydict 🍃") - } + }.menuBarExtraStyle(.menu) Settings { SettingView() } diff --git a/Easydict/NewApp/View/MenuItemView.swift b/Easydict/NewApp/View/MenuItemView.swift index 806abc807..c3a637737 100644 --- a/Easydict/NewApp/View/MenuItemView.swift +++ b/Easydict/NewApp/View/MenuItemView.swift @@ -8,16 +8,27 @@ import Sparkle import SwiftUI +import ZipArchive @available(macOS 13, *) struct MenuItemView: View { var body: some View { + // ️.menuBarExtraStyle为 .menu 时某些控件可能会失效 ,只能显示内容(按照菜单项高度、图像以 template 方式渲染)无法交互 ,比如 Stepper、Slider 等,像基本的 Button、Text、Divider、Image 等还是能正常显示的。 + // Button 和Label的systemImage是不会渲染的 Group { versionItem Divider() + inputItem + screenshotItem + selectWordItem + miniWindowItem + Divider() + ocrItem + Divider() settingItem .keyboardShortcut(.init(",")) checkUpdateItem + helpItem Divider() quitItem .keyboardShortcut(.init("q")) @@ -72,6 +83,70 @@ struct MenuItemView: View { } } + // MARK: - List of functions + + @ViewBuilder + private var inputItem: some View { + Button { + NSLog("输入翻译") + } label: { + HStack { + Image(systemName: "keyboard") + Text("menu_input_translate") + } + } + } + + @ViewBuilder + private var screenshotItem: some View { + Button { + NSLog("输入翻译") + } label: { + HStack { + Image(systemName: "camera.viewfinder") + Text("menu_screenshot_Translate") + } + } + } + + @ViewBuilder + private var selectWordItem: some View { + Button { + NSLog("划词翻译") + } label: { + HStack { + Image(systemName: "highlighter") + Text("menu_selectWord_Translate") + } + } + } + + @ViewBuilder + private var miniWindowItem: some View { + Button { + NSLog("显示迷你窗口") + } label: { + HStack { + Image(systemName: "dock.rectangle") + Text("menu_show_mini_window") + } + } + } + + @ViewBuilder + private var ocrItem: some View { + Button { + NSLog("静默截图OCR") + } label: { + HStack { + Image(systemName: "camera.metering.spot") + Text("menu_silent_screenshot_OCR") + } + } + } + + // MARK: - Setting + @ViewBuilder private var checkUpdateItem: some View { Button("check_updates") { @@ -87,9 +162,46 @@ struct MenuItemView: View { NSApplication.shared.terminate(nil) } } + + @ViewBuilder + private var helpItem: some View { + Menu("Help") { + Button("Feedback") { + guard let versionURL = URL(string: "\(EZGithubRepoEasydictURL)/issues") else { + return + } + openURL(versionURL) + } + Button("Export Log") { + exportLogAction() + } + Button("Log Directory") { + NSLog("日志目录") + let logPath = MMManagerForLog.rootLogDirectory() ?? "" + let directoryURL = URL(fileURLWithPath: logPath) + NSWorkspace.shared.open(directoryURL) + } + } + } + + private func exportLogAction() { + NSLog("导出日志") + let logPath = MMManagerForLog.rootLogDirectory() ?? "" + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH-mm-ss-SSS" + let dataString = dateFormatter.string(from: Date()) + let downloadDirectory = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] + let zipPath = downloadDirectory.appendingPathComponent("Easydict log \(dataString).zip").path(percentEncoded: false) + let success = SSZipArchive.createZipFile(atPath: zipPath, withContentsOfDirectory: logPath, keepParentDirectory: false) + if success { + NSWorkspace.shared.selectFile(zipPath, inFileViewerRootedAtPath: "") + } else { + MMLogInfo("导出日志失败") + } + } } -@available(macOS 13, *) -#Preview { + @available(macOS 13, *) + #Preview { MenuItemView() -} + } From 7b30e193ea0223afca1248c89111e21a9a85c9a4 Mon Sep 17 00:00:00 2001 From: liyafly Date: Sun, 7 Jan 2024 13:09:59 +0800 Subject: [PATCH 2/6] refactor: add Settings page some localizable --- Easydict/App/Localizable.xcstrings | 24 +++++++++++++++++++++--- Easydict/NewApp/View/MenuItemView.swift | 6 +++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index cce887af9..925adcda0 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1674,9 +1674,15 @@ }, "menu_selectWord_Translate" : { "localizations" : { - "zh-Hans" : { + "en" : { "stringUnit" : { "state" : "translated", + "value" : "Select Translate" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", "value" : "划词翻译" } } @@ -1684,9 +1690,15 @@ }, "menu_show_mini_window" : { "localizations" : { - "zh-Hans" : { + "en" : { "stringUnit" : { "state" : "translated", + "value" : "Show Mini Window" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", "value" : "显示迷你窗口" } } @@ -1694,9 +1706,15 @@ }, "menu_silent_screenshot_OCR" : { "localizations" : { - "zh-Hans" : { + "en" : { "stringUnit" : { "state" : "translated", + "value" : "Silent Screenshot OCR" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "needs_review", "value" : "静默截图 OCR" } } diff --git a/Easydict/NewApp/View/MenuItemView.swift b/Easydict/NewApp/View/MenuItemView.swift index c3a637737..d6f5d7d74 100644 --- a/Easydict/NewApp/View/MenuItemView.swift +++ b/Easydict/NewApp/View/MenuItemView.swift @@ -201,7 +201,7 @@ struct MenuItemView: View { } } - @available(macOS 13, *) - #Preview { +@available(macOS 13, *) +#Preview { MenuItemView() - } +} From d8c8741a6440bd4474bd29e1936b7241ea4b48bc Mon Sep 17 00:00:00 2001 From: liyafly Date: Sun, 7 Jan 2024 21:33:52 +0800 Subject: [PATCH 3/6] refactor: Add functionality to list options on menu pages --- Easydict/App/Easydict-Bridging-Header.h | 1 + Easydict/NewApp/EasydictApp.swift | 12 +++++++++++- Easydict/NewApp/View/MenuItemView.swift | 17 ++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 2747a587f..8e40fb2e8 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -23,3 +23,4 @@ #import "EZConfiguration.h" #import "NSString+EZConvenience.h" +#import "EZWindowManager.h" diff --git a/Easydict/NewApp/EasydictApp.swift b/Easydict/NewApp/EasydictApp.swift index be35f41c6..ff553a9b8 100644 --- a/Easydict/NewApp/EasydictApp.swift +++ b/Easydict/NewApp/EasydictApp.swift @@ -6,6 +6,7 @@ // Copyright © 2023 izual. All rights reserved. // +import Sparkle import SwiftUI @main @@ -35,10 +36,19 @@ struct EasydictApp: App { #endif } + private let updaterController: SPUStandardUpdaterController + + init() { + // 参考 https://sparkle-project.org/documentation/programmatic-setup/ + // If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later + // This is where you can also pass an updater delegate if you need one + updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) + } + var body: some Scene { if #available(macOS 13, *) { MenuBarExtra(isInserted: $hideMenuBar.toggledValue) { - MenuItemView() + MenuItemView(updater: updaterController.updater) } label: { Label { Text("Easydict") diff --git a/Easydict/NewApp/View/MenuItemView.swift b/Easydict/NewApp/View/MenuItemView.swift index d6f5d7d74..d93510246 100644 --- a/Easydict/NewApp/View/MenuItemView.swift +++ b/Easydict/NewApp/View/MenuItemView.swift @@ -12,6 +12,12 @@ import ZipArchive @available(macOS 13, *) struct MenuItemView: View { + private let updater: SPUUpdater + + init(updater: SPUUpdater) { + self.updater = updater + } + var body: some View { // ️.menuBarExtraStyle为 .menu 时某些控件可能会失效 ,只能显示内容(按照菜单项高度、图像以 template 方式渲染)无法交互 ,比如 Stepper、Slider 等,像基本的 Button、Text、Divider、Image 等还是能正常显示的。 // Button 和Label的systemImage是不会渲染的 @@ -89,6 +95,7 @@ struct MenuItemView: View { private var inputItem: some View { Button { NSLog("输入翻译") + EZWindowManager.shared().inputTranslate() } label: { HStack { Image(systemName: "keyboard") @@ -100,7 +107,8 @@ struct MenuItemView: View { @ViewBuilder private var screenshotItem: some View { Button { - NSLog("输入翻译") + NSLog("截图翻译") + EZWindowManager.shared().snipTranslate() } label: { HStack { Image(systemName: "camera.viewfinder") @@ -113,6 +121,7 @@ struct MenuItemView: View { private var selectWordItem: some View { Button { NSLog("划词翻译") + EZWindowManager.shared().selectTextTranslate() } label: { HStack { Image(systemName: "highlighter") @@ -125,6 +134,7 @@ struct MenuItemView: View { private var miniWindowItem: some View { Button { NSLog("显示迷你窗口") + EZWindowManager.shared().showMiniFloatingWindow() } label: { HStack { Image(systemName: "dock.rectangle") @@ -137,6 +147,7 @@ struct MenuItemView: View { private var ocrItem: some View { Button { NSLog("静默截图OCR") + EZWindowManager.shared().screenshotOCR() } label: { HStack { Image(systemName: "camera.metering.spot") @@ -151,7 +162,7 @@ struct MenuItemView: View { private var checkUpdateItem: some View { Button("check_updates") { NSLog("检查更新") - SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil).checkForUpdates(nil) + updater.checkForUpdates() } } @@ -203,5 +214,5 @@ struct MenuItemView: View { @available(macOS 13, *) #Preview { - MenuItemView() + MenuItemView(updater: SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil).updater) } From 57a82845d4c51ee98cb9cf9ebf356b1ccb14d5f7 Mon Sep 17 00:00:00 2001 From: Kyle Date: Tue, 9 Jan 2024 01:23:02 +0800 Subject: [PATCH 4/6] Fix Xcode nullable warning --- Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.h | 2 +- Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.h b/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.h index de32711b0..9890d7b89 100644 --- a/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.h +++ b/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.h @@ -12,7 +12,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSViewController (EZWindow) -- (NSWindow *)window; +- (nullable NSWindow *)window; @end diff --git a/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.m b/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.m index a73e9e2aa..a46341981 100644 --- a/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.m +++ b/Easydict/Feature/Utility/EZCategory/NSViewController+EZWindow.m @@ -10,7 +10,7 @@ @implementation NSViewController (EZWindow) -- (NSWindow *)window { +- (nullable NSWindow *)window { NSResponder *responder = self; while ((responder = [responder nextResponder])) { if ([responder isKindOfClass:[NSWindow class]]) { From 0c0f757878b3a2181b7ab76228f3ab188417e51b Mon Sep 17 00:00:00 2001 From: liyafei Date: Tue, 9 Jan 2024 11:09:46 +0800 Subject: [PATCH 5/6] refactor: renames the repeatedly defined properties (cherry picked from commit ca75f5a8bc83e2d9b2db6def8c964010cfdfdf00) --- .../Window/BaseQueryWindow/EZBaseQueryViewController.h | 2 +- .../Window/BaseQueryWindow/EZBaseQueryViewController.m | 6 +++--- .../Window/BaseQueryWindow/EZBaseQueryWindow.m | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.h b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.h index e407b1360..e7301b5c8 100644 --- a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.h +++ b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.h @@ -18,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, copy) NSString *inputText; @property (nonatomic, assign) EZWindowType windowType; -@property (nonatomic, weak) EZBaseQueryWindow *window; +@property (nullable, nonatomic, weak) EZBaseQueryWindow *baseQueryWindow; @property (nonatomic, strong, readonly) NSArray *services; diff --git a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m index fd28c7928..58d48a8a3 100644 --- a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m +++ b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m @@ -522,11 +522,11 @@ - (void)retryQuery { - (void)focusInputTextView { // Fix ⚠️: ERROR: Setting as the first responder for window , but it is in a different window ((null))! This would eventually crash when the view is freed. The first responder will be set to nil. - if (self.queryView.window == self.window) { + if (self.queryView.window == self.baseQueryWindow) { // Need to activate the current application first. [NSApp activateIgnoringOtherApps:YES]; - [self.window makeFirstResponder:self.queryView.textView]; + [self.baseQueryWindow makeFirstResponder:self.queryView.textView]; } } @@ -1420,7 +1420,7 @@ - (void)updateWindowViewHeightWithLock:(BOOL)lockFlag // ???: why set window frame will change tableView height? // ???: why this window animation will block cell rendering? // [self.window setFrame:safeFrame display:NO animate:animateFlag]; - [self.window setFrame:safeFrame display:NO]; + [self.baseQueryWindow setFrame:safeFrame display:NO]; // Restore tableView height. self.tableView.height = tableViewHeight; diff --git a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryWindow.m b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryWindow.m index 1e16d79e8..b2b56b8bd 100644 --- a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryWindow.m +++ b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryWindow.m @@ -70,7 +70,7 @@ - (void)setWindowType:(EZWindowType)windowType { - (void)setQueryViewController:(EZBaseQueryViewController *)viewController { _queryViewController = viewController; - viewController.window = self; + viewController.baseQueryWindow = self; self.contentViewController = viewController; } From a0804de0260a94876b9c1ee963475ec3e58c9e81 Mon Sep 17 00:00:00 2001 From: liyafly Date: Sat, 13 Jan 2024 11:02:06 +0800 Subject: [PATCH 6/6] refactor: Perfect SPUStandardUpdaterController delegate implementation --- Easydict/App/Easydict-Bridging-Header.h | 1 + Easydict/NewApp/EasydictApp.swift | 21 ++++++++++++++++++++- Easydict/NewApp/View/MenuItemView.swift | 19 +++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 8e40fb2e8..3753f8f67 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -24,3 +24,4 @@ #import "NSString+EZConvenience.h" #import "EZWindowManager.h" +#import "NSViewController+EZWindow.h" diff --git a/Easydict/NewApp/EasydictApp.swift b/Easydict/NewApp/EasydictApp.swift index ff553a9b8..c81ba83f4 100644 --- a/Easydict/NewApp/EasydictApp.swift +++ b/Easydict/NewApp/EasydictApp.swift @@ -21,6 +21,22 @@ enum EasydictCmpatibilityEntry { } } +class SPUUpdaterHelper: NSObject, SPUUpdaterDelegate { + func feedURLString(for _: SPUUpdater) -> String? { + var feedURLString = "https://raw.githubusercontent.com/tisfeng/Easydict/main/appcast.xml" + #if DEBUG + feedURLString = "http://localhost:8000/appcast.xml" + #endif + return feedURLString + } +} + +class SPUUserDriverHelper: NSObject, SPUStandardUserDriverDelegate { + var supportsGentleScheduledUpdateReminders: Bool { + true + } +} + struct EasydictApp: App { @NSApplicationDelegateAdaptor var delegate: AppDelegate @@ -36,13 +52,16 @@ struct EasydictApp: App { #endif } + let userDriverHelper = SPUUserDriverHelper() + let upadterHelper = SPUUpdaterHelper() + private let updaterController: SPUStandardUpdaterController init() { // 参考 https://sparkle-project.org/documentation/programmatic-setup/ // If you want to start the updater manually, pass false to startingUpdater and call .startUpdater() later // This is where you can also pass an updater delegate if you need one - updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil) + updaterController = SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: upadterHelper, userDriverDelegate: userDriverHelper) } var body: some Scene { diff --git a/Easydict/NewApp/View/MenuItemView.swift b/Easydict/NewApp/View/MenuItemView.swift index d93510246..999bd03b3 100644 --- a/Easydict/NewApp/View/MenuItemView.swift +++ b/Easydict/NewApp/View/MenuItemView.swift @@ -10,12 +10,23 @@ import Sparkle import SwiftUI import ZipArchive +@available(macOS 13, *) +final class MenuItemStore: ObservableObject { + @Published var canCheckForUpdates = false + var updater: SPUUpdater + init(updater: SPUUpdater) { + self.updater = updater + self.updater.publisher(for: \.canCheckForUpdates) + .assign(to: &$canCheckForUpdates) + } +} + @available(macOS 13, *) struct MenuItemView: View { - private let updater: SPUUpdater + @ObservedObject var store: MenuItemStore init(updater: SPUUpdater) { - self.updater = updater + store = MenuItemStore(updater: updater) } var body: some View { @@ -162,8 +173,8 @@ struct MenuItemView: View { private var checkUpdateItem: some View { Button("check_updates") { NSLog("检查更新") - updater.checkForUpdates() - } + store.updater.checkForUpdates() + }.disabled(!store.canCheckForUpdates) } @ViewBuilder