Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-selection: Dynamic menu #5588

Open
wants to merge 5 commits into
base: feature/ondrej/tab-multi-selection-fab
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 135 additions & 8 deletions app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@ 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.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
Expand All @@ -35,6 +38,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
Expand All @@ -55,12 +60,14 @@ import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.Close
import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.CloseAllTabsRequest
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
Expand Down Expand Up @@ -139,27 +146,36 @@ 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)
configureRecycler()
configureFab()
configureObservers()
configureOnBackPressedListener()

if (tabManagerFeatureFlags.multiSelection().isEnabled()) {
initMenuClickListeners()
}
}

private fun configureFab() {
tabsFab = binding.tabsFab
if (tabManagerFeatureFlags.multiSelection().isEnabled()) {
tabsFab.show()
tabsFab.setOnClickListener {
Expand Down Expand Up @@ -251,6 +267,8 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine

lifecycleScope.launch {
viewModel.viewState.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collectLatest {
invalidateOptionsMenu()

updateFabType(it.fabType)
}
}
Expand Down Expand Up @@ -368,22 +386,126 @@ 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)

val mode = viewModel.viewState.value.mode
when (mode) {
TabSwitcherViewModel.ViewState.Mode.Normal -> {
createNormalModeMenu(menu)
}
is TabSwitcherViewModel.ViewState.Mode.Selection -> {
createSelectionModeMenu(menu, mode.selectedTabs.size)
}
}
} else {
menuInflater.inflate(R.menu.menu_tab_switcher_activity, menu)
layoutTypeMenuItem = menu.findItem(R.id.layoutTypeMenuItem)

when (layoutType) {
LayoutType.GRID -> showListLayoutButton()
LayoutType.LIST -> showGridLayoutButton()
null -> layoutTypeMenuItem?.isVisible = false
}
}

return true
}

private fun createSelectionModeMenu(menu: Menu, numSelectedTabs: Int) {
menu.findItem(R.id.layoutTypeMenuItem).isVisible = false
menu.findItem(R.id.fireMenuItem).isVisible = false

menu.findItem(R.id.bookmarkMenuItem).apply {
if (numSelectedTabs == 0) {
isEnabled = false
iconTintList = ContextCompat.getColorStateList(this@TabSwitcherActivity, com.duckduckgo.mobile.android.R.color.disabledColor)
}
title = resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs)
}
menu.findItem(R.id.shareLinkMenuItem).apply {
if (numSelectedTabs == 0) {
isEnabled = false
iconTintList = ContextCompat.getColorStateList(this@TabSwitcherActivity, com.duckduckgo.mobile.android.R.color.disabledColor)
}
title = resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs)
}

val popupBinding = PopupTabsMenuBinding.bind(popupMenu.contentView)

popupBinding.newTabMenuItem.isVisible = false
popupBinding.selectAllMenuItem.isVisible = true
popupBinding.selectionActionsDivider.isVisible = numSelectedTabs > 0
popupBinding.shareSelectedLinksMenuItem.isVisible = numSelectedTabs > 0
popupBinding.bookmarkSelectedTabsMenuItem.isVisible = numSelectedTabs > 0
popupBinding.selectTabsDivider.isVisible = false
popupBinding.selectTabsMenuItem.isVisible = false
popupBinding.closeOtherTabsMenuItem.isVisible = numSelectedTabs > 0
popupBinding.closeSelectedTabsMenuItem.isVisible = numSelectedTabs > 0
popupBinding.closeAllTabsMenuItem.isVisible = numSelectedTabs == 0

popupBinding.shareSelectedLinksMenuItem.apply {
setPrimaryText(resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs))
}
popupBinding.bookmarkSelectedTabsMenuItem.apply {
setPrimaryText(resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs))
}
popupBinding.closeSelectedTabsMenuItem.apply {
setPrimaryText(resources.getQuantityString(R.plurals.closeTabsMenuItem, numSelectedTabs, numSelectedTabs))
}
}

