Skip to content

Commit c5d6028

Browse files
committed
Use a custom popup menu for the selection
1 parent 3258262 commit c5d6028

File tree

7 files changed

+215
-206
lines changed

7 files changed

+215
-206
lines changed

app/src/main/java/com/duckduckgo/app/tabs/ui/TabSwitcherActivity.kt

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,17 @@ package com.duckduckgo.app.tabs.ui
1818

1919
import android.content.Context
2020
import android.content.Intent
21-
import android.graphics.Color
2221
import android.os.Bundle
23-
import android.text.SpannableString
24-
import android.text.style.ForegroundColorSpan
2522
import android.view.Menu
2623
import android.view.MenuItem
24+
import android.view.View
2725
import android.widget.TextView
2826
import androidx.activity.OnBackPressedCallback
2927
import androidx.appcompat.app.AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR
3028
import androidx.appcompat.content.res.AppCompatResources
3129
import androidx.appcompat.widget.Toolbar
32-
import androidx.core.view.MenuCompat
30+
import androidx.core.content.ContextCompat
31+
import androidx.core.view.isVisible
3332
import androidx.lifecycle.Lifecycle
3433
import androidx.lifecycle.flowWithLifecycle
3534
import androidx.lifecycle.lifecycleScope
@@ -39,6 +38,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
3938
import androidx.recyclerview.widget.RecyclerView
4039
import com.duckduckgo.anvil.annotations.InjectWith
4140
import com.duckduckgo.app.browser.R
41+
import com.duckduckgo.app.browser.databinding.ActivityTabSwitcherBinding
42+
import com.duckduckgo.app.browser.databinding.PopupTabsMenuBinding
4243
import com.duckduckgo.app.browser.favicon.FaviconManager
4344
import com.duckduckgo.app.browser.tabpreview.WebViewPreviewPersister
4445
import com.duckduckgo.app.di.AppCoroutineScope
@@ -59,26 +60,28 @@ import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.Close
5960
import com.duckduckgo.app.tabs.ui.TabSwitcherViewModel.Command.CloseAllTabsRequest
6061
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
6162
import com.duckduckgo.common.ui.DuckDuckGoActivity
63+
import com.duckduckgo.common.ui.menu.PopupMenu
6264
import com.duckduckgo.common.ui.view.button.ButtonType.DESTRUCTIVE
6365
import com.duckduckgo.common.ui.view.button.ButtonType.GHOST_ALT
6466
import com.duckduckgo.common.ui.view.dialog.TextAlertDialogBuilder
6567
import com.duckduckgo.common.ui.view.gone
6668
import com.duckduckgo.common.ui.view.hide
6769
import com.duckduckgo.common.ui.view.show
70+
import com.duckduckgo.common.ui.viewbinding.viewBinding
6871
import com.duckduckgo.common.utils.DispatcherProvider
6972
import com.duckduckgo.di.scopes.ActivityScope
7073
import com.duckduckgo.duckchat.api.DuckChat
7174
import com.duckduckgo.duckchat.impl.DuckChatPixelName
7275
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
7376
import com.google.android.material.snackbar.BaseTransientBottomBar
7477
import com.google.android.material.snackbar.Snackbar
75-
import javax.inject.Inject
76-
import kotlin.coroutines.CoroutineContext
7778
import kotlinx.coroutines.CoroutineScope
7879
import kotlinx.coroutines.SupervisorJob
7980
import kotlinx.coroutines.flow.collectLatest
8081
import kotlinx.coroutines.flow.filterNotNull
8182
import kotlinx.coroutines.launch
83+
import javax.inject.Inject
84+
import kotlin.coroutines.CoroutineContext
8285

