@@ -9,6 +9,14 @@ extension BrowserViewController: TabScrollHandler.Delegate {
99 return calculateOverKeyboardScrollHeight ( safeAreaInsets: UIWindow . keyWindow? . safeAreaInsets)
1010 }
1111
12+ // Checks if minimal address bar is enabled and tab is on reader mode bar or findInPage
13+ private var shouldSendAlphaChangeAction : Bool {
14+ guard let tab = tabManager. selectedTab,
15+ let tabURL = tab. url else { return false }
16+
17+ return isMinimalAddressBarEnabled && !tab. isFindInPageMode && !tabURL. isReaderModeURL
18+ }
19+
1220 private var headerOffset : CGFloat {
1321 let baseOffset = - getHeaderSize( ) . height
1422 let isiPad = UIDevice . current. userInterfaceIdiom == . pad
@@ -20,28 +28,32 @@ extension BrowserViewController: TabScrollHandler.Delegate {
2028 return baseOffset + UX. minimalHeaderOffset
2129 }
2230
23- /// Interactive toolbar transition.
24- /// Top bar moves in [headerOffset, 0] using `originTop - clampProgress`
25- /// Bottom bar moves in [0, height]using `originBottom + clampProgress`
26- /// Values are clamped to their ranges, then layout is requested.
31+ /// Animates the toolbar transition between expanded and collapsed states based on scroll progress.
32+ /// This method applies a smooth translation transform to either the top or bottom toolbar
33+ /// (depending on whether `isBottomSearchBar` is true), animating its movement as the user scrolls.
34+ /// - Parameters:
35+ /// - progress: The current scroll progress used to determine the translation amount.
36+ /// Positive values indicate upward scrolling (collapsing), and negative values indicate downward scrolling (expanding).
37+ /// - state: The target display state of the toolbar (`.collapsed` or `.expanded`).
2738 func updateToolbarTransition( progress: CGFloat , towards state: TabScrollHandler . ToolbarDisplayState ) {
28- // Clamp movement to the intended direction (toward `state`)
29- let clampProgress = ( state == . collapsed) ? max ( 0 , progress) : min ( 0 , progress)
30-
31- // Top toolbar: range [headerOffset ... 0]
32- if !isBottomSearchBar {
33- let originTop : CGFloat = ( state == . expanded) ? 0 : headerOffset
34- let topOffset = clamp ( offset: originTop - clampProgress, min: headerOffset, max: 0 )
35- headerTopConstraint? . update ( offset: topOffset)
36- header. superview? . layoutIfNeeded ( )
39+ let isCollapsing = ( state == . collapsed)
40+ let clampProgress = isCollapsing ? max ( 0 , progress) : min ( 0 , progress)
41+
42+ UIView . animate ( withDuration: 0.2 , delay: 0 , options: [ . curveEaseOut] ) { [ self ] in
43+ if isBottomSearchBar {
44+ let translationY = isCollapsing ? clampProgress : 0
45+ let transform = CGAffineTransform ( translationX: 0 , y: translationY)
46+ bottomContainer. transform = transform
47+ overKeyboardContainer. transform = transform
48+ bottomBlurView. transform = transform
49+ } else {
50+ let topTransform = isCollapsing
51+ ? CGAffineTransform ( translationX: 0 , y: - clampProgress)
52+ : . identity
53+ header. transform = topTransform
54+ topBlurView. transform = topTransform
55+ }
3756 }
38-
39- // Bottom toolbar: range [0 ... height]
40- let bottomContainerHeight = getBottomContainerSize ( ) . height
41- let originBottom : CGFloat = ( state == . expanded) ? bottomContainerHeight : 0
42- let bottomOffset = clamp ( offset: originBottom + clampProgress, min: 0 , max: bottomContainerHeight)
43- bottomContainerConstraint? . update ( offset: bottomOffset)
44- bottomContainer. superview? . layoutIfNeeded ( )
4557 }
4658
4759 func showToolbar( ) {
@@ -67,7 +79,7 @@ extension BrowserViewController: TabScrollHandler.Delegate {
6779
6880 private func updateTopToolbar( topOffset: CGFloat , alpha: CGFloat ) {
6981 guard UIAccessibility . isReduceMotionEnabled else {
70- animateTopToolbar ( topOffset : topOffset , alpha: alpha)
82+ animateTopToolbar ( alpha: alpha)
7183 return
7284 }
7385
@@ -91,9 +103,7 @@ extension BrowserViewController: TabScrollHandler.Delegate {
91103 overKeyboardContainerOffset: CGFloat ,
92104 alpha: CGFloat ) {
93105 guard UIAccessibility . isReduceMotionEnabled else {
94- animateBottomToolbar ( bottomOffset: bottomContainerOffset,
95- overKeyboardOffset: overKeyboardContainerOffset,
96- alpha: alpha)
106+ animateBottomToolbar ( alpha: alpha)
97107 return
98108 }
99109
@@ -114,16 +124,19 @@ extension BrowserViewController: TabScrollHandler.Delegate {
114124 }
115125 }
116126
117- private func animateTopToolbar( topOffset: CGFloat , alpha: CGFloat ) {
118- headerTopConstraint? . update ( offset: topOffset)
119-
127+ private func animateTopToolbar( alpha: CGFloat ) {
128+ let isShowing = alpha == 1
120129 UIView . animate ( withDuration: UX . topToolbarDuration,
121130 delay: 0 ,
122- options: [ . curveEaseOut, . beginFromCurrentState] ) { [ weak self] in
123- guard let self else { return }
124-
125- header. superview? . layoutIfNeeded ( )
126- header. updateAlphaForSubviews ( alpha)
131+ options: [ . curveEaseOut] ) { [ self ] in
132+ if !isShowing {
133+ header. transform = . identity. translatedBy ( x: 0 , y: - topBlurView. frame. height)
134+ topBlurView. transform = . identity. translatedBy ( x: 0 ,
135+ y: - topBlurView. frame. height)
136+ } else {
137+ header. transform = . identity
138+ topBlurView. transform = . identity
139+ }
127140 }
128141
129142 if shouldSendAlphaChangeAction {
@@ -135,20 +148,23 @@ extension BrowserViewController: TabScrollHandler.Delegate {
135148 )
136149 )
137150 }
138- }
139-
140- private func animateBottomToolbar( bottomOffset: CGFloat ,
141- overKeyboardOffset: CGFloat ,
142- alpha: CGFloat ) {
143- bottomContainerConstraint? . update ( offset: bottomOffset)
144- overKeyboardContainerConstraint? . update ( offset: overKeyboardOffset)
151+ }
145152
153+ private func animateBottomToolbar( alpha: CGFloat ) {
154+ let isShowing = alpha == 1
155+ let customOffset : CGFloat = getBottomContainerSize ( ) . height + overKeyboardContainerHeight
146156 UIView . animate ( withDuration: UX . bottomToolbarDuration,
147157 delay: 0 ,
148- options: [ . curveEaseOut, . beginFromCurrentState] ) { [ weak self] in
149- guard let self else { return }
150-
151- bottomContainer. superview? . layoutIfNeeded ( )
158+ options: [ . curveEaseOut] ) { [ self ] in
159+ if !isShowing {
160+ bottomContainer. transform = . identity. translatedBy ( x: 0 , y: customOffset)
161+ overKeyboardContainer. transform = . identity. translatedBy ( x: 0 , y: customOffset)
162+ bottomBlurView. transform = . identity. translatedBy ( x: 0 , y: customOffset)
163+ } else {
164+ bottomContainer. transform = . identity
165+ overKeyboardContainer. transform = . identity
166+ bottomBlurView. transform = . identity
167+ }
152168 }
153169
154170 if shouldSendAlphaChangeAction {
@@ -162,14 +178,6 @@ extension BrowserViewController: TabScrollHandler.Delegate {
162178 }
163179 }
164180
165- // Checks if minimal address bar is enabled and tab is on reader mode bar or findInPage
166- private var shouldSendAlphaChangeAction : Bool {
167- guard let tab = tabManager. selectedTab,
168- let tabURL = tab. url else { return false }
169-
170- return isMinimalAddressBarEnabled && !tab. isFindInPageMode && !tabURL. isReaderModeURL
171- }
172-
173181 /// Helper method for testing overKeyboardScrollHeight behavior.
174182 /// - Parameters:
175183 /// - safeAreaInsets: The safe area insets to use (nil treated as .zero).
0 commit comments