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: Selection mode #5623

Open
wants to merge 21 commits into
base: feature/ondrej/tab-multi-selection-menu
Choose a base branch
from

Conversation

0nko
Copy link
Member

@0nko 0nko commented Feb 10, 2025

Task/Issue URL: https://app.asana.com/0/72649045549333/1207969573539955

Description

This PR adds the tab selection UI and allows the tabs to be selected after triggering the selection mode. The selection mode can be cancelled by tapping anywhere outside a tab.

Some menu items and buttons are already functional, namely:

  • New Tab
  • FAB New Tab
  • Select Tabs (triggers the selection mode)
  • Select All (in selection mode)
  • Close All Tabs (in selection mode)

Non-functional items:

  • All sharing menu items and toolbar buttons
  • All bookmarking menu items and toolbar buttons
  • All selection-related tab closing (including the FAB)

Steps to test this PR

Entering and leaving the selection mode

  • Navigate to the tab switcher
  • Make sure you have at least 3 tabs
  • Tap on the More menu button
  • Tap on Select Tabs
  • Verify the selection mode is triggered
  • Verify all tabs are deselected
  • Verify the sharing and bookmark toolbar buttons are disabled
  • Verify the FAB shows "New Tab"
  • Tap on a tab
  • Verify the tab gets selected (both with a checkmark and decoration)
  • Verify the sharing and bookmark toolbar buttons are enabled
  • Verify the FAB shows "Close Tabs"
  • Verify the toolbar title shows the correct number of selected tabs
  • Tap on the empty area
  • Verify the tab switcher goes back to the normal mode

Selecting all tabs

  • Navigate to the tab switcher
  • Make sure you have at least 3 tabs
  • Trigger the selection mode
  • Tap on the more menu and tap on Select All
  • Verify all tabs are selected
  • Verify the toolbar title shows the correct number of selected tabs
  • Tap on the more menu again
  • Verify the sharing, bookmarking and closing menu items show the correct number of selected tabs
  • Verify the Select all menu item is gone
  • Verify the Close Other Tabs item is gone
  • Deselect one tab
  • Tap on the more menu
  • Verify the Select All and Close Other Tabs are back
  • Verify the correct number of tabs is shown in the menu items and the toolbar

Closing all tabs

  • Navigate to the tab switcher
  • Trigger the selection mode
  • Tap on the more menu and tap on Close All Tabs
  • Verify a confirmation dialog is displayed
  • Tap on Close
  • Verify all tabs are removed

Adding a new tab

  • Navigate to the tab switcher
  • Tap on the more menu and tap on New tab
  • Verify a new tab is added (or selected, if a NTP already exists) and the app navigates back to the browser
  • Go back to the tab switcher
  • Tap on the FAB with a New tab title
  • Verify a new tab is added again the same way

List layout selection

  • Navigate to the tab switcher
  • Tap on the list layout toolbar button
  • Tap on the More menu and tap on Select Tabs
  • Verify the selection mode is triggered
  • Smoke test the selection behavior and verify it behaves the same way as grid layout

Demo

Screen_recording_20250213_123419.mp4

Copy link
Member Author

0nko commented Feb 10, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@0nko 0nko mentioned this pull request Feb 10, 2025
7 tasks
@0nko 0nko marked this pull request as ready for review February 10, 2025 10:05
@0nko 0nko changed the title Add item checkbox resources and image view Multi-selection: Selection mode Feb 11, 2025
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from 974409b to 10a05be Compare February 13, 2025 10:54
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from cedbb62 to 935cf93 Compare February 13, 2025 10:54
@0nko 0nko requested review from mikescamell and removed request for malmstein and nalcalag February 13, 2025 10:59
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 05dd5c4 to 43b3325 Compare February 14, 2025 09:42
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch 2 times, most recently from da7bac5 to ad27238 Compare February 14, 2025 14:01
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 43b3325 to bbcf57e Compare February 14, 2025 14:01
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from ad27238 to e8eb5dd Compare February 27, 2025 17:15
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 33efb4e to ce975ab Compare February 27, 2025 17:15
@0nko 0nko mentioned this pull request Feb 27, 2025
19 tasks
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from ce975ab to c71a9c5 Compare February 28, 2025 17:43
@0nko 0nko mentioned this pull request Feb 28, 2025
31 tasks
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 9c8d735 to c006446 Compare March 3, 2025 10:37
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from c07b112 to 5a35ab9 Compare March 3, 2025 10:37
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from c006446 to f0a0088 Compare March 3, 2025 20:28
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from 5a35ab9 to 8b10fc0 Compare March 3, 2025 20:28
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from 8b10fc0 to 569d1c3 Compare March 3, 2025 20:39
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from f0a0088 to fd772fb Compare March 3, 2025 20:39
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from 569d1c3 to afbde89 Compare March 4, 2025 11:00
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from f7c7eff to 31ebc84 Compare March 5, 2025 10:02
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch from afbde89 to 5c0b727 Compare March 5, 2025 10:02
@mikescamell mikescamell self-assigned this Mar 5, 2025
Copy link
Contributor

