Skip to content

Commit

Permalink
feat: pre validate service (#727)
Browse files Browse the repository at this point in the history
* feat: add validate when user open service

* feat: move handle validate func to view model

* pref: prefect toggle for services

* chore: add some feature for pre validate  services

* prefect: prefect pre validate service

* refactor: provide enable validation for each service

* chore: clean up code remove `self.objectWillChange.send `

* fix: fix review problem

* fix: improve UI, make ProgressView center aligned with Toggle

* fix: add missing localizations

---------

Co-authored-by: Lava <[email protected]>
Co-authored-by: tisfeng <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2024
1 parent 78abbde commit c18a7a1
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 24 deletions.
68 changes: 68 additions & 0 deletions Easydict/App/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -8451,6 +8451,74 @@
}
}
},
"setting.service.unable_enable %@" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"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 %@"
}
}
}
},
"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. "
}
},
"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."
}
}
}
},
"setting.tts_service.options.apple" : {
"localizations" : {
"en" : {
Expand Down
136 changes: 112 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,86 @@ 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()
}
}
}

// 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 +270,59 @@ 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()
// 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) // size: 32*18
}
}
.frame(width: 32)
}
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

0 comments on commit c18a7a1

Please sign in to comment.