forked from BasedHardware/omi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathFloatingBarPresentation.swift
More file actions
186 lines (169 loc) · 6.73 KB
/
Copy pathFloatingBarPresentation.swift
File metadata and controls
186 lines (169 loc) · 6.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import Foundation
import OmiTheme
import SwiftUI
extension FloatingControlBarWindow {
func routePrimaryTextInputToMainAppAfterAgentExit() -> Bool {
guard
FloatingPrimaryTextInputRouting.shouldRouteAgentExitToMainApp(
hasMainConversation: state.hasMainConversation)
else { return false }
closeAIConversation()
AppDelegate.summonWindowTarget()?.openMainAppChat()
return true
}
}
/// Whether a presentation request is allowed to change the user's durable
/// Floating Bar preference. Onboarding may borrow the real bar for a demo, but
/// must leave that preference exactly as it found it.
enum FloatingBarPreferenceMutation: Equatable {
case setEnabled(Bool)
case preserve
/// `nil` means the presentation is transient and must not write settings.
var persistedEnabledValue: Bool? {
switch self {
case .setEnabled(let enabled): return enabled
case .preserve: return nil
}
}
}
extension FloatingControlBarWindow {
var shouldOrderOutAfterConversationClose: Bool {
!FloatingControlBarManager.shared.isEnabled
&& state.currentNotification == nil
&& !state.showingAIConversation
}
/// Starts the token-fenced retraction after presentation eligibility has
/// been established. Keeping this boundary independent of `isVisible`
/// lets lifecycle tests exercise stale deadlines on headless CI runners.
func beginNotchRetraction(then completion: @escaping () -> Void) {
notchRetractionCancellation?.cancel()
cancelInFlightNotchReveal()
// Cancel in-flight *frame* animations. Retract/reveal use dedicated
// generations so a later no-op resize cannot strand the island at 0.01.
frameAnimationToken += 1
notchRetractionGeneration &+= 1
let generation = notchRetractionGeneration
OmiMotion.withGated(.easeIn(duration: 0.18)) {
state.notchRevealProgress = FloatingBarNotchRevealPolicy.retractedProgress
}
notchRetractionCancellation = notchRetractionScheduler.schedule(after: 0.18) { [weak self] in
guard let self else { return }
guard self.notchRetractionGeneration == generation else {
self.restoreNotchRevealProgressIfWindowStillVisible()
return
}
completion()
// Leave the island ready to render for show paths that skip the
// reveal (e.g. showTemporarily) — the next reveal re-zeroes it.
self.state.notchRevealProgress = FloatingBarNotchRevealPolicy.revealedProgress
self.notchRetractionCancellation = nil
}
}
func restoreNotchRevealProgressIfWindowStillVisible() {
guard
FloatingBarNotchRevealPolicy.shouldRestoreProgressAfterCancelledRetract(
windowStillVisible: isVisible,
retractStillInFlight: notchRetractionCancellation != nil,
revealStillInFlight: notchRevealCancellation != nil)
else { return }
state.notchRevealProgress = FloatingBarNotchRevealPolicy.revealedProgress
}
func scheduleNotchRevealCompletion(generation: Int, after duration: TimeInterval) {
notchRevealCancellation?.cancel()
notchRevealCancellation = notchRetractionScheduler.schedule(after: duration) { [weak self] in
guard let self else { return }
guard self.notchRevealGeneration == generation else {
self.restoreNotchRevealProgressIfWindowStillVisible()
return
}
self.state.notchRevealProgress = FloatingBarNotchRevealPolicy.revealedProgress
self.notchRevealCancellation = nil
}
}
func cancelInFlightNotchReveal() {
notchRevealGeneration &+= 1
notchRevealCancellation?.cancel()
notchRevealCancellation = nil
}
}
/// Shared reveal implementation for persistent settings, temporary snoozes,
/// and direct Push-to-Talk presentation.
extension FloatingControlBarManager {
/// Applies the saved launch preference without overriding a temporary
/// notification snooze. Push-to-Talk and Settings call `show()` instead.
func showForLaunch() {
present(.background, preferenceMutation: .preserve)
}
/// Shows the real Floating Bar for an onboarding voice demo without changing
/// the user's saved bar setting.
func showForOnboardingDemo() {
present(.explicitUserAction, preferenceMutation: .preserve)
}
/// Retracts an onboarding voice demo without changing the user's saved bar
/// setting, then restores the durable bar if it is still enabled.
func hideForOnboardingDemo() {
retractThenRestoreDurablePresentation()
}
/// After a demo `orderOut`, put the user's saved bar back. Disabled and
/// snoozed bars stay hidden; the preference is never written.
func restoreDurableBarAfterOnboardingDemo() {
guard
FloatingBarDurableVisibilityPolicy.shouldRestoreAfterOnboardingDemo(
isEnabled: isEnabled,
isSnoozed: isSnoozed)
else { return }
showForLaunch()
}
private func retractThenRestoreDurablePresentation() {
guard let window else { return }
window.retractIntoNotch { [weak self, weak window] in
window?.orderOut(nil)
self?.restoreDurableBarAfterOnboardingDemo()
}
}
func present(
_ request: FloatingBarPresentationRequest,
preferenceMutation: FloatingBarPreferenceMutation
) {
log("FloatingControlBarManager: show() called, window=\(window != nil), isVisible=\(window?.isVisible ?? false)")
if let enabled = preferenceMutation.persistedEnabledValue {
isEnabled = enabled
}
guard FloatingBarPresentationPolicy.shouldPresent(request: request, isSnoozed: isSnoozed) else {
return
}
// Reveal on every hidden→present transition (not just once per session):
// the island should always grow out of the notch instead of popping in.
let shouldPlayNotchReveal =
window?.usesNotchIslandForCurrentScreen == true
&& (window?.isVisible != true || !hasRevealedNotchThisSession)
hasRevealedNotchThisSession = true
window?.applySurfaceLevel()
window?.normalizeForTemporaryShow()
window?.makeKeyAndOrderFront(nil)
if shouldPlayNotchReveal {
window?.playNotchRevealAnimation()
}
log("FloatingControlBarManager: show() done, frame=\(window?.frame ?? .zero)")
// Auto-focus input if AI conversation is open.
if let window, window.state.showingAIConversation && !window.state.showingAIResponse {
if !window.focusInputField() {
// SwiftUI may still be attaching the text view. Retry on its next
// layout pass rather than relying on a fixed wall-clock delay.
DispatchQueue.main.async { [weak window] in
window?.focusInputField()
}
}
}
}
func retract(preferenceMutation: FloatingBarPreferenceMutation) {
if let enabled = preferenceMutation.persistedEnabledValue {
isEnabled = enabled
}
if let window {
window.retractIntoNotch { [weak window] in
window?.orderOut(nil)
}
}
}
}