From 229a96dfe32a9a0cecd3e3fe3d26dbeb6c4faa2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=B6=E6=9D=96?= Date: Tue, 21 Jul 2026 17:06:10 +0800 Subject: [PATCH] Group menu sections --- .../UI/Views/MenuBar/AppInfoView.swift | 40 +++++------ .../UI/Views/MenuBar/ConfiguredAppsView.swift | 62 ++++++++-------- .../UI/Views/MenuBar/IgnoredAppsView.swift | 48 ++++++------- .../UI/Views/MenuBar/MenuBarView.swift | 8 +++ .../UI/Views/MenuBar/SettingsView.swift | 70 +++++++++---------- .../Views/MenuBar/SwitchStatisticsView.swift | 58 ++++++++------- .../Views/MenuBar/UnavailableAppsView.swift | 52 +++++++------- 7 files changed, 166 insertions(+), 172 deletions(-) diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/AppInfoView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/AppInfoView.swift index d75edb0..6e3df2a 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/AppInfoView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/AppInfoView.swift @@ -9,34 +9,30 @@ struct AppInfoView: View { var body: some View { Group { - Section { - Button { - updateMonitor.showUpdate(using: updaterController.updater) - } label: { - Label( - updateMonitor.menuTitle, - systemImage: "arrow.triangle.2.circlepath" - ) - } - .disabled(!updateMonitor.isMenuActionEnabled) + Button { + updateMonitor.showUpdate(using: updaterController.updater) + } label: { + Label( + updateMonitor.menuTitle, + systemImage: "arrow.triangle.2.circlepath" + ) + } + .disabled(!updateMonitor.isMenuActionEnabled) - Button { - AppInfoService.openGitHubRepository() - } label: { - Label(TypeSwitchStrings.Menu.githubRepository, systemImage: "link") - } + Button { + AppInfoService.openGitHubRepository() + } label: { + Label(TypeSwitchStrings.Menu.githubRepository, systemImage: "link") } Divider() - Section { - Button(role: .destructive) { - NSApplication.shared.terminate(nil) - } label: { - Label(TypeSwitchStrings.Menu.quit, systemImage: "power") - } - .keyboardShortcut("q", modifiers: .command) + Button(role: .destructive) { + NSApplication.shared.terminate(nil) + } label: { + Label(TypeSwitchStrings.Menu.quit, systemImage: "power") } + .keyboardShortcut("q", modifiers: .command) } } } diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/ConfiguredAppsView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/ConfiguredAppsView.swift index f1cad17..92b3fa3 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/ConfiguredAppsView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/ConfiguredAppsView.swift @@ -7,44 +7,42 @@ struct ConfiguredAppsView: View { var body: some View { if !store.configuredApps.isEmpty { - Section { - Menu { - if store.hasMissingInputMethodRules { - Button(role: .destructive) { - if MenuConfirmation.confirm( - title: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.title, - message: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.message, - confirmButton: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.confirm - ) { - store.send(.view(.removeMissingInputMethodRulesTapped)) - } - } label: { - Label( - TypeSwitchStrings.InputMethod.clearMissingConfiguration, - systemImage: "trash" - ) + Menu { + if store.hasMissingInputMethodRules { + Button(role: .destructive) { + if MenuConfirmation.confirm( + title: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.title, + message: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.message, + confirmButton: TypeSwitchStrings.InputMethod.ClearMissingConfirmation.confirm + ) { + store.send(.view(.removeMissingInputMethodRulesTapped)) } - - Divider() + } label: { + Label( + TypeSwitchStrings.InputMethod.clearMissingConfiguration, + systemImage: "trash" + ) } - ForEach(store.configuredApps) { item in - AppRowView( - item: item, - inputMethods: store.inputMethods, - onIgnore: { - store.send(.view(.ignoreAppTapped(item.appInfo))) - } - ) { strategy in - store.send(.view(.setStrategy(bundleId: item.bundleId, strategy: strategy))) + Divider() + } + + ForEach(store.configuredApps) { item in + AppRowView( + item: item, + inputMethods: store.inputMethods, + onIgnore: { + store.send(.view(.ignoreAppTapped(item.appInfo))) } + ) { strategy in + store.send(.view(.setStrategy(bundleId: item.bundleId, strategy: strategy))) } - } label: { - Label( - TypeSwitchStrings.Apps.Section.configuredCount(store.configuredApps.count), - systemImage: "list.bullet.rectangle" - ) } + } label: { + Label( + TypeSwitchStrings.Apps.Section.configuredCount(store.configuredApps.count), + systemImage: "list.bullet.rectangle" + ) } } } diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/IgnoredAppsView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/IgnoredAppsView.swift index 244db05..09401f4 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/IgnoredAppsView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/IgnoredAppsView.swift @@ -6,40 +6,38 @@ struct IgnoredAppsView: View { var body: some View { if !store.ignoredAppsForMenu.isEmpty { - Section { - Menu { - Section { - ForEach(store.ignoredAppsForMenu) { app in - Button { - store.send(.view(.restoreIgnoredAppTapped(bundleId: app.bundleId))) - } label: { - Label { - Text(app.name) - } icon: { - app.icon - } + Menu { + Section { + ForEach(store.ignoredAppsForMenu) { app in + Button { + store.send(.view(.restoreIgnoredAppTapped(bundleId: app.bundleId))) + } label: { + Label { + Text(app.name) + } icon: { + app.icon } } - } header: { - Text(TypeSwitchStrings.Apps.Ignored.restoreHint) } + } header: { + Text(TypeSwitchStrings.Apps.Ignored.restoreHint) + } - Divider() + Divider() - Button { - store.send(.view(.restoreAllIgnoredAppsTapped)) - } label: { - Label( - TypeSwitchStrings.Apps.Ignored.restoreAll, - systemImage: "arrow.uturn.backward" - ) - } + Button { + store.send(.view(.restoreAllIgnoredAppsTapped)) } label: { Label( - TypeSwitchStrings.Apps.Ignored.menuTitle(store.ignoredAppsForMenu.count), - systemImage: "eye.slash" + TypeSwitchStrings.Apps.Ignored.restoreAll, + systemImage: "arrow.uturn.backward" ) } + } label: { + Label( + TypeSwitchStrings.Apps.Ignored.menuTitle(store.ignoredAppsForMenu.count), + systemImage: "eye.slash" + ) } } } diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/MenuBarView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/MenuBarView.swift index 5a0023c..d91e008 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/MenuBarView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/MenuBarView.swift @@ -13,6 +13,14 @@ struct MenuBarView: View { Group { CurrentAppView(store: store) RunningAppsView(store: store) + + if store.currentAppMenuItem != nil + || !store.runningUnconfiguredMenuItems.isEmpty + || !store.runningConfiguredMenuItems.isEmpty + { + Divider() + } + ConfiguredAppsView(store: store) UnavailableAppsView(store: store) IgnoredAppsView(store: store) diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/SettingsView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/SettingsView.swift index 7aba216..7ee30f7 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/SettingsView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/SettingsView.swift @@ -6,47 +6,45 @@ struct SettingsView: View { @Bindable var store: StoreOf var body: some View { - Section { - Menu { - InputMethodStrategyMenuContent( - context: .fallbackRule, - strategy: store.fallbackStrategy, - inputMethods: store.inputMethods, - defaultOptionLabel: TypeSwitchStrings.InputMethod.fallbackDefaultOption, - followLastOptionLabel: TypeSwitchStrings.InputMethod.followLastEmptyOption - ) { strategy in - store.send(.view(.setFallbackStrategy(strategy))) - } - } label: { - Label( - TypeSwitchStrings.Settings.Fallback.defaultInputMethod, - systemImage: "keyboard" - ) - if let selectedLabel = store.fallbackSelectedLabel { - Text(selectedLabel) - .foregroundStyle(store.fallbackHasMissingInputMethod ? .secondary : .primary) - } + Menu { + InputMethodStrategyMenuContent( + context: .fallbackRule, + strategy: store.fallbackStrategy, + inputMethods: store.inputMethods, + defaultOptionLabel: TypeSwitchStrings.InputMethod.fallbackDefaultOption, + followLastOptionLabel: TypeSwitchStrings.InputMethod.followLastEmptyOption + ) { strategy in + store.send(.view(.setFallbackStrategy(strategy))) + } + } label: { + Label( + TypeSwitchStrings.Settings.Fallback.defaultInputMethod, + systemImage: "keyboard" + ) + if let selectedLabel = store.fallbackSelectedLabel { + Text(selectedLabel) + .foregroundStyle(store.fallbackHasMissingInputMethod ? .secondary : .primary) } + } - Toggle( - TypeSwitchStrings.Settings.General.autoLaunch, - isOn: Binding( - get: { store.launchAtLoginEnabled }, - set: { store.send(.view(.setLaunchAtLogin($0))) } - ) + Toggle( + TypeSwitchStrings.Settings.General.autoLaunch, + isOn: Binding( + get: { store.launchAtLoginEnabled }, + set: { store.send(.view(.setLaunchAtLogin($0))) } ) - .toggleStyle(.checkbox) + ) + .toggleStyle(.checkbox) - if store.launchAtLoginRequiresApproval { - Text(TypeSwitchStrings.Settings.General.autoLaunchRequiresApproval) - .font(.footnote) - .foregroundStyle(.secondary) + if store.launchAtLoginRequiresApproval { + Text(TypeSwitchStrings.Settings.General.autoLaunchRequiresApproval) + .font(.footnote) + .foregroundStyle(.secondary) - Button { - LaunchAtLoginService.openSystemSettingsLoginItems() - } label: { - Label(TypeSwitchStrings.Settings.General.openLoginItems, systemImage: "gear") - } + Button { + LaunchAtLoginService.openSystemSettingsLoginItems() + } label: { + Label(TypeSwitchStrings.Settings.General.openLoginItems, systemImage: "gear") } } } diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/SwitchStatisticsView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/SwitchStatisticsView.swift index f5d2fba..421dae0 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/SwitchStatisticsView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/SwitchStatisticsView.swift @@ -5,43 +5,41 @@ struct SwitchStatisticsView: View { let store: StoreOf var body: some View { - Section { - Menu { - if store.switchStatisticsItems.isEmpty { - Text(TypeSwitchStrings.SwitchStatistics.empty) - .foregroundStyle(.secondary) - } else { - ForEach(store.switchStatisticsItems) { item in - Button {} label: { - if item.path != nil { - AppInfo(bundleId: item.bundleId, name: item.name, path: item.path).icon - } - Text(item.name) - Text(TypeSwitchStrings.SwitchStatistics.appCount(item.count)) + Menu { + if store.switchStatisticsItems.isEmpty { + Text(TypeSwitchStrings.SwitchStatistics.empty) + .foregroundStyle(.secondary) + } else { + ForEach(store.switchStatisticsItems) { item in + Button {} label: { + if item.path != nil { + AppInfo(bundleId: item.bundleId, name: item.name, path: item.path).icon } - .disabled(true) + Text(item.name) + Text(TypeSwitchStrings.SwitchStatistics.appCount(item.count)) } + .disabled(true) + } - Divider() + Divider() - Button(role: .destructive) { - if MenuConfirmation.confirm( - title: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.title, - message: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.message, - confirmButton: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.confirm - ) { - store.send(.view(.clearSwitchStatisticsTapped)) - } - } label: { - Label(TypeSwitchStrings.SwitchStatistics.clear, systemImage: "trash") + Button(role: .destructive) { + if MenuConfirmation.confirm( + title: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.title, + message: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.message, + confirmButton: TypeSwitchStrings.SwitchStatistics.ClearConfirmation.confirm + ) { + store.send(.view(.clearSwitchStatisticsTapped)) } + } label: { + Label(TypeSwitchStrings.SwitchStatistics.clear, systemImage: "trash") } - } label: { - Label( - TypeSwitchStrings.SwitchStatistics.menuTitle(store.totalSuccessfulSwitchCount), - systemImage: "chart.bar" - ) } + } label: { + Label( + TypeSwitchStrings.SwitchStatistics.menuTitle(store.totalSuccessfulSwitchCount), + systemImage: "chart.bar" + ) } } } diff --git a/TypeSwitch/Sources/UI/Views/MenuBar/UnavailableAppsView.swift b/TypeSwitch/Sources/UI/Views/MenuBar/UnavailableAppsView.swift index edc7586..cd2008d 100644 --- a/TypeSwitch/Sources/UI/Views/MenuBar/UnavailableAppsView.swift +++ b/TypeSwitch/Sources/UI/Views/MenuBar/UnavailableAppsView.swift @@ -6,39 +6,37 @@ struct UnavailableAppsView: View { var body: some View { if !store.unavailableApps.isEmpty { - Section { - Menu { - Button(role: .destructive) { - if MenuConfirmation.confirm( - title: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.title, - message: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.message, - confirmButton: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.confirm - ) { - store.send(.view(.removeUnavailableRulesTapped)) - } - } label: { - Label(TypeSwitchStrings.Apps.clearUnavailable, systemImage: "trash") + Menu { + Button(role: .destructive) { + if MenuConfirmation.confirm( + title: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.title, + message: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.message, + confirmButton: TypeSwitchStrings.Apps.ClearUnavailableConfirmation.confirm + ) { + store.send(.view(.removeUnavailableRulesTapped)) } + } label: { + Label(TypeSwitchStrings.Apps.clearUnavailable, systemImage: "trash") + } - Divider() + Divider() - ForEach(store.unavailableApps) { item in - AppRowView( - item: item, - inputMethods: store.inputMethods, - onIgnore: { - store.send(.view(.ignoreAppTapped(item.appInfo))) - } - ) { strategy in - store.send(.view(.setStrategy(bundleId: item.bundleId, strategy: strategy))) + ForEach(store.unavailableApps) { item in + AppRowView( + item: item, + inputMethods: store.inputMethods, + onIgnore: { + store.send(.view(.ignoreAppTapped(item.appInfo))) } + ) { strategy in + store.send(.view(.setStrategy(bundleId: item.bundleId, strategy: strategy))) } - } label: { - Label( - TypeSwitchStrings.Apps.Section.unavailableCount(store.unavailableApps.count), - systemImage: "exclamationmark.triangle" - ) } + } label: { + Label( + TypeSwitchStrings.Apps.Section.unavailableCount(store.unavailableApps.count), + systemImage: "exclamationmark.triangle" + ) } } }