Skip to content

Commit ea4f1b5

Browse files
authored
update find in page feature in FadeOmnibarLayout (#5927)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1208671518894266/task/1209874980847232 ### Description The "find in page" state is currently fully managed by `BrowserTabViewModel`. This PR makes the omnibar aware of the visibility portion of that state - just enough for it to react and pass it down to its children. Functionally, there’s no change for the production omnibar. But in the experimental omnibar - where the "find in page" UI is moved inside the card - we can now detect when it’s shown and hide other content accordingly. This lets us reuse the omnibar card’s existing design and animations. This PR keeps the changes minimal, focused on adapting "find in page" for the experimental omnibar. As a follow up, in #5926, I'm attempting to migrate the whole of "find in page" state management out of `BrowserTabViewModel` and into the omnibar, along with further cleanup.
1 parent c105d8b commit ea4f1b5

File tree

12 files changed

+318
-55
lines changed

12 files changed

+318
-55
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2025 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.omnibar
18+
19+
import android.view.ViewGroup
20+
import android.widget.EditText
21+
import android.widget.ImageView
22+
import com.duckduckgo.app.browser.databinding.IncludeFadeOmnibarFindInPageBinding
23+
import com.duckduckgo.app.browser.databinding.IncludeFindInPageBinding
24+
import com.duckduckgo.common.ui.view.text.DaxTextView
25+
26+
/**
27+
* Compatibility interface for accessing [IncludeFindInPageBinding] or [IncludeFadeOmnibarFindInPageBinding], depending on which omnibar is used.
28+
*/
29+
interface FindInPage {
30+
val findInPageContainer: ViewGroup
31+
val findInPageInput: EditText
32+
val findInPageMatches: DaxTextView
33+
val previousSearchTermButton: ImageView
34+
val nextSearchTermButton: ImageView
35+
val closeFindInPagePanel: ImageView
36+
}
37+
38+
class FindInPageImpl(
39+
override val findInPageContainer: ViewGroup,
40+
override val findInPageInput: EditText,
41+
override val findInPageMatches: DaxTextView,
42+
override val previousSearchTermButton: ImageView,
43+
override val nextSearchTermButton: ImageView,
44+
override val closeFindInPagePanel: ImageView,
45+
) : FindInPage {
46+
47+
constructor(binding: IncludeFindInPageBinding) : this(
48+
findInPageContainer = binding.findInPageContainer,
49+
findInPageInput = binding.findInPageInput,
50+
findInPageMatches = binding.findInPageMatches,
51+
previousSearchTermButton = binding.previousSearchTermButton,
52+
nextSearchTermButton = binding.nextSearchTermButton,
53+
closeFindInPagePanel = binding.closeFindInPagePanel,
54+
)
55+
56+
constructor(binding: IncludeFadeOmnibarFindInPageBinding) : this(
57+
findInPageContainer = binding.findInPageContainer,
58+
findInPageInput = binding.findInPageInput,
59+
findInPageMatches = binding.findInPageMatches,
60+
previousSearchTermButton = binding.previousSearchTermButton,
61+
nextSearchTermButton = binding.nextSearchTermButton,
62+
closeFindInPagePanel = binding.closeFindInPagePanel,
63+
)
64+
}

app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import com.airbnb.lottie.LottieAnimationView
2828
import com.duckduckgo.app.browser.BrowserTabFragment.Companion.KEYBOARD_DELAY
2929
import com.duckduckgo.app.browser.R
3030
import com.duckduckgo.app.browser.databinding.FragmentBrowserTabBinding
31-
import com.duckduckgo.app.browser.databinding.IncludeFindInPageBinding
3231
import com.duckduckgo.app.browser.navigation.bar.view.BrowserNavigationBarView
3332
import com.duckduckgo.app.browser.omnibar.Omnibar.ViewMode.CustomTab
3433
import com.duckduckgo.app.browser.omnibar.Omnibar.ViewMode.Error
@@ -208,7 +207,7 @@ class Omnibar(
208207
}
209208
}
210209