8386
@InjectWith(ActivityScope::class)
8487
class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, CoroutineScope {
@@ -144,17 +147,21 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
144147
private lateinit var toolbar: Toolbar
145148
private lateinit var tabsFab: ExtendedFloatingActionButton
146149

150+
private var popupMenuItem: MenuItem? = null
147151
private var layoutTypeMenuItem: MenuItem? = null
148152
private var layoutType: LayoutType? = null
149153

154+
private val binding: ActivityTabSwitcherBinding by viewBinding()
155+
private val popupMenu by lazy {
156+
PopupMenu(layoutInflater, R.layout.popup_tabs_menu)
157+
}
158+
150159
override fun onCreate(savedInstanceState: Bundle?) {
151160
super.onCreate(savedInstanceState)
152-
setContentView(R.layout.activity_tab_switcher)
161+
setContentView(binding.root)
153162

154163
firstTimeLoadingTabsList = savedInstanceState?.getBoolean(KEY_FIRST_TIME_LOADING) ?: true
155164

156-
tabsFab = findViewById(R.id.tabsFab)
157-
158165
extractIntentExtras()
159166
configureViewReferences()
160167
setupToolbar(toolbar)
@@ -165,6 +172,7 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
165172
}
166173

167174
private fun configureFab() {
175+
tabsFab = binding.tabsFab
168176
if (tabManagerFeatureFlags.multiSelection().isEnabled()) {
169177
tabsFab.show()
170178
tabsFab.setOnClickListener {
@@ -377,24 +385,21 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
377385

378386
override fun onCreateOptionsMenu(menu: Menu): Boolean {
379387
if (tabManagerFeatureFlags.multiSelection().isEnabled()) {
388+
menuInflater.inflate(R.menu.menu_tab_switcher_activity_with_selection, menu)
389+
popupMenuItem = menu.findItem(R.id.popupMenuItem)
390+
380391
val mode = viewModel.viewState.value.mode
381392
when (mode) {
382393
TabSwitcherViewModel.ViewState.Mode.Normal -> {
383394
createNormalModeMenu(menu)
384395
}
385396
is TabSwitcherViewModel.ViewState.Mode.Selection -> {
386-
if (mode.selectedTabs.isNotEmpty()) {
387-
createSelectionModeMenu(menu, mode.selectedTabs.size)
388-
} else {
389-
createEmptySelectionModeMenu(menu)
390-
}
397+
createSelectionModeMenu(menu, mode.selectedTabs.size)
391398
}
392399
}
393-
394-
MenuCompat.setGroupDividerEnabled(menu, true)
395400
} else {
396401
menuInflater.inflate(R.menu.menu_tab_switcher_activity, menu)
397-
layoutTypeMenuItem = menu.findItem(R.id.layoutType)
402+
layoutTypeMenuItem = menu.findItem(R.id.layoutTypeMenuItem)
398403

399404
when (layoutType) {
400405
LayoutType.GRID -> showListLayoutButton()
@@ -407,54 +412,86 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
407412
}
408413

409414
private fun createSelectionModeMenu(menu: Menu, numSelectedTabs: Int) {
410-
menuInflater.inflate(R.menu.menu_tab_switcher_selection_mode, menu)
411-
menu.findItem(R.id.shareSelectedLinks).title = resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs)
412-
menu.findItem(R.id.shareSelectedLinksIcon).title = resources.getQuantityString(
413-
R.plurals.shareLinksMenuItem,
414-
numSelectedTabs,
415-
numSelectedTabs,
416-
)
417-
menu.findItem(R.id.bookmarkSelectedTabs).title = resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs)
418-
menu.findItem(R.id.bookmarkSelectedTabsIcon).title = resources.getQuantityString(
419-
R.plurals.bookmarkTabsMenuItem,
420-
numSelectedTabs,
421-
numSelectedTabs,
422-
)
423-
menu.findItem(R.id.closeSelectedTabs).title = resources.getQuantityString(R.plurals.closeTabsMenuItem, numSelectedTabs, numSelectedTabs)
424-
menu.findItem(R.id.closeSelectedTabs).colorRed()
425-
menu.findItem(R.id.closeOtherTabs).colorRed()
426-
}
415+
menu.findItem(R.id.layoutTypeMenuItem).isVisible = false
416+
menu.findItem(R.id.fireMenuItem).isVisible = false
417+
418+
menu.findItem(R.id.bookmarkMenuItem).apply {
419+
if (numSelectedTabs == 0) {
420+
isEnabled = false
421+
iconTintList = ContextCompat.getColorStateList(this@TabSwitcherActivity, com.duckduckgo.mobile.android.R.color.disabledColor)
422+
}
423+
title = resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs)
424+
}
425+
menu.findItem(R.id.shareLinkMenuItem).apply {
426+
if (numSelectedTabs == 0) {
427+
isEnabled = false
428+
iconTintList = ContextCompat.getColorStateList(this@TabSwitcherActivity, com.duckduckgo.mobile.android.R.color.disabledColor)
429+
}
430+
title = resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs)
431+
}
427432

428-
private fun createEmptySelectionModeMenu(menu: Menu) {
429-
menuInflater.inflate(R.menu.menu_tab_switcher_selection_mode_no_selection, menu)
430-
menu.findItem(R.id.closeAllTabs).colorRed()
433+
val popupBinding = PopupTabsMenuBinding.bind(popupMenu.contentView)
434+
435+
popupBinding.newTabMenuItem.isVisible = false
436+
popupBinding.selectAllMenuItem.isVisible = true
437+
popupBinding.selectionActionsDivider.isVisible = numSelectedTabs > 0
438+
popupBinding.shareSelectedLinksMenuItem.isVisible = numSelectedTabs > 0
439+
popupBinding.bookmarkSelectedTabsMenuItem.isVisible = numSelectedTabs > 0
440+
popupBinding.selectTabsDivider.isVisible = false
441+
popupBinding.selectTabsMenuItem.isVisible = false
442+
popupBinding.closeOtherTabsMenuItem.isVisible = numSelectedTabs > 0
443+
popupBinding.closeSelectedTabsMenuItem.isVisible = numSelectedTabs > 0
444+
popupBinding.closeAllTabsMenuItem.isVisible = numSelectedTabs == 0
445+
446+
popupBinding.shareSelectedLinksMenuItem.apply {
447+
setPrimaryText(resources.getQuantityString(R.plurals.shareLinksMenuItem, numSelectedTabs, numSelectedTabs))
448+
}
449+
popupBinding.bookmarkSelectedTabsMenuItem.apply {
450+
setPrimaryText(resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, numSelectedTabs, numSelectedTabs))
451+
}
452+
popupBinding.closeSelectedTabsMenuItem.apply {
453+
setPrimaryText(resources.getQuantityString(R.plurals.closeTabsMenuItem, numSelectedTabs, numSelectedTabs))
454+
}
431455
}
432456

433457
private fun createNormalModeMenu(menu: Menu) {
434-
menuInflater.inflate(R.menu.menu_tab_switcher_normal_mode, menu)
435-
layoutTypeMenuItem = menu.findItem(R.id.layoutType)
458+
layoutTypeMenuItem = menu.findItem(R.id.layoutTypeMenuItem)
436459

437460
when (layoutType) {
438461
LayoutType.GRID -> showListLayoutButton()
439462
LayoutType.LIST -> showGridLayoutButton()
440463
null -> layoutTypeMenuItem?.isVisible = false
441464
}
442465

443-
menu.findItem(R.id.shareSelectedLinks).title = resources.getQuantityString(R.plurals.shareLinksMenuItem, 1)
444-
menu.findItem(R.id.bookmarkSelectedTabs).title = resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, 1)
445-
menu.findItem(R.id.closeAllTabs).colorRed()
446-
}
466+
menu.findItem(R.id.bookmarkMenuItem).isVisible = false
467+
menu.findItem(R.id.shareLinkMenuItem).isVisible = false
468+
469+
val popupBinding = PopupTabsMenuBinding.bind(popupMenu.contentView)
447470

