Merge pull request #16 from hkgood/cursor/settings-keyboard-ui-tweaks-6baf

feat(ui): keyboard chip cleanup and settings preferences merge
This commit is contained in:
cursor[bot]
2026-07-03 08:30:33 +00:00
committed by GitHub
3 changed files with 91 additions and 101 deletions
+40 -28
View File
@@ -24,41 +24,51 @@ import OSGKeyboardShared
@MainActor @MainActor
struct PersonalDictionaryView: View { struct PersonalDictionaryView: View {
@Environment(\.themePalette) private var palette: ThemePalette @Environment(\.themePalette) private var palette: ThemePalette
@Environment(\.dismiss) private var dismiss @ObservedObject private var config = ProviderConfig.shared
@State private var dictionary: PersonalDictionary = AppGroupStore().personalDictionary @State private var dictionary: PersonalDictionary = AppGroupStore().personalDictionary
@State private var searchText: String = "" @State private var searchText: String = ""
@State private var showClearAllConfirmation = false
private let store = AppGroupStore() private let store = AppGroupStore()
var body: some View { var body: some View {
NavigationStack { Group {
VStack(spacing: 0) { if dictionary.entries.isEmpty {
PageHeaderRow(title: "settings.personalDictionary.title") { emptyState
if !dictionary.entries.isEmpty { } else {
PageHeaderConfirmButton( list
systemImage: "trash", }
accessibilityLabel: "settings.personalDictionary.clearAll", }
confirmTitle: "settings.personalDictionary.clearAll.confirmTitle", .background(palette.background.ignoresSafeArea())
confirmMessage: "settings.personalDictionary.clearAll.message", .navigationTitle("settings.personalDictionary.title")
confirmActionTitle: "settings.personalDictionary.clearAll.confirm" .navigationBarTitleDisplayMode(.inline)
) { .toolbar(.visible, for: .navigationBar)
clearAll() .toolbar {
} if !dictionary.entries.isEmpty {
} ToolbarItem(placement: .topBarTrailing) {
} Button {
showClearAllConfirmation = true
ZStack { } label: {
palette.background.ignoresSafeArea() Image(systemName: "trash")
if dictionary.entries.isEmpty {
emptyState
} else {
list
} }
.accessibilityLabel(AppL10n.string("settings.personalDictionary.clearAll"))
} }
} }
.background(palette.background) }
.toolbar(.hidden, for: .navigationBar) .toolbarBackground(palette.background, for: .navigationBar)
.toolbarBackground(.visible, for: .navigationBar)
.confirmationDialog(
AppL10n.string("settings.personalDictionary.clearAll.confirmTitle"),
isPresented: $showClearAllConfirmation,
titleVisibility: .visible
) {
Button(AppL10n.string("settings.personalDictionary.clearAll.confirm"), role: .destructive) {
clearAll()
}
Button(AppL10n.string("common.cancel"), role: .cancel) {}
} message: {
Text("settings.personalDictionary.clearAll.message")
} }
} }
@@ -103,7 +113,7 @@ struct PersonalDictionaryView: View {
private func section(for category: PersonalDictionary.Entry.Category, items: [PersonalDictionary.Entry]) -> some View { private func section(for category: PersonalDictionary.Entry.Category, items: [PersonalDictionary.Entry]) -> some View {
VStack(alignment: .leading, spacing: Spacing.sm) { VStack(alignment: .leading, spacing: Spacing.sm) {
Text(LocalizedStringKey(category.labelKey)) Text(SharedL10n.string(category.labelKey, language: config.uiLanguage))
.font(TypeStyle.caption2) .font(TypeStyle.caption2)
.foregroundStyle(palette.textSecondary) .foregroundStyle(palette.textSecondary)
.textCase(.uppercase) .textCase(.uppercase)
@@ -131,7 +141,7 @@ struct PersonalDictionaryView: View {
.foregroundStyle(palette.textPrimary) .foregroundStyle(palette.textPrimary)
.lineLimit(1) .lineLimit(1)
HStack(spacing: 6) { HStack(spacing: 6) {
Text(LocalizedStringKey(entry.source.labelKey)) Text(SharedL10n.string(entry.source.labelKey, language: config.uiLanguage))
.font(TypeStyle.caption2) .font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary) .foregroundStyle(palette.textTertiary)
if entry.usageCount > 1 { if entry.usageCount > 1 {
@@ -231,7 +241,9 @@ struct PersonalDictionaryView: View {
#if DEBUG #if DEBUG
#Preview { #Preview {
ThemedRoot { ThemedRoot {
PersonalDictionaryView() NavigationStack {
PersonalDictionaryView()
}
} }
} }
#endif #endif
+49 -62
View File
@@ -74,11 +74,9 @@ struct SettingsView: View {
providerSection providerSection
apiSection apiSection
} }
polishIntensitySection
if config.engineMode == "local" { if config.engineMode == "local" {
localEngineSettingsSection localEngineSettingsSection
} }
personalDictionaryLinkSection
if presentation == .tab { if presentation == .tab {
preferencesSection preferencesSection
footerLinks footerLinks
@@ -268,6 +266,19 @@ struct SettingsView: View {
set: { config.handednessPreference = $0 } set: { config.handednessPreference = $0 }
) )
) )
Divider().background(palette.divider)
polishIntensityPreferenceRows
Divider().background(palette.divider)
NavigationLink {
PersonalDictionaryView()
} label: {
personalDictionaryPreferenceRow
}
.buttonStyle(.plain)
} }
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)) .background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.xl, style: .continuous))
.overlay( .overlay(
@@ -277,74 +288,50 @@ struct SettingsView: View {
} }
} }
// MARK: - Polish intensity (v0.3.0) private var polishIntensityPreferenceRows: some View {
VStack(alignment: .leading, spacing: Spacing.sm) {
private var polishIntensitySection: some View { Text("settings.polishIntensity.title")
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) { .font(TypeStyle.body)
sectionHeader("settings.polishIntensity.title") .foregroundStyle(palette.textPrimary)
VStack(spacing: 0) {
Picker("", selection: $config.polishIntensity) {
ForEach(PolishIntensity.allCases, id: \.self) { intensity in
Text(LocalizedStringKey(intensity.labelKey))
.tag(intensity)
}
}
.pickerStyle(.segmented)
.padding(.horizontal, Spacing.md) .padding(.horizontal, Spacing.md)
.padding(.vertical, Spacing.sm) .padding(.top, Spacing.sm)
Text(LocalizedStringKey(config.polishIntensity.descriptionKey)) Picker("", selection: $config.polishIntensity) {
.font(TypeStyle.caption2) ForEach(PolishIntensity.allCases, id: \.self) { intensity in
.foregroundStyle(palette.textSecondary) Text(SharedL10n.string(intensity.labelKey, language: config.uiLanguage))
.frame(maxWidth: .infinity, alignment: .leading) .tag(intensity)
.padding(.horizontal, Spacing.md) }
.padding(.bottom, Spacing.sm)
} }
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.large, style: .continuous)) .pickerStyle(.segmented)
.overlay( .padding(.horizontal, Spacing.md)
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
.stroke(palette.divider, lineWidth: 0.5) Text(SharedL10n.string(config.polishIntensity.descriptionKey, language: config.uiLanguage))
) .font(TypeStyle.caption2)
.foregroundStyle(palette.textSecondary)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, Spacing.md)
.padding(.bottom, Spacing.sm)
} }
} }
// MARK: - Personal dictionary (v0.3.0) private var personalDictionaryPreferenceRow: some View {
HStack(spacing: Spacing.sm) {
private var personalDictionaryLinkSection: some View { VStack(alignment: .leading, spacing: 2) {
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) { Text("settings.personalDictionary.title")
sectionHeader("settings.personalDictionary.sectionTitle") .font(TypeStyle.body)
VStack(spacing: 0) { .foregroundStyle(palette.textPrimary)
NavigationLink { Text("settings.personalDictionary.summary")
PersonalDictionaryView() .font(TypeStyle.caption2)
} label: { .foregroundStyle(palette.textSecondary)
HStack(spacing: Spacing.sm) {
MaterialIcon(name: .bookmark, size: 18)
.foregroundStyle(palette.accent)
VStack(alignment: .leading, spacing: 2) {
Text("settings.personalDictionary.title")
.font(TypeStyle.body)
.foregroundStyle(palette.textPrimary)
Text("settings.personalDictionary.summary")
.font(TypeStyle.caption2)
.foregroundStyle(palette.textSecondary)
}
Spacer()
Image(systemName: "chevron.right")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(palette.textTertiary)
}
.padding(.horizontal, Spacing.md)
.frame(minHeight: SettingsListMetrics.singleLineMinHeight)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
} }
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.large, style: .continuous)) Spacer()
.overlay( Image(systemName: "chevron.right")
RoundedRectangle(cornerRadius: Radius.large, style: .continuous) .font(.system(size: 14, weight: .semibold))
.stroke(palette.divider, lineWidth: 0.5) .foregroundStyle(palette.textTertiary)
)
} }
.padding(.horizontal, Spacing.md)
.frame(minHeight: SettingsListMetrics.doubleLineMinHeight)
.contentShape(Rectangle())
} }
// MARK: - Footer links (tab settings only) // MARK: - Footer links (tab settings only)
+2 -11
View File
@@ -140,17 +140,8 @@ public struct KeyboardRootView: View {
} else { } else {
CloudEngineChip() CloudEngineChip()
} }
if state.isPolishScenarioChipVisible { // Polish scenario and ASR locale are configured in the main-app
ScenarioChip(state: state) // Settings tab only keep the keyboard top bar uncluttered.
}
LocaleChip(localeId: state.localeId) { newId in
state.setLocale(newId)
}
// v0.3.0: detected app context the per-app polish mode.
// The chip mirrors `AppGroupStore.detectedAppContext` and
// writes overrides back so the next LLM call uses the new
// tone. Hidden during onboarding (the overlay reads better
// without chip clutter).
if state.hasCompletedOnboarding { if state.hasCompletedOnboarding {
AppContextChip(state: state) AppContextChip(state: state)
} }