211-
private val findInPage: IncludeFindInPageBinding by lazy {
210+
private val findInPage: FindInPage by lazy {
212211
newOmnibar.findInPage
213212
}
214213

app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.HighlightOmni
5757
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.LaunchCookiesAnimation
5858
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.LaunchTrackersAnimation
5959
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.Mode
60-
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.Outline
6160
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.PrivacyShieldChanged
6261
import com.duckduckgo.app.browser.omnibar.OmnibarLayout.Decoration.QueueCookiesAnimation
6362
import com.duckduckgo.app.browser.omnibar.OmnibarLayoutViewModel.Command
@@ -125,7 +124,6 @@ open class OmnibarLayout @JvmOverloads constructor(
125124
val privacyShield: Boolean,
126125
) : Decoration()
127126

128-
data class Outline(val enabled: Boolean) : Decoration()
129127
data class DisableVoiceSearch(val url: String) : Decoration()
130128
}
131129

@@ -167,7 +165,9 @@ open class OmnibarLayout @JvmOverloads constructor(
167165
private var lastViewMode: Mode? = null
168166
private var stateBuffer: MutableList<StateChange> = mutableListOf()
169167

170-
internal val findInPage by lazy { IncludeFindInPageBinding.bind(findViewById(R.id.findInPage)) }
168+
internal open val findInPage: FindInPage by lazy {
169+
FindInPageImpl(IncludeFindInPageBinding.bind(findViewById(R.id.findInPage)))
170+
}
171171
internal val omnibarTextInput: KeyboardAwareEditText by lazy { findViewById(R.id.omnibarTextInput) }
172172
internal val tabsMenu: TabSwitcherButton by lazy { findViewById(R.id.tabsMenu) }
173173
internal val fireIconMenu: FrameLayout by lazy { findViewById(R.id.fireIconMenu) }
@@ -613,10 +613,6 @@ open class OmnibarLayout @JvmOverloads constructor(
613613
viewModel.onPrivacyShieldChanged(decoration.privacyShield)
614614
}
615615

616-
is Outline -> {
617-
viewModel.onOutlineEnabled(decoration.enabled)
618-
}
619-
620616
Decoration.CancelAnimations -> {
621617
cancelTrackersAnimation()
622618
}

app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -374,15 +374,6 @@ class OmnibarLayoutViewModel @Inject constructor(
374374
}
375375
}
376376