private fun createNormalModeMenu(menu: Menu) {
layoutTypeMenuItem = menu.findItem(R.id.layoutTypeMenuItem)

when (layoutType) {
LayoutType.GRID -> showListLayoutButton()
LayoutType.LIST -> showGridLayoutButton()
null -> layoutTypeMenuItem?.isVisible = false
}

return true
menu.findItem(R.id.bookmarkMenuItem).isVisible = false
menu.findItem(R.id.shareLinkMenuItem).isVisible = false

val popupBinding = PopupTabsMenuBinding.bind(popupMenu.contentView)

popupBinding.newTabMenuItem.isVisible = true
popupBinding.selectAllMenuItem.isVisible = false
popupBinding.selectionActionsDivider.isVisible = true
popupBinding.shareSelectedLinksMenuItem.isVisible = true
popupBinding.bookmarkSelectedTabsMenuItem.isVisible = true
popupBinding.selectTabsDivider.isVisible = true
popupBinding.selectTabsMenuItem.isVisible = true
popupBinding.closeSelectedTabsMenuItem.isVisible = false
popupBinding.closeOtherTabsMenuItem.isVisible = false
popupBinding.closeAllTabsMenuItem.isVisible = true

popupBinding.shareSelectedLinksMenuItem.apply {
setPrimaryText(resources.getQuantityString(R.plurals.shareLinksMenuItem, 1))
}
popupBinding.bookmarkSelectedTabsMenuItem.apply {
setPrimaryText(resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, 1))
}
}

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.bookmarkAllTabsMenuItem)) { viewModel.onBookmarkAllTabs() }
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 -> duckChat.openDuckChat()
Expand All @@ -399,6 +521,11 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
return super.onOptionsItemSelected(item)
}

private fun showPopupMenu(itemId: Int) {
val anchorView = findViewById<View>(itemId)
popupMenu.show(binding.root, anchorView)
}

override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
val closeAllTabsMenuItem = menu?.findItem(R.id.closeAllTabs)
closeAllTabsMenuItem?.isVisible = viewModel.tabs.value?.isNotEmpty() == true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@ class TabSwitcherViewModel @Inject constructor(
pixel.fire(AppPixelName.TAB_MANAGER_MENU_CLOSE_ALL_TABS_PRESSED)
}

fun onSelectAllTabs() {
}

fun onShareSelectedTabs() {
}

fun onBookmarkSelectedTabs() {
}

fun onBookmarkAllTabs() {
}

fun onSelectionModeRequested() {
}

fun onCloseSelectedTabs() {
}

fun onCloseOtherTabs() {
}

fun onCloseAllTabsConfirmed() {
viewModelScope.launch(dispatcherProvider.io()) {
tabs.value?.forEach {
Expand Down Expand Up @@ -189,19 +210,34 @@ 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) }
}
}
}
}

data class ViewState(
val fabType: FabType = FabType.NEW_TAB,
val mode: Mode = Mode.Selection(emptyList<String>()),
) {
enum class FabType {
NEW_TAB,
CLOSE_TABS,
}

sealed interface Mode {
data object Normal : Mode
data class Selection(
val selectedTabs: List<String> = emptyList<String>(),
) : Mode
}
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/drawable/ic_add_bookmark_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M8.25,2C6.455,2 5,3.455 5,5.25V20.194C5,21.545 6.465,22.386 7.632,21.706L12,19.158L16.368,21.706C17.535,22.386 19,21.545 19,20.194V20C19,19.448 18.552,19 18,19C17.525,19 17.128,19.33 17.026,19.774L12.882,17.357C12.337,17.039 11.663,17.039 11.118,17.357L7,19.759V5.25C7,4.56 7.56,4 8.25,4H16C16.307,4 16.581,4.138 16.765,4.356C17.014,4.651 17.313,5 17.7,5L17.7,5C18.418,5 19.025,4.391 18.723,3.74C18.247,2.713 17.207,2 16,2H8.25Z"
android:fillColor="?attr/daxColorPrimaryIcon" />
<path
android:pathData="M18,7C18.552,7 19,7.448 19,8V11H22C22.552,11 23,11.448 23,12C23,12.552 22.552,13 22,13H19V16C19,16.552 18.552,17 18,17C17.448,17 17,16.552 17,16V13H14C13.448,13 13,12.552 13,12C13,11.448 13.448,11 14,11H17V8C17,7.448 17.448,7 18,7Z"
android:fillColor="?attr/daxColorPrimaryIcon" />
</vector>
Loading
Loading