448-
private fun MenuItem.colorRed() {
449-
val text = SpannableString(title)
450-
text.setSpan(ForegroundColorSpan(Color.RED), 0, text.length, 0)
451-
title = text
471+
popupBinding.newTabMenuItem.isVisible = true
472+
popupBinding.selectAllMenuItem.isVisible = false
473+
popupBinding.selectionActionsDivider.isVisible = true
474+
popupBinding.shareSelectedLinksMenuItem.isVisible = true
475+
popupBinding.bookmarkSelectedTabsMenuItem.isVisible = true
476+
popupBinding.selectTabsDivider.isVisible = true
477+
popupBinding.selectTabsMenuItem.isVisible = true
478+
popupBinding.closeSelectedTabsMenuItem.isVisible = false
479+
popupBinding.closeOtherTabsMenuItem.isVisible = false
480+
popupBinding.closeAllTabsMenuItem.isVisible = true
481+
482+
popupBinding.shareSelectedLinksMenuItem.apply {
483+
setPrimaryText(resources.getQuantityString(R.plurals.shareLinksMenuItem, 1))
484+
}
485+
popupBinding.bookmarkSelectedTabsMenuItem.apply {
486+
setPrimaryText(resources.getQuantityString(R.plurals.bookmarkTabsMenuItem, 1))
487+
}
452488
}
453489

