diff --git a/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionDomainMatcher.kt b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionDomainMatcher.kt index 4d6ceab3a8b7..83b71ad50815 100644 --- a/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionDomainMatcher.kt +++ b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/AdBlockingExtensionDomainMatcher.kt @@ -16,6 +16,7 @@ package com.duckduckgo.adblocking.impl +import android.net.Uri import androidx.core.net.toUri import com.duckduckgo.app.browser.Domain import com.duckduckgo.app.browser.UriString @@ -32,9 +33,11 @@ class AdBlockingExtensionDomainMatcher @Inject constructor() { fun matches(url: String?): Boolean { val uri = url?.toUri() ?: return false - return DOMAINS.any { UriString.sameOrSubdomain(uri, it) } + return matches(uri) } + fun matches(uri: Uri): Boolean = DOMAINS.any { UriString.sameOrSubdomain(uri, it) } + private companion object { val DOMAINS = listOf( Domain("youtube.com"), diff --git a/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/AdBlockingMenuStateProvider.kt b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/AdBlockingMenuStateProvider.kt new file mode 100644 index 000000000000..4234de52dd9f --- /dev/null +++ b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/domain/AdBlockingMenuStateProvider.kt @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2026 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.adblocking.impl.domain + +import android.net.Uri +import com.duckduckgo.adblocking.impl.AdBlockingExtensionDomainMatcher +import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesBinding +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +/** + * State of the YouTube ad-blocking browser-menu item for a given page. [Hidden] when the item should not + * appear at all; [Enabled]/[Disabled] reflect the effective blocking state so the item can label itself + * "Disable…"/"Enable…" respectively. + */ +sealed interface AdBlockingMenuState { + data object Hidden : AdBlockingMenuState + data object Enabled : AdBlockingMenuState + data object Disabled : AdBlockingMenuState +} + +interface AdBlockingMenuStateProvider { + fun observe(url: Uri): Flow +} + +@ContributesBinding(AppScope::class) +class RealAdBlockingMenuStateProvider @Inject constructor( + private val feature: AdBlockingExtensionFeature, + private val statusChecker: AdBlockingStatusChecker, + private val domainMatcher: AdBlockingExtensionDomainMatcher, +) : AdBlockingMenuStateProvider { + + override fun observe(url: Uri): Flow { + if (!domainMatcher.matches(url)) return flowOf(AdBlockingMenuState.Hidden) + return combine( + feature.self().enabled(), + feature.adBlockingUXImprovements().enabled(), + feature.enableContingencyMode().enabled(), + statusChecker.observeState(), + ) { killSwitchOn, phase2On, contingencyOn, state -> + when { + !killSwitchOn || !phase2On || contingencyOn -> AdBlockingMenuState.Hidden + state is AdBlockingState.Enabled -> AdBlockingMenuState.Enabled + else -> AdBlockingMenuState.Disabled + } + } + } +} diff --git a/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/AdBlockingMenuItemView.kt b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/AdBlockingMenuItemView.kt new file mode 100644 index 000000000000..a2bc0eaf82b1 --- /dev/null +++ b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/AdBlockingMenuItemView.kt @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2026 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.adblocking.impl.menu + +import android.content.Context +import android.net.Uri +import android.util.AttributeSet +import android.widget.LinearLayout +import androidx.core.view.isGone +import com.duckduckgo.adblocking.impl.R +import com.duckduckgo.adblocking.impl.domain.AdBlockingMenuState +import com.duckduckgo.adblocking.impl.domain.AdBlockingMenuStateProvider +import com.duckduckgo.anvil.annotations.InjectWith +import com.duckduckgo.common.ui.view.MenuItemView +import com.duckduckgo.common.ui.view.MenuItemViewSize +import com.duckduckgo.common.utils.DispatcherProvider +import com.duckduckgo.di.scopes.ViewScope +import dagger.android.support.AndroidSupportInjection +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import javax.inject.Inject + +@InjectWith(ViewScope::class) +class AdBlockingMenuItemView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, +) : LinearLayout(context, attrs, defStyle) { + + @Inject + lateinit var menuStateProvider: AdBlockingMenuStateProvider + + @Inject + lateinit var dispatcherProvider: DispatcherProvider + + private val menuItem: MenuItemView by lazy { + MenuItemView(context).apply { + setSize(MenuItemViewSize.MEDIUM) + setIcon(R.drawable.video_ad_blocked_24) + } + } + + private var url: Uri? = null + private var onHostClick: (() -> Unit)? = null + private var scope: CoroutineScope? = null + + init { + orientation = VERTICAL + addView(menuItem) + } + + fun bind(url: Uri, onHostClick: () -> Unit) { + this.url = url + this.onHostClick = onHostClick + } + + override fun onAttachedToWindow() { + AndroidSupportInjection.inject(this) + super.onAttachedToWindow() + + val url = this.url ?: return + menuItem.setOnClickListener { onHostClick?.invoke() } + + scope?.cancel() + scope = CoroutineScope(SupervisorJob() + dispatcherProvider.main()).also { scope -> + menuStateProvider.observe(url) + .flowOn(dispatcherProvider.io()) + .onEach { render(it) } + .launchIn(scope) + } + } + + override fun onDetachedFromWindow() { + scope?.cancel() + scope = null + super.onDetachedFromWindow() + } + + private fun render(state: AdBlockingMenuState) { + when (state) { + AdBlockingMenuState.Hidden -> isGone = true + AdBlockingMenuState.Enabled -> { + isGone = false + menuItem.label(context.getString(R.string.ad_blocking_menu_disable)) + } + AdBlockingMenuState.Disabled -> { + isGone = false + menuItem.label(context.getString(R.string.ad_blocking_menu_enable)) + } + } + } +} diff --git a/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/YouTubeAdBlockingTopInContextSection.kt b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/YouTubeAdBlockingTopInContextSection.kt new file mode 100644 index 000000000000..08b009a66843 --- /dev/null +++ b/ad-blocking/ad-blocking-impl/src/main/java/com/duckduckgo/adblocking/impl/menu/YouTubeAdBlockingTopInContextSection.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2026 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.adblocking.impl.menu + +import android.content.Context +import android.net.Uri +import android.view.View +import com.duckduckgo.anvil.annotations.PriorityKey +import com.duckduckgo.app.browser.menu.TopInContextSection +import com.duckduckgo.di.scopes.AppScope +import com.squareup.anvil.annotations.ContributesMultibinding +import javax.inject.Inject + +@ContributesMultibinding(AppScope::class) +@PriorityKey(100) +class YouTubeAdBlockingTopInContextSection @Inject constructor() : TopInContextSection { + override fun getView(url: Uri, context: Context, onClick: () -> Unit): View = + AdBlockingMenuItemView(context).also { it.bind(url, onHostClick = onClick) } +} diff --git a/ad-blocking/ad-blocking-impl/src/main/res/drawable/video_ad_blocked_24.xml b/ad-blocking/ad-blocking-impl/src/main/res/drawable/video_ad_blocked_24.xml new file mode 100644 index 000000000000..dc44e3ddd272 --- /dev/null +++ b/ad-blocking/ad-blocking-impl/src/main/res/drawable/video_ad_blocked_24.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/ad-blocking/ad-blocking-impl/src/main/res/values/donottranslate.xml b/ad-blocking/ad-blocking-impl/src/main/res/values/donottranslate.xml index 9b74451203e6..a06fec1df3a2 100644 --- a/ad-blocking/ad-blocking-impl/src/main/res/values/donottranslate.xml +++ b/ad-blocking/ad-blocking-impl/src/main/res/values/donottranslate.xml @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + Disable YouTube Ad Blocking + Enable YouTube Ad Blocking diff --git a/ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/domain/RealAdBlockingMenuStateProviderTest.kt b/ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/domain/RealAdBlockingMenuStateProviderTest.kt new file mode 100644 index 000000000000..5fe5a7dd0251 --- /dev/null +++ b/ad-blocking/ad-blocking-impl/src/test/java/com/duckduckgo/adblocking/impl/domain/RealAdBlockingMenuStateProviderTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2026 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.adblocking.impl.domain + +import androidx.core.net.toUri +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.duckduckgo.adblocking.impl.AdBlockingExtensionDomainMatcher +import com.duckduckgo.adblocking.impl.remoteconfig.AdBlockingExtensionFeature +import com.duckduckgo.feature.toggles.api.Toggle +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class RealAdBlockingMenuStateProviderTest { + + private val killSwitchFlow = MutableStateFlow(true) + private val phase2Flow = MutableStateFlow(true) + private val contingencyFlow = MutableStateFlow(false) + private val stateFlow = MutableStateFlow(AdBlockingState.Enabled.UserEnabled) + + private val selfToggle: Toggle = mock { on { enabled() } doReturn killSwitchFlow } + private val phase2Toggle: Toggle = mock { on { enabled() } doReturn phase2Flow } + private val contingencyToggle: Toggle = mock { on { enabled() } doReturn contingencyFlow } + private val feature: AdBlockingExtensionFeature = mock { + on { self() } doReturn selfToggle + on { adBlockingUXImprovements() } doReturn phase2Toggle + on { enableContingencyMode() } doReturn contingencyToggle + } + private val statusChecker: AdBlockingStatusChecker = mock { + on { observeState() } doReturn stateFlow + } + + private val youtubeUrl = "https://m.youtube.com/watch?v=abc".toUri() + private val nonYoutubeUrl = "https://example.com/watch".toUri() + + private val domainMatcher: AdBlockingExtensionDomainMatcher = mock { + on { matches(youtubeUrl) } doReturn true + on { matches(nonYoutubeUrl) } doReturn false + } + + private val provider = RealAdBlockingMenuStateProvider(feature, statusChecker, domainMatcher) + + @Test + fun whenNonYoutubeUrlThenHidden() = runTest { + assertEquals(AdBlockingMenuState.Hidden, provider.observe(nonYoutubeUrl).first()) + } + + @Test + fun whenPhase2FlagOffThenHidden() = runTest { + phase2Flow.value = false + + assertEquals(AdBlockingMenuState.Hidden, provider.observe(youtubeUrl).first()) + } + + @Test + fun whenKillSwitchOffThenHidden() = runTest { + killSwitchFlow.value = false + + assertEquals(AdBlockingMenuState.Hidden, provider.observe(youtubeUrl).first()) + } + + @Test + fun whenContingencyModeOnThenHidden() = runTest { + contingencyFlow.value = true + + assertEquals(AdBlockingMenuState.Hidden, provider.observe(youtubeUrl).first()) + } + + @Test + fun whenYoutubeAndUserEnabledThenEnabled() = runTest { + stateFlow.value = AdBlockingState.Enabled.UserEnabled + + assertEquals(AdBlockingMenuState.Enabled, provider.observe(youtubeUrl).first()) + } + + @Test + fun whenYoutubeAndEnabledByDefaultThenEnabled() = runTest { + stateFlow.value = AdBlockingState.Enabled.Default + + assertEquals(AdBlockingMenuState.Enabled, provider.observe(youtubeUrl).first()) + } + + @Test + fun whenYoutubeAndDisabledThenDisabled() = runTest { + stateFlow.value = AdBlockingState.Disabled + + assertEquals(AdBlockingMenuState.Disabled, provider.observe(youtubeUrl).first()) + } +} 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 88967c0eb2eb..e6fc4a187f86 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt @@ -149,6 +149,7 @@ import com.duckduckgo.app.browser.history.NavigationHistorySheet.NavigationHisto import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore import com.duckduckgo.app.browser.logindetection.DOMLoginDetector import com.duckduckgo.app.browser.menu.BrowserMenuViewStateFactory +import com.duckduckgo.app.browser.menu.TopInContextSection import com.duckduckgo.app.browser.menu.VpnMenuStore import com.duckduckgo.app.browser.model.BasicAuthenticationCredentials import com.duckduckgo.app.browser.model.BasicAuthenticationRequest @@ -543,6 +544,9 @@ class BrowserTabFragment : @Inject lateinit var faviconManager: FaviconManager + @Inject + lateinit var topInContextSections: PluginPoint + @Inject lateinit var gridViewColumnCalculator: GridViewColumnCalculator @@ -1819,6 +1823,8 @@ class BrowserTabFragment : pixel.fire(AppPixelName.BROWSING_MENU_USED_UNIQUE, type = Unique()) pixel.fire(AppPixelName.BROWSING_MENU_USED, type = Count) }, + topInContextSections = topInContextSections.getPlugins(), + currentUrl = viewModel.url?.toUri(), ) when (browserMode) { diff --git a/app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt b/app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt index dab900089027..cf28250c3b47 100644 --- a/app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt +++ b/app/src/main/java/com/duckduckgo/app/browser/di/BrowserModule.kt @@ -48,6 +48,7 @@ import com.duckduckgo.app.browser.httperrors.StringSiteErrorHandler import com.duckduckgo.app.browser.httperrors.StringSiteErrorHandlerImpl import com.duckduckgo.app.browser.logindetection.* import com.duckduckgo.app.browser.menu.BrowserMenuHighlightPlugin +import com.duckduckgo.app.browser.menu.TopInContextSection import com.duckduckgo.app.browser.pageloadpixel.PageLoadedPixelDao import com.duckduckgo.app.browser.pageloadpixel.firstpaint.PagePaintedPixelDao import com.duckduckgo.app.browser.tabpreview.FileBasedWebViewPreviewGenerator @@ -393,3 +394,6 @@ private interface DuckAiChatDeletionListenerPluginPoint @ContributesPluginPoint(scope = AppScope::class, boundType = BrowserMenuHighlightPlugin::class) private interface BrowserMenuHighlightPluginPoint + +@ContributesPluginPoint(scope = AppScope::class, boundType = TopInContextSection::class) +private interface TopInContextSectionPluginPoint diff --git a/browser-api/src/main/java/com/duckduckgo/app/browser/menu/TopInContextSection.kt b/browser-api/src/main/java/com/duckduckgo/app/browser/menu/TopInContextSection.kt new file mode 100644 index 000000000000..7a6d8d876560 --- /dev/null +++ b/browser-api/src/main/java/com/duckduckgo/app/browser/menu/TopInContextSection.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2026 DuckDuckGo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.duckduckgo.app.browser.menu + +import android.content.Context +import android.net.Uri +import android.view.View + +/** + * Contributes a section shown at the top of the browser overflow menu, above the built-in items. + */ +interface TopInContextSection { + /** + * Returns a menu section view. + * + * @param url the URL of the page the menu was opened on. + * @param context an Activity context suitable for inflating views and showing dialogs. + * @param onClick invoked by the view after the user taps the item, so the host can perform any + * standard actions (e.g. firing the menu-used pixel and dismissing the menu). + */ + fun getView(url: Uri, context: Context, onClick: () -> Unit): View +} diff --git a/browser/browser-ui/src/main/java/com/duckduckgo/browser/ui/browsermenu/BrowserMenuBottomSheet.kt b/browser/browser-ui/src/main/java/com/duckduckgo/browser/ui/browsermenu/BrowserMenuBottomSheet.kt index b244ac7bfd14..8fc3295a46f3 100644 --- a/browser/browser-ui/src/main/java/com/duckduckgo/browser/ui/browsermenu/BrowserMenuBottomSheet.kt +++ b/browser/browser-ui/src/main/java/com/duckduckgo/browser/ui/browsermenu/BrowserMenuBottomSheet.kt @@ -18,6 +18,7 @@ package com.duckduckgo.browser.ui.browsermenu import android.annotation.SuppressLint import android.content.Context +import android.net.Uri import android.view.LayoutInflater import android.view.View import android.widget.LinearLayout @@ -26,6 +27,7 @@ import androidx.core.view.isVisible import androidx.lifecycle.coroutineScope import com.bumptech.glide.Glide import com.duckduckgo.app.browser.favicon.FaviconManager +import com.duckduckgo.app.browser.menu.TopInContextSection import com.duckduckgo.browser.ui.R import com.duckduckgo.browser.ui.databinding.BottomSheetBrowserMenuBinding import com.duckduckgo.browser.ui.databinding.ViewBrowserMenuDuckaiSectionBinding @@ -46,6 +48,8 @@ class BrowserMenuBottomSheet( private val faviconManager: FaviconManager, private val onDismissListener: () -> Unit, private val onMenuItemClickListener: () -> Unit, + private val topInContextSections: Collection = emptyList(), + private val currentUrl: Uri? = null, ) : BottomSheetDialog(context) { private val binding = BottomSheetBrowserMenuBinding.inflate(LayoutInflater.from(context)) @@ -62,6 +66,8 @@ class BrowserMenuBottomSheet( .findViewById(R.id.menuItemView) .setSize(MenuItemViewSize.MEDIUM) + addTopInContextSections() + setOnShowListener { dialogInterface -> (dialogInterface as BottomSheetDialog).setRoundCorners() @@ -199,6 +205,30 @@ class BrowserMenuBottomSheet( menuItemsContainer.addView(duckAiSectionBinding.root, menuItemsContainer.indexOfChild(anchor)) } + /** + * Adds the contributed top-of-menu sections (if any) into their container, and keeps the single + * trailing divider shown only while at least one section is visible. Contributed views hide + * themselves asynchronously, so a global-layout listener re-derives the divider visibility on every + * layout pass (the guarded write prevents a relayout loop). + */ + private fun addTopInContextSections() { + val url = currentUrl ?: return + topInContextSections.forEach { section -> + binding.topInContextSection.addView(section.getView(url, context) { onContributedItemClicked() }) + } + binding.topInContextSection.viewTreeObserver.addOnGlobalLayoutListener { + val shouldShow = binding.topInContextSection.isVisible && binding.topInContextSection.children.any { it.isVisible } + if (binding.topInContextSectionDivider.isVisible != shouldShow) { + binding.topInContextSectionDivider.isVisible = shouldShow + } + } + } + + private fun onContributedItemClicked() { + onMenuItemClickListener() + dismiss() + } + fun onMenuItemClicked(view: View, onClick: () -> Unit) { view.setOnClickListener { onMenuItemClickListener() @@ -232,6 +262,7 @@ class BrowserMenuBottomSheet( } private fun renderBrowserMenu(viewState: BrowserMenuViewState.Browser) { + binding.topInContextSection.isVisible = true backMenuItem.isEnabled = viewState.canGoBack forwardMenuItem.isEnabled = viewState.canGoForward newDuckChatMenuItem.isEnabled = viewState.showDuckChatOption @@ -348,6 +379,7 @@ class BrowserMenuBottomSheet( } private fun renderCustomTabsMenu(viewState: BrowserMenuViewState.CustomTabs) { + binding.topInContextSection.isVisible = true backMenuItem.isEnabled = viewState.canGoBack forwardMenuItem.isEnabled = viewState.canGoForward newTabMenuItem.isVisible = false diff --git a/browser/browser-ui/src/main/res/layout/bottom_sheet_browser_menu.xml b/browser/browser-ui/src/main/res/layout/bottom_sheet_browser_menu.xml index 0ae3843fd16d..e055190a0799 100644 --- a/browser/browser-ui/src/main/res/layout/bottom_sheet_browser_menu.xml +++ b/browser/browser-ui/src/main/res/layout/bottom_sheet_browser_menu.xml @@ -124,6 +124,22 @@ app:defaultPadding="false" app:fullWidth="false" /> + + + +