Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pre validate service #727

Merged
merged 12 commits into from
Nov 21, 2024
43 changes: 43 additions & 0 deletions Easydict/App/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1964,6 +1964,17 @@
}
}
},
"fail_validate_service %@" : {
"extractionState" : "stale",
AkaShark marked this conversation as resolved.
Show resolved Hide resolved
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : " Fail to enable @%"
}
}
}
},
"Failed to allocate memory" : {
"comment" : "Error reason",
"localizations" : {
Expand Down Expand Up @@ -8451,6 +8462,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" : {
Expand Down
130 changes: 106 additions & 24 deletions Easydict/Swift/View/SettingView/Tabs/TabView/ServiceTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,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)
Expand All @@ -154,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
Expand All @@ -170,17 +171,87 @@ private class ServiceItemViewModel: ObservableObject {
)
}

// MARK: Public

public func enableService() {
// filter
if service.serviceType() == .appleDictionary ||
service.serviceType() == .apple {
service.enabled = true
service.enabledQuery = true
EZLocalStorage.shared().setService(service, windowType: viewModel.windowType)
viewModel.postUpdateServiceNotification()
return
}

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 {
logInfo("\(self.service.serviceType().rawValue) validate error: \(error)")
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 {
logInfo("\(self.service.serviceType().rawValue) validate translated text is empty")
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: self.viewModel.windowType)
self.viewModel.postUpdateServiceNotification()
// self.objectWillChange.send()
AkaShark marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

// MARK: Internal

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] = []

@EnvironmentObject private var serviceTabViewModel: ServiceTabViewModel

private var serviceUpdatePublisher: AnyPublisher<Notification, Never> {
NotificationCenter.default
.publisher(for: .serviceHasUpdated)
Expand All @@ -200,41 +271,52 @@ 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

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 }
service.enabled = newValue
if newValue {
service.enabledQuery = newValue
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)
}
}
EZLocalStorage.shared().setService(service, windowType: viewModel.windowType)
viewModel.postUpdateServiceNotification()
}
.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
Expand Down