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] 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() + } + } }