refactor(flow): remove Live Activity and keep-alive mode picker
Keep voice sessions on silent low-profile PiP only, and fix custom polish style editor presentation via sheet(item:).
This commit is contained in:
@@ -57,8 +57,6 @@ public struct AppGroupConfiguration: Sendable, Equatable {
|
||||
public static let settingsCloudPayloadV2 = "config.settings.cloudPayload.v2"
|
||||
/// When true, the host app auto-returns to the source app after a cold-start handoff.
|
||||
public static let flowSkipAppSwitch = "config.flowSkipAppSwitch"
|
||||
/// Raw `FlowKeepAliveMode` value; mutually exclusive PiP vs Live Activity path.
|
||||
public static let flowKeepAliveMode = "config.flowKeepAliveMode"
|
||||
/// Raw `FlowInactivityDuration` value; session expires after this idle window.
|
||||
public static let flowInactivityDuration = "config.flowInactivityDuration"
|
||||
/// One-shot: remap previous product defaults (30m / 10m) → 5m.
|
||||
@@ -102,8 +100,6 @@ public struct AppGroupConfiguration: Sendable, Equatable {
|
||||
public var settingsICloudSyncEnabled: Bool
|
||||
/// Auto-return to the host app after `startflow` cold start (default on).
|
||||
public var flowSkipAppSwitch: Bool
|
||||
/// PiP vs Live Activity keep-alive strategy (mutually exclusive).
|
||||
public var flowKeepAliveMode: FlowKeepAliveMode
|
||||
/// Idle timeout before the Flow session ends; resets on each utterance.
|
||||
public var flowInactivityDuration: FlowInactivityDuration
|
||||
/// Whether local `SpeechAnalyzer` should attach the prepared custom language model.
|
||||
@@ -292,9 +288,6 @@ public struct AppGroupConfiguration: Sendable, Equatable {
|
||||
}
|
||||
return defaults.bool(forKey: Keys.flowSkipAppSwitch)
|
||||
}(),
|
||||
flowKeepAliveMode: FlowKeepAliveMode.fromStored(
|
||||
defaults.string(forKey: Keys.flowKeepAliveMode)
|
||||
),
|
||||
flowInactivityDuration: FlowInactivityDuration.fromStored(
|
||||
defaults.string(forKey: Keys.flowInactivityDuration)
|
||||
),
|
||||
@@ -406,7 +399,6 @@ public struct AppGroupConfiguration: Sendable, Equatable {
|
||||
defaults.set(llmThinkingEnabled, forKey: Keys.llmThinkingEnabled)
|
||||
defaults.set(activePolishStyleId, forKey: Keys.activePolishStyleId)
|
||||
defaults.set(flowSkipAppSwitch, forKey: Keys.flowSkipAppSwitch)
|
||||
defaults.set(flowKeepAliveMode.rawValue, forKey: Keys.flowKeepAliveMode)
|
||||
defaults.set(flowInactivityDuration.rawValue, forKey: Keys.flowInactivityDuration)
|
||||
defaults.set(localASRCustomLanguageModelEnabled, forKey: Keys.localASRCustomLanguageModelEnabled)
|
||||
defaults.set(personalDictionaryICloudSyncEnabled, forKey: Keys.personalDictionaryICloudSyncEnabled)
|
||||
|
||||
@@ -32,18 +32,6 @@ public enum FlowInactivityDuration: String, CaseIterable, Identifiable, Sendable
|
||||
}
|
||||
}
|
||||
|
||||
public var labelKey: String {
|
||||
switch self {
|
||||
case .oneMinute: return "settings.flow.inactivity.1m"
|
||||
case .fiveMinutes: return "settings.flow.inactivity.5m"
|
||||
case .tenMinutes: return "settings.flow.inactivity.10m"
|
||||
case .thirtyMinutes: return "settings.flow.inactivity.30m"
|
||||
case .threeHours: return "settings.flow.inactivity.3h"
|
||||
case .twelveHours: return "settings.flow.inactivity.12h"
|
||||
case .twentyFourHours: return "settings.flow.inactivity.24h"
|
||||
}
|
||||
}
|
||||
|
||||
public static func fromStored(_ raw: String?) -> FlowInactivityDuration {
|
||||
guard let raw, let value = FlowInactivityDuration(rawValue: raw) else {
|
||||
return .default
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// FlowKeepAliveMode.swift
|
||||
// OSGKeyboard · Shared
|
||||
//
|
||||
// User-selectable Flow session keep-alive strategy (mutually exclusive).
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum FlowKeepAliveMode: String, CaseIterable, Identifiable, Sendable, Codable {
|
||||
/// Continuous audio capture + Live Activity.
|
||||
case liveActivity = "liveActivity"
|
||||
/// Picture-in-picture waveform keep-alive; mic released between utterances.
|
||||
case pictureInPicture = "pictureInPicture"
|
||||
|
||||
public var id: String { rawValue }
|
||||
|
||||
/// Used when no valid keep-alive preference has been stored.
|
||||
public static let `default`: FlowKeepAliveMode = .pictureInPicture
|
||||
|
||||
public var labelKey: String {
|
||||
switch self {
|
||||
case .liveActivity: return "settings.flow.keepAlive.liveActivity"
|
||||
case .pictureInPicture: return "settings.flow.keepAlive.pictureInPicture"
|
||||
}
|
||||
}
|
||||
|
||||
public var subtitleKey: String {
|
||||
switch self {
|
||||
case .liveActivity: return "settings.flow.keepAlive.liveActivity.subtitle"
|
||||
case .pictureInPicture: return "settings.flow.keepAlive.pictureInPicture.subtitle"
|
||||
}
|
||||
}
|
||||
|
||||
public static func fromStored(_ raw: String?) -> FlowKeepAliveMode {
|
||||
guard let raw, let value = FlowKeepAliveMode(rawValue: raw) else {
|
||||
return .default
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -259,22 +259,7 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
/// PiP vs Live Activity keep-alive (mutually exclusive).
|
||||
@Published public var flowKeepAliveMode: FlowKeepAliveMode {
|
||||
didSet {
|
||||
guard !isApplyingConfiguration, flowKeepAliveMode != configuration.flowKeepAliveMode else { return }
|
||||
configuration.flowKeepAliveMode = flowKeepAliveMode
|
||||
if flowKeepAliveMode == .pictureInPicture {
|
||||
configuration.flowSkipAppSwitch = true
|
||||
if flowSkipAppSwitch != true {
|
||||
flowSkipAppSwitch = true
|
||||
}
|
||||
}
|
||||
persistConfiguration()
|
||||
}
|
||||
}
|
||||
|
||||
/// Idle window before an active Flow session expires; Live Activity mode only.
|
||||
/// Retained for storage compatibility; persistent PiP sessions do not expire from inactivity.
|
||||
@Published public var flowInactivityDuration: FlowInactivityDuration {
|
||||
didSet {
|
||||
guard !isApplyingConfiguration,
|
||||
@@ -406,7 +391,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
polishIntensity = configuration.polishIntensity
|
||||
llmThinkingEnabled = configuration.llmThinkingEnabled
|
||||
flowSkipAppSwitch = configuration.flowSkipAppSwitch
|
||||
flowKeepAliveMode = configuration.flowKeepAliveMode
|
||||
flowInactivityDuration = configuration.flowInactivityDuration
|
||||
localASRCustomLanguageModelEnabled = configuration.localASRCustomLanguageModelEnabled
|
||||
isSyncingProviderAPIKey = true
|
||||
@@ -498,7 +482,6 @@ public final class ProviderConfig: ObservableObject, @unchecked Sendable {
|
||||
polishIntensity = fresh.polishIntensity
|
||||
llmThinkingEnabled = fresh.llmThinkingEnabled
|
||||
flowSkipAppSwitch = fresh.flowSkipAppSwitch
|
||||
flowKeepAliveMode = fresh.flowKeepAliveMode
|
||||
flowInactivityDuration = fresh.flowInactivityDuration
|
||||
localASRCustomLanguageModelEnabled = fresh.localASRCustomLanguageModelEnabled
|
||||
isSyncingProviderAPIKey = true
|
||||
|
||||
@@ -31,7 +31,6 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
public var activePolishStyleId: SyncedField<String>
|
||||
public var llmThinkingEnabled: SyncedField<Bool>
|
||||
public var flowSkipAppSwitch: SyncedField<Bool>
|
||||
public var flowKeepAliveMode: SyncedField<FlowKeepAliveMode>
|
||||
public var flowInactivityDuration: SyncedField<FlowInactivityDuration>
|
||||
|
||||
public init(
|
||||
@@ -55,7 +54,6 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
activePolishStyleId: SyncedField<String>,
|
||||
llmThinkingEnabled: SyncedField<Bool>,
|
||||
flowSkipAppSwitch: SyncedField<Bool>,
|
||||
flowKeepAliveMode: SyncedField<FlowKeepAliveMode>,
|
||||
flowInactivityDuration: SyncedField<FlowInactivityDuration>
|
||||
) {
|
||||
self.schemaVersion = schemaVersion
|
||||
@@ -82,7 +80,6 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
self.activePolishStyleId = activePolishStyleId
|
||||
self.llmThinkingEnabled = llmThinkingEnabled
|
||||
self.flowSkipAppSwitch = flowSkipAppSwitch
|
||||
self.flowKeepAliveMode = flowKeepAliveMode
|
||||
self.flowInactivityDuration = flowInactivityDuration
|
||||
}
|
||||
|
||||
@@ -107,10 +104,13 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
case activePolishStyleId
|
||||
case llmThinkingEnabled
|
||||
case flowSkipAppSwitch
|
||||
case flowKeepAliveMode
|
||||
case flowInactivityDuration
|
||||
}
|
||||
|
||||
private enum LegacyCodingKeys: String, CodingKey {
|
||||
case flowKeepAliveMode
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
schemaVersion = try container.decode(Int.self, forKey: .schemaVersion)
|
||||
@@ -167,18 +167,15 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
deviceID: keyboardHapticIntensity.deviceID
|
||||
)
|
||||
flowSkipAppSwitch = try container.decode(SyncedField<Bool>.self, forKey: .flowSkipAppSwitch)
|
||||
flowKeepAliveMode = try container.decodeIfPresent(
|
||||
SyncedField<FlowKeepAliveMode>.self,
|
||||
forKey: .flowKeepAliveMode
|
||||
) ?? SyncedField(
|
||||
value: .liveActivity,
|
||||
updatedAt: flowSkipAppSwitch.updatedAt,
|
||||
deviceID: flowSkipAppSwitch.deviceID
|
||||
)
|
||||
flowInactivityDuration = try container.decode(
|
||||
SyncedField<FlowInactivityDuration>.self,
|
||||
forKey: .flowInactivityDuration
|
||||
)
|
||||
let legacyContainer = try decoder.container(keyedBy: LegacyCodingKeys.self)
|
||||
_ = try legacyContainer.decodeIfPresent(
|
||||
SyncedField<String>.self,
|
||||
forKey: .flowKeepAliveMode
|
||||
)
|
||||
|
||||
if let asrProvider = try container.decodeIfPresent(SyncedField<String>.self, forKey: .asrProviderId) {
|
||||
asrProviderId = asrProvider
|
||||
@@ -224,7 +221,6 @@ public struct SyncedAppSettingsV2: Codable, Equatable, Sendable {
|
||||
activePolishStyleId.updatedAt,
|
||||
llmThinkingEnabled.updatedAt,
|
||||
flowSkipAppSwitch.updatedAt,
|
||||
flowKeepAliveMode.updatedAt,
|
||||
flowInactivityDuration.updatedAt,
|
||||
].max() ?? .distantPast
|
||||
}
|
||||
@@ -264,7 +260,6 @@ public extension SyncedAppSettingsV2 {
|
||||
activePolishStyleId: field(configuration.activePolishStyleId),
|
||||
llmThinkingEnabled: field(configuration.llmThinkingEnabled),
|
||||
flowSkipAppSwitch: field(configuration.flowSkipAppSwitch),
|
||||
flowKeepAliveMode: field(configuration.flowKeepAliveMode),
|
||||
flowInactivityDuration: field(configuration.flowInactivityDuration)
|
||||
)
|
||||
}
|
||||
@@ -296,7 +291,6 @@ public extension SyncedAppSettingsV2 {
|
||||
activePolishStyleId: field(PolishStylePackCatalog.defaultID),
|
||||
llmThinkingEnabled: field(false),
|
||||
flowSkipAppSwitch: field(legacy.flowSkipAppSwitch),
|
||||
flowKeepAliveMode: field(.liveActivity),
|
||||
flowInactivityDuration: field(legacy.flowInactivityDuration)
|
||||
)
|
||||
}
|
||||
@@ -340,7 +334,6 @@ public extension SyncedAppSettingsV2 {
|
||||
),
|
||||
llmThinkingEnabled: .merge(local: local.llmThinkingEnabled, remote: remote.llmThinkingEnabled),
|
||||
flowSkipAppSwitch: .merge(local: local.flowSkipAppSwitch, remote: remote.flowSkipAppSwitch),
|
||||
flowKeepAliveMode: .merge(local: local.flowKeepAliveMode, remote: remote.flowKeepAliveMode),
|
||||
flowInactivityDuration: .merge(
|
||||
local: local.flowInactivityDuration,
|
||||
remote: remote.flowInactivityDuration
|
||||
@@ -368,7 +361,6 @@ public extension SyncedAppSettingsV2 {
|
||||
configuration.activePolishStyleId = activePolishStyleId.value
|
||||
configuration.llmThinkingEnabled = llmThinkingEnabled.value
|
||||
configuration.flowSkipAppSwitch = flowSkipAppSwitch.value
|
||||
configuration.flowKeepAliveMode = flowKeepAliveMode.value
|
||||
configuration.flowInactivityDuration = flowInactivityDuration.value
|
||||
}
|
||||
|
||||
@@ -398,7 +390,6 @@ public extension SyncedAppSettingsV2 {
|
||||
patch(©.activePolishStyleId, value: configuration.activePolishStyleId)
|
||||
patch(©.llmThinkingEnabled, value: configuration.llmThinkingEnabled)
|
||||
patch(©.flowSkipAppSwitch, value: configuration.flowSkipAppSwitch)
|
||||
patch(©.flowKeepAliveMode, value: configuration.flowKeepAliveMode)
|
||||
patch(©.flowInactivityDuration, value: configuration.flowInactivityDuration)
|
||||
return copy
|
||||
}
|
||||
@@ -431,7 +422,6 @@ public extension SyncedAppSettingsV2 {
|
||||
touch(©.activePolishStyleId, value: configuration.activePolishStyleId)
|
||||
touch(©.llmThinkingEnabled, value: configuration.llmThinkingEnabled)
|
||||
touch(©.flowSkipAppSwitch, value: configuration.flowSkipAppSwitch)
|
||||
touch(©.flowKeepAliveMode, value: configuration.flowKeepAliveMode)
|
||||
touch(©.flowInactivityDuration, value: configuration.flowInactivityDuration)
|
||||
return copy
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public enum FlowKeyboardHostWarming {
|
||||
/// Keep the mic green after the session has already proven ready.
|
||||
///
|
||||
/// Inter-utterance PiP flaps (mic release, ack lag, brief `reason=.starting`)
|
||||
/// used to flash yellow「正在启动画中画」even though Picture in Picture was
|
||||
/// used to flash yellow「正在启动…」even though the keep-alive surface was
|
||||
/// already running. Hold ready through those windows; real cold starts still
|
||||
/// go through `isHostWarming` while `sessionProvenReady` is false.
|
||||
public static func shouldHoldReady(
|
||||
|
||||
@@ -405,9 +405,8 @@ public enum FlowSessionBridge {
|
||||
// a permanent orange `preparingSession` state.
|
||||
clearHostReady(defaults: store, notify: false)
|
||||
}
|
||||
if let expires = snapshot.sessionExpiresAt {
|
||||
store.set(expires, forKey: FlowSessionKeys.flowSessionExpires)
|
||||
}
|
||||
// PiP sessions are persistent; clear expiry left by older Live Activity builds.
|
||||
store.removeObject(forKey: FlowSessionKeys.flowSessionExpires)
|
||||
// Only a genuinely live host — ready, or actively serving an
|
||||
// utterance — may refresh the heartbeat here. A host stuck in a
|
||||
// failed cold start would otherwise keep "reviving" itself on every
|
||||
@@ -430,20 +429,7 @@ public enum FlowSessionBridge {
|
||||
|
||||
// MARK: - Session lifecycle (host app)
|
||||
|
||||
public static func markSessionActive(
|
||||
duration: TimeInterval? = nil,
|
||||
sessionId: UUID? = nil,
|
||||
defaults: UserDefaults? = nil
|
||||
) {
|
||||
let store = resolvedDefaults(defaults)
|
||||
if FlowSessionPolicy.usesInactivityExpiry(defaults: store) {
|
||||
markSessionActiveWithExpiry(duration: duration, sessionId: sessionId, defaults: store)
|
||||
} else {
|
||||
markSessionActivePersistent(sessionId: sessionId, defaults: store)
|
||||
}
|
||||
}
|
||||
|
||||
/// PiP keep-alive: session stays valid until explicit teardown (no idle expiry).
|
||||
/// PiP keep-alive: session stays valid until explicit teardown.
|
||||
public static func markSessionActivePersistent(
|
||||
sessionId: UUID? = nil,
|
||||
defaults: UserDefaults? = nil
|
||||
@@ -480,44 +466,6 @@ public enum FlowSessionBridge {
|
||||
flush(store)
|
||||
}
|
||||
|
||||
private static func markSessionActiveWithExpiry(
|
||||
duration: TimeInterval? = nil,
|
||||
sessionId: UUID? = nil,
|
||||
defaults: UserDefaults
|
||||
) {
|
||||
let resolvedDuration = duration ?? FlowSessionPolicy.sessionDuration(defaults: defaults)
|
||||
let now = Date().timeIntervalSince1970
|
||||
let expires = now + resolvedDuration
|
||||
defaults.set(true, forKey: FlowSessionKeys.flowSessionActive)
|
||||
defaults.set(expires, forKey: FlowSessionKeys.flowSessionExpires)
|
||||
defaults.set(now, forKey: FlowSessionKeys.lastActivityAt)
|
||||
writeHeartbeat(defaults: defaults)
|
||||
clearTranscription(defaults: defaults)
|
||||
defaults.removeObject(forKey: FlowSessionKeys.flowCommandPayload)
|
||||
defaults.removeObject(forKey: FlowSessionKeys.flowCommandJournalPayload)
|
||||
defaults.removeObject(forKey: FlowSessionKeys.flowResultPayload)
|
||||
defaults.removeObject(forKey: FlowSessionKeys.flowAckPayload)
|
||||
defaults.removeObject(forKey: FlowSessionKeys.pendingKeyboardUtteranceId)
|
||||
if let sessionId {
|
||||
let snapshot = FlowReadySnapshot(
|
||||
sessionId: sessionId,
|
||||
ready: false,
|
||||
reason: .starting,
|
||||
heartbeatAt: now,
|
||||
engineMode: AppGroupConfiguration.load(fromAvailable: defaults).engineMode,
|
||||
localeId: AppGroupConfiguration.load(fromAvailable: defaults).localeId,
|
||||
sessionExpiresAt: expires,
|
||||
hostGeneration: defaults.string(forKey: FlowSessionKeys.hostGeneration)
|
||||
)
|
||||
if let data = encode(snapshot) {
|
||||
defaults.set(data, forKey: FlowSessionKeys.flowReadyPayload)
|
||||
}
|
||||
} else {
|
||||
defaults.removeObject(forKey: FlowSessionKeys.flowReadyPayload)
|
||||
}
|
||||
flush(defaults)
|
||||
}
|
||||
|
||||
public static func markSessionInactive(defaults: UserDefaults? = nil) {
|
||||
let store = resolvedDefaults(defaults)
|
||||
store.set(false, forKey: FlowSessionKeys.flowSessionActive)
|
||||
@@ -544,31 +492,6 @@ public enum FlowSessionBridge {
|
||||
flush(store)
|
||||
}
|
||||
|
||||
public static func extendSession(
|
||||
by duration: TimeInterval? = nil,
|
||||
defaults: UserDefaults? = nil
|
||||
) {
|
||||
let store = resolvedDefaults(defaults)
|
||||
guard FlowSessionPolicy.usesInactivityExpiry(defaults: store) else { return }
|
||||
let resolvedDuration = duration ?? FlowSessionPolicy.sessionDuration(defaults: store)
|
||||
let expires = Date().timeIntervalSince1970 + resolvedDuration
|
||||
store.set(true, forKey: FlowSessionKeys.flowSessionActive)
|
||||
store.set(expires, forKey: FlowSessionKeys.flowSessionExpires)
|
||||
flush(store)
|
||||
}
|
||||
|
||||
/// Resets the inactivity timer after utterance completion or explicit activity.
|
||||
public static func touchLastActivity(defaults: UserDefaults? = nil) {
|
||||
let store = resolvedDefaults(defaults)
|
||||
guard FlowSessionPolicy.usesInactivityExpiry(defaults: store) else { return }
|
||||
let now = Date().timeIntervalSince1970
|
||||
let duration = FlowSessionPolicy.sessionDuration(defaults: store)
|
||||
store.set(now, forKey: FlowSessionKeys.lastActivityAt)
|
||||
store.set(now + duration, forKey: FlowSessionKeys.flowSessionExpires)
|
||||
store.set(true, forKey: FlowSessionKeys.flowSessionActive)
|
||||
flush(store)
|
||||
}
|
||||
|
||||
// MARK: - Host return (scheme D)
|
||||
|
||||
public static func setPendingHostBundleId(_ bundleId: String?, defaults: UserDefaults? = nil) {
|
||||
@@ -592,18 +515,11 @@ public enum FlowSessionBridge {
|
||||
|
||||
// MARK: - Session validity (keyboard)
|
||||
|
||||
/// True when the App Group session contract is still valid (not expired).
|
||||
/// True while the persistent PiP session contract is active.
|
||||
/// Does **not** mean the host can accept utterances — use `isHostReady()`.
|
||||
public static func isSessionActive(defaults: UserDefaults? = nil) -> Bool {
|
||||
let store = resolvedDefaults(defaults)
|
||||
guard store.bool(forKey: FlowSessionKeys.flowSessionActive) else { return false }
|
||||
|
||||
if !FlowSessionPolicy.usesInactivityExpiry(defaults: store) {
|
||||
return true
|
||||
}
|
||||
|
||||
let expires = store.double(forKey: FlowSessionKeys.flowSessionExpires)
|
||||
return expires > Date().timeIntervalSince1970
|
||||
return store.bool(forKey: FlowSessionKeys.flowSessionActive)
|
||||
}
|
||||
|
||||
/// Seconds since the host last wrote `flowHeartbeat`; nil when never written.
|
||||
@@ -800,19 +716,6 @@ public enum FlowSessionBridge {
|
||||
return true
|
||||
}
|
||||
|
||||
public static func sessionExpiresAt(defaults: UserDefaults? = nil) -> TimeInterval? {
|
||||
let store = resolvedDefaults(defaults)
|
||||
let expires = store.double(forKey: FlowSessionKeys.flowSessionExpires)
|
||||
return expires > 0 ? expires : nil
|
||||
}
|
||||
|
||||
/// Seconds until session expiry; nil when expired or never started.
|
||||
public static func remainingSessionDuration(defaults: UserDefaults? = nil) -> TimeInterval? {
|
||||
guard let expires = sessionExpiresAt(defaults: defaults) else { return nil }
|
||||
let remaining = expires - Date().timeIntervalSince1970
|
||||
return remaining > 0 ? remaining : nil
|
||||
}
|
||||
|
||||
// MARK: - Recording signals (keyboard → host)
|
||||
|
||||
public static func setRecordingState(
|
||||
|
||||
@@ -25,18 +25,6 @@ public enum FlowSessionPolicy {
|
||||
inactivityDuration(defaults: defaults).timeInterval
|
||||
}
|
||||
|
||||
public static func keepAliveMode(defaults: UserDefaults? = nil) -> FlowKeepAliveMode {
|
||||
let store = resolvedDefaults(defaults)
|
||||
return FlowKeepAliveMode.fromStored(
|
||||
store.string(forKey: AppGroupConfiguration.Keys.flowKeepAliveMode)
|
||||
)
|
||||
}
|
||||
|
||||
/// PiP sessions have no inactivity expiry; only the Live Activity path times out.
|
||||
public static func usesInactivityExpiry(defaults: UserDefaults? = nil) -> Bool {
|
||||
keepAliveMode(defaults: defaults) == .liveActivity
|
||||
}
|
||||
|
||||
private static func resolvedDefaults(_ defaults: UserDefaults?) -> UserDefaults {
|
||||
if let defaults { return defaults }
|
||||
guard let available = AppGroup.defaultsIfAvailable else {
|
||||
|
||||
@@ -36,15 +36,8 @@ public enum FlowDebugAppGroupSnapshot {
|
||||
}
|
||||
return String(g.prefix(8))
|
||||
}()
|
||||
let expires: String = {
|
||||
guard let ts = FlowSessionBridge.sessionExpiresAt(defaults: defaults) else { return "nil" }
|
||||
let remaining = ts - Date().timeIntervalSince1970
|
||||
return String(format: "%.0fs", remaining)
|
||||
}()
|
||||
|
||||
return [
|
||||
FlowDebugRow("sessionActive", FlowSessionBridge.isSessionActive(defaults: defaults) ? "1" : "0"),
|
||||
FlowDebugRow("expiresIn", expires),
|
||||
FlowDebugRow("hostReachable", FlowSessionBridge.isHostReachable(defaults: defaults) ? "1" : "0"),
|
||||
FlowDebugRow("hostReady", FlowSessionBridge.isHostReady(defaults: defaults) ? "1" : "0"),
|
||||
FlowDebugRow("hostStale", FlowSessionBridge.isHostStale(defaults: defaults) ? "1" : "0"),
|
||||
|
||||
Reference in New Issue
Block a user