1616
1717package com.duckduckgo.app.browser.omnibar
1818
19+ import android.animation.ValueAnimator
1920import android.annotation.SuppressLint
2021import android.content.Context
2122import android.graphics.Color
22- import android.graphics.drawable.ColorDrawable
23+ import android.os.Build
2324import android.text.Editable
2425import android.transition.ChangeBounds
2526import android.transition.Fade
@@ -29,6 +30,7 @@ import android.util.AttributeSet
2930import android.view.KeyEvent
3031import android.view.View
3132import android.view.ViewGroup
33+ import android.view.ViewTreeObserver.OnGlobalLayoutListener
3234import android.view.animation.OvershootInterpolator
3335import android.view.inputmethod.EditorInfo
3436import android.widget.FrameLayout
@@ -37,10 +39,13 @@ import android.widget.ProgressBar
3739import android.widget.TextView
3840import androidx.appcompat.widget.Toolbar
3941import androidx.coordinatorlayout.widget.CoordinatorLayout
42+ import androidx.core.graphics.drawable.toDrawable
4043import androidx.core.transition.doOnEnd
4144import androidx.core.view.doOnLayout
45+ import androidx.core.view.isGone
4246import androidx.core.view.isInvisible
4347import androidx.core.view.isVisible
48+ import androidx.core.view.updateLayoutParams
4449import androidx.lifecycle.LifecycleOwner
4550import androidx.lifecycle.ViewModelProvider
4651import androidx.lifecycle.findViewTreeLifecycleOwner
@@ -91,9 +96,11 @@ import com.duckduckgo.browser.ui.omnibar.OmnibarPosition
9196import com.duckduckgo.common.ui.DuckDuckGoActivity
9297import com.duckduckgo.common.ui.view.KeyboardAwareEditText
9398import com.duckduckgo.common.ui.view.KeyboardAwareEditText.ShowSuggestionsListener
99+ import com.duckduckgo.common.ui.view.addBottomShadow
94100import com.duckduckgo.common.ui.view.gone
95101import com.duckduckgo.common.ui.view.hide
96102import com.duckduckgo.common.ui.view.show
103+ import com.duckduckgo.common.ui.view.toPx
97104import com.duckduckgo.common.utils.ConflatedJob
98105import com.duckduckgo.common.utils.DispatcherProvider
99106import com.duckduckgo.common.utils.FragmentViewModelFactory
@@ -102,9 +109,12 @@ import com.duckduckgo.common.utils.text.TextChangedWatcher
102109import com.duckduckgo.di.scopes.FragmentScope
103110import com.duckduckgo.duckchat.api.DuckAiFeatureState
104111import com.duckduckgo.duckchat.api.DuckChat
112+ import com.duckduckgo.navigation.api.GlobalActivityStarter
105113import com.duckduckgo.serp.logos.api.SerpEasterEggLogosToggles
106114import com.duckduckgo.serp.logos.api.SerpLogos
107115import com.google.android.material.appbar.AppBarLayout
116+ import com.google.android.material.card.MaterialCardView
117+ import dagger.android.support.AndroidSupportInjection
108118import kotlinx.coroutines.flow.collectLatest
109119import kotlinx.coroutines.flow.map
110120import kotlinx.coroutines.launch
@@ -147,7 +157,7 @@ open class OmnibarLayout @JvmOverloads constructor(
147157 ) : Decoration()
148158
149159 data class PrivacyShieldChanged (
150- val privacyShield : com.duckduckgo.app.global.model. PrivacyShield ,
160+ val privacyShield : PrivacyShieldState ,
151161 ) : Decoration()
152162
153163 data class HighlightOmnibarItem (
@@ -215,6 +225,9 @@ open class OmnibarLayout @JvmOverloads constructor(
215225 @Inject
216226 lateinit var serpEasterEggLogosToggles: SerpEasterEggLogosToggles
217227
228+ @Inject
229+ lateinit var globalActivityStarter: GlobalActivityStarter
230+
218231 @Inject
219232 lateinit var addressBarTrackersAnimationFeatureToggle: AddressBarTrackersAnimationFeatureToggle
220233
@@ -237,6 +250,54 @@ open class OmnibarLayout @JvmOverloads constructor(
237250 private var lastViewMode: Mode ? = null
238251 private var stateBuffer: MutableList <StateChange > = mutableListOf ()
239252
253+ private val omnibarCardShadow: MaterialCardView by lazy { findViewById(R .id.omniBarContainerShadow) }
254+ private val iconsContainer: View by lazy { findViewById(R .id.iconsContainer) }
255+ private val shieldIconPulseAnimationContainer: View by lazy { findViewById(R .id.shieldIconPulseAnimationContainer) }
256+ private val omniBarContentContainer: View by lazy { findViewById(R .id.omniBarContentContainer) }
257+ private val backIcon: ImageView by lazy { findViewById(R .id.backIcon) }
258+ private val customTabToolbarContainerWrapper: ViewGroup by lazy { findViewById(R .id.customTabToolbarContainerWrapper) }
259+
260+ private var isFindInPageVisible = false
261+ private val findInPageLayoutVisibilityChangeListener =
262+ OnGlobalLayoutListener {
263+ val isVisible = findInPage.findInPageContainer.isVisible
264+ if (isFindInPageVisible != isVisible) {
265+ isFindInPageVisible = isVisible
266+ if (isVisible) {
267+ onFindInPageShown()
268+ } else {
269+ onFindInPageHidden()
270+ }
271+ }
272+ }
273+
274+ private val experimentalOmnibarCardMarginTop by lazy {
275+ resources.getDimensionPixelSize(CommonR .dimen.omnibarCardMarginTop)
276+ }
277+
278+ private val experimentalOmnibarCardMarginBottom by lazy {
279+ resources.getDimensionPixelSize(CommonR .dimen.omnibarCardMarginBottom)
280+ }
281+
282+ private var focusAnimator: ValueAnimator ? = null
283+
284+ val omnibarPosition: OmnibarPosition
285+
286+ init {
287+ inflate(context, R .layout.view_omnibar, this )
288+
289+ val attr = context.theme.obtainStyledAttributes(attrs, R .styleable.OmnibarLayout , defStyle, 0 )
290+ omnibarPosition = OmnibarPosition .entries[attr.getInt(R .styleable.OmnibarLayout_omnibarPosition , 0 )]
291+
292+ AndroidSupportInjection .inject(this )
293+
294+ renderPosition()
295+
296+ if (Build .VERSION .SDK_INT >= 28 ) {
297+ omnibarCardShadow.addBottomShadow()
298+ }
299+ }
300+
240301 internal val findInPage: IncludeFindInPageBinding by lazy {
241302 IncludeFindInPageBinding .bind(findViewById(R .id.findInPage))
242303 }
@@ -338,8 +399,6 @@ open class OmnibarLayout @JvmOverloads constructor(
338399 }
339400 }
340401
341- open var omnibarPosition: OmnibarPosition = OmnibarPosition .TOP
342-
343402 private val smoothProgressAnimator by lazy { SmoothProgressAnimator (pageLoadingIndicator) }
344403
345404 protected val viewModel: OmnibarLayoutViewModel by lazy {
@@ -393,11 +452,14 @@ open class OmnibarLayout @JvmOverloads constructor(
393452 }
394453
395454 animatorHelper.setListener(this )
455+ findInPage.findInPageContainer.viewTreeObserver.addOnGlobalLayoutListener(findInPageLayoutVisibilityChangeListener)
396456 }
397457
398458 override fun onDetachedFromWindow () {
399459 conflatedStateJob.cancel()
400460 conflatedCommandJob.cancel()
461+ focusAnimator?.cancel()
462+ findInPage.findInPageContainer.viewTreeObserver.removeOnGlobalLayoutListener(findInPageLayoutVisibilityChangeListener)
401463 super .onDetachedFromWindow()
402464 }
403465
@@ -528,13 +590,17 @@ open class OmnibarLayout @JvmOverloads constructor(
528590 voiceSearchButton.setOnClickListener {
529591 omnibarItemPressedListener?.onVoiceSearchPressed()
530592 }
593+ backIcon.setOnClickListener {
594+ viewModel.onBackButtonPressed()
595+ omnibarItemPressedListener?.onBackButtonPressed()
596+ }
531597 }
532598
533599 fun setLogoClickListener (logoClickListener : Omnibar .LogoClickListener ) {
534600 omnibarLogoClickedListener = logoClickListener
535601 }
536602
537- open fun render (viewState : ViewState ) {
603+ fun render (viewState : ViewState ) {
538604 when (viewState.viewMode) {
539605 is CustomTab -> {
540606 renderCustomTabMode(viewState, viewState.viewMode)
@@ -551,14 +617,81 @@ open class OmnibarLayout @JvmOverloads constructor(
551617 lastSeenPrivacyShield = null
552618 }
553619
620+ if (viewState.hasFocus || isFindInPageVisible) {
621+ animateOmnibarFocusedState(focused = true )
622+ } else {
623+ animateOmnibarFocusedState(focused = false )
624+ }
625+
626+ omnibarCardShadow.isGone = viewState.viewMode is CustomTab && ! isFindInPageVisible
627+
554628 renderButtons(viewState)
555629
556630 omniBarButtonTransitionSet.doOnEnd {
557631 omnibarTextInput.requestLayout()
558632 }
559633 }
560634
561- open fun processCommand (command : OmnibarLayoutViewModel .Command ) {
635+ private fun renderPosition () {
636+ when (omnibarPosition) {
637+ OmnibarPosition .TOP -> {
638+ if (Build .VERSION .SDK_INT < 28 ) {
639+ omnibarCardShadow.cardElevation = 2f .toPx(context)
640+ }
641+
642+ shieldIconPulseAnimationContainer.updateLayoutParams {
643+ (this as MarginLayoutParams ).apply {
644+ if (addressBarTrackersAnimationFeatureToggle.feature().isEnabled()) {
645+ // TODO when the animation is made permanent we should add this adjustment to the actual layout
646+ marginStart = 1 .toPx()
647+ }
648+ }
649+ }
650+ }
651+
652+ OmnibarPosition .BOTTOM -> {
653+ // When omnibar is at the bottom, we're adding an additional space at the top
654+ omnibarCardShadow.updateLayoutParams {
655+ (this as MarginLayoutParams ).apply {
656+ topMargin = experimentalOmnibarCardMarginBottom
657+ bottomMargin = experimentalOmnibarCardMarginTop
658+ }
659+ }
660+
661+ iconsContainer.updateLayoutParams {
662+ (this as MarginLayoutParams ).apply {
663+ topMargin = experimentalOmnibarCardMarginBottom
664+ bottomMargin = experimentalOmnibarCardMarginTop
665+ }
666+ }
667+
668+ shieldIconPulseAnimationContainer.updateLayoutParams {
669+ (this as MarginLayoutParams ).apply {
670+ topMargin = experimentalOmnibarCardMarginBottom
671+ bottomMargin = experimentalOmnibarCardMarginTop
672+ if (addressBarTrackersAnimationFeatureToggle.feature().isEnabled()) {
673+ // TODO when the animation is made permanent we should add this adjustment to the actual layout
674+ marginStart = 1 .toPx()
675+ }
676+ }
677+ }
678+
679+ shieldIconPulseAnimationContainer.setPadding(
680+ shieldIconPulseAnimationContainer.paddingLeft,
681+ shieldIconPulseAnimationContainer.paddingTop,
682+ shieldIconPulseAnimationContainer.paddingRight,
683+ 6 .toPx(),
684+ )
685+
686+ // Try to reduce the bottom omnibar material shadow when not using the custom shadow
687+ if (Build .VERSION .SDK_INT < 28 ) {
688+ omnibarCardShadow.cardElevation = 0.5f .toPx(context)
689+ }
690+ }
691+ }
692+ }
693+
694+ fun processCommand (command : Command ) {
562695 when (command) {
563696 Command .CancelAnimations -> {
564697 cancelAddressBarAnimations()
@@ -673,7 +806,10 @@ open class OmnibarLayout @JvmOverloads constructor(
673806 }
674807 }
675808
676- open fun renderButtons (viewState : ViewState ) {
809+ fun renderButtons (viewState : ViewState ) {
810+ if (viewState.showButtons) {
811+ }
812+
677813 val newTransitionState =
678814 TransitionState (
679815 showClearButton = viewState.showClearButton,
@@ -709,6 +845,18 @@ open class OmnibarLayout @JvmOverloads constructor(
709845 previousTransitionState = newTransitionState
710846
711847 enableTextInputClickCatcher(viewState.showTextInputClickCatcher)
848+
849+ val showBackArrow = viewState.hasFocus
850+ if (showBackArrow) {
851+ backIcon.show()
852+ searchIcon.gone()
853+ shieldIcon.gone()
854+ daxIcon.gone()
855+ globeIcon.gone()
856+ duckPlayerIcon.gone()
857+ } else {
858+ backIcon.hide()
859+ }
712860 }
713861
714862 private fun renderBrowserMode (viewState : ViewState ) {
@@ -912,7 +1060,7 @@ open class OmnibarLayout @JvmOverloads constructor(
9121060 omniBarContainer.isPressed = enabled
9131061 }
9141062
915- private fun configureCustomTabOmnibar (customTab : ViewMode . CustomTab ) {
1063+ private fun configureCustomTabOmnibar (customTab : CustomTab ) {
9161064 if (! customTabToolbarContainer.customTabToolbar.isVisible) {
9171065 customTabToolbarContainer.customTabCloseIcon.setOnClickListener {
9181066 omnibarItemPressedListener?.onCustomTabClosePressed()
@@ -924,8 +1072,8 @@ open class OmnibarLayout @JvmOverloads constructor(
9241072
9251073 omniBarContainer.hide()
9261074
927- toolbar.background = ColorDrawable ( customTab.toolbarColor)
928- toolbarContainer.background = ColorDrawable ( customTab.toolbarColor)
1075+ toolbar.background = customTab.toolbarColor.toDrawable( )
1076+ toolbarContainer.background = customTab.toolbarColor.toDrawable( )
9291077
9301078 customTabToolbarContainer.customTabToolbar.show()
9311079
@@ -1043,4 +1191,32 @@ open class OmnibarLayout @JvmOverloads constructor(
10431191 viewModel.onTextInputClickCatcherClicked()
10441192 }
10451193 }
1194+
1195+ private fun animateOmnibarFocusedState (focused : Boolean ) {
1196+ // temporarily disable focus animation
1197+ }
1198+
1199+ private fun onFindInPageShown () {
1200+ omniBarContentContainer.hide()
1201+ customTabToolbarContainerWrapper.hide()
1202+ if (viewModel.viewState.value.viewMode is ViewMode .CustomTab ) {
1203+ omniBarContainer.show()
1204+ browserMenu.gone()
1205+ }
1206+ animateOmnibarFocusedState(focused = true )
1207+ viewModel.onFindInPageRequested()
1208+ }
1209+
1210+ private fun onFindInPageHidden () {
1211+ omniBarContentContainer.show()
1212+ customTabToolbarContainerWrapper.show()
1213+ if (viewModel.viewState.value.viewMode is ViewMode .CustomTab ) {
1214+ omniBarContainer.hide()
1215+ browserMenu.isVisible = viewModel.viewState.value.showBrowserMenu
1216+ }
1217+ if (! viewModel.viewState.value.hasFocus) {
1218+ animateOmnibarFocusedState(focused = false )
1219+ }
1220+ viewModel.onFindInPageDismissed()
1221+ }
10461222}
0 commit comments