From 4498f745924939f0abb885aa41eaed4b0c2426c4 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 15:15:21 -0800 Subject: [PATCH 01/17] service tab refactor --- Easydict.xcodeproj/project.pbxproj | 4 - Easydict/App/Localizable.xcstrings | 3 + Easydict/NewApp/View/ServiceItemView.swift | 41 --- .../NewApp/View/SettingView/SettingView.swift | 33 ++- .../View/SettingView/Tabs/ServiceTab.swift | 236 ++++++++++-------- 5 files changed, 156 insertions(+), 161 deletions(-) delete mode 100644 Easydict/NewApp/View/ServiceItemView.swift diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 7438afb68..31b5c6e7e 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -229,7 +229,6 @@ 03FD68BB2B1DC59600FD388E /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 03FD68BA2B1DC59600FD388E /* CryptoSwift */; }; 03FD68BE2B1E151A00FD388E /* String+EncryptAES.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FD68BD2B1E151A00FD388E /* String+EncryptAES.swift */; }; 0A057D6D2B499A000025C51D /* ServiceTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A057D6C2B499A000025C51D /* ServiceTab.swift */; }; - 0A057D6F2B499A0B0025C51D /* ServiceItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A057D6E2B499A0B0025C51D /* ServiceItemView.swift */; }; 0A2BA9602B49A989002872A4 /* Binding+DidSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2BA95F2B49A989002872A4 /* Binding+DidSet.swift */; }; 0A2BA9642B4A3CCD002872A4 /* Notification+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2BA9632B4A3CCD002872A4 /* Notification+Name.swift */; }; 0AC11B222B4D16A500F07198 /* WindowAccessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC11B212B4D16A500F07198 /* WindowAccessor.swift */; }; @@ -703,7 +702,6 @@ 03FD68BD2B1E151A00FD388E /* String+EncryptAES.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+EncryptAES.swift"; sourceTree = ""; }; 06E15747A7BD34D510ADC6A8 /* Pods-Easydict.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Easydict.debug.xcconfig"; path = "Target Support Files/Pods-Easydict/Pods-Easydict.debug.xcconfig"; sourceTree = ""; }; 0A057D6C2B499A000025C51D /* ServiceTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTab.swift; sourceTree = ""; }; - 0A057D6E2B499A0B0025C51D /* ServiceItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceItemView.swift; sourceTree = ""; }; 0A2BA95F2B49A989002872A4 /* Binding+DidSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+DidSet.swift"; sourceTree = ""; }; 0A2BA9632B4A3CCD002872A4 /* Notification+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Name.swift"; sourceTree = ""; }; 0AC11B212B4D16A500F07198 /* WindowAccessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAccessor.swift; sourceTree = ""; }; @@ -2052,7 +2050,6 @@ isa = PBXGroup; children = ( 27FE980A2B3DD5D1000AD654 /* MenuItemView.swift */, - 0A057D6E2B499A0B0025C51D /* ServiceItemView.swift */, 0AC11B212B4D16A500F07198 /* WindowAccessor.swift */, 0AC11B232B4E46B300F07198 /* TapHandlerView.swift */, 27FE98072B3DD52B000AD654 /* SettingView */, @@ -2605,7 +2602,6 @@ 03991166292A8A4400E1B06D /* EZTitleBarMoveView.m in Sources */, 03542A582937CC3200C34C33 /* EZConfiguration.m in Sources */, 27FE98092B3DD536000AD654 /* SettingView.swift in Sources */, - 0A057D6F2B499A0B0025C51D /* ServiceItemView.swift in Sources */, 035E37E72A0953120061DFAF /* EZToast.m in Sources */, 03542A492937B5CF00C34C33 /* EZGoogleTranslate.m in Sources */, 03D0435A2928C4C800E7559E /* EZWindowManager.m in Sources */, diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index c75689b1e..0940db90f 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -2742,6 +2742,9 @@ } } } + }, + "setting.service.detail.no_selection" : { + }, "setting.tts_service.options.apple" : { "localizations" : { diff --git a/Easydict/NewApp/View/ServiceItemView.swift b/Easydict/NewApp/View/ServiceItemView.swift deleted file mode 100644 index d21f17e98..000000000 --- a/Easydict/NewApp/View/ServiceItemView.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ServiceItemView.swift -// Easydict -// -// Created by phlpsong on 2024/1/6. -// Copyright © 2024 izual. All rights reserved. -// - -import SwiftUI - -@available(macOS 13.0, *) -struct ServiceItemView: View { - @Binding var service: QueryService - - var toggleValueChanged: (Bool) -> Void - - var body: some View { - HStack { - Image(nsImage: NSImage(named: service.serviceType().rawValue) ?? NSImage()) - .resizable() - .frame(maxWidth: 18.0, maxHeight: 18.0) - - Text(service.name()) - - Toggle(isOn: $service.enabled.didSet(execute: { value in - toggleValueChanged(value) - })) {} - .toggleStyle(.switch) - .controlSize(.small) - } - .padding(4.0) - } -} - -@available(macOS 13, *) -#Preview { - let service = EZLocalStorage.shared().allServices(.mini).first ?? QueryService() - return ServiceItemView(service: .constant(service)) { val in - print("toggle value changed: \(val)") - } -} diff --git a/Easydict/NewApp/View/SettingView/SettingView.swift b/Easydict/NewApp/View/SettingView/SettingView.swift index 8c4fec008..a7e27c991 100644 --- a/Easydict/NewApp/View/SettingView/SettingView.swift +++ b/Easydict/NewApp/View/SettingView/SettingView.swift @@ -17,41 +17,48 @@ enum SettingTab: Int { @available(macOS 13, *) struct SettingView: View { - @State private var selection = SettingTab.general.rawValue + @State private var selection = SettingTab.general @State private var window: NSWindow? var body: some View { - TabView(selection: $selection.didSet(execute: { _ in - resizeWindowFrame() - })) { + TabView(selection: $selection) { GeneralTab() .tabItem { Label("setting_general", systemImage: "gear") } - .tag(SettingTab.general.rawValue) + .tag(SettingTab.general) ServiceTab() .tabItem { Label("service", systemImage: "briefcase") } - .tag(SettingTab.service.rawValue) + .tag(SettingTab.service) PrivacyTab() .tabItem { Label("privacy", systemImage: "hand.raised.square") } - .tag(SettingTab.privacy.rawValue) + .tag(SettingTab.privacy) AboutTab() .tabItem { Label("about", systemImage: "info.bubble") } - .tag(SettingTab.about.rawValue) + .tag(SettingTab.about) } - .background(WindowAccessor(window: $window.didSet(execute: { _ in - // reset frame when first launch + .background( + WindowAccessor(window: $window.didSet(execute: { _ in + // reset frame when first launch + resizeWindowFrame() + })) + ) + .onChange(of: selection) { _ in resizeWindowFrame() - }))) + } } func resizeWindowFrame() { guard let window else { return } let originalFrame = window.frame - let newSize = selection == SettingTab.service.rawValue - ? CGSize(width: 360, height: 520) : CGSize(width: 500, height: 400) + let newSize = switch selection { + case .general, .privacy, .about: + CGSize(width: 500, height: 520) + case .service: + CGSize(width: 800, height: 520) + } let newY = originalFrame.origin.y + originalFrame.size.height - newSize.height let newRect = NSRect(origin: CGPoint(x: originalFrame.origin.x, y: newY), size: newSize) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 3da7ca423..995846756 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -6,141 +6,171 @@ // Copyright © 2024 izual. All rights reserved. // +import Combine import SwiftUI @available(macOS 13, *) struct ServiceTab: View { - @State private var windowTypeValue = EZWindowType.mini.rawValue - @State private var serviceTypes: [ServiceType] = [] - @State private var services: [QueryService] = [] - @State private var selectedIndex: Int? - // workaround for tap gesture conflict with onMove - @State private var isNeedTapHandler = true - - var segmentCtrl: some View { - Picker("", selection: $windowTypeValue) { - Text("mini_window") - .tag(EZWindowType.mini.rawValue) - - Text("fixed_window") - .tag(EZWindowType.fixed.rawValue) - - Text("main_window") - .tag(EZWindowType.main.rawValue) - } - .padding() - .pickerStyle(.segmented) - .onChange(of: windowTypeValue) { type in - loadService(type: type) - selectedIndex = nil - } - } + @State private var windowType = EZWindowType.mini + @State private var selectedService: QueryService? - var serviceList: some View { - List { - ForEach(Array(zip(serviceTypes.indices, serviceTypes)), id: \.0) { index, _ in - ServiceItemView( - service: $services[index] - ) { isEnable in - serviceToggled(index: index, isEnable: isEnable) - selectedIndex = nil - isNeedTapHandler = false - } - .frame(height: 30) - .tag(index) - .listRowBackground(selectedIndex == index ? Color("service_cell_highlight") : Color.clear) - .overlay(TapHandler(tapAction: { - if !isNeedTapHandler { - isNeedTapHandler.toggle() - return - } - if selectedIndex == nil || selectedIndex != index { - selectedIndex = index - } else { - selectedIndex = nil + var body: some View { + HStack { + List(selection: $selectedService) { + WindowTypePicker(windowType: $windowType) + ServiceItems(windowType: windowType) + } + .frame(maxWidth: 300) + .listStyle(.sidebar) + .scrollIndicators(.hidden) + Group { + if let service = selectedService { + Text(service.name()) + } else { + VStack { + Text("setting.service.detail.no_selection") } - })) + } } - .onMove(perform: { indices, newOffset in - onServiceItemMove(fromOffsets: indices, toOffset: newOffset) - selectedIndex = nil - }) - .listRowSeparator(.hidden) + .frame(width: 500) + } + .onChange(of: windowType) { _ in + selectedService = nil } - .scrollIndicators(.hidden) - .listStyle(.plain) - .clipShape(RoundedRectangle(cornerRadius: 8.0)) - .padding([.horizontal, .bottom]) } +} - var body: some View { - VStack { - segmentCtrl +@available(macOS 13.0, *) +private struct ServiceItems: View { + let windowType: EZWindowType - serviceList - } - .onAppear { - loadService(type: windowTypeValue) + private var services: [QueryService] { + EZLocalStorage.shared().allServices(windowType) + } + + private var servicesWithID: [(QueryService, String)] { + services.map { service in + (service, "\(service.name())\(windowType)") } } - func loadService(type: Int) { - let windowType = EZWindowType(rawValue: type) ?? .none - services = EZLocalStorage.shared().allServices(windowType) - serviceTypes = services.compactMap { $0.serviceType() } + var body: some View { + ForEach(servicesWithID, id: \.1) { service, _ in + ServiceItemView(service: service, windowType: windowType) + .tag(service) + } + .onMove(perform: onServiceItemMove) } - func serviceToggled(index: Int, isEnable: Bool) { - let service = services[index] - service.enabled = isEnable - if isEnable { - service.enabledQuery = true + private func onServiceItemMove(fromOffsets: IndexSet, toOffset: Int) { + var services = services + + services.move(fromOffsets: fromOffsets, toOffset: toOffset) + + let serviceTypes = services.map { service in + service.serviceType() } - let windowType = EZWindowType(rawValue: windowTypeValue) ?? .none - EZLocalStorage.shared().setService(services[index], windowType: windowType) - // refresh service list - loadService(type: windowTypeValue) + + EZLocalStorage.shared().setAllServiceTypes(serviceTypes, windowType: windowType) + postUpdateServiceNotification() + + // Trigger rerender to update view with new position + refresh.objectWillChange.send() } - func enabledServices(in services: [QueryService]) -> [QueryService] { - services.filter(\.enabled) + private func postUpdateServiceNotification() { + let userInfo: [String: Any] = [EZWindowTypeKey: windowType.rawValue] + let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) + NotificationCenter.default.post(notification) } - func onServiceItemMove(fromOffsets: IndexSet, toOffset: Int) { - let oldEnabledServices = enabledServices(in: services) + private class RefreshObservableObject: ObservableObject {} + @StateObject private var refresh: RefreshObservableObject = .init() +} - services.move(fromOffsets: fromOffsets, toOffset: toOffset) - serviceTypes.move(fromOffsets: fromOffsets, toOffset: toOffset) +@available(macOS 13.0, *) +private struct ServiceItemView: View { + @StateObject private var service: QueryServiceWrapper - let windowType = EZWindowType(rawValue: windowTypeValue) ?? .none - EZLocalStorage.shared().setAllServiceTypes(serviceTypes, windowType: windowType) - let newServices = EZLocalStorage.shared().allServices(windowType) - let newEnabledServices = enabledServices(in: newServices) + init(service: QueryService, windowType: EZWindowType) { + _service = .init(wrappedValue: .init(queryService: service, windowType: windowType)) + } - // post notification after enabled services order changed - if isEnabledServicesOrderChanged(source: oldEnabledServices, dest: newEnabledServices) { - postUpdateServiceNotification() + var body: some View { + Toggle(isOn: $service.enabled) { + Label { + Text(service.inner.name()) + } icon: { + Image(service.inner.serviceType().rawValue) + .resizable() + .scaledToFit() + } } + .toggleStyle(.switch) } - func isEnabledServicesOrderChanged( - source: [QueryService], - dest: [QueryService] - ) -> Bool { - !source.elementsEqual(dest) { sItem, dItem in - sItem.serviceType() == dItem.serviceType() && sItem.name() == dItem.name() + private class QueryServiceWrapper: ObservableObject { + let windowType: EZWindowType + var inner: QueryService + + var enabled: Bool { + get { + inner.enabled + } set { + inner.enabled = newValue + if newValue { + inner.enabledQuery = newValue + } + save() + } } - } - func postUpdateServiceNotification() { - let userInfo: [String: Any] = [EZWindowTypeKey: windowTypeValue] - let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) - NotificationCenter.default.post(notification) + private var cancellables: Set = [] + + init(queryService: QueryService, windowType: EZWindowType) { + inner = queryService + self.windowType = windowType + + enabled = queryService.enabled + } + + private func save() { + EZLocalStorage.shared().setService(inner, windowType: windowType) + postUpdateServiceNotification() + } + + private func postUpdateServiceNotification() { + let userInfo: [String: Any] = [EZWindowTypeKey: windowType.rawValue] + let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) + NotificationCenter.default.post(notification) + } } } @available(macOS 13, *) -#Preview { - ServiceTab() +private struct WindowTypePicker: View { + @Binding var windowType: EZWindowType + + var body: some View { + Picker("", selection: $windowType) { + ForEach([EZWindowType]([.mini, .fixed, .main]), id: \.rawValue) { windowType in + Text(windowType.localizedStringResource) + .tag(windowType) + } + } + .pickerStyle(.segmented) + } +} + +private struct SplitView: View { + @ViewBuilder let sidebar: () -> S + @ViewBuilder let content: () -> C + + var body: some View { + HStack { + sidebar() + content() + } + } } From a7977ca00809c46068f75453e508ded19007e275 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:28:48 -0800 Subject: [PATCH 02/17] service configuration view --- Easydict.xcodeproj/project.pbxproj | 36 ++++++ Easydict/App/Easydict-Bridging-Header.h | 2 + Easydict/App/Localizable.xcstrings | 117 +++++++++++++++++- Easydict/Feature/Service/Ali/AliService.swift | 7 +- .../Service/Caiyun/CaiyunService.swift | 3 +- .../Feature/Service/OpenAI/EZOpenAIService.m | 1 + .../Service/Tencent/TencentService.swift | 5 +- .../NewApp/Configuration/Configuration.swift | 35 ++++++ .../OpenAIService+ConfigurableService.swift | 35 ++++++ .../Protocol/ConfigurableService.swift | 27 ++++ .../ServiceConfigurationSection.swift | 79 ++++++++++++ .../View/SettingView/Tabs/ServiceTab.swift | 23 ++-- 12 files changed, 351 insertions(+), 19 deletions(-) create mode 100644 Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift create mode 100644 Easydict/NewApp/Utility/Protocol/ConfigurableService.swift create mode 100644 Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 31b5c6e7e..a034dfbd0 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -272,6 +272,9 @@ EA9943EE2B5353AB00EE7B97 /* WindowTypeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */; }; EA9943F02B5354C400EE7B97 /* ShowWindowPositionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */; }; EA9943F22B5358BF00EE7B97 /* LanguageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9943F12B5358BF00EE7B97 /* LanguageExtensions.swift */; }; + EAED41EC2B54AA920005FE0A /* ServiceConfigurationSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAED41EB2B54AA920005FE0A /* ServiceConfigurationSection.swift */; }; + EAED41EF2B54B1430005FE0A /* ConfigurableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAED41EE2B54B1430005FE0A /* ConfigurableService.swift */; }; + EAED41F22B54B39D0005FE0A /* OpenAIService+ConfigurableService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAED41F12B54B39D0005FE0A /* OpenAIService+ConfigurableService.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -760,6 +763,9 @@ EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowTypeExtensions.swift; sourceTree = ""; }; EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowWindowPositionExtensions.swift; sourceTree = ""; }; EA9943F12B5358BF00EE7B97 /* LanguageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageExtensions.swift; sourceTree = ""; }; + EAED41EB2B54AA920005FE0A /* ServiceConfigurationSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceConfigurationSection.swift; sourceTree = ""; }; + EAED41EE2B54B1430005FE0A /* ConfigurableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurableService.swift; sourceTree = ""; }; + EAED41F12B54B39D0005FE0A /* OpenAIService+ConfigurableService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenAIService+ConfigurableService.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2069,6 +2075,7 @@ 27FE980C2B3DD749000AD654 /* Tabs */ = { isa = PBXGroup; children = ( + EAED41EA2B54A4900005FE0A /* ServiceConfiguration */, 278540332B3DE04F004E9488 /* GeneralTab.swift */, 0A057D6C2B499A000025C51D /* ServiceTab.swift */, 276742042B3DC230002A2C75 /* PrivacyTab.swift */, @@ -2202,6 +2209,7 @@ EA9943DD2B534BAE00EE7B97 /* Utility */ = { isa = PBXGroup; children = ( + EAED41ED2B54B1390005FE0A /* Protocol */, EA9943E62B534D7C00EE7B97 /* Extensions */, ); path = Utility; @@ -2218,6 +2226,7 @@ EA9943E62B534D7C00EE7B97 /* Extensions */ = { isa = PBXGroup; children = ( + EAED41F02B54B1A60005FE0A /* QueryService+ConfigurableService */, EA9943E72B534D8900EE7B97 /* LanguageDetectOptimizeExtensions.swift */, EA9943ED2B5353AB00EE7B97 /* WindowTypeExtensions.swift */, EA9943EF2B5354C400EE7B97 /* ShowWindowPositionExtensions.swift */, @@ -2226,6 +2235,30 @@ path = Extensions; sourceTree = ""; }; + EAED41EA2B54A4900005FE0A /* ServiceConfiguration */ = { + isa = PBXGroup; + children = ( + EAED41EB2B54AA920005FE0A /* ServiceConfigurationSection.swift */, + ); + path = ServiceConfiguration; + sourceTree = ""; + }; + EAED41ED2B54B1390005FE0A /* Protocol */ = { + isa = PBXGroup; + children = ( + EAED41EE2B54B1430005FE0A /* ConfigurableService.swift */, + ); + path = Protocol; + sourceTree = ""; + }; + EAED41F02B54B1A60005FE0A /* QueryService+ConfigurableService */ = { + isa = PBXGroup; + children = ( + EAED41F12B54B39D0005FE0A /* OpenAIService+ConfigurableService.swift */, + ); + path = "QueryService+ConfigurableService"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -2633,6 +2666,7 @@ 03F14A3B2956016B00CB7379 /* EZVolcanoTranslate.m in Sources */, 03B0230429231FA6001C7E63 /* EZHoverButton.m in Sources */, 0342A9812AD64924002A9F5F /* NSString+EZSplit.m in Sources */, + EAED41EF2B54B1430005FE0A /* ConfigurableService.swift in Sources */, 03BD2825294875AE00F5891A /* EZMyLabel.m in Sources */, 03B0233029231FA6001C7E63 /* MMCrashUncaughtExceptionHandler.m in Sources */, 03D5FCFF2A5EF4E400AD26BE /* EZDeviceSystemInfo.m in Sources */, @@ -2773,6 +2807,7 @@ 039F5508294B6E29004AB940 /* EZAboutViewController.m in Sources */, 03D8A6592A42A1A300D9A968 /* EZAppModel.m in Sources */, 036E7D7B293F4FC8002675DF /* EZOpenLinkButton.m in Sources */, + EAED41EC2B54AA920005FE0A /* ServiceConfigurationSection.swift in Sources */, 276742092B3DC230002A2C75 /* AboutTab.swift in Sources */, 03008B2E2941956D0062B821 /* EZURLSchemeHandler.m in Sources */, DC6D9C872B352EBC0055EFFC /* FontSizeHintView.swift in Sources */, @@ -2788,6 +2823,7 @@ 03008B3F29444B0A0062B821 /* NSView+EZAnimatedHidden.m in Sources */, 03B022FD29231FA6001C7E63 /* EZFixedQueryWindow.m in Sources */, 03B0232C29231FA6001C7E63 /* NSView+MM.m in Sources */, + EAED41F22B54B39D0005FE0A /* OpenAIService+ConfigurableService.swift in Sources */, 033C31002A74CECE0095926A /* EZAppleDictionary.m in Sources */, 03E2BF752A298F2B00E010F3 /* NSString+EZUtils.m in Sources */, 03B022F529231FA6001C7E63 /* EZDetectManager.m in Sources */, diff --git a/Easydict/App/Easydict-Bridging-Header.h b/Easydict/App/Easydict-Bridging-Header.h index 8030b13fa..f98fcafec 100644 --- a/Easydict/App/Easydict-Bridging-Header.h +++ b/Easydict/App/Easydict-Bridging-Header.h @@ -26,3 +26,5 @@ #import "NSString+EZConvenience.h" #import "EZWindowManager.h" #import "NSViewController+EZWindow.h" + +#import "EZOpenAIService.h" diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0940db90f..17f64eef0 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -2285,6 +2285,92 @@ } } }, + "service.configuration.openai.api_key.footer" : { + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "API Key的一些说明或者加入链接" + } + } + } + }, + "service.configuration.openai.api_key.header" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OpenAI API Key" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "OpenAI API Key" + } + } + } + }, + "service.configuration.openai.api_key.prompt" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + } + } + } + }, + "service.configuration.openai.api_key.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "OpenAI API Key" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "OpenAI API Key" + } + } + } + }, + "service.configuration.openai.translation.footer" : { + + }, + "service.configuration.openai.translation.header" : { + + }, + "service.configuration.openai.translation.prompt" : { + + }, + "service.configuration.openai.translation.title" : { + + }, + "service.service_configuration.reset" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Reset" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "重置" + } + } + } + }, "setting_general" : { "localizations" : { "en" : { @@ -2743,8 +2829,37 @@ } } }, + "setting.service.detail.no_configuration %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No configuration for %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@没有可供配置的选项" + } + } + } + }, "setting.service.detail.no_selection" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select a service to show configuration" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择服务以查看配置" + } + } + } }, "setting.tts_service.options.apple" : { "localizations" : { diff --git a/Easydict/Feature/Service/Ali/AliService.swift b/Easydict/Feature/Service/Ali/AliService.swift index 6ba555109..9788fc063 100644 --- a/Easydict/Feature/Service/Ali/AliService.swift +++ b/Easydict/Feature/Service/Ali/AliService.swift @@ -8,6 +8,7 @@ import Alamofire import CryptoKit +import Defaults import Foundation @objc(EZAliService) @@ -76,8 +77,10 @@ class AliService: QueryService { easydict://writeKeyValue?EZAliAccessKeyId= easydict://writeKeyValue?EZAliAccessKeySecret= */ - if let id = UserDefaults.standard.string(forKey: EZAliAccessKeyId), - let secret = UserDefaults.standard.string(forKey: EZAliAccessKeySecret), !id.isEmpty, !secret.isEmpty + if let id = Defaults[.aliAccessKeyId], + let secret = Defaults[.aliAccessKeySecret], + !id.isEmpty, + !secret.isEmpty { requestByAPI(id: id, secret: secret, transType: transType, text: text, from: from, to: to, completion: completion) } else { // use web api diff --git a/Easydict/Feature/Service/Caiyun/CaiyunService.swift b/Easydict/Feature/Service/Caiyun/CaiyunService.swift index 2c6b3dcf3..b36cfbdf8 100644 --- a/Easydict/Feature/Service/Caiyun/CaiyunService.swift +++ b/Easydict/Feature/Service/Caiyun/CaiyunService.swift @@ -7,6 +7,7 @@ // import Alamofire +import Defaults import Foundation @objc(EZCaiyunService) @@ -44,7 +45,7 @@ public final class CaiyunService: QueryService { // easydict://writeKeyValue?EZCaiyunToken= private var token: String { - let token = UserDefaults.standard.string(forKey: EZCaiyunToken) + let token = Defaults[.caiyunToken] if let token, !token.isEmpty { return token } else { diff --git a/Easydict/Feature/Service/OpenAI/EZOpenAIService.m b/Easydict/Feature/Service/OpenAI/EZOpenAIService.m index ad01eff55..1123049e4 100644 --- a/Easydict/Feature/Service/OpenAI/EZOpenAIService.m +++ b/Easydict/Feature/Service/OpenAI/EZOpenAIService.m @@ -28,6 +28,7 @@ @interface EZOpenAIService () @end + @implementation EZOpenAIService - (instancetype)init { diff --git a/Easydict/Feature/Service/Tencent/TencentService.swift b/Easydict/Feature/Service/Tencent/TencentService.swift index cc75b7db0..aaa07b9ab 100644 --- a/Easydict/Feature/Service/Tencent/TencentService.swift +++ b/Easydict/Feature/Service/Tencent/TencentService.swift @@ -7,6 +7,7 @@ // import Alamofire +import Defaults import Foundation @objc(EZTencentService) @@ -64,7 +65,7 @@ public final class TencentService: QueryService { // easydict://writeKeyValue?EZTencentSecretId=xxx private var secretId: String { - let secretId = UserDefaults.standard.string(forKey: EZTencentSecretId) + let secretId = Defaults[.tencentSecretId] if let secretId, !secretId.isEmpty { return secretId } else { @@ -74,7 +75,7 @@ public final class TencentService: QueryService { // easydict://writeKeyValue?EZTencentSecretKey=xxx private var secretKey: String { - let secretKey = UserDefaults.standard.string(forKey: EZTencentSecretKey) + let secretKey = Defaults[.tencentSecretKey] if let secretKey, !secretKey.isEmpty { return secretKey } else { diff --git a/Easydict/NewApp/Configuration/Configuration.swift b/Easydict/NewApp/Configuration/Configuration.swift index 9ecdff830..679c02735 100644 --- a/Easydict/NewApp/Configuration/Configuration.swift +++ b/Easydict/NewApp/Configuration/Configuration.swift @@ -9,6 +9,7 @@ import Defaults import Foundation +// Setting extension Defaults.Keys { // rename `from` static let queryFromLanguage = Key("EZConfiguration_kFromKey", default: .auto) @@ -53,3 +54,37 @@ extension Defaults.Keys { static let appearanceType = Key("EZConfiguration_kApperanceKey", default: .followSystem) static let fontSizeOptionIndex = Key("EZConfiguration_kTranslationControllerFontKey", default: 0) } + +// Service Configuration +extension Defaults.Keys { + // OPENAI + static let openAIAPI = Key("EZOpenAIAPIKey") + static let openAITranslation = Key("EZOpenAITranslationKey") + static let openAIDictionary = Key("EZOpenAIDictionaryKey") + static let openAISentence = Key("EZOpenAISentenceKey") + static let openAIServiceUsageStatus = Key("EZOpenAIServiceUsageStatusKey") + static let openAIDomain = Key("EZOpenAIDomainKey") + static let openAIEndPoint = Key("EZOpenAIEndPointKey") + static let openAIModel = Key("EZOpenAIModelKey") + + // DEEPL + static let deepLAuth = Key("EZDeepLAuthKey") + static let deepLTranslateEndPointKey = Key("EZDeepLTranslateEndPointKey") + + // BING + static let bingCookieKey = Key("EZBingCookieKey") + + // niu + static let niuTransAPIKey = Key("EZNiuTransAPIKey") + + // Caiyun + static let caiyunToken = Key("EZCaiyunToken") + + // tencent + static let tencentSecretId = Key("EZTencentSecretId") + static let tencentSecretKey = Key("EZTencentSecretKey") + + // ALI + static let aliAccessKeyId = Key("EZAliAccessKeyId") + static let aliAccessKeySecret = Key("EZAliAccessKeySecret") +} diff --git a/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift b/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift new file mode 100644 index 000000000..2750da04b --- /dev/null +++ b/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift @@ -0,0 +1,35 @@ +// +// OpenAIService+ConfigurableService.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/14. +// Copyright © 2024 izual. All rights reserved. +// + +import Foundation +import SwiftUI + +@available(macOS 12.0, *) +extension EZOpenAIService: ConfigurableService { + func configurationListItems() -> some View { + ServiceStringConfigurationSection( + textFieldTitleKey: "service.configuration.openai.api_key.header", + headerTitleKey: "service.configuration.openai.api_key.title", + key: .openAIAPI, + prompt: "service.configuration.openai.api_key.prompt", + footer: { + Text("service.configuration.openai.api_key.footer") + } + ) + + ServiceStringConfigurationSection( + textFieldTitleKey: "service.configuration.openai.translation.header", + headerTitleKey: "service.configuration.openai.translation.title", + key: .openAITranslation, + prompt: "service.configuration.openai.translation.prompt", + footer: { + Text("service.configuration.openai.translation.footer") + } + ) + } +} diff --git a/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift new file mode 100644 index 000000000..3bc016300 --- /dev/null +++ b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift @@ -0,0 +1,27 @@ +// +// ConfigurableService.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/14. +// Copyright © 2024 izual. All rights reserved. +// + +import Foundation +import SwiftUI + +protocol ConfigurableService { + associatedtype T: View + + @ViewBuilder + func configurationListItems() -> T +} + +@available(macOS 13.0, *) +extension ConfigurableService { + func configurationView() -> some View { + Form { + configurationListItems() + } + .formStyle(.grouped) + } +} diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift new file mode 100644 index 000000000..f3d6cc833 --- /dev/null +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift @@ -0,0 +1,79 @@ +// +// ServiceConfigurationSection.swift +// Easydict +// +// Created by 戴藏龙 on 2024/1/14. +// Copyright © 2024 izual. All rights reserved. +// + +import Defaults +import SwiftUI + +@available(macOS 12.0, *) +struct ServiceStringConfigurationSection: View { + let textFieldTitleKey: LocalizedStringKey + let headerTitleKey: LocalizedStringKey + let key: Defaults.Key + let prompt: LocalizedStringKey + @ViewBuilder let footer: () -> F + + var body: some View { + ServiceConfigurationSection( + headerTitleKey, + key: key, + field: { value in + let value = Binding.init { + value.wrappedValue ?? "" + } set: { newValue in + value.wrappedValue = newValue + } + TextField(textFieldTitleKey, text: value, prompt: Text(prompt)) + }, + footer: footer + ) + } +} + +@available(macOS 12.0, *) +struct ServiceConfigurationSection: View { + @Default var value: T + + init( + _ titleKey: LocalizedStringKey, + key: Defaults.Key, + @ViewBuilder field: @escaping (Binding) -> V, + footer: (() -> F)? + ) { + self.titleKey = titleKey + _value = .init(key) + self.footer = footer + self.field = field + } + + let field: (Binding) -> V + let footer: (() -> F)? + + let titleKey: LocalizedStringKey + + var body: some View { + Section { + field($value) + } header: { + HStack(alignment: .lastTextBaseline) { + Text(titleKey) + Spacer() + Button("service.service_configuration.reset") { + _value.reset() + } + .buttonStyle(.plain) + .foregroundStyle(Color.accentColor) + .font(.footnote) + } + } footer: { + if let footer { + footer() + .font(.footnote) + } + } + } +} diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 995846756..c147de8b8 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -25,7 +25,16 @@ struct ServiceTab: View { .scrollIndicators(.hidden) Group { if let service = selectedService { - Text(service.name()) + // To provide configuration options for a service, follow these steps + // 1. If the Service is an object of Objc, expose it to Swift. + // 2. Create a new file in the Utility - Extensions - QueryService+ConfigurableService, + // 3. referring to OpenAIService+ConfigurableService, `extension` the Service as `ConfigurableService` to provide the configuration items. + if let service = service as? (any ConfigurableService) { + AnyView(service.configurationView()) + } else { + // No configuration for service xxx + Text("setting.service.detail.no_configuration \(service.name())") + } } else { VStack { Text("setting.service.detail.no_selection") @@ -162,15 +171,3 @@ private struct WindowTypePicker: View { .pickerStyle(.segmented) } } - -private struct SplitView: View { - @ViewBuilder let sidebar: () -> S - @ViewBuilder let content: () -> C - - var body: some View { - HStack { - sidebar() - content() - } - } -} From 9bcbdbef24a8335970fd49bda073de0442c2132d Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:40:37 -0800 Subject: [PATCH 03/17] add comment for ServiceStringConfigurationSection --- .../ServiceConfiguration/ServiceConfigurationSection.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift index f3d6cc833..42c59d0ee 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceConfiguration/ServiceConfigurationSection.swift @@ -11,10 +11,15 @@ import SwiftUI @available(macOS 12.0, *) struct ServiceStringConfigurationSection: View { + /// Title of text field let textFieldTitleKey: LocalizedStringKey + /// Header of section. If there is no need to add an header, just leave empty string let headerTitleKey: LocalizedStringKey + /// Defaults key for configuration. Please refer to `Configuration` - `Configuration` let key: Defaults.Key + /// Prompt of text field let prompt: LocalizedStringKey + /// Footer of section. Add comments, footnotes or links to describe the field. @ViewBuilder let footer: () -> F var body: some View { From 0297aa101785f90253600117e3479ccf7b11f977 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:44:09 -0800 Subject: [PATCH 04/17] add comments for ConfigurableService --- Easydict/NewApp/Utility/Protocol/ConfigurableService.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift index 3bc016300..6d3afe4de 100644 --- a/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift +++ b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift @@ -9,9 +9,11 @@ import Foundation import SwiftUI +/// A service can provide configuration view in setting protocol ConfigurableService { associatedtype T: View - + + /// Items in Configuration Form. Use ServiceStringConfigurationSection or other customize view. @ViewBuilder func configurationListItems() -> T } From 4c04c97341c5ea63c86ddc3dff975370d9c79585 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 17:46:00 -0800 Subject: [PATCH 05/17] rename openAIAPI with openAIAPIKey --- Easydict/NewApp/Configuration/Configuration.swift | 2 +- .../OpenAIService+ConfigurableService.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Easydict/NewApp/Configuration/Configuration.swift b/Easydict/NewApp/Configuration/Configuration.swift index 679c02735..03a0932ad 100644 --- a/Easydict/NewApp/Configuration/Configuration.swift +++ b/Easydict/NewApp/Configuration/Configuration.swift @@ -58,7 +58,7 @@ extension Defaults.Keys { // Service Configuration extension Defaults.Keys { // OPENAI - static let openAIAPI = Key("EZOpenAIAPIKey") + static let openAIAPIKey = Key("EZOpenAIAPIKey") static let openAITranslation = Key("EZOpenAITranslationKey") static let openAIDictionary = Key("EZOpenAIDictionaryKey") static let openAISentence = Key("EZOpenAISentenceKey") diff --git a/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift b/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift index 2750da04b..960cada25 100644 --- a/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift +++ b/Easydict/NewApp/Utility/Extensions/QueryService+ConfigurableService/OpenAIService+ConfigurableService.swift @@ -15,7 +15,7 @@ extension EZOpenAIService: ConfigurableService { ServiceStringConfigurationSection( textFieldTitleKey: "service.configuration.openai.api_key.header", headerTitleKey: "service.configuration.openai.api_key.title", - key: .openAIAPI, + key: .openAIAPIKey, prompt: "service.configuration.openai.api_key.prompt", footer: { Text("service.configuration.openai.api_key.footer") From 9ba1a40aa26a91db2341e83fb77d505b7269a9d3 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 20:51:53 -0800 Subject: [PATCH 06/17] UI optimization --- .../xcshareddata/xcschemes/Easydict.xcscheme | 1 + Easydict/App/Localizable.xcstrings | 3 - .../Protocol/ConfigurableService.swift | 2 +- .../View/SettingView/Tabs/ServiceTab.swift | 66 ++++++++++++++----- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme b/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme index 7eabe063b..62e746048 100644 --- a/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme +++ b/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme @@ -56,6 +56,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "en" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 17f64eef0..59d2bcf35 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1,9 +1,6 @@ { "sourceLanguage" : "en", "strings" : { - "" : { - - }, "about" : { "comment" : "about", "localizations" : { diff --git a/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift index 6d3afe4de..058151a5c 100644 --- a/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift +++ b/Easydict/NewApp/Utility/Protocol/ConfigurableService.swift @@ -12,7 +12,7 @@ import SwiftUI /// A service can provide configuration view in setting protocol ConfigurableService { associatedtype T: View - + /// Items in Configuration Form. Use ServiceStringConfigurationSection or other customize view. @ViewBuilder func configurationListItems() -> T diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index c147de8b8..2b017cbc6 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -16,13 +16,22 @@ struct ServiceTab: View { var body: some View { HStack { - List(selection: $selectedService) { + VStack { WindowTypePicker(windowType: $windowType) - ServiceItems(windowType: windowType) + .padding() + List { + ServiceItems(windowType: windowType, selectedService: $selectedService) + } + .scrollContentBackground(.hidden) + .listItemTint(Color("service_cell_highlight")) + .listStyle(.plain) + .scrollIndicators(.never) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .background(.white, in: RoundedRectangle(cornerRadius: 10)) + .padding(.bottom) + .padding(.horizontal) } - .frame(maxWidth: 300) - .listStyle(.sidebar) - .scrollIndicators(.hidden) + .background(Color(nsColor: .windowBackgroundColor)) Group { if let service = selectedService { // To provide configuration options for a service, follow these steps @@ -41,7 +50,7 @@ struct ServiceTab: View { } } } - .frame(width: 500) + .frame(minWidth: 500) } .onChange(of: windowType) { _ in selectedService = nil @@ -52,6 +61,7 @@ struct ServiceTab: View { @available(macOS 13.0, *) private struct ServiceItems: View { let windowType: EZWindowType + @Binding var selectedService: QueryService? private var services: [QueryService] { EZLocalStorage.shared().allServices(windowType) @@ -65,7 +75,7 @@ private struct ServiceItems: View { var body: some View { ForEach(servicesWithID, id: \.1) { service, _ in - ServiceItemView(service: service, windowType: windowType) + ServiceItemView(service: service, windowType: windowType, selectedService: $selectedService) .tag(service) } .onMove(perform: onServiceItemMove) @@ -102,21 +112,40 @@ private struct ServiceItems: View { private struct ServiceItemView: View { @StateObject private var service: QueryServiceWrapper - init(service: QueryService, windowType: EZWindowType) { + @Binding private var selectedService: QueryService? + + init(service: QueryService, windowType: EZWindowType, selectedService: Binding) { _service = .init(wrappedValue: .init(queryService: service, windowType: windowType)) + _selectedService = selectedService } var body: some View { Toggle(isOn: $service.enabled) { - Label { - Text(service.inner.name()) - } icon: { + HStack { Image(service.inner.serviceType().rawValue) .resizable() .scaledToFit() + .frame(width: 20.0, height: 20.0) + Text(service.inner.name()) + } + .onTapGesture { + selectedService = service.inner } } + .padding(4.0) .toggleStyle(.switch) + .controlSize(.small) + .listRowSeparator(.hidden) + .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : Color.clear) + .listRowInsets(.init()) + .padding(10) + .background { + if selectedService != service.inner { + Color.white.onTapGesture { + selectedService = service.inner + } + } + } } private class QueryServiceWrapper: ObservableObject { @@ -162,12 +191,17 @@ private struct WindowTypePicker: View { @Binding var windowType: EZWindowType var body: some View { - Picker("", selection: $windowType) { - ForEach([EZWindowType]([.mini, .fixed, .main]), id: \.rawValue) { windowType in - Text(windowType.localizedStringResource) - .tag(windowType) + HStack { + Picker(selection: $windowType) { + ForEach([EZWindowType]([.mini, .fixed, .main]), id: \.rawValue) { windowType in + Text(windowType.localizedStringResource) + .tag(windowType) + } + } label: { + EmptyView() } + .labelsHidden() + .pickerStyle(.segmented) } - .pickerStyle(.segmented) } } From 53ccbd46d7b0c023ad09417bc0b87709b7342710 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 20:54:37 -0800 Subject: [PATCH 07/17] revert schema --- Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme | 1 - 1 file changed, 1 deletion(-) diff --git a/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme b/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme index 62e746048..7eabe063b 100644 --- a/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme +++ b/Easydict.xcodeproj/xcshareddata/xcschemes/Easydict.xcscheme @@ -56,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "en" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" From cab5d007e06a1ba458034b1ed82e342019e82d7e Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 21:12:59 -0800 Subject: [PATCH 08/17] fix: service setting in dark mode --- .../View/SettingView/Tabs/ServiceTab.swift | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 2b017cbc6..296d6147e 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -14,6 +14,16 @@ struct ServiceTab: View { @State private var windowType = EZWindowType.mini @State private var selectedService: QueryService? + @Environment(\.colorScheme) private var colorScheme + + var bgColor: Color { + Color(nsColor: colorScheme == .light ? .windowBackgroundColor : .controlBackgroundColor) + } + + var tableColor: Color { + Color(nsColor: colorScheme == .light ? .ez_tableRowViewBgLight() : .ez_tableRowViewBgDark()) + } + var body: some View { HStack { VStack { @@ -23,15 +33,14 @@ struct ServiceTab: View { ServiceItems(windowType: windowType, selectedService: $selectedService) } .scrollContentBackground(.hidden) - .listItemTint(Color("service_cell_highlight")) .listStyle(.plain) .scrollIndicators(.never) .clipShape(RoundedRectangle(cornerRadius: 10)) - .background(.white, in: RoundedRectangle(cornerRadius: 10)) + .background(bgColor, in: RoundedRectangle(cornerRadius: 10)) .padding(.bottom) .padding(.horizontal) } - .background(Color(nsColor: .windowBackgroundColor)) + .background(bgColor) Group { if let service = selectedService { // To provide configuration options for a service, follow these steps @@ -136,12 +145,12 @@ private struct ServiceItemView: View { .toggleStyle(.switch) .controlSize(.small) .listRowSeparator(.hidden) - .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : Color.clear) + .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : tableColor) .listRowInsets(.init()) .padding(10) .background { if selectedService != service.inner { - Color.white.onTapGesture { + tableColor.onTapGesture { selectedService = service.inner } } @@ -184,6 +193,12 @@ private struct ServiceItemView: View { NotificationCenter.default.post(notification) } } + + @Environment(\.colorScheme) private var colorScheme + + private var tableColor: Color { + Color(nsColor: colorScheme == .light ? .ez_tableRowViewBgLight() : .ez_tableRowViewBgDark()) + } } @available(macOS 13, *) From 5b31ce41b73882e33bc43252846c7ebb3fd3d0de Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Sun, 14 Jan 2024 21:56:30 -0800 Subject: [PATCH 09/17] fix: cannot move and scroll position error --- .../View/SettingView/Tabs/ServiceTab.swift | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 296d6147e..80e2a6e3f 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -61,9 +61,6 @@ struct ServiceTab: View { } .frame(minWidth: 500) } - .onChange(of: windowType) { _ in - selectedService = nil - } } } @@ -78,7 +75,7 @@ private struct ServiceItems: View { private var servicesWithID: [(QueryService, String)] { services.map { service in - (service, "\(service.name())\(windowType)") + (service, service.name()) } } @@ -137,22 +134,17 @@ private struct ServiceItemView: View { .frame(width: 20.0, height: 20.0) Text(service.inner.name()) } - .onTapGesture { - selectedService = service.inner - } } .padding(4.0) .toggleStyle(.switch) .controlSize(.small) .listRowSeparator(.hidden) - .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : tableColor) .listRowInsets(.init()) .padding(10) - .background { - if selectedService != service.inner { - tableColor.onTapGesture { - selectedService = service.inner - } + .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : tableColor) + .overlay { + TapHandler { + selectedService = service.inner } } } From 2e6db539fb8ff74c9507695e059845bfccd1ec55 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:20:54 -0800 Subject: [PATCH 10/17] introduce a view model in service tab --- .../View/SettingView/Tabs/ServiceTab.swift | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 80e2a6e3f..6a01460ee 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -11,8 +11,7 @@ import SwiftUI @available(macOS 13, *) struct ServiceTab: View { - @State private var windowType = EZWindowType.mini - @State private var selectedService: QueryService? + @StateObject private var viewModel: ServiceTabViewModel = .init() @Environment(\.colorScheme) private var colorScheme @@ -27,10 +26,10 @@ struct ServiceTab: View { var body: some View { HStack { VStack { - WindowTypePicker(windowType: $windowType) + WindowTypePicker(windowType: $viewModel.windowType) .padding() List { - ServiceItems(windowType: windowType, selectedService: $selectedService) + ServiceItems() } .scrollContentBackground(.hidden) .listStyle(.plain) @@ -42,7 +41,7 @@ struct ServiceTab: View { } .background(bgColor) Group { - if let service = selectedService { + if let service = viewModel.selectedService { // To provide configuration options for a service, follow these steps // 1. If the Service is an object of Objc, expose it to Swift. // 2. Create a new file in the Utility - Extensions - QueryService+ConfigurableService, @@ -61,33 +60,33 @@ struct ServiceTab: View { } .frame(minWidth: 500) } + .environmentObject(viewModel) } } -@available(macOS 13.0, *) -private struct ServiceItems: View { - let windowType: EZWindowType - @Binding var selectedService: QueryService? - - private var services: [QueryService] { - EZLocalStorage.shared().allServices(windowType) +private class ServiceTabViewModel: ObservableObject { + @Published var windowType = EZWindowType.mini { + didSet { + if oldValue != windowType { + updateServices() + } + } } - private var servicesWithID: [(QueryService, String)] { - services.map { service in - (service, service.name()) - } + @Published var selectedService: QueryService? + + lazy var services: [QueryService] = EZLocalStorage.shared().allServices(windowType) + + func updateServices() { + services = getServices() } - var body: some View { - ForEach(servicesWithID, id: \.1) { service, _ in - ServiceItemView(service: service, windowType: windowType, selectedService: $selectedService) - .tag(service) - } - .onMove(perform: onServiceItemMove) + func getServices() -> [QueryService] { + EZLocalStorage.shared().allServices(windowType) } - private func onServiceItemMove(fromOffsets: IndexSet, toOffset: Int) { + func onServiceItemMove(fromOffsets: IndexSet, toOffset: Int) { + guard fromOffsets.first != toOffset else { return } var services = services services.move(fromOffsets: fromOffsets, toOffset: toOffset) @@ -100,29 +99,44 @@ private struct ServiceItems: View { postUpdateServiceNotification() - // Trigger rerender to update view with new position - refresh.objectWillChange.send() + updateServices() + + objectWillChange.send() } - private func postUpdateServiceNotification() { + func postUpdateServiceNotification() { let userInfo: [String: Any] = [EZWindowTypeKey: windowType.rawValue] let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) NotificationCenter.default.post(notification) } +} + +@available(macOS 13.0, *) +private struct ServiceItems: View { + @EnvironmentObject private var viewModel: ServiceTabViewModel + + private var servicesWithID: [(QueryService, String)] { + viewModel.services.map { service in + (service, service.name()) + } + } - private class RefreshObservableObject: ObservableObject {} - @StateObject private var refresh: RefreshObservableObject = .init() + var body: some View { + ForEach(servicesWithID, id: \.1) { service, _ in + ServiceItemView(service: service, windowType: viewModel.windowType) + .tag(service) + } + .onMove(perform: viewModel.onServiceItemMove) + } } @available(macOS 13.0, *) private struct ServiceItemView: View { @StateObject private var service: QueryServiceWrapper + @EnvironmentObject private var viewModel: ServiceTabViewModel - @Binding private var selectedService: QueryService? - - init(service: QueryService, windowType: EZWindowType, selectedService: Binding) { + init(service: QueryService, windowType: EZWindowType) { _service = .init(wrappedValue: .init(queryService: service, windowType: windowType)) - _selectedService = selectedService } var body: some View { @@ -141,10 +155,10 @@ private struct ServiceItemView: View { .listRowSeparator(.hidden) .listRowInsets(.init()) .padding(10) - .listRowBackground(selectedService == service.inner ? Color("service_cell_highlight") : tableColor) + .listRowBackground(viewModel.selectedService == service.inner ? Color("service_cell_highlight") : tableColor) .overlay { TapHandler { - selectedService = service.inner + viewModel.selectedService = service.inner } } } From 1e46822104b29c48a5bed35ddcc2c631149dc015 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 15 Jan 2024 14:33:24 -0800 Subject: [PATCH 11/17] delete unused code --- Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 6a01460ee..dc7cc88b8 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -86,7 +86,6 @@ private class ServiceTabViewModel: ObservableObject { } func onServiceItemMove(fromOffsets: IndexSet, toOffset: Int) { - guard fromOffsets.first != toOffset else { return } var services = services services.move(fromOffsets: fromOffsets, toOffset: toOffset) From ffd53ec56582de46ac2c7931da85227d7d736706 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Tue, 16 Jan 2024 12:45:21 +0800 Subject: [PATCH 12/17] fix: do not post update notification if service enabled is not changed --- Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index dc7cc88b8..a975f36b8 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -170,11 +170,13 @@ private struct ServiceItemView: View { get { inner.enabled } set { - inner.enabled = newValue - if newValue { - inner.enabledQuery = newValue + if inner.enabled != newValue { + inner.enabled = newValue + if newValue { + inner.enabledQuery = newValue + } + save() } - save() } } From 5d6fc3e42ac39938564f2f1932a094f4e05d8e12 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 15 Jan 2024 21:37:26 -0800 Subject: [PATCH 13/17] fix: resizing windows animation in service view --- .../View/SettingView/Tabs/ServiceTab.swift | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index a975f36b8..37727f8b9 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -49,16 +49,22 @@ struct ServiceTab: View { if let service = service as? (any ConfigurableService) { AnyView(service.configurationView()) } else { - // No configuration for service xxx - Text("setting.service.detail.no_configuration \(service.name())") + HStack { + Spacer() + // No configuration for service xxx + Text("setting.service.detail.no_configuration \(service.name())") + Spacer() + } } } else { - VStack { + HStack { + Spacer() Text("setting.service.detail.no_selection") + Spacer() } } } - .frame(minWidth: 500) + .layoutPriority(1) } .environmentObject(viewModel) } @@ -96,7 +102,7 @@ private class ServiceTabViewModel: ObservableObject { EZLocalStorage.shared().setAllServiceTypes(serviceTypes, windowType: windowType) - postUpdateServiceNotification() +// postUpdateServiceNotification() updateServices() @@ -146,6 +152,8 @@ private struct ServiceItemView: View { .scaledToFit() .frame(width: 20.0, height: 20.0) Text(service.inner.name()) + .lineLimit(1) + .fixedSize() } } .padding(4.0) @@ -191,7 +199,7 @@ private struct ServiceItemView: View { private func save() { EZLocalStorage.shared().setService(inner, windowType: windowType) - postUpdateServiceNotification() +// postUpdateServiceNotification() } private func postUpdateServiceNotification() { From ce30b3e9e759d8c08345b3c228de76d1dcab5e06 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 15 Jan 2024 21:46:28 -0800 Subject: [PATCH 14/17] small refactor on viewmodels --- .../View/SettingView/Tabs/ServiceTab.swift | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 37727f8b9..548029fd0 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -81,7 +81,7 @@ private class ServiceTabViewModel: ObservableObject { @Published var selectedService: QueryService? - lazy var services: [QueryService] = EZLocalStorage.shared().allServices(windowType) + @Published private(set) var services: [QueryService] = EZLocalStorage.shared().allServices(.mini) func updateServices() { services = getServices() @@ -102,11 +102,9 @@ private class ServiceTabViewModel: ObservableObject { EZLocalStorage.shared().setAllServiceTypes(serviceTypes, windowType: windowType) -// postUpdateServiceNotification() + postUpdateServiceNotification() updateServices() - - objectWillChange.send() } func postUpdateServiceNotification() { @@ -178,13 +176,12 @@ private struct ServiceItemView: View { get { inner.enabled } set { - if inner.enabled != newValue { - inner.enabled = newValue - if newValue { - inner.enabledQuery = newValue - } - save() + guard inner.enabled != newValue else { return } + inner.enabled = newValue + if newValue { + inner.enabledQuery = newValue } + save() } } @@ -193,13 +190,11 @@ private struct ServiceItemView: View { init(queryService: QueryService, windowType: EZWindowType) { inner = queryService self.windowType = windowType - - enabled = queryService.enabled } private func save() { EZLocalStorage.shared().setService(inner, windowType: windowType) -// postUpdateServiceNotification() + postUpdateServiceNotification() } private func postUpdateServiceNotification() { From 5cba85f3f0788e8e5cc3b561785c05ce896440e6 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:58:40 -0800 Subject: [PATCH 15/17] refactor: ServiceItems --- .../View/SettingView/Tabs/ServiceTab.swift | 65 ++++++------------- 1 file changed, 20 insertions(+), 45 deletions(-) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 548029fd0..6b4262522 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -126,7 +126,7 @@ private struct ServiceItems: View { var body: some View { ForEach(servicesWithID, id: \.1) { service, _ in - ServiceItemView(service: service, windowType: viewModel.windowType) + ServiceItemView(service: service) .tag(service) } .onMove(perform: viewModel.onServiceItemMove) @@ -135,21 +135,32 @@ private struct ServiceItems: View { @available(macOS 13.0, *) private struct ServiceItemView: View { - @StateObject private var service: QueryServiceWrapper + let service: QueryService + @EnvironmentObject private var viewModel: ServiceTabViewModel - init(service: QueryService, windowType: EZWindowType) { - _service = .init(wrappedValue: .init(queryService: service, windowType: windowType)) + private var enabled: Binding { + .init { + service.enabled + } set: { newValue in + guard service.enabled != newValue else { return } + service.enabled = newValue + if newValue { + service.enabledQuery = newValue + } + EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() + } } var body: some View { - Toggle(isOn: $service.enabled) { + Toggle(isOn: enabled) { HStack { - Image(service.inner.serviceType().rawValue) + Image(service.serviceType().rawValue) .resizable() .scaledToFit() .frame(width: 20.0, height: 20.0) - Text(service.inner.name()) + Text(service.name()) .lineLimit(1) .fixedSize() } @@ -160,48 +171,12 @@ private struct ServiceItemView: View { .listRowSeparator(.hidden) .listRowInsets(.init()) .padding(10) - .listRowBackground(viewModel.selectedService == service.inner ? Color("service_cell_highlight") : tableColor) + .listRowBackground(viewModel.selectedService == service ? Color("service_cell_highlight") : tableColor) .overlay { TapHandler { - viewModel.selectedService = service.inner - } - } - } - - private class QueryServiceWrapper: ObservableObject { - let windowType: EZWindowType - var inner: QueryService - - var enabled: Bool { - get { - inner.enabled - } set { - guard inner.enabled != newValue else { return } - inner.enabled = newValue - if newValue { - inner.enabledQuery = newValue - } - save() + viewModel.selectedService = service } } - - private var cancellables: Set = [] - - init(queryService: QueryService, windowType: EZWindowType) { - inner = queryService - self.windowType = windowType - } - - private func save() { - EZLocalStorage.shared().setService(inner, windowType: windowType) - postUpdateServiceNotification() - } - - private func postUpdateServiceNotification() { - let userInfo: [String: Any] = [EZWindowTypeKey: windowType.rawValue] - let notification = Notification(name: .serviceHasUpdated, object: nil, userInfo: userInfo) - NotificationCenter.default.post(notification) - } } @Environment(\.colorScheme) private var colorScheme From df37873b06cdafb8d4bfaa58a81efec1af9d109d Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:59:42 -0800 Subject: [PATCH 16/17] reset selection after window type changes --- Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift index 6b4262522..5399f9af7 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/ServiceTab.swift @@ -75,6 +75,7 @@ private class ServiceTabViewModel: ObservableObject { didSet { if oldValue != windowType { updateServices() + selectedService = nil } } } From 50bed6ae8c5c585faa1bfe2e1f399ae1954d1046 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Wed, 17 Jan 2024 09:24:44 +0800 Subject: [PATCH 17/17] perf: update Localizable.xcstrings --- Easydict/App/Localizable.xcstrings | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 0d94d0f14..2114e3a63 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1,16 +1,6 @@ { "sourceLanguage" : "en", "strings" : { - "" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "" - } - } - } - }, "about" : { "comment" : "about", "localizations" : { @@ -505,7 +495,7 @@ }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", + "state" : "translated", "value" : "[Beta] SwiftUI App模式" } } @@ -1827,7 +1817,14 @@ } }, "none_window" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "" + } + } + } }, "ocr_result_is_empty" : { "localizations" : { @@ -3323,7 +3320,14 @@ } }, "unknown_option" : { - + "localizations" : { + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "" + } + } + } }, "unpin" : { "localizations" : {