@mikescamell mikescamell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and worked well! 🥳 A few comments but nothing blocking

android:padding="@dimen/keyline_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is there a reason we can't make this accessible?

@@ -32,43 +33,63 @@ class TabItemDecorator(
var tabSwitcherItemId: String?,
) : RecyclerView.ItemDecoration() {

private val borderStroke: Paint = Paint().apply {
var highlightedTabId: String? = tabSwitcherItemId
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why do we need to hold this state? Can it not be held outside of this class? I would have imagined we would know the state of this externally to the decorator class

private fun loadSelectionState(holder: TabViewHolder, tab: TabSwitcherItem.Tab) {
if (uiMode is Selection) {
if (tab.isSelected) {
holder.selectionIndicator.setImageResource(com.duckduckgo.mobile.android.R.drawable.ic_check_blue_round_24)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Add import for R class

)
}

private fun loadSelectionState(holder: TabViewHolder, tab: TabSwitcherItem.Tab) {
if (uiMode is Selection) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Seeing as we're using a sealed interface it might be worth opting to use a when in case any new states are added in future

Comment on lines 168 to 177
(_selectionViewState.value.mode as? SelectionViewState.Mode.Selection)?.let { selectionMode ->
_selectionViewState.update {
it.copy(mode = SelectionViewState.Mode.Selection(selectionMode.selectedTabs + tab.tabId))
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Personal preference but I find this easier to read, same goes for ln156

Suggested change
(_selectionViewState.value.mode as? SelectionViewState.Mode.Selection)?.let { selectionMode ->
_selectionViewState.update {
it.copy(mode = SelectionViewState.Mode.Selection(selectionMode.selectedTabs + tab.tabId))
}
}
val selectionMode = _selectionViewState.value.mode
if(selectionMode is SelectionViewState.Mode.Selection) {
_selectionViewState.update {
it.copy(mode = SelectionViewState.Mode.Selection(selectionMode.selectedTabs - tab.tabId))
}
}

@@ -142,6 +185,11 @@ class TabSwitcherViewModel @Inject constructor(
}

fun onSelectAllTabs() {
_selectionViewState.update { it.copy(mode = SelectionViewState.Mode.Selection(tabSwitcherItems.value?.map { it.id } ?: emptyList())) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ What is the second it? It might be better to use an explicit parameter name in this case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Maybe this would be easier to read

Suggested change
_selectionViewState.update { it.copy(mode = SelectionViewState.Mode.Selection(tabSwitcherItems.value?.map { it.id } ?: emptyList())) }
val selectedTabIds = tabSwitcherItems.value?.map { tabSwitcherItem -> tabSwitcherItem.id } ?: emptyList()
_selectionViewState.update { it.copy(mode = SelectionViewState.Mode.Selection(selectedTabIds)) }

Comment on lines 213 to 286
val newLayoutType = if (layoutType.value == GRID) {
pixel.fire(AppPixelName.TAB_MANAGER_LIST_VIEW_BUTTON_CLICKED)
LIST
} else {
pixel.fire(AppPixelName.TAB_MANAGER_GRID_VIEW_BUTTON_CLICKED)
GRID
val newLayoutType = when (layoutType.value) {
GRID -> {
pixel.fire(AppPixelName.TAB_MANAGER_LIST_VIEW_BUTTON_CLICKED)
LIST
}
LIST -> {
pixel.fire(AppPixelName.TAB_MANAGER_GRID_VIEW_BUTTON_CLICKED)
GRID
}
else -> null
}
tabRepository.setTabLayoutType(newLayoutType)
newLayoutType?.let { tabRepository.setTabLayoutType(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why did we need to change this? I do prefer the when statement but do we really need an else? Shouldn't this never be null?

class TabSwitcherItemDiffCallback(
old: List<TabSwitcherItem>,
new: List<TabSwitcherItem>,
private val oldMode: Mode = Mode.Normal,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡Something doesn't sit right with us passing in some external state when we're using the TabSwitcherItemDiffCallback to compare items and their changes.

Did you explore anything where the Tab knows what selection mode it's in so then it can reflect that in it's UI?

@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 52c96f8 to 9a26c97 Compare March 6, 2025 14:12
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-menu branch 2 times, most recently from e304fab to e2083c1 Compare March 10, 2025 10:07
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 9a26c97 to 963f6b3 Compare March 10, 2025 10:07
@0nko 0nko force-pushed the feature/ondrej/tab-multi-selection-select-mode branch from 963f6b3 to 8f17089 Compare March 10, 2025 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants