From 8a15e8a6818554eb9c7d66f00e2058ecfcee6eed Mon Sep 17 00:00:00 2001 From: Roger Yang Date: Wed, 22 Sep 2021 15:16:50 -0400 Subject: [PATCH] Close #21451: Add active search term tab groups on home --- .../org/mozilla/fenix/ext/BrowserState.kt | 69 +++++- .../mozilla/fenix/home/HomeFragmentStore.kt | 10 +- .../home/recenttabs/RecentTabsListFeature.kt | 50 ++-- .../controller/RecentTabController.kt | 10 + .../interactor/RecentTabInteractor.kt | 7 + .../recenttabs/view/RecentTabViewHolder.kt | 3 +- .../fenix/home/recenttabs/view/RecentTabs.kt | 143 +++++++++-- .../SessionControlInteractor.kt | 4 + .../home/sessioncontrol/SessionControlView.kt | 4 +- .../fenix/tabstray/browser/TabGroup.kt | 24 ++ .../fenix/tabstray/browser/TabGroupAdapter.kt | 7 +- app/src/main/res/drawable/ic_all_tabs.xml | 10 + app/src/main/res/values/strings.xml | 4 + .../org/mozilla/fenix/ext/BrowserStateTest.kt | 73 +++++- .../fenix/home/HomeFragmentStoreTest.kt | 6 +- .../fenix/home/RecentTabsListFeatureTest.kt | 233 ++++++++++++++++-- .../home/SessionControlInteractorTest.kt | 7 + .../sessioncontrol/SessionControlViewTest.kt | 16 +- 18 files changed, 588 insertions(+), 92 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroup.kt create mode 100644 app/src/main/res/drawable/ic_all_tabs.xml diff --git a/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt b/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt index 31847b13a..ed67aa676 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/BrowserState.kt @@ -9,24 +9,34 @@ import mozilla.components.browser.state.selector.selectedNormalTab import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.feature.tabs.ext.hasMediaPlayed +import org.mozilla.fenix.FeatureFlags +import org.mozilla.fenix.home.recenttabs.RecentTab +import org.mozilla.fenix.tabstray.browser.TabGroup +import org.mozilla.fenix.tabstray.browser.maxActiveTime +import org.mozilla.fenix.tabstray.ext.isNormalTabActiveWithSearchTerm +import kotlin.math.max /** - * Get the last opened normal tab and the last tab with in progress media, if available. + * Get the last opened normal tab, last tab with in progress media and last search term group, if available. * - * @return A list of the last opened tab and the last tab with in progress media + * @return A list of the last opened tab, last tab with in progress media and last search term group * if distinct and available or an empty list. */ -fun BrowserState.asRecentTabs(): List { - return mutableListOf().apply { +fun BrowserState.asRecentTabs(): List { + return mutableListOf().apply { val lastOpenedNormalTab = lastOpenedNormalTab val inProgressMediaTab = inProgressMediaTab - lastOpenedNormalTab?.let { add(it) } + lastOpenedNormalTab?.let { add(RecentTab.Tab(it)) } if (inProgressMediaTab == lastOpenedNormalTab) { - secondToLastOpenedNormalTab?.let { add(it) } + secondToLastOpenedNormalTab?.let { add(RecentTab.Tab(it)) } } else { - inProgressMediaTab?.let { add(it) } + inProgressMediaTab?.let { add(RecentTab.Tab(it)) } + } + + if (FeatureFlags.tabGroupFeature) { + lastSearchGroup?.let { add(it) } } } } @@ -54,3 +64,48 @@ val BrowserState.inProgressMediaTab: TabSessionState? get() = normalTabs .filter { it.hasMediaPlayed() } .maxByOrNull { it.lastMediaAccessState.lastMediaAccess } + +/** + * Get the most recent search term group. + */ +val BrowserState.lastSearchGroup: RecentTab.SearchGroup? + get() { + val tabGroup = normalTabs.toSearchGroup().lastOrNull() ?: return null + val firstTab = tabGroup.tabs.firstOrNull() ?: return null + + return RecentTab.SearchGroup( + tabGroup.searchTerm, + firstTab.id, + firstTab.content.url, + firstTab.content.thumbnail, + tabGroup.tabs.count() + ) + } + +/** + * Get search term groups sorted by last access time. + */ +private fun List.toSearchGroup(): List { + val data = filter { + it.isNormalTabActiveWithSearchTerm(maxActiveTime) + }.groupBy { + when { + it.content.searchTerms.isNotBlank() -> it.content.searchTerms + else -> it.historyMetadata?.searchTerm ?: "" + }.lowercase() + } + + return data.map { mapEntry -> + val searchTerm = mapEntry.key.replaceFirstChar(Char::uppercase) + val groupTabs = mapEntry.value + val groupMax = groupTabs.fold(0L) { acc, tab -> + max(tab.lastAccess, acc) + } + + TabGroup( + searchTerm = searchTerm, + tabs = groupTabs, + lastAccess = groupMax + ) + }.sortedBy { it.lastAccess } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt index 9944db642..b993d6fdf 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragmentStore.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.home import android.graphics.Bitmap -import mozilla.components.browser.state.state.TabSessionState import mozilla.components.concept.storage.BookmarkNode import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite @@ -17,6 +16,7 @@ import mozilla.components.service.pocket.PocketRecommendedStory import org.mozilla.fenix.components.tips.Tip import org.mozilla.fenix.ext.getFilteredStories import org.mozilla.fenix.historymetadata.HistoryMetadataGroup +import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.POCKET_STORIES_TO_SHOW_COUNT import org.mozilla.fenix.home.sessioncontrol.viewholders.pocket.PocketRecommendedStoryCategory @@ -50,7 +50,7 @@ data class Tab( * @property tip The current [Tip] to show on the [HomeFragment]. * @property showCollectionPlaceholder If true, shows a placeholder when there are no collections. * @property showSetAsDefaultBrowserCard If true, shows the default browser card - * @property recentTabs The list of recent [TabSessionState] in the [HomeFragment]. + * @property recentTabs The list of recent [RecentTab] in the [HomeFragment]. * @property recentBookmarks The list of recently saved [BookmarkNode]s to show on the [HomeFragment]. * @property historyMetadata The list of [HistoryMetadataGroup]. * @property pocketArticles The list of [PocketRecommendedStory]. @@ -63,7 +63,7 @@ data class HomeFragmentState( val tip: Tip? = null, val showCollectionPlaceholder: Boolean = false, val showSetAsDefaultBrowserCard: Boolean = false, - val recentTabs: List = emptyList(), + val recentTabs: List = emptyList(), val recentBookmarks: List = emptyList(), val historyMetadata: List = emptyList(), val pocketStories: List = emptyList(), @@ -77,7 +77,7 @@ sealed class HomeFragmentAction : Action { val collections: List, val tip: Tip? = null, val showCollectionPlaceholder: Boolean, - val recentTabs: List, + val recentTabs: List, val recentBookmarks: List, val historyMetadata: List ) : @@ -90,7 +90,7 @@ sealed class HomeFragmentAction : Action { data class ModeChange(val mode: Mode) : HomeFragmentAction() data class TopSitesChange(val topSites: List) : HomeFragmentAction() data class RemoveTip(val tip: Tip) : HomeFragmentAction() - data class RecentTabsChange(val recentTabs: List) : HomeFragmentAction() + data class RecentTabsChange(val recentTabs: List) : HomeFragmentAction() data class RecentBookmarksChange(val recentBookmarks: List) : HomeFragmentAction() data class HistoryMetadataChange(val historyMetadata: List) : HomeFragmentAction() data class SelectPocketStoriesCategory(val categoryName: String) : HomeFragmentAction() diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/RecentTabsListFeature.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/RecentTabsListFeature.kt index 658945ea4..f4b1816d9 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recenttabs/RecentTabsListFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/RecentTabsListFeature.kt @@ -4,16 +4,17 @@ package org.mozilla.fenix.home.recenttabs +import android.graphics.Bitmap import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect -import mozilla.components.browser.state.selector.normalTabs +import kotlinx.coroutines.flow.map import mozilla.components.browser.state.state.BrowserState +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.store.BrowserStore import mozilla.components.lib.state.helpers.AbstractBinding -import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged +import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged import org.mozilla.fenix.ext.asRecentTabs -import org.mozilla.fenix.ext.lastOpenedNormalTab import org.mozilla.fenix.home.HomeFragmentAction import org.mozilla.fenix.home.HomeFragmentStore @@ -28,20 +29,39 @@ class RecentTabsListFeature( ) : AbstractBinding(browserStore) { override suspend fun onState(flow: Flow) { + // Listen for changes regarding the currently selected tab, in progress media tab + // and search term groups. flow - // Listen for changes regarding the currently selected tab and the in progress media tab - // and also for changes (close, undo) in normal tabs that could involve these. - .ifAnyChanged { - val lastOpenedNormalTab = it.lastOpenedNormalTab - arrayOf( - lastOpenedNormalTab?.id, - lastOpenedNormalTab?.content?.title, - lastOpenedNormalTab?.content?.icon, - it.normalTabs - ) - } + .map { it.asRecentTabs() } + .ifChanged() .collect { - homeStore.dispatch(HomeFragmentAction.RecentTabsChange(browserStore.state.asRecentTabs())) + homeStore.dispatch(HomeFragmentAction.RecentTabsChange(it)) } } } + +sealed class RecentTab { + /** + * A tab that was recently viewed + * + * @param state Recently viewed [TabSessionState] + */ + data class Tab(val state: TabSessionState) : RecentTab() + + /** + * A search term group that was recently viewed + * + * @param searchTerm The search term that was recently viewed + * @param tabId The id of the tab that was recently viewed + * @param url The url that was recently viewed + * @param thumbnail The thumbnail of the search term that was recently viewed + * @param count The number of tabs in the search term group + */ + data class SearchGroup( + val searchTerm: String, + val tabId: String, + val url: String, + val thumbnail: Bitmap?, + val count: Int + ) : RecentTab() +} 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 2e14c66ed..4fccfeacd 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 @@ -26,6 +26,11 @@ interface RecentTabController { */ fun handleRecentTabClicked(tabId: String) + /** + * @see [RecentTabInteractor.onRecentSearchGroupClicked] + */ + fun handleRecentSearchGroupClicked(tabId: String) + /** * @see [RecentTabInteractor.onRecentTabShowAllClicked] */ @@ -62,6 +67,11 @@ class DefaultRecentTabsController( navController.navigate(HomeFragmentDirections.actionGlobalTabsTrayFragment()) } + override fun handleRecentSearchGroupClicked(tabId: String) { + selectTabUseCase.invoke(tabId) + navController.navigate(HomeFragmentDirections.actionGlobalTabsTrayFragment()) + } + @VisibleForTesting(otherwise = PRIVATE) fun dismissSearchDialogIfDisplayed() { if (navController.currentDestination?.id == R.id.searchDialogFragment) { diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt index a2568108b..c40a3d44b 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/interactor/RecentTabInteractor.kt @@ -15,6 +15,13 @@ interface RecentTabInteractor { */ fun onRecentTabClicked(tabId: String) + /** + * Opens the tabs tray and scroll to the search group. Called when a user clicks on a search group. + * + * @param tabId The ID of the tab to open. + */ + fun onRecentSearchGroupClicked(tabId: String) + /** * Show the tabs tray. Called when a user clicks on the "Show all" button besides the recent * tabs. diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt index 735f4fe0c..3cf8cb3a1 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabViewHolder.kt @@ -36,7 +36,8 @@ class RecentTabViewHolder( FirefoxTheme { RecentTabs( recentTabs = recentTabs.value ?: emptyList(), - onRecentTabClick = { interactor.onRecentTabClicked(it) } + onRecentTabClick = { interactor.onRecentTabClicked(it) }, + onRecentSearchGroupClicked = { interactor.onRecentSearchGroupClicked(it) } ) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabs.kt b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabs.kt index bbe081a4f..2ea26bd90 100644 --- a/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabs.kt +++ b/app/src/main/java/org/mozilla/fenix/home/recenttabs/view/RecentTabs.kt @@ -24,47 +24,69 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card +import androidx.compose.material.Icon import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import mozilla.components.browser.icons.compose.Loader import mozilla.components.browser.icons.compose.Placeholder import mozilla.components.browser.icons.compose.WithIcon -import mozilla.components.browser.state.state.TabSessionState import mozilla.components.support.ktx.kotlin.getRepresentativeSnippet import mozilla.components.ui.colors.PhotonColors +import org.mozilla.fenix.R import org.mozilla.fenix.components.components +import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.theme.FirefoxTheme /** * A list of recent tabs to jump back to. * - * @param recentTabs List of [TabSessionState] to display. + * @param recentTabs List of [RecentTab] to display. * @param onRecentTabClick Invoked when the user clicks on a recent tab. + * @param onRecentSearchGroupClicked Invoked when the user clicks on a recent search group. */ @Composable fun RecentTabs( - recentTabs: List, - onRecentTabClick: (String) -> Unit = {} + recentTabs: List, + onRecentTabClick: (String) -> Unit = {}, + onRecentSearchGroupClicked: (String) -> Unit = {} ) { Column( modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp) ) { recentTabs.forEach { tab -> - RecentTabItem( - tabId = tab.id, - url = tab.content.url, - title = tab.content.title, - icon = tab.content.icon, - onRecentTabClick = onRecentTabClick - ) + when (tab) { + is RecentTab.Tab -> { + RecentTabItem( + tabId = tab.state.id, + url = tab.state.content.url, + title = tab.state.content.title, + thumbnail = tab.state.content.thumbnail, + onRecentTabClick = onRecentTabClick + ) + } + is RecentTab.SearchGroup -> { + RecentSearchGroupItem( + searchTerm = tab.searchTerm, + tabId = tab.tabId, + url = tab.url, + thumbnail = tab.thumbnail, + count = tab.count, + onSearchGroupClicked = onRecentSearchGroupClicked + ) + } + } } } } @@ -72,18 +94,20 @@ fun RecentTabs( /** * A recent tab item. * - * @param tabId Tbe id of the tab. + * @param tabId The id of the tab. * @param url The loaded URL of the tab. * @param title The title of the tab. - * @param icon The icon of the tab. + * @param thumbnail The icon of the tab. * @param onRecentTabClick Invoked when the user clicks on a recent tab. */ +@Suppress("LongParameterList") @Composable private fun RecentTabItem( tabId: String, url: String, title: String, icon: Bitmap? = null, + thumbnail: Bitmap? = null, onRecentTabClick: (String) -> Unit = {} ) { Card( @@ -101,7 +125,9 @@ private fun RecentTabItem( RecentTabImage( url = url, modifier = Modifier.size(116.dp, 84.dp), - icon = icon + icon = thumbnail, + contentScale = ContentScale.FillWidth, + alignment = Alignment.TopCenter ) Spacer(modifier = Modifier.width(16.dp)) @@ -112,7 +138,82 @@ private fun RecentTabItem( ) { RecentTabTitle(title = title) - RecentTabSubtitle(url = url) + RecentTabSubtitle(subtitle = url) + + Row { + RecentTabImage( + url = url, + modifier = Modifier.size(18.dp, 18.dp), + icon = icon + ) + + Spacer(modifier = Modifier.width(8.dp)) + + RecentTabSubtitle(subtitle = url) + } + } + } + } +} + +/** + * A recent search group item. + * + * @param searchTerm The search term for the group. + * @param tabId The id of the last accessed tab in the group. + * @param url The loaded URL of the last accessed tab in the group. + * @param thumbnail The icon of the group. + * @param count Count of how many tabs belongs to the group. + * @param onSearchGroupClicked Invoked when the user clicks on a group. + */ +@Suppress("LongParameterList") +@Composable +private fun RecentSearchGroupItem( + searchTerm: String, + tabId: String, + url: String, + thumbnail: Bitmap?, + count: Int, + onSearchGroupClicked: (String) -> Unit = {} +) { + Card( + modifier = Modifier + .fillMaxWidth() + .height(116.dp) + .clickable { onSearchGroupClicked(tabId) }, + shape = RoundedCornerShape(8.dp), + backgroundColor = FirefoxTheme.colors.surface, + elevation = 6.dp + ) { + Row( + modifier = Modifier.padding(16.dp) + ) { + RecentTabImage( + url = url, + modifier = Modifier.size(116.dp, 84.dp), + icon = thumbnail, + contentScale = ContentScale.FillWidth, + alignment = Alignment.TopCenter + ) + + Spacer(modifier = Modifier.width(16.dp)) + + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceBetween + ) { + RecentTabTitle(title = stringResource(R.string.recent_tabs_search_term, searchTerm)) + + Row { + Icon( + painter = painterResource(id = R.drawable.ic_all_tabs), + contentDescription = null // decorative element + ) + + Spacer(modifier = Modifier.width(8.dp)) + + RecentTabSubtitle(subtitle = stringResource(R.string.recent_tabs_search_term_count, count)) + } } } } @@ -130,6 +231,8 @@ private fun RecentTabItem( private fun RecentTabImage( url: String, modifier: Modifier = Modifier, + contentScale: ContentScale = ContentScale.Fit, + alignment: Alignment = Alignment.Center, icon: Bitmap? = null ) { if (icon != null) { @@ -137,6 +240,8 @@ private fun RecentTabImage( painter = BitmapPainter(icon.asImageBitmap()), contentDescription = null, modifier = modifier, + contentScale = contentScale, + alignment = alignment ) } else { components.core.icons.Loader( @@ -157,7 +262,7 @@ private fun RecentTabImage( Image( painter = icon.painter, contentDescription = null, - modifier = modifier, + modifier = modifier ) } } @@ -183,12 +288,12 @@ private fun RecentTabTitle(title: String) { /** * A recent tab subtitle. * - * @param url The loaded URL of the tab. + * @param subtitle The loaded URL of the tab. */ @Composable -private fun RecentTabSubtitle(url: String) { +private fun RecentTabSubtitle(subtitle: String) { Text( - text = url.getRepresentativeSnippet(), + text = subtitle.getRepresentativeSnippet(), color = FirefoxTheme.colors.textSecondary, fontSize = 12.sp, overflow = TextOverflow.Ellipsis, diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt index d204df8bc..31dbbfaf2 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlInteractor.kt @@ -355,6 +355,10 @@ class SessionControlInteractor( recentTabController.handleRecentTabClicked(tabId) } + override fun onRecentSearchGroupClicked(tabId: String) { + recentTabController.handleRecentSearchGroupClicked(tabId) + } + override fun onRecentTabShowAllClicked() { recentTabController.handleRecentTabShowAllClicked() } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt index db316128e..497c61ec6 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlView.kt @@ -11,7 +11,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import mozilla.components.browser.state.state.TabSessionState import mozilla.components.concept.storage.BookmarkNode import mozilla.components.feature.tab.collections.TabCollection import mozilla.components.feature.top.sites.TopSite @@ -25,6 +24,7 @@ import org.mozilla.fenix.home.HomeFragmentStore import org.mozilla.fenix.home.HomeScreenViewModel import org.mozilla.fenix.home.Mode import org.mozilla.fenix.home.OnboardingState +import org.mozilla.fenix.home.recenttabs.RecentTab import org.mozilla.fenix.utils.Settings // This method got a little complex with the addition of the tab tray feature flag @@ -40,7 +40,7 @@ internal fun normalModeAdapterItems( recentBookmarks: List, showCollectionsPlaceholder: Boolean, showSetAsDefaultBrowserCard: Boolean, - recentTabs: List, + recentTabs: List, historyMetadata: List, pocketStories: List ): List { diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroup.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroup.kt new file mode 100644 index 000000000..d1e6c9f9c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroup.kt @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.fenix.tabstray.browser + +import mozilla.components.browser.state.state.TabSessionState + +data class TabGroup( + /** + * The search term used for the tab group. + */ + val searchTerm: String, + + /** + * The list of tabSessionStates belonging to this tab group. + */ + val tabs: List, + + /** + * The last time tabs in this group was accessed. + */ + val lastAccess: Long +) diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupAdapter.kt b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupAdapter.kt index 6801053d2..3eea9e5a1 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/browser/TabGroupAdapter.kt @@ -20,7 +20,6 @@ import org.mozilla.fenix.components.Components import org.mozilla.fenix.ext.components import org.mozilla.fenix.selection.SelectionHolder import org.mozilla.fenix.tabstray.TabsTrayStore -import org.mozilla.fenix.tabstray.browser.TabGroupAdapter.Group import kotlin.math.max import mozilla.components.concept.tabstray.Tab as TabsTrayTab import mozilla.components.support.base.observer.Observable @@ -42,8 +41,10 @@ class TabGroupAdapter( private val store: TabsTrayStore, private val featureName: String, delegate: TrayObservable = ObserverRegistry() -) : ListAdapter(DiffCallback), TabsTray, TrayObservable by delegate { +) : ListAdapter(DiffCallback), TabsTray, TrayObservable by delegate { + // TODO use [List.toSearchGroup()] + // see https://github.com/mozilla-mobile/android-components/issues/11012 data class Group( /** * A title for the tab group. @@ -138,6 +139,6 @@ class TabGroupAdapter( } } -internal fun Group.containsTabId(tabId: String): Boolean { +internal fun TabGroupAdapter.Group.containsTabId(tabId: String): Boolean { return tabs.firstOrNull { it.id == tabId } != null } diff --git a/app/src/main/res/drawable/ic_all_tabs.xml b/app/src/main/res/drawable/ic_all_tabs.xml new file mode 100644 index 000000000..06064ec41 --- /dev/null +++ b/app/src/main/res/drawable/ic_all_tabs.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d0d132562..1ae096094 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,6 +118,10 @@ Jump back in Show all + + Your search for \"%1$s\" + + %1$s sites