diff --git a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt index b6b399183938..d4cc69fd582b 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt @@ -21,11 +21,12 @@ import android.content.Intent import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR -import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -35,6 +36,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.duckduckgo.anvil.annotations.InjectWith import com.duckduckgo.app.browser.R +import com.duckduckgo.app.browser.databinding.ActivityTabSwitcherBinding +import com.duckduckgo.app.browser.databinding.PopupTabsMenuBinding import com.duckduckgo.app.browser.favicon.FaviconManager import com.duckduckgo.app.browser.tabpreview.WebViewPreviewPersister import com.duckduckgo.app.di.AppCoroutineScope @@ -53,18 +56,20 @@ import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.Close import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.CloseAllTabsRequest +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.Mode.Selection import com.duckduckgo.appbuildconfig.api.AppBuildConfig import com.duckduckgo.common.ui.DuckDuckGoActivity +import com.duckduckgo.common.ui.menu.PopupMenu import com.duckduckgo.common.ui.view.button.ButtonType.DESTRUCTIVE import com.duckduckgo.common.ui.view.button.ButtonType.GHOST_ALT import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder import com.duckduckgo.common.ui.view.gone import com.duckduckgo.common.ui.view.hide import com.duckduckgo.common.ui.view.show +import com.duckduckgo.common.ui.viewbinding.viewBinding import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.di.scopes.ActivityScope import com.duckduckgo.duckchat.api.DuckChat -import com.duckduckgo.mobile.android.R as commonR import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar @@ -140,17 +145,20 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine private lateinit var toolbar: Toolbar private lateinit var tabsFab: ExtendedFloatingActionButton + private var popupMenuItem: MenuItem? = null private var layoutTypeMenuItem: MenuItem? = null - private var layoutType: LayoutType? = null + + private val binding: ActivityTabSwitcherBinding by viewBinding() + private val popupMenu by lazy { + PopupMenu(layoutInflater, R.layout.popup_tabs_menu) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_tab_switcher) + setContentView(binding.root) firstTimeLoadingTabsList = savedInstanceState?.getBoolean(KEY_FIRST_TIME_LOADING) ?: true - tabsFab = findViewById(R.id.tabsFab) - extractIntentExtras() configureViewReferences() setupToolbar(toolbar) @@ -158,9 +166,14 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine configureFab() configureObservers() configureOnBackPressedListener() + + if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + initMenuClickListeners() + } } private fun configureFab() { + tabsFab = binding.tabsFab if (tabManagerFeatureFlags.multiSelection().isEnabled()) { tabsFab.show() tabsFab.setOnClickListener { @@ -253,7 +266,7 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine lifecycleScope.launch { viewModel.viewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collectLatest { - updateFabType(it.fabType) + invalidateOptionsMenu() } } @@ -267,7 +280,6 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine val centerOffsetPercent = getCurrentCenterOffset() - this.layoutType = layoutType when (layoutType) { LayoutType.GRID -> { val gridLayoutManager = GridLayoutManager( @@ -297,19 +309,6 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine tabsRecycler.show() } - private fun updateFabType(fabType: TabSwitcherViewModel.ViewState.FabType) { - when (fabType) { - TabSwitcherViewModel.ViewState.FabType.NEW_TAB -> { - tabsFab.icon = AppCompatResources.getDrawable(this, commonR.drawable.ic_add_24) - tabsFab.setText(R.string.tabSwitcherFabNewTab) - } - TabSwitcherViewModel.ViewState.FabType.CLOSE_TABS -> { - tabsFab.icon = AppCompatResources.getDrawable(this, commonR.drawable.ic_close_24) - tabsFab.setText(R.string.tabSwitcherFabCloseTabs) - } - } - } - private fun scrollToPreviousCenterOffset(centerOffsetPercent: Float) { tabsRecycler.post { val newRange = tabsRecycler.computeVerticalScrollRange() @@ -370,10 +369,21 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine } override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.menu_tab_switcher_activity, menu) - layoutTypeMenuItem = menu.findItem(R.id.layoutType) + if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + menuInflater.inflate(R.menu.menu_tab_switcher_activity_with_selection, menu) + popupMenuItem = menu.findItem(R.id.popupMenuItem) - when (layoutType) { + val popupBinding = PopupTabsMenuBinding.bind(popupMenu.contentView) + val viewState = viewModel.viewState.value + val numSelectedTabs = (viewModel.viewState.value.mode as? Selection)?.selectedTabs?.size ?: 0 + + layoutTypeMenuItem = menu.createDynamicInterface(numSelectedTabs, popupBinding, binding.tabsFab, viewState.dynamicInterface) + } else { + menuInflater.inflate(R.menu.menu_tab_switcher_activity, menu) + layoutTypeMenuItem = menu.findItem(R.id.layoutTypeMenuItem) + } + + when (viewModel.layoutType.value) { LayoutType.GRID -> showListLayoutButton() LayoutType.LIST -> showGridLayoutButton() null -> layoutTypeMenuItem?.isVisible = false @@ -382,10 +392,22 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine return true } + private fun initMenuClickListeners() { + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.newTabMenuItem)) { onNewTabRequested(fromOverflowMenu = true) } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.selectAllMenuItem)) { viewModel.onSelectAllTabs() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.shareSelectedLinksMenuItem)) { viewModel.onShareSelectedTabs() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.bookmarkSelectedTabsMenuItem)) { viewModel.onBookmarkSelectedTabs() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.selectTabsMenuItem)) { viewModel.onSelectionModeRequested() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.closeSelectedTabsMenuItem)) { viewModel.onCloseSelectedTabs() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.closeOtherTabsMenuItem)) { viewModel.onCloseOtherTabs() } + popupMenu.onMenuItemClicked(popupMenu.contentView.findViewById(R.id.closeAllTabsMenuItem)) { viewModel.onCloseAllTabsRequested() } + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.layoutType -> onLayoutTypeToggled() - R.id.fire -> onFire() + R.id.layoutTypeMenuItem -> onLayoutTypeToggled() + R.id.fireMenuItem -> onFire() + R.id.popupMenuItem -> showPopupMenu(item.itemId) R.id.newTab -> onNewTabRequested(fromOverflowMenu = false) R.id.newTabOverflow -> onNewTabRequested(fromOverflowMenu = true) R.id.duckChat -> { @@ -403,13 +425,20 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine return super.onOptionsItemSelected(item) } + private fun showPopupMenu(itemId: Int) { + val anchorView = findViewById(itemId) + popupMenu.show(binding.root, anchorView) + } + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { - val closeAllTabsMenuItem = menu?.findItem(R.id.closeAllTabs) - closeAllTabsMenuItem?.isVisible = viewModel.tabSwitcherItems.value?.isNotEmpty() == true val duckChatMenuItem = menu?.findItem(R.id.duckChat) duckChatMenuItem?.isVisible = duckChat.showInBrowserMenu() - return super.onPrepareOptionsMenu(menu) + return if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + viewModel.viewState.value.dynamicInterface.isMoreMenuItemEnabled + } else { + super.onPrepareOptionsMenu(menu) + } } override fun onMenuOpened(featureId: Int, menu: Menu): Boolean { diff --git a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherDynamicMenu.kt b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherDynamicMenu.kt new file mode 100644 index 000000000000..5bebbcf941f8 --- /dev/null +++ b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherDynamicMenu.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 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.tabs.ui + +import android.view.Menu +import android.view.MenuItem +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.view.isVisible +import com.duckduckgo.app.browser.R +import com.duckduckgo.app.browser.databinding.PopupTabsMenuBinding +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.DynamicInterface +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.FabType +import com.duckduckgo.mobile.android.R as CommonR +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + +fun Menu.createDynamicInterface( + numSelectedTabs: Int, + popupMenu: PopupTabsMenuBinding, + fab: ExtendedFloatingActionButton, + dynamicMenu: DynamicInterface, +): MenuItem { + findItem(R.id.fireMenuItem).isVisible = dynamicMenu.isFireButtonVisible + val layoutButton = findItem(R.id.layoutTypeMenuItem).apply { + isVisible = dynamicMenu.isLayoutTypeButtonVisible + } + + popupMenu.newTabMenuItem.isVisible = dynamicMenu.isNewTabVisible + popupMenu.selectAllMenuItem.isVisible = dynamicMenu.isSelectAllVisible + // popupMenu.deselectAllMenuItem.isVisible = dynamicMenu.isDeselectAllVisible + popupMenu.selectionActionsDivider.isVisible = dynamicMenu.isSelectionActionsDividerVisible + popupMenu.shareSelectedLinksMenuItem.isVisible = dynamicMenu.isShareSelectedLinksVisible + popupMenu.bookmarkSelectedTabsMenuItem.isVisible = dynamicMenu.isBookmarkSelectedTabsVisible + popupMenu.selectTabsDivider.isVisible = dynamicMenu.isSelectTabsDividerVisible + popupMenu.selectTabsMenuItem.isVisible = dynamicMenu.isSelectTabsVisible + popupMenu.closeSelectedTabsMenuItem.isVisible = dynamicMenu.isCloseSelectedTabsVisible + popupMenu.closeOtherTabsMenuItem.isVisible = dynamicMenu.isCloseOtherTabsVisible + popupMenu.closeAllTabsMenuItem.isVisible = dynamicMenu.isCloseAllTabsVisible + + popupMenu.shareSelectedLinksMenuItem.apply { + setPrimaryText(resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs)) + } + popupMenu.bookmarkSelectedTabsMenuItem.apply { + setPrimaryText(resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs)) + } + popupMenu.closeSelectedTabsMenuItem.apply { + setPrimaryText(resources.getQuantityString(R.plurals.closeTabsMenuItem, numSelectedTabs, numSelectedTabs)) + } + + fab.apply { + isVisible = dynamicMenu.isFabVisible + when (dynamicMenu.fabType) { + FabType.NEW_TAB -> { + text = resources.getString(R.string.newTabMenuItem) + icon = AppCompatResources.getDrawable(context, CommonR.drawable.ic_add_24) + } + FabType.CLOSE_TABS -> { + text = resources.getQuantityString(R.plurals.closeTabsMenuItem, numSelectedTabs, numSelectedTabs) + icon = AppCompatResources.getDrawable(context, CommonR.drawable.ic_close_24) + } + } + } + + return layoutButton +} diff --git a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt index c617e848d855..f0fb9a4f2c47 100644 --- a/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt +++ b/app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModel.kt @@ -28,11 +28,13 @@ import com.duckduckgo.app.browser.session.WebViewSessionStorage import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily +import com.duckduckgo.app.tabs.TabManagerFeatureFlags import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabRepository import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType.GRID import com.duckduckgo.app.tabs.model.TabSwitcherData.LayoutType.LIST -import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.FabType +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.Mode +import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.ViewState.Mode.Normal import com.duckduckgo.common.utils.DispatcherProvider import com.duckduckgo.common.utils.SingleLiveEvent import com.duckduckgo.common.utils.extensions.toBinaryString @@ -42,7 +44,7 @@ import com.duckduckgo.duckchat.impl.DuckChatPixelName import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -58,6 +60,7 @@ class TabSwitcherViewModel @Inject constructor( private val pixel: Pixel, private val swipingTabsFeature: SwipingTabsFeatureProvider, private val duckChat: DuckChat, + private val tabManagerFeatureFlags: TabManagerFeatureFlags, ) : ViewModel() { val tabSwitcherItems: LiveData> = tabRepository.liveTabs.map { tabEntities -> tabEntities.map { TabSwitcherItem.Tab(it) } @@ -67,14 +70,25 @@ class TabSwitcherViewModel @Inject constructor( context = viewModelScope.coroutineContext, ) - val layoutType = tabRepository.tabSwitcherData - .map { it.layoutType } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) - val command: SingleLiveEvent = SingleLiveEvent() private val _viewState = MutableStateFlow(ViewState()) - val viewState = _viewState.asStateFlow() + val viewState = combine(_viewState, tabRepository.flowTabs) { viewState, tabs -> + viewState.copy(items = tabs.map { TabSwitcherItem.Tab(it) }) + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ViewState()) + + val layoutType = if (tabManagerFeatureFlags.multiSelection().isEnabled()) { + combine(tabRepository.tabSwitcherData, viewState) { tabSwitcherData, viewState -> + if (viewState.dynamicInterface.isLayoutTypeButtonVisible) { + tabSwitcherData.layoutType + } else { + null + } + } + } else { + tabRepository.tabSwitcherData + .map { it.layoutType } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) sealed class Command { data object Close : Command() @@ -141,6 +155,24 @@ class TabSwitcherViewModel @Inject constructor( pixel.fire(AppPixelName.TAB_MANAGER_MENU_CLOSE_ALL_TABS_PRESSED) } + fun onSelectAllTabs() { + } + + fun onShareSelectedTabs() { + } + + fun onBookmarkSelectedTabs() { + } + + fun onSelectionModeRequested() { + } + + fun onCloseSelectedTabs() { + } + + fun onCloseOtherTabs() { + } + fun onCloseAllTabsConfirmed() { viewModelScope.launch(dispatcherProvider.io()) { tabSwitcherItems.value?.forEach { tabSwitcherItem -> @@ -201,10 +233,17 @@ class TabSwitcherViewModel @Inject constructor( } fun onFabClicked() { - if (viewState.value.fabType == FabType.NEW_TAB) { - _viewState.update { it.copy(FabType.CLOSE_TABS) } - } else { - _viewState.update { it.copy(FabType.NEW_TAB) } + when { + _viewState.value.mode is ViewState.Mode.Normal -> { + _viewState.update { it.copy(mode = ViewState.Mode.Selection(emptyList())) } + } + _viewState.value.mode is ViewState.Mode.Selection -> { + if ((_viewState.value.mode as ViewState.Mode.Selection).selectedTabs.isEmpty()) { + _viewState.update { it.copy(mode = ViewState.Mode.Selection(listOf("123", "456"))) } + } else { + _viewState.update { it.copy(mode = ViewState.Mode.Normal) } + } + } } } @@ -221,11 +260,85 @@ class TabSwitcherViewModel @Inject constructor( } data class ViewState( - val fabType: FabType = FabType.NEW_TAB, + val mode: Mode = Normal, + val items: List = emptyList(), ) { + val dynamicInterface: DynamicInterface + get() = when (mode) { + is Normal -> { + val isThereNotJustNewTabPage = items.size != 1 || (items.first() as? TabSwitcherItem.Tab)?.tabEntity?.url != null + DynamicInterface( + isLayoutTypeButtonVisible = true, + isFireButtonVisible = true, + isNewTabVisible = true, + isSelectAllVisible = false, + isDeselectAllVisible = false, + isSelectionActionsDividerVisible = false, + isShareSelectedLinksVisible = false, + isBookmarkSelectedTabsVisible = false, + isSelectTabsDividerVisible = true, + isSelectTabsVisible = true, + isCloseSelectedTabsVisible = false, + isCloseOtherTabsVisible = false, + isCloseAllTabsVisible = true, + isMoreMenuItemEnabled = isThereNotJustNewTabPage, + isFabVisible = isThereNotJustNewTabPage, + fabType = FabType.NEW_TAB, + ) + } + is Mode.Selection -> { + val areNoTabsSelected = mode.selectedTabs.isNotEmpty() + val areAllTabsSelected = mode.selectedTabs.size == items.size + DynamicInterface( + isLayoutTypeButtonVisible = false, + isFireButtonVisible = false, + isNewTabVisible = false, + isSelectAllVisible = !areAllTabsSelected, + isDeselectAllVisible = areAllTabsSelected, + isSelectionActionsDividerVisible = areNoTabsSelected, + isShareSelectedLinksVisible = areNoTabsSelected, + isBookmarkSelectedTabsVisible = areNoTabsSelected, + isSelectTabsDividerVisible = areNoTabsSelected, + isSelectTabsVisible = false, + isCloseSelectedTabsVisible = areNoTabsSelected, + isCloseOtherTabsVisible = areNoTabsSelected, + isCloseAllTabsVisible = false, + isMoreMenuItemEnabled = true, + isFabVisible = areNoTabsSelected, + fabType = FabType.CLOSE_TABS, + ) + } + } + + data class DynamicInterface( + val isLayoutTypeButtonVisible: Boolean, + val isFireButtonVisible: Boolean, + val isNewTabVisible: Boolean, + val isSelectAllVisible: Boolean, + val isDeselectAllVisible: Boolean, + val isSelectionActionsDividerVisible: Boolean, + val isShareSelectedLinksVisible: Boolean, + val isBookmarkSelectedTabsVisible: Boolean, + val isSelectTabsDividerVisible: Boolean, + val isSelectTabsVisible: Boolean, + val isCloseSelectedTabsVisible: Boolean, + val isCloseOtherTabsVisible: Boolean, + val isCloseAllTabsVisible: Boolean, + val isMoreMenuItemEnabled: Boolean, + val isFabVisible: Boolean, + val fabType: FabType, + ) + enum class FabType { NEW_TAB, CLOSE_TABS, } + + sealed interface Mode { + data object Normal : Mode + data class Selection( + val selectedTabs: List = emptyList(), + ) : Mode + } } } diff --git a/app/src/main/res/drawable/ic_add_bookmark_24.xml b/app/src/main/res/drawable/ic_add_bookmark_24.xml new file mode 100644 index 000000000000..b1832fb911e1 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_bookmark_24.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/layout/popup_tabs_menu.xml b/app/src/main/res/layout/popup_tabs_menu.xml new file mode 100644 index 000000000000..8e37bcba235c --- /dev/null +++ b/app/src/main/res/layout/popup_tabs_menu.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_tab_switcher_activity.xml b/app/src/main/res/menu/menu_tab_switcher_activity.xml index 0c826ba0b93b..e40b8ab980df 100644 --- a/app/src/main/res/menu/menu_tab_switcher_activity.xml +++ b/app/src/main/res/menu/menu_tab_switcher_activity.xml @@ -20,14 +20,14 @@ tools:ignore="AlwaysShowAction"> diff --git a/app/src/main/res/menu/menu_tab_switcher_activity_with_selection.xml b/app/src/main/res/menu/menu_tab_switcher_activity_with_selection.xml new file mode 100644 index 000000000000..332a11df4038 --- /dev/null +++ b/app/src/main/res/menu/menu_tab_switcher_activity_with_selection.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index 3d4a9cba1147..192693e647cf 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -79,8 +79,21 @@ Skip Onboarding" - New Tab - Close Tabs + Select Tabs + Select All + + Share Link + Share %1$d Links + + + Bookmark Tab + Bookmark %1$d Tabs + + + Close Tab + Close %1$d Tabs + + Close Other Tabs Set As Default Browser diff --git a/app/src/test/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModelTest.kt b/app/src/test/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModelTest.kt index 4144eb243669..be7f9263423c 100644 --- a/app/src/test/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModelTest.kt +++ b/app/src/test/java/com/duckduckgo/app/tabs/ui/TabSwitcherViewModelTest.kt @@ -29,6 +29,7 @@ import com.duckduckgo.app.pixels.AppPixelName import com.duckduckgo.app.statistics.pixels.Pixel import com.duckduckgo.app.statistics.pixels.Pixel.PixelType.Daily import com.duckduckgo.app.statistics.store.StatisticsDataStore +import com.duckduckgo.app.tabs.TabManagerFeatureFlags import com.duckduckgo.app.tabs.model.TabEntity import com.duckduckgo.app.tabs.model.TabRepository import com.duckduckgo.app.tabs.model.TabSwitcherData @@ -97,8 +98,9 @@ class TabSwitcherViewModelTest { @Mock private lateinit var duckChatMock: DuckChat - private val swipingTabsFeature = FakeFeatureToggleFactory.create(SwipingTabsFeature::class.java) + private val tabManagerFeatureFlags = FakeFeatureToggleFactory.create(TabManagerFeatureFlags::class.java) + private val swipingTabsFeature = FakeFeatureToggleFactory.create(SwipingTabsFeature::class.java) private val swipingTabsFeatureProvider = SwipingTabsFeatureProvider(swipingTabsFeature) private lateinit var testee: TabSwitcherViewModel @@ -114,6 +116,7 @@ class TabSwitcherViewModelTest { MockitoAnnotations.openMocks(this) swipingTabsFeature.self().setRawStoredState(State(enable = true)) + tabManagerFeatureFlags.multiSelection().setRawStoredState(State(enable = false)) whenever(mockTabRepository.flowDeletableTabs) .thenReturn(repoDeletableTabs.consumeAsFlow()) @@ -138,6 +141,7 @@ class TabSwitcherViewModelTest { mockPixel, swipingTabsFeatureProvider, duckChatMock, + tabManagerFeatureFlags, ) testee.command.observeForever(mockCommandObserver) }