feat(flow): add Picture in Picture keep-alive mode
- Add FlowKeepAliveMode (Dynamic Island vs PiP) with iCloud sync - Settings: keep-alive picker; inactivity timeout only for Live Activity - PiP: waveform sample-buffer controller, mic released between utterances - PiP sessions have no idle expiry; user closing PiP ends the session - FlowSessionManager branches hostReady, session start, and utterance paths - UIBackgroundModes picture-in-picture; bilingual strings and tests Co-authored-by: Rocky <hkgood@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,7 @@ final class AppGroupConfigurationTests: XCTestCase {
|
||||
XCTAssertEqual(config.polishIntensity, .default)
|
||||
XCTAssertTrue(config.personalDictionary.entries.isEmpty)
|
||||
XCTAssertTrue(config.flowSkipAppSwitch)
|
||||
XCTAssertEqual(config.flowKeepAliveMode, .liveActivity)
|
||||
XCTAssertEqual(config.flowInactivityDuration, .fiveMinutes)
|
||||
}
|
||||
|
||||
@@ -58,6 +59,7 @@ final class AppGroupConfigurationTests: XCTestCase {
|
||||
config.cursorDragNavigationEnabled = false
|
||||
config.polishIntensity = .light
|
||||
config.flowSkipAppSwitch = false
|
||||
config.flowKeepAliveMode = .pictureInPicture
|
||||
// Use a non-default value so the round-trip actually proves persistence.
|
||||
config.flowInactivityDuration = .threeHours
|
||||
config.save(to: defaults)
|
||||
@@ -81,6 +83,7 @@ final class AppGroupConfigurationTests: XCTestCase {
|
||||
XCTAssertFalse(loaded.cursorDragNavigationEnabled)
|
||||
XCTAssertEqual(loaded.polishIntensity, .light)
|
||||
XCTAssertFalse(loaded.flowSkipAppSwitch)
|
||||
XCTAssertEqual(loaded.flowKeepAliveMode, .pictureInPicture)
|
||||
XCTAssertEqual(loaded.flowInactivityDuration, .threeHours)
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,25 @@ final class FlowSessionPolicyTests: XCTestCase {
|
||||
XCTAssertEqual(FlowInactivityDuration.tenMinutes.timeInterval, 10 * 60)
|
||||
}
|
||||
|
||||
func testKeepAliveModeDefaultsToLiveActivity() {
|
||||
let defaults = makeDefaults()
|
||||
XCTAssertEqual(FlowSessionPolicy.keepAliveMode(defaults: defaults), .liveActivity)
|
||||
XCTAssertTrue(FlowSessionPolicy.usesInactivityExpiry(defaults: defaults))
|
||||
}
|
||||
|
||||
func testPiPSessionHasNoInactivityExpiry() {
|
||||
let defaults = makeDefaults()
|
||||
defaults.set(FlowKeepAliveMode.pictureInPicture.rawValue,
|
||||
forKey: AppGroupConfiguration.Keys.flowKeepAliveMode)
|
||||
FlowSessionBridge.markSessionActive(sessionId: UUID(), defaults: defaults)
|
||||
|
||||
XCTAssertTrue(FlowSessionBridge.isSessionActive(defaults: defaults))
|
||||
XCTAssertNil(FlowSessionBridge.sessionExpiresAt(defaults: defaults))
|
||||
|
||||
FlowSessionBridge.touchLastActivity(defaults: defaults)
|
||||
XCTAssertNil(FlowSessionBridge.sessionExpiresAt(defaults: defaults))
|
||||
}
|
||||
|
||||
func testTouchLastActivityExtendsExpiry() {
|
||||
let defaults = makeDefaults()
|
||||
defaults.set(FlowInactivityDuration.tenMinutes.rawValue, forKey: AppGroupConfiguration.Keys.flowInactivityDuration)
|
||||
|
||||
@@ -62,6 +62,7 @@ final class SettingsCloudSyncTests: XCTestCase {
|
||||
polishIntensity: SyncedField(value: .medium, updatedAt: stampA, deviceID: deviceA),
|
||||
llmThinkingEnabled: SyncedField(value: false, updatedAt: stampA, deviceID: deviceA),
|
||||
flowSkipAppSwitch: SyncedField(value: true, updatedAt: stampA, deviceID: deviceA),
|
||||
flowKeepAliveMode: SyncedField(value: .liveActivity, updatedAt: stampA, deviceID: deviceA),
|
||||
flowInactivityDuration: SyncedField(value: .twelveHours, updatedAt: stampA, deviceID: deviceA)
|
||||
)
|
||||
let remote = SyncedAppSettingsV2(
|
||||
@@ -82,6 +83,7 @@ final class SettingsCloudSyncTests: XCTestCase {
|
||||
polishIntensity: SyncedField(value: .light, updatedAt: stampB, deviceID: deviceB),
|
||||
llmThinkingEnabled: SyncedField(value: true, updatedAt: stampB, deviceID: deviceB),
|
||||
flowSkipAppSwitch: SyncedField(value: false, updatedAt: stampB, deviceID: deviceB),
|
||||
flowKeepAliveMode: SyncedField(value: .pictureInPicture, updatedAt: stampB, deviceID: deviceB),
|
||||
flowInactivityDuration: SyncedField(value: .threeHours, updatedAt: stampB, deviceID: deviceB)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user