From 967178279a76c3d7cad4a1dd48f75ded710153f3 Mon Sep 17 00:00:00 2001 From: 0nko Date: Fri, 31 Oct 2025 13:25:33 +0100 Subject: [PATCH 1/4] Hide the icon container for split omnibar --- .../duckduckgo/app/browser/BrowserActivity.kt | 6 + .../bar/view/BrowserNavigationBarViewModel.kt | 6 +- .../app/browser/omnibar/OmnibarLayout.kt | 10 +- .../browser/omnibar/OmnibarLayoutViewModel.kt | 23 +- .../view_browser_navigation_bar.xml | 242 ++++++++++-------- .../view_browser_navigation_bar_mockup.xml | 158 ++++++++++++ app/src/main/res/layout/activity_browser.xml | 7 + .../layout/include_omnibar_toolbar_mockup.xml | 2 +- .../layout/view_browser_navigation_bar.xml | 226 ++++++++-------- .../view_browser_navigation_bar_mockup.xml | 137 ++++++++++ app/src/main/res/layout/view_omnibar.xml | 2 +- .../omnibar/OmnibarLayoutViewModelTest.kt | 40 +++ 12 files changed, 624 insertions(+), 235 deletions(-) create mode 100644 app/src/main/res/layout-w600dp/view_browser_navigation_bar_mockup.xml create mode 100644 app/src/main/res/layout/view_browser_navigation_bar_mockup.xml diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt index d861b09654fb..29e2d56eaaeb 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt @@ -990,6 +990,7 @@ open class BrowserActivity : DuckDuckGoActivity() { if (this::omnibarToolbarMockupBottomBinding.isInitialized) { omnibarToolbarMockupBottomBinding.appBarLayoutMockup.visibility = View.GONE } + binding.navigationBarMockup.root.gone() }, 300, ) @@ -1412,6 +1413,11 @@ open class BrowserActivity : DuckDuckGoActivity() { if (Build.VERSION.SDK_INT >= 28) { omnibarToolbarMockupBinding.mockOmniBarContainerShadow.addBottomShadow() } + + if (settingsDataStore.omnibarType == OmnibarType.SPLIT) { + binding.topMockupSingleToolbar.iconsContainer.gone() + binding.navigationBarMockup.root.show() + } } OmnibarType.SINGLE_BOTTOM -> { diff --git a/app/src/main/java/com/duckduckgo/app/browser/navigation/bar/view/BrowserNavigationBarViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/navigation/bar/view/BrowserNavigationBarViewModel.kt index 595c660ac754..408bdcf9a51f 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/navigation/bar/view/BrowserNavigationBarViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/navigation/bar/view/BrowserNavigationBarViewModel.kt @@ -163,9 +163,9 @@ class BrowserNavigationBarViewModel @Inject constructor( } data class ViewState( - val isVisible: Boolean = false, - val newTabButtonVisible: Boolean = false, - val autofillButtonVisible: Boolean = true, + val isVisible: Boolean = true, + val newTabButtonVisible: Boolean = true, + val autofillButtonVisible: Boolean = false, val bookmarksButtonVisible: Boolean = true, val fireButtonVisible: Boolean = true, val fireButtonHighlighted: Boolean = false, diff --git a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt index 6985a46169c1..06ee4014b97d 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt @@ -588,6 +588,7 @@ class OmnibarLayout @JvmOverloads constructor( } omnibarCardShadow.isGone = viewState.viewMode is ViewMode.CustomTab && !isFindInPageVisible + iconsContainer.isVisible = viewState.showFireIcon || viewState.showTabsMenu || viewState.showBrowserMenu renderButtons(viewState) @@ -779,9 +780,12 @@ class OmnibarLayout @JvmOverloads constructor( showSpacer = viewState.showClearButton || viewState.showVoiceSearch, ) - if (omnibarAnimationManager.isFeatureEnabled() && - previousTransitionState != null && - newTransitionState != previousTransitionState + if (omnibarAnimationManager.isFeatureEnabled() && previousTransitionState != null && + ( + newTransitionState.showFireIcon != previousTransitionState?.showFireIcon || + newTransitionState.showTabsMenu != previousTransitionState?.showTabsMenu || + newTransitionState.showBrowserMenu != previousTransitionState?.showBrowserMenu + ) ) { TransitionManager.beginDelayedTransition(toolbarContainer, omniBarButtonTransitionSet) } diff --git a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt index fd1586fe8abe..134b41a01160 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayoutViewModel.kt @@ -55,6 +55,7 @@ import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Unique import com.duckduckgo.app.tabs.model.TabRepository import com.duckduckgo.app.trackerdetection.model.Entity import com.duckduckgo.browser.api.UserBrowserProperties +import com.duckduckgo.browser.ui.omnibar.OmnibarType import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.FragmentScope import com.duckduckgo.duckchat.api.DuckAiFeatureState @@ -103,9 +104,14 @@ class OmnibarLayoutViewModel @Inject constructor( private val serpEasterEggLogosToggles: SerpEasterEggLogosToggles, ) : ViewModel() { + private val isSplitOmnibarEnabled = settingsDataStore.omnibarType == OmnibarType.SPLIT + private val _viewState = MutableStateFlow( ViewState( showChatMenu = duckAiFeatureState.showOmnibarShortcutInAllStates.value, + showFireIcon = !isSplitOmnibarEnabled, + showTabsMenu = !isSplitOmnibarEnabled, + showBrowserMenu = !isSplitOmnibarEnabled, ), ) @@ -256,7 +262,7 @@ class OmnibarLayoutViewModel @Inject constructor( ) { logcat { "Omnibar: onOmnibarFocusChanged" } val showClearButton = hasFocus && inputFieldText.isNotBlank() - val showControls = inputFieldText.isBlank() + val showControls = inputFieldText.isBlank() && !isSplitOmnibarEnabled if (hasFocus) { viewModelScope.launch { @@ -326,9 +332,9 @@ class OmnibarLayoutViewModel @Inject constructor( previousLeadingIconState = null, highlightFireButton = HighlightableButton.Visible(highlighted = false), showClearButton = false, - showTabsMenu = true, - showFireIcon = true, - showBrowserMenu = true, + showTabsMenu = !isSplitOmnibarEnabled, + showFireIcon = !isSplitOmnibarEnabled, + showBrowserMenu = !isSplitOmnibarEnabled, showVoiceSearch = shouldShowVoiceSearch( hasFocus = false, query = _viewState.value.omnibarText, @@ -481,7 +487,6 @@ class OmnibarLayoutViewModel @Inject constructor( AppPixelName.ADDRESS_BAR_SERP_ENTRY_CLEARED, AppPixelName.ADDRESS_BAR_WEBSITE_ENTRY_CLEARED, ) - val showControls = true _viewState.update { it.copy( @@ -489,9 +494,9 @@ class OmnibarLayoutViewModel @Inject constructor( updateOmnibarText = true, expanded = true, showClearButton = false, - showBrowserMenu = showControls, - showTabsMenu = showControls, - showFireIcon = showControls, + showBrowserMenu = !isSplitOmnibarEnabled, + showTabsMenu = !isSplitOmnibarEnabled, + showFireIcon = !isSplitOmnibarEnabled, ) } } @@ -546,7 +551,7 @@ class OmnibarLayoutViewModel @Inject constructor( deleteLastCharacter: Boolean, ) { val showClearButton = hasFocus && query.isNotBlank() - val showControls = !hasFocus || query.isBlank() + val showControls = (!hasFocus || query.isBlank()) && !isSplitOmnibarEnabled logcat { "Omnibar: onInputStateChanged query $query hasFocus $hasFocus clearQuery $clearQuery deleteLastCharacter $deleteLastCharacter" } diff --git a/app/src/main/res/layout-w600dp/view_browser_navigation_bar.xml b/app/src/main/res/layout-w600dp/view_browser_navigation_bar.xml index e16c08bcd1f2..728887f3a06f 100644 --- a/app/src/main/res/layout-w600dp/view_browser_navigation_bar.xml +++ b/app/src/main/res/layout-w600dp/view_browser_navigation_bar.xml @@ -13,156 +13,172 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + android:layout_height="wrap_content"> - + + - + - + + + + + + + + + + + + + + + + + android:background="@drawable/selectable_item_experimental_background"> + android:src="@drawable/ic_bookmarks_24" /> + + + android:background="@drawable/selectable_item_experimental_background"> + android:src="@drawable/ic_fire_24" /> - + - + - + - - - - - - - - - - - - - - - - - - - - + android:background="@drawable/selectable_item_experimental_background"> + + + + + + + - - + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp/view_browser_navigation_bar_mockup.xml b/app/src/main/res/layout-w600dp/view_browser_navigation_bar_mockup.xml new file mode 100644 index 000000000000..33a0b6bee3ca --- /dev/null +++ b/app/src/main/res/layout-w600dp/view_browser_navigation_bar_mockup.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_browser.xml b/app/src/main/res/layout/activity_browser.xml index 8294383d4208..068500447871 100644 --- a/app/src/main/res/layout/activity_browser.xml +++ b/app/src/main/res/layout/activity_browser.xml @@ -60,4 +60,11 @@ android:layout_alignParentBottom="true" layout="@layout/include_omnibar_toolbar_mockup_bottom" /> + diff --git a/app/src/main/res/layout/include_omnibar_toolbar_mockup.xml b/app/src/main/res/layout/include_omnibar_toolbar_mockup.xml index 14fd7d6be16d..a6ce8aa814a9 100644 --- a/app/src/main/res/layout/include_omnibar_toolbar_mockup.xml +++ b/app/src/main/res/layout/include_omnibar_toolbar_mockup.xml @@ -23,6 +23,7 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?daxColorToolbar" + android:paddingEnd="@dimen/keyline_1" android:clipChildren="false"> - - - + android:layout_height="wrap_content"> + + + + + + + + + + + + + + + + + + + android:background="@drawable/selectable_item_experimental_background"> + android:src="@drawable/ic_bookmarks_24" /> + + + android:background="@drawable/selectable_item_experimental_background"> + android:src="@drawable/ic_fire_24" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_browser_navigation_bar_mockup.xml b/app/src/main/res/layout/view_browser_navigation_bar_mockup.xml new file mode 100644 index 000000000000..8ca267f6463f --- /dev/null +++ b/app/src/main/res/layout/view_browser_navigation_bar_mockup.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_omnibar.xml b/app/src/main/res/layout/view_omnibar.xml index f914f936273e..64760e637ea3 100644 --- a/app/src/main/res/layout/view_omnibar.xml +++ b/app/src/main/res/layout/view_omnibar.xml @@ -36,6 +36,7 @@ android:id="@+id/toolbarContainer" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/keyline_1" android:background="?attr/daxColorToolbar"> Date: Tue, 4 Nov 2025 13:03:47 +0100 Subject: [PATCH 2/4] Fix the split omnibar address bar end margin --- .../duckduckgo/app/browser/BrowserActivity.kt | 16 +++++++++------- .../app/browser/omnibar/OmnibarLayout.kt | 1 - app/src/main/res/layout/activity_browser.xml | 4 ++-- .../layout/include_omnibar_toolbar_mockup.xml | 13 ++++++++++--- app/src/main/res/layout/view_omnibar.xml | 2 +- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt index 29e2d56eaaeb..d6d3d735773b 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserActivity.kt @@ -1400,11 +1400,11 @@ open class BrowserActivity : DuckDuckGoActivity() { when (settingsDataStore.omnibarType) { OmnibarType.SINGLE_TOP, OmnibarType.SPLIT -> { if (Build.VERSION.SDK_INT < 28) { - binding.topMockupSingleToolbar.mockOmniBarContainerShadow.cardElevation = 2f.toPx(this) + binding.topMockupToolbar.mockOmniBarContainerShadow.cardElevation = 2f.toPx(this) } - binding.bottomMockupSingleToolbar.appBarLayoutMockup.gone() - omnibarToolbarMockupBinding = binding.topMockupSingleToolbar + binding.bottomMockupToolbar.appBarLayoutMockup.gone() + omnibarToolbarMockupBinding = binding.topMockupToolbar if (!duckAiFeatureState.showOmnibarShortcutOnNtpAndOnFocus.value) { omnibarToolbarMockupBinding.aiChatIconMockup.isVisible = false @@ -1415,18 +1415,20 @@ open class BrowserActivity : DuckDuckGoActivity() { } if (settingsDataStore.omnibarType == OmnibarType.SPLIT) { - binding.topMockupSingleToolbar.iconsContainer.gone() + binding.topMockupToolbar.tabsMenu.gone() + binding.topMockupToolbar.browserMenu.gone() + binding.topMockupToolbar.fireIconMenu.gone() binding.navigationBarMockup.root.show() } } OmnibarType.SINGLE_BOTTOM -> { if (Build.VERSION.SDK_INT < 28) { - binding.bottomMockupSingleToolbar.mockOmniBarContainerShadow.cardElevation = 0.5f.toPx(this) + binding.bottomMockupToolbar.mockOmniBarContainerShadow.cardElevation = 0.5f.toPx(this) } - binding.topMockupSingleToolbar.appBarLayoutMockup.gone() - omnibarToolbarMockupBottomBinding = binding.bottomMockupSingleToolbar + binding.topMockupToolbar.appBarLayoutMockup.gone() + omnibarToolbarMockupBottomBinding = binding.bottomMockupToolbar if (!duckAiFeatureState.showOmnibarShortcutOnNtpAndOnFocus.value) { omnibarToolbarMockupBottomBinding.aiChatIconMockup.isVisible = false diff --git a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt index 06ee4014b97d..fbe5deb4c13c 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/omnibar/OmnibarLayout.kt @@ -588,7 +588,6 @@ class OmnibarLayout @JvmOverloads constructor( } omnibarCardShadow.isGone = viewState.viewMode is ViewMode.CustomTab && !isFindInPageVisible - iconsContainer.isVisible = viewState.showFireIcon || viewState.showTabsMenu || viewState.showBrowserMenu renderButtons(viewState) diff --git a/app/src/main/res/layout/activity_browser.xml b/app/src/main/res/layout/activity_browser.xml index 068500447871..d63ac7811d50 100644 --- a/app/src/main/res/layout/activity_browser.xml +++ b/app/src/main/res/layout/activity_browser.xml @@ -23,7 +23,7 @@ tools:context="com.duckduckgo.app.browser.BrowserActivity"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_omnibar.xml b/app/src/main/res/layout/view_omnibar.xml index 64760e637ea3..bd290fa2d16f 100644 --- a/app/src/main/res/layout/view_omnibar.xml +++ b/app/src/main/res/layout/view_omnibar.xml @@ -36,7 +36,6 @@ android:id="@+id/toolbarContainer" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginEnd="@dimen/keyline_1" android:background="?attr/daxColorToolbar"> Date: Tue, 4 Nov 2025 13:33:40 +0100 Subject: [PATCH 3/4] Fix the bottom outline when split omnibar selected --- .../com/duckduckgo/app/browser/BrowserTabFragment.kt | 2 +- .../java/com/duckduckgo/app/browser/omnibar/Omnibar.kt | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index d21141fccdc8..450a4a4eba7e 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -1631,7 +1631,7 @@ class BrowserTabFragment : .flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED) .collectLatest { hasFavorites -> binding.includeNewBrowserTab.topNtpOutlineStroke.isVisible = hasFavorites - binding.includeNewBrowserTab.bottomNtpOutlineStroke.isVisible = hasFavorites + binding.includeNewBrowserTab.bottomNtpOutlineStroke.isVisible = hasFavorites && !omnibarFeatureRepository.isSplitOmnibarEnabled } } diff --git a/app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt b/app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt index 1068e4d05624..a056f66c88cc 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/omnibar/Omnibar.kt @@ -184,11 +184,16 @@ class Omnibar( binding.rootView.removeView(binding.singleOmnibarLayoutBottom) return when (omnibarType) { - OmnibarType.SINGLE_TOP, OmnibarType.SPLIT -> { + OmnibarType.SINGLE_TOP -> { binding.rootView.removeView(binding.omnibarLayoutBottom) binding.omnibarLayoutTop } - + OmnibarType.SPLIT -> { + binding.rootView.removeView(binding.omnibarLayoutBottom) + binding.bottomBrowserOutlineStroke.gone() + binding.includeNewBrowserTab.bottomNtpOutlineStroke.gone() + binding.omnibarLayoutTop + } OmnibarType.SINGLE_BOTTOM -> { binding.rootView.removeView(binding.omnibarLayoutTop) adjustCoordinatorLayoutBehaviorForBottomOmnibar() From e20786f4f96f990f6e089fd797e13a5e9b26ee95 Mon Sep 17 00:00:00 2001 From: 0nko Date: Tue, 4 Nov 2025 22:06:27 +0100 Subject: [PATCH 4/4] Allow extra time for the bottom bar to anchor the menu --- .../com/duckduckgo/app/browser/BrowserTabFragment.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt index 450a4a4eba7e..23d4c4619da9 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -930,7 +930,10 @@ class BrowserTabFragment : } InputScreenActivityResultCodes.MENU_REQUESTED -> { - launchPopupMenu(omnibarFeatureRepository.isSplitOmnibarEnabled) + launchPopupMenu( + anchorToNavigationBar = omnibarFeatureRepository.isSplitOmnibarEnabled, + addExtraDelay = omnibarFeatureRepository.isSplitOmnibarEnabled, + ) } InputScreenActivityResultCodes.TAB_SWITCHER_REQUESTED -> { @@ -1425,11 +1428,12 @@ class BrowserTabFragment : startActivity(intent) } - private fun launchPopupMenu(anchorToNavigationBar: Boolean) { + private fun launchPopupMenu(anchorToNavigationBar: Boolean, addExtraDelay: Boolean = false) { val isFocusedNtp = omnibar.viewMode == ViewMode.NewTab && omnibar.getText().isEmpty() && omnibar.omnibarTextInput.hasFocus() + val delay = if (addExtraDelay) POPUP_MENU_DELAY * 2 else POPUP_MENU_DELAY // small delay added to let keyboard disappear and avoid jarring transition - binding.rootView.postDelayed(POPUP_MENU_DELAY) { + binding.rootView.postDelayed(delay) { if (isAdded) { // Check if VPN menu item will be shown to non-subscribed user and increment count val currentViewState = viewModel.browserViewState.value