From 36a5077c932b1c34527366443649cc53feb8b694 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Wed, 6 Nov 2024 14:06:31 +0800 Subject: [PATCH 01/10] feat: add validate when user open service --- .../SettingView/Tabs/TabView/ServiceTab.swift | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 40a1b4030..b7e4a04ba 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -222,12 +222,15 @@ private struct ServiceItemView: View { } .onReceive(serviceItemViewModel.$isEnable) { newValue in guard service.enabled != newValue else { return } - service.enabled = newValue + // open service if newValue { - service.enabledQuery = newValue + // validate service enabled + handleValidateServiceEnable(isEnable: true) + } else { // close service + service.enabled = false + EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() } - EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) - viewModel.postUpdateServiceNotification() } .toggleStyle(.switch) .controlSize(.small) @@ -236,6 +239,32 @@ private struct ServiceItemView: View { .padding(.horizontal, 8) .padding(.vertical, 12) } + + func handleValidateServiceEnable(isEnable: Bool) { + + service.validate { result, error in + // Validate existence error + guard error == nil else { + // close switch + serviceItemViewModel.isEnable = false + logInfo("\(service.serviceType().rawValue) validate error") + return + } + + // If error is nil but result text is also empty, we should report error. + guard let translatedText = result.translatedText, !translatedText.isEmpty else { + serviceItemViewModel.isEnable = false + logInfo("\(service.serviceType().rawValue) validate translated text is empty") + return + } + + // service enabel open the switch and toggle enable status + service.enabled = isEnable + service.enabledQuery = isEnable + EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() + } + } // MARK: Private From 525729e1640b9c3344a482d83fc78f15e8ba0827 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Wed, 6 Nov 2024 15:12:04 +0800 Subject: [PATCH 02/10] feat: move handle validate func to view model --- .../SettingView/Tabs/TabView/ServiceTab.swift | 58 ++++++++++--------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index b7e4a04ba..69e6d509d 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -170,6 +170,34 @@ private class ServiceItemViewModel: ObservableObject { ) } + // MARK: Public + + public func handleValidateServiceEnable(isEnable: Bool) { + service.validate { [weak self] result, error in + guard let self = self else { return } + // Validate existence error + guard error == nil else { + // close switch + self.isEnable = false + logInfo("\(self.service.serviceType().rawValue) validate error") + return + } + + // If error is nil but result text is also empty, we should report error. + guard let translatedText = result.translatedText, !translatedText.isEmpty else { + self.isEnable = false + logInfo("\(self.service.serviceType().rawValue) validate translated text is empty") + return + } + + // service enabel open the switch and toggle enable status + self.service.enabled = isEnable + self.service.enabledQuery = isEnable + EZLocalStorage.shared().setService(self.service, windowType: self.serviceTabViewModel.windowType) + self.serviceTabViewModel.postUpdateServiceNotification() + } + } + // MARK: Internal let service: QueryService @@ -181,6 +209,8 @@ private class ServiceItemViewModel: ObservableObject { private var cancellables: [AnyCancellable] = [] + @EnvironmentObject private var serviceTabViewModel: ServiceTabViewModel + private var serviceUpdatePublisher: AnyPublisher { NotificationCenter.default .publisher(for: .serviceHasUpdated) @@ -225,7 +255,7 @@ private struct ServiceItemView: View { // open service if newValue { // validate service enabled - handleValidateServiceEnable(isEnable: true) + serviceItemViewModel.handleValidateServiceEnable(isEnable: true) } else { // close service service.enabled = false EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) @@ -239,32 +269,6 @@ private struct ServiceItemView: View { .padding(.horizontal, 8) .padding(.vertical, 12) } - - func handleValidateServiceEnable(isEnable: Bool) { - - service.validate { result, error in - // Validate existence error - guard error == nil else { - // close switch - serviceItemViewModel.isEnable = false - logInfo("\(service.serviceType().rawValue) validate error") - return - } - - // If error is nil but result text is also empty, we should report error. - guard let translatedText = result.translatedText, !translatedText.isEmpty else { - serviceItemViewModel.isEnable = false - logInfo("\(service.serviceType().rawValue) validate translated text is empty") - return - } - - // service enabel open the switch and toggle enable status - service.enabled = isEnable - service.enabledQuery = isEnable - EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) - viewModel.postUpdateServiceNotification() - } - } // MARK: Private From 9363fa43876df87a93c6c932526f567b17b7adc9 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Tue, 12 Nov 2024 14:04:22 +0800 Subject: [PATCH 03/10] pref: prefect toggle for services --- .../SettingView/Tabs/TabView/ServiceTab.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 69e6d509d..bc51fc9f3 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -172,29 +172,29 @@ private class ServiceItemViewModel: ObservableObject { // MARK: Public - public func handleValidateServiceEnable(isEnable: Bool) { + public func handleValidateServiceEnable(isEnable: Bool, viewModel: ServiceTabViewModel) { service.validate { [weak self] result, error in - guard let self = self else { return } + guard let weakSelf = self else { return } // Validate existence error guard error == nil else { // close switch - self.isEnable = false - logInfo("\(self.service.serviceType().rawValue) validate error") + weakSelf.isEnable = false + logInfo("\(weakSelf.service.serviceType().rawValue) validate error") return } // If error is nil but result text is also empty, we should report error. guard let translatedText = result.translatedText, !translatedText.isEmpty else { - self.isEnable = false - logInfo("\(self.service.serviceType().rawValue) validate translated text is empty") + weakSelf.isEnable = false + logInfo("\(weakSelf.service.serviceType().rawValue) validate translated text is empty") return } // service enabel open the switch and toggle enable status - self.service.enabled = isEnable - self.service.enabledQuery = isEnable - EZLocalStorage.shared().setService(self.service, windowType: self.serviceTabViewModel.windowType) - self.serviceTabViewModel.postUpdateServiceNotification() + weakSelf.service.enabled = isEnable + weakSelf.service.enabledQuery = isEnable + EZLocalStorage.shared().setService(weakSelf.service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() } } @@ -255,7 +255,7 @@ private struct ServiceItemView: View { // open service if newValue { // validate service enabled - serviceItemViewModel.handleValidateServiceEnable(isEnable: true) + serviceItemViewModel.handleValidateServiceEnable(isEnable: true, viewModel: viewModel) } else { // close service service.enabled = false EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) From f66289ccfedd2e8008c0c486eb2635f4c94d5b49 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Tue, 12 Nov 2024 15:18:47 +0800 Subject: [PATCH 04/10] chore: add some feature for pre validate services --- .../SettingView/Tabs/TabView/ServiceTab.swift | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index bc51fc9f3..7a409962a 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -31,6 +31,12 @@ struct ServiceTab: View { .onReceive(serviceHasUpdatedNotification) { _ in viewModel.updateServices() } + + if viewModel.isValidating { + ProgressView() + .controlSize(.small) + .progressViewStyle(.circular) + } } Group { @@ -85,6 +91,8 @@ private class ServiceTabViewModel: ObservableObject { // MARK: Internal + @Published var isValidating: Bool = false + @Published var selectedService: QueryService? @Published private(set) var services: [QueryService] @@ -174,27 +182,30 @@ private class ServiceItemViewModel: ObservableObject { public func handleValidateServiceEnable(isEnable: Bool, viewModel: ServiceTabViewModel) { service.validate { [weak self] result, error in - guard let weakSelf = self else { return } - // Validate existence error - guard error == nil else { - // close switch - weakSelf.isEnable = false - logInfo("\(weakSelf.service.serviceType().rawValue) validate error") - return - } + // check into main thread + DispatchQueue.main.async { + guard let weakSelf = self else { return } + // Validate existence error + guard error == nil else { + // close switch + weakSelf.isEnable = false + logInfo("\(weakSelf.service.serviceType().rawValue) validate error") + return + } - // If error is nil but result text is also empty, we should report error. - guard let translatedText = result.translatedText, !translatedText.isEmpty else { - weakSelf.isEnable = false - logInfo("\(weakSelf.service.serviceType().rawValue) validate translated text is empty") - return - } + // If error is nil but result text is also empty, we should report error. + guard let translatedText = result.translatedText, !translatedText.isEmpty else { + weakSelf.isEnable = false + logInfo("\(weakSelf.service.serviceType().rawValue) validate translated text is empty") + return + } - // service enabel open the switch and toggle enable status - weakSelf.service.enabled = isEnable - weakSelf.service.enabledQuery = isEnable - EZLocalStorage.shared().setService(weakSelf.service, windowType: viewModel.windowType) - viewModel.postUpdateServiceNotification() + // service enabel open the switch and toggle enable status + weakSelf.service.enabled = isEnable + weakSelf.service.enabledQuery = isEnable + EZLocalStorage.shared().setService(weakSelf.service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() + } } } From 9d5f3da1d3d7ef88341e0094f2fa9ccca7f23377 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Mon, 18 Nov 2024 11:26:39 +0800 Subject: [PATCH 05/10] prefect: prefect pre validate service --- .../SettingView/Tabs/TabView/ServiceTab.swift | 129 ++++++++++-------- 1 file changed, 74 insertions(+), 55 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 7a409962a..fc7bb8ca0 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -15,58 +15,65 @@ struct ServiceTab: View { // MARK: Internal var body: some View { - HStack(alignment: .top, spacing: 0) { - VStack { - WindowTypePicker(windowType: $viewModel.windowType) - .padding() - List(selection: $viewModel.selectedService) { - ServiceItems() - } - .listStyle(.plain) - .scrollIndicators(.never) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .padding(.bottom) - .padding(.horizontal) - .frame(minWidth: 260) - .onReceive(serviceHasUpdatedNotification) { _ in - viewModel.updateServices() - } - - if viewModel.isValidating { - ProgressView() - .controlSize(.small) - .progressViewStyle(.circular) - } - } - - Group { - if let service = viewModel.selectedService { - VStack(alignment: .leading) { - Button("setting.service.back") { - viewModel.selectedService = nil - } + ZStack { + HStack(alignment: .top, spacing: 0) { + VStack { + WindowTypePicker(windowType: $viewModel.windowType) .padding() + List(selection: $viewModel.selectedService) { + ServiceItems() + } + .listStyle(.plain) + .scrollIndicators(.never) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .padding(.bottom) + .padding(.horizontal) + .frame(minWidth: 260) + .onReceive(serviceHasUpdatedNotification) { _ in + viewModel.updateServices() + } + } - if let view = service.configurationListItems() as? (any View) { - Form { - AnyView(view) + Group { + if let service = viewModel.selectedService { + VStack(alignment: .leading) { + Button("setting.service.back") { + viewModel.selectedService = nil } - .formStyle(.grouped) - } else { - Spacer() - HStack { + .padding() + + if let view = service.configurationListItems() as? (any View) { + Form { + AnyView(view) + } + .formStyle(.grouped) + } else { Spacer() - Text("setting.service.detail.no_configuration \(service.name())") + HStack { + Spacer() + Text("setting.service.detail.no_configuration \(service.name())") + Spacer() + } Spacer() } - Spacer() } + } else { + WindowConfigurationView(windowType: viewModel.windowType) } - } else { - WindowConfigurationView(windowType: viewModel.windowType) } + .layoutPriority(1) + } + + if viewModel.isValidating { + VStack { + ProgressView() + .controlSize(.small) + .progressViewStyle(.circular) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black.opacity(0.4)) + .zIndex(1) } - .layoutPriority(1) } .environmentObject(viewModel) } @@ -180,31 +187,43 @@ private class ServiceItemViewModel: ObservableObject { // MARK: Public - public func handleValidateServiceEnable(isEnable: Bool, viewModel: ServiceTabViewModel) { - service.validate { [weak self] result, error in + public func handleValidateServiceEnable(viewModel: ServiceTabViewModel) { + // filter + if service.serviceType() == .appleDictionary || + service.serviceType() == .apple { + service.enabled = true + service.enabledQuery = true + EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() + return + } + + viewModel.isValidating = true + service.validate { [self] result, error in // check into main thread DispatchQueue.main.async { - guard let weakSelf = self else { return } // Validate existence error - guard error == nil else { - // close switch - weakSelf.isEnable = false - logInfo("\(weakSelf.service.serviceType().rawValue) validate error") + if let error = error { + self.isEnable = false + logInfo("\(self.service.serviceType().rawValue) validate error: \(error)") + viewModel.isValidating = false return } // If error is nil but result text is also empty, we should report error. guard let translatedText = result.translatedText, !translatedText.isEmpty else { - weakSelf.isEnable = false - logInfo("\(weakSelf.service.serviceType().rawValue) validate translated text is empty") + self.isEnable = false + logInfo("\(self.service.serviceType().rawValue) validate translated text is empty") + viewModel.isValidating = false return } // service enabel open the switch and toggle enable status - weakSelf.service.enabled = isEnable - weakSelf.service.enabledQuery = isEnable - EZLocalStorage.shared().setService(weakSelf.service, windowType: viewModel.windowType) + self.service.enabled = true + self.service.enabledQuery = true + EZLocalStorage.shared().setService(self.service, windowType: viewModel.windowType) viewModel.postUpdateServiceNotification() + viewModel.isValidating = false } } } @@ -266,7 +285,7 @@ private struct ServiceItemView: View { // open service if newValue { // validate service enabled - serviceItemViewModel.handleValidateServiceEnable(isEnable: true, viewModel: viewModel) + serviceItemViewModel.handleValidateServiceEnable(viewModel: viewModel) } else { // close service service.enabled = false EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) From b48fd7b82dda51b044e68004135537e2d28d0563 Mon Sep 17 00:00:00 2001 From: Lava <34743145+CanglongCl@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:57:56 -0800 Subject: [PATCH 06/10] refactor: provide enable validation for each service --- Easydict/App/Localizable.xcstrings | 113 ++++++---- .../SettingView/Tabs/TabView/ServiceTab.swift | 193 ++++++++++-------- 2 files changed, 184 insertions(+), 122 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index b6e2913f7..c8c25f77f 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -799,40 +799,6 @@ } } }, - "setting.service.back" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back" - } - }, - "en-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Späť" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "返回" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "返回" - } - } - } - }, "Baidu" : { "extractionState" : "manual", "localizations" : { @@ -1998,6 +1964,17 @@ } } }, + "fail_validate_service %@" : { + "extractionState" : "stale", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : " Fail to enable @%" + } + } + } + }, "Failed to allocate memory" : { "comment" : "Error reason", "localizations" : { @@ -8314,6 +8291,40 @@ } } }, + "setting.service.back" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back" + } + }, + "en-CA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Späť" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "返回" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "返回" + } + } + } + }, "setting.service.detail.no_configuration %@" : { "localizations" : { "en" : { @@ -8416,6 +8427,38 @@ } } }, + "setting.service.unable_enable %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to enable translation service %@" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启用%@翻译服务失败" + } + } + } + }, + "setting.service.validate.error.empty_translate_result" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fail to validate service since test translation query returned invalid empty result. " + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "验证翻译服务时,返回了无效的空结果" + } + } + } + }, "setting.tts_service.options.apple" : { "localizations" : { "en" : { @@ -10246,4 +10289,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index fc7bb8ca0..870916e12 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -15,65 +15,52 @@ struct ServiceTab: View { // MARK: Internal var body: some View { - ZStack { - HStack(alignment: .top, spacing: 0) { - VStack { - WindowTypePicker(windowType: $viewModel.windowType) - .padding() - List(selection: $viewModel.selectedService) { - ServiceItems() - } - .listStyle(.plain) - .scrollIndicators(.never) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .padding(.bottom) - .padding(.horizontal) - .frame(minWidth: 260) - .onReceive(serviceHasUpdatedNotification) { _ in - viewModel.updateServices() - } + HStack(alignment: .top, spacing: 0) { + VStack { + WindowTypePicker(windowType: $viewModel.windowType) + .padding() + List(selection: $viewModel.selectedService) { + ServiceItems() } + .listStyle(.plain) + .scrollIndicators(.never) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .padding(.bottom) + .padding(.horizontal) + .frame(minWidth: 260) + .onReceive(serviceHasUpdatedNotification) { _ in + viewModel.updateServices() + } + } - Group { - if let service = viewModel.selectedService { - VStack(alignment: .leading) { - Button("setting.service.back") { - viewModel.selectedService = nil + Group { + if let service = viewModel.selectedService { + VStack(alignment: .leading) { + Button("setting.service.back") { + viewModel.selectedService = nil + } + .padding() + + if let view = service.configurationListItems() as? (any View) { + Form { + AnyView(view) } - .padding() - - if let view = service.configurationListItems() as? (any View) { - Form { - AnyView(view) - } - .formStyle(.grouped) - } else { + .formStyle(.grouped) + } else { + Spacer() + HStack { Spacer() - HStack { - Spacer() - Text("setting.service.detail.no_configuration \(service.name())") - Spacer() - } + Text("setting.service.detail.no_configuration \(service.name())") Spacer() } + Spacer() } - } else { - WindowConfigurationView(windowType: viewModel.windowType) } + } else { + WindowConfigurationView(windowType: viewModel.windowType) } - .layoutPriority(1) - } - - if viewModel.isValidating { - VStack { - ProgressView() - .controlSize(.small) - .progressViewStyle(.circular) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black.opacity(0.4)) - .zIndex(1) } + .layoutPriority(1) } .environmentObject(viewModel) } @@ -98,8 +85,6 @@ private class ServiceTabViewModel: ObservableObject { // MARK: Internal - @Published var isValidating: Bool = false - @Published var selectedService: QueryService? @Published private(set) var services: [QueryService] @@ -150,7 +135,7 @@ private struct ServiceItems: View { var body: some View { ForEach(servicesWithID, id: \.1) { service, _ in - ServiceItemView(service: service) + ServiceItemView(service: service, viewModel: viewModel) .tag(service) } .onMove(perform: viewModel.onServiceItemMove) @@ -169,13 +154,14 @@ private struct ServiceItems: View { // MARK: - ServiceItemViewModel +@MainActor private class ServiceItemViewModel: ObservableObject { // MARK: Lifecycle - init(_ service: QueryService) { + init(_ service: QueryService, viewModel: ServiceTabViewModel) { self.service = service - self.isEnable = service.enabled self.name = service.name() + self.viewModel = viewModel cancellables.append( serviceUpdatePublisher @@ -187,7 +173,7 @@ private class ServiceItemViewModel: ObservableObject { // MARK: Public - public func handleValidateServiceEnable(viewModel: ServiceTabViewModel) { + public func enableService() { // filter if service.serviceType() == .appleDictionary || service.serviceType() == .apple { @@ -198,32 +184,37 @@ private class ServiceItemViewModel: ObservableObject { return } - viewModel.isValidating = true + isValidating = true + service.validate { [self] result, error in // check into main thread DispatchQueue.main.async { + defer { self.isValidating = false } // Validate existence error if let error = error { - self.isEnable = false logInfo("\(self.service.serviceType().rawValue) validate error: \(error)") - viewModel.isValidating = false + self.error = error + self.showErrorAlert = true return } // If error is nil but result text is also empty, we should report error. guard let translatedText = result.translatedText, !translatedText.isEmpty else { - self.isEnable = false logInfo("\(self.service.serviceType().rawValue) validate translated text is empty") - viewModel.isValidating = false + self.showErrorAlert = true + self.error = EZError( + type: .API, + description: String(localized: "setting.service.validate.error.empty_translate_result") + ) return } // service enabel open the switch and toggle enable status self.service.enabled = true self.service.enabledQuery = true - EZLocalStorage.shared().setService(self.service, windowType: viewModel.windowType) - viewModel.postUpdateServiceNotification() - viewModel.isValidating = false + EZLocalStorage.shared().setService(self.service, windowType: self.viewModel.windowType) + self.viewModel.postUpdateServiceNotification() + self.objectWillChange.send() } } } @@ -232,9 +223,29 @@ private class ServiceItemViewModel: ObservableObject { let service: QueryService - @Published var isEnable = false + @Published var isValidating = false @Published var name = "" + @Published var showErrorAlert = false + @Published var error: (any Error)? + + unowned var viewModel: ServiceTabViewModel + + var isEnable: Bool { + get { + service.enabled + } set { + if newValue { + // validate service enabled + enableService() + } else { // close service + service.enabled = false + EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) + viewModel.postUpdateServiceNotification() + } + } + } + // MARK: Private private var cancellables: [AnyCancellable] = [] @@ -260,9 +271,9 @@ private class ServiceItemViewModel: ObservableObject { private struct ServiceItemView: View { // MARK: Lifecycle - init(service: QueryService) { + init(service: QueryService, viewModel: ServiceTabViewModel) { self.service = service - self.serviceItemViewModel = ServiceItemViewModel(service) + self.serviceItemViewModel = ServiceItemViewModel(service, viewModel: viewModel) } // MARK: Internal @@ -270,34 +281,42 @@ private struct ServiceItemView: View { let service: QueryService var body: some View { - Toggle(isOn: $serviceItemViewModel.isEnable) { + Group { HStack { - Image(service.serviceType().rawValue) - .resizable() - .scaledToFit() - .frame(width: 20.0, height: 20.0) - Text(service.name()) - .lineLimit(1) - } - } - .onReceive(serviceItemViewModel.$isEnable) { newValue in - guard service.enabled != newValue else { return } - // open service - if newValue { - // validate service enabled - serviceItemViewModel.handleValidateServiceEnable(viewModel: viewModel) - } else { // close service - service.enabled = false - EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) - viewModel.postUpdateServiceNotification() + HStack { + Image(service.serviceType().rawValue) + .resizable() + .scaledToFit() + .frame(width: 20.0, height: 20.0) + Text(service.name()) + .lineLimit(1) + } + Spacer() + if serviceItemViewModel.isValidating { + ProgressView() + .controlSize(.small) + } else { + Toggle(serviceItemViewModel.service.name(), isOn: $serviceItemViewModel.isEnable) + .labelsHidden() + .toggleStyle(.switch) + .controlSize(.small) + } } } - .toggleStyle(.switch) - .controlSize(.small) .listRowSeparator(.hidden) .listRowInsets(.init()) .padding(.horizontal, 8) .padding(.vertical, 12) + .alert( + "setting.service.unable_enable \(serviceItemViewModel.service.name())", + isPresented: $serviceItemViewModel.showErrorAlert + ) { + Button("ok") { + serviceItemViewModel.showErrorAlert = false + } + } message: { + Text(serviceItemViewModel.error?.localizedDescription ?? "error_unknown") + } } // MARK: Private From e453b00e717977a9a48ba4e3e80e003794ae55a3 Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Tue, 19 Nov 2024 14:13:52 +0800 Subject: [PATCH 07/10] chore: clean up code remove `self.objectWillChange.send ` --- Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 870916e12..4241d347a 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -214,7 +214,7 @@ private class ServiceItemViewModel: ObservableObject { self.service.enabledQuery = true EZLocalStorage.shared().setService(self.service, windowType: self.viewModel.windowType) self.viewModel.postUpdateServiceNotification() - self.objectWillChange.send() +// self.objectWillChange.send() } } } From 0924762321ae4fdc96d2f3ab8767e7aebc08eb3b Mon Sep 17 00:00:00 2001 From: Sharker <1548742234@qq.com> Date: Tue, 19 Nov 2024 22:32:18 +0800 Subject: [PATCH 08/10] fix: fix review problem --- Easydict/App/Localizable.xcstrings | 11 ----------- .../View/SettingView/Tabs/TabView/ServiceTab.swift | 1 - 2 files changed, 12 deletions(-) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index b32b25432..9dc223381 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1964,17 +1964,6 @@ } } }, - "fail_validate_service %@" : { - "extractionState" : "stale", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : " Fail to enable @%" - } - } - } - }, "Failed to allocate memory" : { "comment" : "Error reason", "localizations" : { diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 4241d347a..13bf9a0a2 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -214,7 +214,6 @@ private class ServiceItemViewModel: ObservableObject { self.service.enabledQuery = true EZLocalStorage.shared().setService(self.service, windowType: self.viewModel.windowType) self.viewModel.postUpdateServiceNotification() -// self.objectWillChange.send() } } } From 0b51d9adbfd971ce022a5f7aa5a0c8e1f8167fb1 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Tue, 19 Nov 2024 23:00:48 +0800 Subject: [PATCH 09/10] fix: improve UI, make ProgressView center aligned with Toggle --- .../SettingView/Tabs/TabView/ServiceTab.swift | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift index 13bf9a0a2..79db2df8a 100644 --- a/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift +++ b/Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift @@ -175,8 +175,7 @@ private class ServiceItemViewModel: ObservableObject { public func enableService() { // filter - if service.serviceType() == .appleDictionary || - service.serviceType() == .apple { + if service.serviceType() == .appleDictionary || service.serviceType() == .apple { service.enabled = true service.enabledQuery = true EZLocalStorage.shared().setService(service, windowType: viewModel.windowType) @@ -233,7 +232,8 @@ private class ServiceItemViewModel: ObservableObject { var isEnable: Bool { get { service.enabled - } set { + } + set { if newValue { // validate service enabled enableService() @@ -291,15 +291,22 @@ private struct ServiceItemView: View { .lineLimit(1) } Spacer() - if serviceItemViewModel.isValidating { - ProgressView() - .controlSize(.small) - } else { - Toggle(serviceItemViewModel.service.name(), isOn: $serviceItemViewModel.isEnable) + // Use a fixed width container for both controls, to make sure they are center aligned. + ZStack { + if serviceItemViewModel.isValidating { + ProgressView() + .controlSize(.small) + } else { + Toggle( + serviceItemViewModel.service.name(), + isOn: $serviceItemViewModel.isEnable + ) .labelsHidden() .toggleStyle(.switch) - .controlSize(.small) + .controlSize(.small) // size: 32*18 + } } + .frame(width: 32) } } .listRowSeparator(.hidden) From 018a2f354210c0b387dc5f5c77a8f0402a654eb5 Mon Sep 17 00:00:00 2001 From: tisfeng Date: Tue, 19 Nov 2024 23:26:46 +0800 Subject: [PATCH 10/10] fix: add missing localizations --- Easydict/App/Localizable.xcstrings | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index 9dc223381..4a44dd98e 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -8459,11 +8459,29 @@ "value" : "Unable to enable translation service %@" } }, + "en-CA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to enable translation service %@" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "启用%@翻译服务失败" } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "啟用%@翻譯服務失敗" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nemôže aktivovať prekladovú službu %@" + } } } }, @@ -8475,11 +8493,29 @@ "value" : "Fail to validate service since test translation query returned invalid empty result. " } }, + "en-CA" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fail to validate service since test translation query returned invalid empty result. " + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "验证翻译服务时,返回了无效的空结果" } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "驗證翻譯服務時,返回了無效的空結果" + } + }, + "sk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Validácia prekladovej služby zlyhala, pretože testový prekladový dotaz vrátil neplatný prázdny výsledok." + } } } },