chore(release): bump version to 1.6.5 (build 51)
Ship automatic low-profile PiP keep-alive so background dictation stays ready without visible startup UI or idle audio work.
This commit is contained in:
@@ -197,6 +197,11 @@ public final class KeyboardViewController: UIInputViewController {
|
||||
disableSystemGestureDelays()
|
||||
keyboardHeightConstraint?.constant = targetKeyboardHeight
|
||||
refreshReturnKeyRole()
|
||||
// Run after the extension is fully presented so UIKit accepts the
|
||||
// containing-app handoff even when typing mode is the default surface.
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.flowCoordinator.ensurePiPReadyOnKeyboardOpen()
|
||||
}
|
||||
OSGDiag.log(
|
||||
"KVC.viewDidAppear done height=\(targetKeyboardHeight) \(OSGDiag.memoryTag())",
|
||||
category: "boot"
|
||||
|
||||
@@ -116,6 +116,33 @@ final class KeyboardFlowCoordinator {
|
||||
stopHostReadyWait()
|
||||
}
|
||||
|
||||
/// Ensure the containing app has armed its low-profile PiP even when the
|
||||
/// keyboard opens directly into Pinyin/English typing mode. The mic stays
|
||||
/// visually ready; if the host contract is missing, one automatic handoff
|
||||
/// prepares PiP so the next press does not need another app switch.
|
||||
func ensurePiPReadyOnKeyboardOpen() {
|
||||
guard FlowHandoffPolicy.allowsProactiveHostAutoLaunch,
|
||||
FlowSessionPolicy.keepAliveMode() == .pictureInPicture,
|
||||
state.hasCompletedOnboarding,
|
||||
hasFullAccess(),
|
||||
AppGroup.isAvailable,
|
||||
!isPendingFlowStart,
|
||||
!isFlowRecording,
|
||||
!isAwaitingFlowResult else { return }
|
||||
|
||||
FlowSessionBridge.reloadFromDisk()
|
||||
if FlowSessionBridge.isHostReady() { return }
|
||||
|
||||
if let reason = FlowSessionBridge.readySnapshot()?.reason,
|
||||
reason == .recording || reason == .processing || reason == .awaitingDelivery {
|
||||
return
|
||||
}
|
||||
|
||||
detectAndStoreAppContext()
|
||||
beginFlowStart(recordAfterHandoff: false)
|
||||
traceState("keyboardOpen.autoArmPiP")
|
||||
}
|
||||
|
||||
func refreshSessionState() {
|
||||
FlowSessionBridge.reloadFromDisk()
|
||||
refreshConfigFromAppGroup()
|
||||
@@ -527,11 +554,7 @@ final class KeyboardFlowCoordinator {
|
||||
isPendingFlowStart = true
|
||||
isFlowRecording = false
|
||||
flowStartDeadline = Date().timeIntervalSince1970 + FlowWatchdog.startTimeout
|
||||
state.lastTranscript = ExtL10n.string(
|
||||
FlowSessionPolicy.keepAliveMode() == .pictureInPicture
|
||||
? "keyboard.flow.startingSession.pip"
|
||||
: "keyboard.flow.startingSession"
|
||||
)
|
||||
state.lastTranscript = ""
|
||||
recomputeMicVoiceAvailability()
|
||||
OSGDiag.log(
|
||||
"beginFlowStart → openHostApp(startflow) recordAfterHandoff=\(recordAfterHandoff) "
|
||||
|
||||
@@ -280,17 +280,15 @@ public struct KeyboardRootView: View {
|
||||
}
|
||||
|
||||
private var buttonPhase: RecordButton.Phase {
|
||||
if case .error = state.phase { return .error }
|
||||
if case .denied = state.phase { return .error }
|
||||
switch state.micVoiceAvailability {
|
||||
case .ready:
|
||||
return .idleReady
|
||||
case .unavailable:
|
||||
return .idleUnavailable
|
||||
case .recording:
|
||||
return .recording
|
||||
case .processing:
|
||||
return .processing
|
||||
case .ready, .unavailable:
|
||||
// Host/PiP readiness is handled by an automatic app handoff. Keep
|
||||
// the idle mic visually green instead of exposing startup state.
|
||||
return .idleReady
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,7 +407,16 @@ private struct TranscriptLine: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var idleHint: some View {
|
||||
let isWarning = micVoiceAvailability.isUnavailable
|
||||
let isWarning: Bool = {
|
||||
switch micVoiceAvailability {
|
||||
case .unavailable(.hostNotReady), .unavailable(.preparingSession):
|
||||
return false
|
||||
case .unavailable:
|
||||
return true
|
||||
case .ready, .recording, .processing:
|
||||
return false
|
||||
}
|
||||
}()
|
||||
Group {
|
||||
switch micVoiceAvailability {
|
||||
case .ready:
|
||||
@@ -417,17 +424,9 @@ private struct TranscriptLine: View {
|
||||
case .unavailable(.missingAPIKey):
|
||||
Text(micDisabledHint)
|
||||
case .unavailable(.hostNotReady):
|
||||
if FlowSessionPolicy.keepAliveMode() == .pictureInPicture {
|
||||
ExtL10n.text("keyboard.flow.sessionInactive.pip")
|
||||
} else {
|
||||
ExtL10n.text("keyboard.flow.sessionInactive")
|
||||
}
|
||||
ExtL10n.text("keyboard.placeholder.idle")
|
||||
case .unavailable(.preparingSession):
|
||||
if FlowSessionPolicy.keepAliveMode() == .pictureInPicture {
|
||||
ExtL10n.text("keyboard.flow.startingSession.pip")
|
||||
} else {
|
||||
ExtL10n.text("keyboard.flow.startingSession")
|
||||
}
|
||||
ExtL10n.text("keyboard.placeholder.idle")
|
||||
case .unavailable(.noFullAccess):
|
||||
ExtL10n.text("keyboard.error.fullAccessRequired")
|
||||
case .unavailable(.appGroupUnavailable):
|
||||
|
||||
Reference in New Issue
Block a user