Remove manual polish scenario UI; rely on auto AppContext only
- Hide AppContext chip and remove manual override from keyboard - Remove polish scenario picker and custom system prompt from settings - Remove scenario section from onboarding - Translation prompt uses auto-detected AppContext instead of polish scenario - Delete ScenarioPrompt, ScenarioStyleDirective, PolishScenario, and related views Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
@@ -78,7 +78,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
/// Grace period after a chip-side translation write during which the
|
||||
/// 1 Hz App Group poll must not overwrite `translationTargetLocaleId`.
|
||||
private var translationConfigProtectedUntil: Date?
|
||||
private var polishScenarioConfigProtectedUntil: Date?
|
||||
private var isAwaitingFlowResult = false
|
||||
private var lastFlowAutoStartAttempt: TimeInterval = 0
|
||||
private static let flowAutoStartCooldown: TimeInterval = 20
|
||||
@@ -132,7 +131,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
// from Settings.app or the host app, and the App Group is the
|
||||
// only thing both processes see consistently.
|
||||
syncOnboardingStateFromAppGroup()
|
||||
syncAppContextFromAppGroup()
|
||||
// Auto-advance past step 3 ("Enable Keyboard") if the user has
|
||||
// enabled the keyboard in Settings.app while we were away.
|
||||
// This is the "automatic return from jump" feature: no manual
|
||||
@@ -185,11 +183,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
// v0.2.1 follow-up: removed `setTranslationEnabled` — the chip
|
||||
// / picker now writes the locale id directly; `enabled` is derived.
|
||||
state.setTranslationTargetLocaleId = { [weak self] id in self?.persistTranslationTargetLocaleId(id) }
|
||||
state.setPolishScenarioId = { [weak self] id in self?.persistPolishScenarioId(id) }
|
||||
// v0.3.0: per-app context override. Writes to the App Group
|
||||
// (so `PolishingService` reads it on the next take) and mirrors
|
||||
// into local state.
|
||||
state.setAppContext = { [weak self] c in self?.persistAppContext(c) }
|
||||
// v0.3.0: in-keyboard onboarding actions. Persist via the App
|
||||
// Group so the host app's `ProviderConfig` stays in sync (and
|
||||
// the next launch of the host app opens at the same page).
|
||||
@@ -220,14 +213,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
state.onboardingPage = 4
|
||||
}
|
||||
|
||||
// MARK: - App context override (v0.3.0)
|
||||
|
||||
private func persistAppContext(_ context: AppContext) {
|
||||
let store = AppGroupStore()
|
||||
store.setDetectedAppContext(context, at: Date())
|
||||
state.appContext = context
|
||||
}
|
||||
|
||||
// MARK: - Permission requests from the extension (v0.3.0)
|
||||
|
||||
/// The keyboard extension cannot present `AVAudioSession` /
|
||||
@@ -333,13 +318,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
state.onboardingPage = store.onboardingPage
|
||||
}
|
||||
|
||||
/// Mirror the detected app context (or the user's last manual
|
||||
/// override) so the AppContextChip stays in sync after a jump.
|
||||
private func syncAppContextFromAppGroup() {
|
||||
let store = AppGroupStore()
|
||||
state.appContext = store.detectedAppContext?.context ?? .unknown
|
||||
}
|
||||
|
||||
/// If the user has finished the "Enable Keyboard" step (i.e. the
|
||||
/// keyboard is now in the system list with full access), and the
|
||||
/// overlay is currently sitting on that step, advance to the API
|
||||
@@ -389,16 +367,14 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
private func refreshConfigFromAppGroup() {
|
||||
persistor.refreshRuntimeFlags(
|
||||
into: state,
|
||||
protectTranslationUntil: translationConfigProtectedUntil,
|
||||
protectPolishScenarioUntil: polishScenarioConfigProtectedUntil
|
||||
protectTranslationUntil: translationConfigProtectedUntil
|
||||
)
|
||||
}
|
||||
|
||||
private func refreshFlowSessionState() {
|
||||
persistor.refreshRuntimeFlags(
|
||||
into: state,
|
||||
protectTranslationUntil: translationConfigProtectedUntil,
|
||||
protectPolishScenarioUntil: polishScenarioConfigProtectedUntil
|
||||
protectTranslationUntil: translationConfigProtectedUntil
|
||||
)
|
||||
consumePendingFlowDeliveryIfNeeded()
|
||||
|
||||
@@ -555,10 +531,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
storedCache: store.detectedAppContext
|
||||
)
|
||||
store.setDetectedAppContext(context)
|
||||
// v0.3.0: mirror into local state so the chip updates without
|
||||
// waiting for the next `viewWillAppear`. The user gets
|
||||
// immediate visual feedback that the polish tone just changed.
|
||||
state.appContext = context
|
||||
}
|
||||
|
||||
private func startUtteranceCountdown() {
|
||||
@@ -871,13 +843,6 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
persistor.persist(translationTargetLocaleId: resolved)
|
||||
}
|
||||
|
||||
private func persistPolishScenarioId(_ id: String) {
|
||||
let resolved = PolishScenarioCatalog.resolve(id).id
|
||||
state.polishScenarioId = resolved
|
||||
polishScenarioConfigProtectedUntil = Date().addingTimeInterval(2.5)
|
||||
persistor.persist(polishScenarioId: resolved)
|
||||
}
|
||||
|
||||
// MARK: - Open host app
|
||||
|
||||
private func openHostApp(path: String = "settings") {
|
||||
|
||||
@@ -40,7 +40,6 @@ public struct AppGroupPersistor {
|
||||
// startup; `refreshRuntimeFlags` keeps the chip in sync while
|
||||
// the keyboard stays open.
|
||||
state.translationTargetLocaleId = store.translationTargetLocaleId
|
||||
state.polishScenarioId = store.polishScenarioId
|
||||
state.handednessPreference = store.handednessPreference
|
||||
state.localModeCloudPolishEnabled = store.localModeCloudPolishEnabled
|
||||
// v0.2.0: iOS `SpeechAnalyzer` is always ready; mirror that
|
||||
@@ -84,8 +83,7 @@ public struct AppGroupPersistor {
|
||||
/// a chip selection the user just wrote to the App Group.
|
||||
public func refreshRuntimeFlags(
|
||||
into state: KeyboardViewController.State,
|
||||
protectTranslationUntil: Date? = nil,
|
||||
protectPolishScenarioUntil: Date? = nil
|
||||
protectTranslationUntil: Date? = nil
|
||||
) {
|
||||
guard AppGroup.isAvailable else { return }
|
||||
let store = AppGroupStore()
|
||||
@@ -96,10 +94,6 @@ public struct AppGroupPersistor {
|
||||
if !shouldProtectTranslation {
|
||||
state.translationTargetLocaleId = store.translationTargetLocaleId
|
||||
}
|
||||
let shouldProtectScenario = protectPolishScenarioUntil.map { Date() < $0 } ?? false
|
||||
if !shouldProtectScenario {
|
||||
state.polishScenarioId = store.polishScenarioId
|
||||
}
|
||||
state.handednessPreference = store.handednessPreference
|
||||
// v0.2.0: iOS `SpeechAnalyzer` is always ready. Keep these
|
||||
// toggles here so the keyboard UI doesn't flicker if the host
|
||||
@@ -145,9 +139,4 @@ public struct AppGroupPersistor {
|
||||
guard AppGroup.isAvailable else { return }
|
||||
AppGroupStore().setTranslationTargetLocaleId(translationTargetLocaleId)
|
||||
}
|
||||
|
||||
public func persist(polishScenarioId: String) {
|
||||
guard AppGroup.isAvailable else { return }
|
||||
AppGroupStore().setPolishScenarioId(polishScenarioId)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
// AppContextChip.swift
|
||||
// OSGKeyboard · Keyboard Extension
|
||||
//
|
||||
// Surfaces the v0.3.0 per-app polish context on the keyboard top
|
||||
// bar. The LLM prompt already adapts to the detected context (see
|
||||
// `PolishingService.buildPrompt(for:context:)`), but without a UI
|
||||
// cue the user has no way to know "I'm currently in code mode" —
|
||||
// and no way to override the heuristic when it guesses wrong.
|
||||
//
|
||||
// Tap the chip → cycle through the five `AppContext` cases. The new
|
||||
// value is written to `AppGroupStore.setDetectedAppContext(_:at:)`,
|
||||
// so the next `PolishingService` call picks it up immediately.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct AppContextChip: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
|
||||
@ObservedObject var state: KeyboardViewController.State
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
ForEach(AppContext.allCases, id: \.self) { context in
|
||||
Button {
|
||||
state.setAppContext(context)
|
||||
} label: {
|
||||
if context == state.appContext {
|
||||
Label(menuLabel(for: context), systemImage: "checkmark")
|
||||
} else {
|
||||
Text(menuLabel(for: context))
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
label
|
||||
}
|
||||
.menuStyle(.button)
|
||||
.accessibilityLabel(ExtL10n.text("keyboard.appContext.a11y"))
|
||||
.accessibilityHint(ExtL10n.text("keyboard.appContext.a11yHint"))
|
||||
}
|
||||
|
||||
private var label: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: iconName(for: state.appContext))
|
||||
Text(chipText)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
}
|
||||
.font(TypeStyle.caption2)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
.padding(.horizontal, Spacing.xs + 2)
|
||||
.padding(.vertical, 6)
|
||||
.frame(minHeight: 28)
|
||||
.background(palette.surfaceElevated, in: Capsule())
|
||||
.overlay(Capsule().stroke(palette.divider, lineWidth: 0.5))
|
||||
}
|
||||
|
||||
private var chipText: String {
|
||||
ExtL10n.string("keyboard.appContext.chip.\(state.appContext.rawValue)")
|
||||
}
|
||||
|
||||
private func menuLabel(for context: AppContext) -> String {
|
||||
ExtL10n.string("keyboard.appContext.menu.\(context.rawValue)")
|
||||
}
|
||||
|
||||
private func iconName(for context: AppContext) -> String {
|
||||
switch context {
|
||||
case .code: return "chevron.left.forwardslash.chevron.right"
|
||||
case .email: return "envelope"
|
||||
case .chat: return "bubble.left"
|
||||
case .document: return "doc.text"
|
||||
case .unknown: return "questionmark.circle"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,15 +140,7 @@ public struct KeyboardRootView: View {
|
||||
} else {
|
||||
CloudEngineChip()
|
||||
}
|
||||
// Polish scenario and ASR locale are configured in the main-app
|
||||
// Settings tab only — keep the keyboard top bar uncluttered.
|
||||
if state.hasCompletedOnboarding {
|
||||
AppContextChip(state: state)
|
||||
}
|
||||
// v0.3: always show the translation chip when the active
|
||||
// engine can run the cloud LLM step — off-by-default keeps
|
||||
// the menu reachable so the user can pick a target language
|
||||
// without opening Settings.
|
||||
// App context is auto-detected on each mic press — no UI.
|
||||
if state.isTranslationChipVisible {
|
||||
TranslationChip(state: state)
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// ScenarioChip.swift
|
||||
// OSGKeyboard · Keyboard Extension
|
||||
//
|
||||
// Compact chip on the keyboard top bar for quick polish scenario
|
||||
// switching. Same Menu pattern as `TranslationChip`.
|
||||
|
||||
import SwiftUI
|
||||
import OSGKeyboardShared
|
||||
|
||||
struct ScenarioChip: View {
|
||||
@Environment(\.themePalette) private var palette: ThemePalette
|
||||
|
||||
@ObservedObject var state: KeyboardViewController.State
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
ForEach(PolishScenarioCatalog.all) { scenario in
|
||||
Button {
|
||||
state.setPolishScenarioId(scenario.id)
|
||||
} label: {
|
||||
if scenario.id == state.polishScenarioId {
|
||||
Label(displayLabel(for: scenario), systemImage: "checkmark")
|
||||
} else {
|
||||
Text(displayLabel(for: scenario))
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
label
|
||||
}
|
||||
.menuStyle(.button)
|
||||
.accessibilityLabel(ExtL10n.text("keyboard.scenario.a11y"))
|
||||
.accessibilityHint(ExtL10n.text("keyboard.scenario.a11yHint"))
|
||||
}
|
||||
|
||||
private var label: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "text.bubble")
|
||||
Text(chipText)
|
||||
Image(systemName: "chevron.down")
|
||||
.font(.system(size: 8, weight: .bold))
|
||||
}
|
||||
.font(TypeStyle.caption2)
|
||||
.foregroundStyle(palette.textPrimary)
|
||||
.padding(.horizontal, Spacing.xs + 2)
|
||||
.padding(.vertical, 6)
|
||||
.frame(minHeight: 28)
|
||||
.background(palette.surfaceElevated, in: Capsule())
|
||||
.overlay(Capsule().stroke(palette.divider, lineWidth: 0.5))
|
||||
}
|
||||
|
||||
private var chipText: String {
|
||||
PolishScenarioCatalog.chipLabel(for: state.polishScenarioId)
|
||||
}
|
||||
|
||||
private func displayLabel(for scenario: PolishScenario) -> String {
|
||||
PolishScenarioCatalog.displayName(for: scenario.id)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user