377-
fun onOutlineEnabled(enabled: Boolean) {
378-
Timber.d("Omnibar: onOutlineEnabled")
379-
_viewState.update {
380-
it.copy(
381-
hasFocus = enabled,
382-
)
383-
}
384-
}
385-
386377
fun onClearTextButtonPressed() {
387378
Timber.d("Omnibar: onClearTextButtonPressed")
388379
firePixelBasedOnCurrentUrl(

app/src/main/java/com/duckduckgo/app/browser/omnibar/experiments/FadeOmnibarLayout.kt

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.content.Context
2121
import android.util.AttributeSet
2222
import android.view.View
2323
import android.view.ViewOutlineProvider
24+
import android.view.ViewTreeObserver.OnGlobalLayoutListener
2425
import android.view.animation.DecelerateInterpolator
2526
import android.widget.ImageView
2627
import android.widget.LinearLayout
@@ -32,7 +33,10 @@ import androidx.core.view.marginTop
3233
import androidx.core.view.updatePadding
3334
import com.duckduckgo.anvil.annotations.InjectWith
3435
import com.duckduckgo.app.browser.R
36+
import com.duckduckgo.app.browser.databinding.IncludeFadeOmnibarFindInPageBinding
3537
import com.duckduckgo.app.browser.navigation.bar.view.BrowserNavigationBarView
38+
import com.duckduckgo.app.browser.omnibar.FindInPage
39+
import com.duckduckgo.app.browser.omnibar.FindInPageImpl
3640
import com.duckduckgo.app.browser.omnibar.Omnibar.ViewMode
3741
import com.duckduckgo.app.browser.omnibar.OmnibarLayout
3842
import com.duckduckgo.app.browser.omnibar.OmnibarLayoutViewModel.ViewState
@@ -57,6 +61,22 @@ class FadeOmnibarLayout @JvmOverloads constructor(
5761
private val omniBarContentContainer: View by lazy { findViewById(R.id.omniBarContentContainer) }
5862
private val backIcon: ImageView by lazy { findViewById(R.id.backIcon) }
5963

64+
override val findInPage: FindInPage by lazy {
65+
FindInPageImpl(IncludeFadeOmnibarFindInPageBinding.bind(findViewById(R.id.findInPage)))
66+
}
67+
private var isFindInPageVisible = false
68+
private val findInPageLayoutVisibilityChangeListener = OnGlobalLayoutListener {
69+
val isVisible = findInPage.findInPageContainer.isVisible
70+
if (isFindInPageVisible != isVisible) {
71+
isFindInPageVisible = isVisible
72+
if (isVisible) {
73+
onFindInPageShown()
74+
} else {
75+
onFindInPageHidden()
76+
}
77+
}
78+
}
79+
6080
/**
6181
* Returns the [BrowserNavigationBarView] reference if it's embedded inside of this omnibar layout, otherwise, returns null.
6282
*/
@@ -120,9 +140,15 @@ class FadeOmnibarLayout @JvmOverloads constructor(
120140
}
121141
}
122142

143+
override fun onAttachedToWindow() {
144+
super.onAttachedToWindow()
145+
findInPage.findInPageContainer.viewTreeObserver.addOnGlobalLayoutListener(findInPageLayoutVisibilityChangeListener)
146+
}
147+
123148
override fun onDetachedFromWindow() {
124149
super.onDetachedFromWindow()
125150
focusAnimator?.cancel()
151+
findInPage.findInPageContainer.viewTreeObserver.removeOnGlobalLayoutListener(findInPageLayoutVisibilityChangeListener)
126152
}
127153

128154
override fun render(viewState: ViewState) {
@@ -158,8 +184,7 @@ class FadeOmnibarLayout @JvmOverloads constructor(
158184
backIcon.gone()
159185
}
160186

161-
omniBarContainer.isPressed = viewState.hasFocus
162-
if (viewState.hasFocus) {
187+
if (viewState.hasFocus || isFindInPageVisible) {
163188
animateOmnibarFocusedState(focused = true)
164189
} else {
165190
animateOmnibarFocusedState(focused = false)
@@ -182,10 +207,10 @@ class FadeOmnibarLayout @JvmOverloads constructor(
182207
val startCardMarginBottom = omnibarCard.marginBottom
183208
val startCardMarginStart = omnibarCard.marginStart
184209
val startCardMarginEnd = omnibarCard.marginEnd
185-
val startContentPaddingTop = omniBarContentContainer.paddingTop
186-
val startContentPaddingBottom = omniBarContentContainer.paddingBottom
187-
val startContentPaddingStart = omniBarContentContainer.paddingStart
188-
val startContentPaddingEnd = omniBarContentContainer.paddingEnd
210+
val startContentPaddingTop = omnibarCard.contentPaddingTop
211+
val startContentPaddingBottom = omnibarCard.contentPaddingBottom
212+
val startContentPaddingStart = omnibarCard.contentPaddingLeft
213+
val startContentPaddingEnd = omnibarCard.contentPaddingRight
189214
val startCardStrokeWidth = omnibarCard.strokeWidth
190215

191216
val endCardMarginTop: Int
@@ -243,7 +268,7 @@ class FadeOmnibarLayout @JvmOverloads constructor(
243268
params.bottomMargin = animatedCardMarginBottom
244269
omnibarCard.setLayoutParams(params)
245270

246-
omniBarContentContainer.setPadding(
271+
omnibarCard.setContentPadding(
247272
animatedContentPaddingStart,
248273
animatedContentPaddingTop,
249274
animatedContentPaddingEnd,
@@ -257,6 +282,18 @@ class FadeOmnibarLayout @JvmOverloads constructor(
257282
focusAnimator = animator
258283
}
259284

285+
private fun onFindInPageShown() {
286+
omniBarContentContainer.gone()
287+
animateOmnibarFocusedState(focused = true)
288+
}
289+
290+
private fun onFindInPageHidden() {
291+
omniBarContentContainer.show()
292+
if (!viewModel.viewState.value.hasFocus) {
293+
animateOmnibarFocusedState(focused = false)
294+
}
295+
}
296+
260297
fun setFadeOmnibarItemPressedListener(itemPressedListener: FadeOmnibarItemPressedListener) {
261298
fadeOmnibarItemPressedListener = itemPressedListener
262299
aiChat.setOnClickListener {
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="24dp"
19+
android:height="24dp"
20+
android:viewportWidth="24"
21+
android:viewportHeight="24">
22+
<path
23+
android:pathData="M3.22,8.22C2.927,8.513 2.927,8.987 3.22,9.28L10.232,16.293C11.208,17.269 12.792,17.269 13.768,16.293L20.905,9.155C21.198,8.862 21.198,8.388 20.905,8.095C20.612,7.802 20.138,7.802 19.845,8.095L12.707,15.232C12.317,15.623 11.683,15.623 11.293,15.232L4.28,8.22C3.987,7.927 3.513,7.927 3.22,8.22Z"
24+
android:fillColor="?attr/daxColorSecondaryIcon"
25+
android:fillType="evenOdd"/>
26+
</vector>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="24dp"
19+
android:height="24dp"
20+
android:viewportWidth="24"
21+
android:viewportHeight="24">
22+
<path
23+
android:pathData="M3.22,15.78C2.927,15.487 2.927,15.013 3.22,14.72L10.232,7.707C11.208,6.731 12.792,6.731 13.768,7.707L20.905,14.845C21.198,15.138 21.198,15.612 20.905,15.905C20.612,16.198 20.138,16.198 19.845,15.905L12.707,8.768C12.317,8.377 11.683,8.377 11.293,8.768L4.28,15.78C3.987,16.073 3.513,16.073 3.22,15.78Z"
24+
android:fillColor="?attr/daxColorSecondaryIcon"
25+
android:fillType="evenOdd"/>
26+
</vector>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="24dp"
19+
android:height="24dp"
20+
android:viewportWidth="24"
21+
android:viewportHeight="24">
22+
<path
23+
android:pathData="M18.78,5.22C19.073,5.513 19.073,5.987 18.78,6.28L6.28,18.78C5.987,19.073 5.513,19.073 5.22,18.78C4.927,18.487 4.927,18.013 5.22,17.72L17.72,5.22C18.013,4.927 18.487,4.927 18.78,5.22Z"
24+
android:fillColor="?attr/daxColorPrimaryIcon"
25+
android:fillType="evenOdd"/>
26+
<path
27+
android:pathData="M5.22,5.22C5.513,4.927 5.987,4.927 6.28,5.22L18.78,17.72C19.073,18.013 19.073,18.487 18.78,18.78C18.487,19.073 18.013,19.073 17.72,18.78L5.22,6.28C4.927,5.987 4.927,5.513 5.22,5.22Z"
28+
android:fillColor="?attr/daxColorPrimaryIcon"
29+
android:fillType="evenOdd"/>
30+
</vector>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
18+
android:width="24dp"
19+
android:height="24dp"
20+
android:viewportWidth="24"
21+
android:viewportHeight="24">
22+
<path
23+
android:pathData="M8,1.969C5.221,1.969 2.969,4.221 2.969,7V17C2.969,19.779 5.221,22.031 8,22.031H15C15.089,22.031 15.179,22.032 15.269,22.032C15.95,22.038 16.223,21.192 15.752,20.699C15.609,20.549 15.411,20.469 15.203,20.469H8C6.084,20.469 4.531,18.916 4.531,17V7C4.531,5.084 6.084,3.531 8,3.531H16C17.916,3.531 19.469,5.084 19.469,7V15.947C19.469,16.159 19.552,16.361 19.704,16.508C20.204,16.989 21.031,16.693 21.031,16V7C21.031,4.221 18.779,1.969 16,1.969H8Z"
24+
android:fillColor="?attr/daxColorPrimaryIcon"/>
25+
<path
26+
android:pathData="M16.119,15.889C16.694,15.072 17.031,14.075 17.031,13C17.031,10.221 14.779,7.969 12,7.969C9.221,7.969 6.969,10.221 6.969,13C6.969,15.779 9.221,18.031 12,18.031C13.139,18.031 14.191,17.652 15.034,17.014L18.166,20.146C18.471,20.451 18.966,20.451 19.271,20.146C19.576,19.841 19.576,19.346 19.271,19.041L16.119,15.889ZM8.531,13C8.531,11.084 10.084,9.531 12,9.531C13.916,9.531 15.469,11.084 15.469,13C15.469,14.916 13.916,16.469 12,16.469C10.084,16.469 8.531,14.916 8.531,13Z"
27+
android:fillColor="?attr/daxColorPrimaryIcon"
28+
android:fillType="evenOdd"/>
29+
</vector>

0 commit comments

Comments
 (0)