-
Notifications
You must be signed in to change notification settings - Fork 949
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
base: feature/ondrej/tab-multi-selection-menu
Are you sure you want to change the base?
Multi-selection: Selection mode #5623
Conversation
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.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
974409b
to
10a05be
Compare
cedbb62
to
935cf93
Compare
05dd5c4
to
43b3325
Compare
da7bac5
to
ad27238
Compare
43b3325
to
bbcf57e
Compare
ad27238
to
e8eb5dd
Compare
33efb4e
to
ce975ab
Compare
ce975ab
to
c71a9c5
Compare
9c8d735
to
c006446
Compare
c07b112
to
5a35ab9
Compare
c006446
to
f0a0088
Compare
5a35ab9
to
8b10fc0
Compare
8b10fc0
to
569d1c3
Compare
f0a0088
to
fd772fb
Compare
569d1c3
to
afbde89
Compare
f7c7eff
to
31ebc84
Compare
afbde89
to
5c0b727
Compare
There was a problem hiding this 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" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
(_selectionViewState.value.mode as? SelectionViewState.Mode.Selection)?.let { selectionMode -> | ||
_selectionViewState.update { | ||
it.copy(mode = SelectionViewState.Mode.Selection(selectionMode.selectedTabs + tab.tabId)) | ||
} | ||
} |
There was a problem hiding this comment.
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
(_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())) } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
_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)) } |
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) } |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
52c96f8
to
9a26c97
Compare
e304fab
to
e2083c1
Compare
9a26c97
to
963f6b3
Compare
963f6b3
to
8f17089
Compare
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:
Non-functional items:
Steps to test this PR
Entering and leaving the selection mode
Selecting all tabs
Closing all tabs
Adding a new tab
List layout selection
Demo
Screen_recording_20250213_123419.mp4