diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt index 9648375702..b37de99fdc 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/controller/RecentTabController.kt @@ -68,9 +68,12 @@ class DefaultRecentTabsController( } override fun handleRecentSearchGroupClicked(tabId: String) { - selectTabUseCase.invoke(tabId) metrics.track(Event.JumpBackInGroupTapped) - navController.navigate(HomeFragmentDirections.actionGlobalTabsTrayFragment()) + navController.navigate( + HomeFragmentDirections.actionGlobalTabsTrayFragment( + focusGroupTabId = tabId + ) + ) } @VisibleForTesting(otherwise = PRIVATE) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index cbc8791d9d..0874967cf2 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -123,7 +123,8 @@ class TabsTrayFragment : AppCompatDialogFragment() { tabsTrayStore = StoreProvider.get(this) { TabsTrayStore( initialState = TabsTrayState( - mode = initialMode + mode = initialMode, + focusGroupTabId = args.focusGroupTabId ) ) } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt index cba2707dd1..08d9d06a1d 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayStore.kt @@ -18,11 +18,13 @@ import mozilla.components.lib.state.Store * currently selected tabs. * @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired * devices. + * @property focusGroupTabId The search group tab id to focus. Defaults to null. */ data class TabsTrayState( val selectedPage: Page = Page.NormalTabs, val mode: Mode = Mode.Normal, - val syncing: Boolean = false + val syncing: Boolean = false, + val focusGroupTabId: String? = null ) : State { /** @@ -119,6 +121,11 @@ sealed class TabsTrayAction : Action { * no sync action was able to be performed. */ object SyncCompleted : TabsTrayAction() + + /** + * Removes the [TabsTrayState.focusGroupTabId] of the [TabsTrayState]. + */ + object ConsumeFocusGroupTabIdAction : TabsTrayAction() } /** @@ -149,6 +156,8 @@ internal object TabsTrayReducer { state.copy(syncing = true) is TabsTrayAction.SyncCompleted -> state.copy(syncing = false) + is TabsTrayAction.ConsumeFocusGroupTabIdAction -> + state.copy(focusGroupTabId = null) } } } diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt index 6bbe5b0bd7..dfe8318028 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/viewholders/NormalBrowserPageViewHolder.kt @@ -17,6 +17,7 @@ import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.AppStore import org.mozilla.fenix.ext.settings import org.mozilla.fenix.selection.SelectionHolder +import org.mozilla.fenix.tabstray.TabsTrayAction import org.mozilla.fenix.tabstray.TabsTrayInteractor import org.mozilla.fenix.tabstray.TabsTrayStore import org.mozilla.fenix.tabstray.browser.containsTabId @@ -84,6 +85,10 @@ class NormalBrowserPageViewHolder( val searchTermTabGroupsAreEnabled = containerView.context.settings().searchTermTabGroupsAreEnabled val selectedTab = browserStore.state.selectedNormalTab ?: return + // It's safe to read the state directly (i.e. won't cause bugs because of the store actions + // processed on a separate thread) instead of observing it because this value is only set during + // the initialState of the TabsTrayStore being created. + val focusGroupTabId = tabsTrayStore.state.focusGroupTabId // Update tabs into the inactive adapter. if (inactiveTabsAreEnabled && selectedTab.isNormalTabInactive(maxActiveTime)) { @@ -106,7 +111,13 @@ class NormalBrowserPageViewHolder( } // Updates tabs into the search term group adapter. - if (searchTermTabGroupsAreEnabled && selectedTab.isNormalTabActiveWithSearchTerm(maxActiveTime)) { + if (searchTermTabGroupsAreEnabled && ( + !focusGroupTabId.isNullOrEmpty() || + selectedTab.isNormalTabActiveWithSearchTerm(maxActiveTime) + ) + ) { + val tabId = focusGroupTabId ?: selectedTab.id + tabGroupAdapter.observeFirstInsert { // With a grouping, we need to use the list of the adapter that is already grouped // together for the UI, so we know the final index of the grouping to scroll to. @@ -117,35 +128,40 @@ class NormalBrowserPageViewHolder( // [DiffUtil.calculateDiff] directly to submit a changed list which evades the `ListAdapter` from being // notified of updates, so it therefore returns an empty list. tabGroupAdapter.currentList.forEachIndexed { groupIndex, group -> - if (group.containsTabId(selectedTab.id)) { + if (group.containsTabId(tabId)) { // Index is based on tabs above (inactive) with our calculated index. val indexToScrollTo = inactiveTabAdapter.itemCount + groupIndex layoutManager.scrollToPosition(indexToScrollTo) + if (focusGroupTabId != null) { + tabsTrayStore.dispatch(TabsTrayAction.ConsumeFocusGroupTabIdAction) + } return@observeFirstInsert } } } } - // Updates tabs into the normal browser tabs adapter. - browserAdapter.observeFirstInsert { - val activeTabsList = browserStore.state.getNormalTrayTabs( - searchTermTabGroupsAreEnabled, - inactiveTabsAreEnabled - ) - activeTabsList.forEachIndexed { tabIndex, trayTab -> - if (trayTab.id == selectedTab.id) { + if (focusGroupTabId.isNullOrEmpty()) { + // Updates tabs into the normal browser tabs adapter. + browserAdapter.observeFirstInsert { + val activeTabsList = browserStore.state.getNormalTrayTabs( + searchTermTabGroupsAreEnabled, + inactiveTabsAreEnabled + ) + activeTabsList.forEachIndexed { tabIndex, trayTab -> + if (trayTab.id == selectedTab.id) { - // Index is based on tabs above (inactive + groups + header) with our calculated index. - val indexToScrollTo = inactiveTabAdapter.itemCount + - tabGroupAdapter.itemCount + - headerAdapter.itemCount + tabIndex + // Index is based on tabs above (inactive + groups + header) with our calculated index. + val indexToScrollTo = inactiveTabAdapter.itemCount + + tabGroupAdapter.itemCount + + headerAdapter.itemCount + tabIndex - layoutManager.scrollToPosition(indexToScrollTo) + layoutManager.scrollToPosition(indexToScrollTo) - return@observeFirstInsert + return@observeFirstInsert + } } } } diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index e0a1173c7a..6d9b80bb8f 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -150,6 +150,11 @@ android:name="enterMultiselect" android:defaultValue="false" app:argType="boolean" /> +