454490
override fun onOptionsItemSelected(item: MenuItem): Boolean {
455491
when (item.itemId) {
456-
R.id.layoutType -> onLayoutTypeToggled()
457-
R.id.fire -> onFire()
492+
R.id.layoutTypeMenuItem -> onLayoutTypeToggled()
493+
R.id.fireMenuItem -> onFire()
494+
R.id.popupMenuItem -> showPopupMenu(item.itemId)
458495
R.id.newTab -> onNewTabRequested(fromOverflowMenu = false)
459496
R.id.newTabOverflow -> onNewTabRequested(fromOverflowMenu = true)
460497
R.id.duckChat -> {
@@ -473,6 +510,11 @@ class TabSwitcherActivity : DuckDuckGoActivity(), TabSwitcherListener, Coroutine
473510
return super.onOptionsItemSelected(item)
474511
}
475512

513+
private fun showPopupMenu(itemId: Int) {
514+
val anchorView = findViewById<View>(itemId)
515+
popupMenu.show(binding.root, anchorView)
516+
}
517+
476518
override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
477519
val closeAllTabsMenuItem = menu?.findItem(R.id.closeAllTabs)
478520
closeAllTabsMenuItem?.isVisible = viewModel.tabSwitcherItems.value?.isNotEmpty() == true
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Copyright (c) 2025 DuckDuckGo
3+
~
4+
~ Licensed under the Apache License, Version 2.0 (the "License");
5+
~ you may not use this file except in compliance with the License.
6+
~ You may obtain a copy of the License at
7+
~
8+
~ http://www.apache.org/licenses/LICENSE-2.0
9+
~
10+
~ Unless required by applicable law or agreed to in writing, software
11+
~ distributed under the License is distributed on an "AS IS" BASIS,
12+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
~ See the License for the specific language governing permissions and
14+
~ limitations under the License.
15+
-->
16+
17+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:app="http://schemas.android.com/apk/res-auto"
19+
android:layout_width="match_parent"
20+
android:layout_height="wrap_content"
21+
android:orientation="vertical"
22+
android:background="@drawable/popup_menu_bg">
23+
24+
<com.duckduckgo.common.ui.view.PopupMenuItemView
25+
android:id="@+id/newTabMenuItem"
26+
android:layout_width="match_parent"
27+
android:layout_height="wrap_content"
28+
app:primaryText="@string/newTabMenuItem" />
29+
30+
<com.duckduckgo.common.ui.view.PopupMenuItemView
31+
android:id="@+id/selectAllMenuItem"
32+
android:layout_width="match_parent"
33+
android:layout_height="wrap_content"
34+
app:primaryText="@string/selectAllTabsMenuItem" />
35+
36+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
37+
android:id="@+id/selectionActionsDivider"
38+
android:layout_width="match_parent"
39+
android:layout_height="wrap_content"
40+
app:defaultPadding="false" />
41+
42+
<com.duckduckgo.common.ui.view.PopupMenuItemView
43+
android:id="@+id/shareSelectedLinksMenuItem"
44+
android:layout_width="match_parent"
45+
android:layout_height="wrap_content" />
46+
47+
<com.duckduckgo.common.ui.view.PopupMenuItemView
48+
android:id="@+id/bookmarkSelectedTabsMenuItem"
49+
android:layout_width="match_parent"
50+
android:layout_height="wrap_content" />
51+
52+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
53+
android:layout_width="match_parent"
54+
android:layout_height="wrap_content"
55+
app:defaultPadding="false" />
56+
57+
<com.duckduckgo.common.ui.view.PopupMenuItemView
58+
android:id="@+id/bookmarkAllTabsMenuItem"
59+
android:layout_width="match_parent"
60+
android:layout_height="wrap_content"
61+
app:primaryText="@string/bookmarkAllTabsMenuItem" />
62+
63+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
64+
android:id="@+id/selectTabsDivider"
65+
android:layout_width="match_parent"
66+
android:layout_height="wrap_content"
67+
app:defaultPadding="false" />
68+
69+
<com.duckduckgo.common.ui.view.PopupMenuItemView
70+
android:id="@+id/selectTabsMenuItem"
71+
android:layout_width="match_parent"
72+
android:layout_height="wrap_content"
73+
app:primaryText="@string/selectTabsMenuItem" />
74+
75+
<com.duckduckgo.common.ui.view.divider.HorizontalDivider
76+
android:layout_width="match_parent"
77+
android:layout_height="wrap_content"
78+
app:defaultPadding="false" />
79+
80+
<com.duckduckgo.common.ui.view.PopupMenuItemView
81+
android:id="@+id/closeAllTabsMenuItem"
82+
android:layout_width="match_parent"
83+
android:layout_height="wrap_content"
84+
app:primaryText="@string/closeAllTabsMenuItem"
85+
app:primaryTextType="destructive" />
86+
87+
<com.duckduckgo.common.ui.view.PopupMenuItemView
88+
android:id="@+id/closeOtherTabsMenuItem"
89+
android:layout_width="match_parent"
90+
android:layout_height="wrap_content"
91+
app:primaryText="@string/closeOtherTabsMenuItem"
92+
app:primaryTextType="destructive" />
93+
94+
<com.duckduckgo.common.ui.view.PopupMenuItemView
95+
android:id="@+id/closeSelectedTabsMenuItem"
96+
android:layout_width="match_parent"
97+
android:layout_height="wrap_content"
98+
app:primaryTextType="destructive" />
99+
100+
</LinearLayout>

app/src/main/res/menu/menu_tab_switcher_activity.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
tools:ignore="AlwaysShowAction">
2121

2222
<item
23-
android:id="@+id/layoutType"
23+
android:id="@+id/layoutTypeMenuItem"
2424
android:icon="@drawable/ic_list_view_24"
2525
android:title="@string/tabSwitcherListViewMenu"
2626
android:visible="false"
2727
app:showAsAction="always" />
2828

2929
<item
30-
android:id="@+id/fire"
30+
android:id="@+id/fireMenuItem"
3131
android:icon="@drawable/ic_fire"
3232
android:title="@string/fireMenu"
3333
app:showAsAction="always" />

0 commit comments

Comments
 (0)