Closes #19090: Show snackbar on adding to bookmarks from tabs tray (#19807)

upstream-sync
Roger Yang 3 years ago committed by GitHub
parent 811dd3e618
commit 2e4635334a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -912,7 +912,6 @@ class SmokeTest {
}
}
@Ignore("Disabling until re-implemented by #19090")
@Test
fun verifyExpandedCollectionItemsTest() {
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -22,12 +22,10 @@ import org.mozilla.fenix.ext.getDefaultCollectionNumber
/**
* A lambda that is invoked when a confirmation button in a [CollectionsDialog] is clicked.
*
* A [TabCollection] of the selected collected is passed to the delegate when confirmed. If null,
* then a new collection is created.
*
* A list of [TabSessionState] is returned that will be put into the collections storage.
* The [TabCollection] ID of the selected collection is passed to the delegate when confirmed.
* If the selected collection was newly created then [Boolean] is set to true.
*/
typealias OnPositiveButtonClick = (collection: TabCollection?) -> List<TabSessionState>
typealias OnPositiveButtonClick = (id: Long?, isNewCollection: Boolean) -> Unit
/**
* A lambda that is invoked when a cancel button in a [CollectionsDialog] is clicked.
@ -42,6 +40,7 @@ typealias OnNegativeButtonClick = () -> Unit
*/
data class CollectionsDialog(
val storage: TabCollectionStorage,
val sessionList: List<TabSessionState>,
val onPositiveButtonClick: OnPositiveButtonClick,
val onNegativeButtonClick: OnNegativeButtonClick
)
@ -67,10 +66,10 @@ fun CollectionsDialog.show(
val selectedCollection =
(list.adapter as CollectionsListAdapter).getSelectedCollection()
val collection = storage.cachedTabCollections[selectedCollection]
val sessionList = onPositiveButtonClick.invoke(collection)
MainScope().launch {
storage.addTabsToCollection(collection, sessionList)
val id = storage.addTabsToCollection(collection, sessionList)
onPositiveButtonClick.invoke(id, false)
}
dialog.dismiss()
@ -112,13 +111,13 @@ internal fun CollectionsDialog.showAddNewDialog(
AlertDialog.Builder(context)
.setTitle(R.string.tab_tray_add_new_collection)
.setView(layout).setPositiveButton(android.R.string.ok) { dialog, _ ->
val sessionList = onPositiveButtonClick.invoke(null)
MainScope().launch {
storage.createCollection(
val id = storage.createCollection(
collectionNameEditText.text.toString(),
sessionList
)
onPositiveButtonClick.invoke(id, true)
}
dialog.dismiss()

@ -11,16 +11,17 @@ import androidx.lifecycle.asLiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.feature.tab.collections.Tab
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tab.collections.TabCollectionStorage
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.utils.Mockable
@Mockable
@ -59,15 +60,21 @@ class TabCollectionStorage(
}
}
suspend fun createCollection(title: String, sessions: List<TabSessionState>) = ioScope.launch {
val id = collectionStorage.createCollection(title, sessions)
notifyObservers { onCollectionCreated(title, sessions, id) }
}.join()
suspend fun createCollection(title: String, sessions: List<TabSessionState>): Long? {
return withContext(ioScope.coroutineContext) {
val id = collectionStorage.createCollection(title, sessions)
notifyObservers { onCollectionCreated(title, sessions, id) }
id
}
}
suspend fun addTabsToCollection(tabCollection: TabCollection, sessions: List<TabSessionState>) = ioScope.launch {
collectionStorage.addTabsToCollection(tabCollection, sessions)
notifyObservers { onTabsAdded(tabCollection, sessions) }
}.join()
suspend fun addTabsToCollection(tabCollection: TabCollection, sessions: List<TabSessionState>): Long? {
return withContext(ioScope.coroutineContext) {
val id = collectionStorage.addTabsToCollection(tabCollection, sessions)
notifyObservers { onTabsAdded(tabCollection, sessions) }
id
}
}
fun getCollections(): LiveData<List<TabCollection>> {
return collectionStorage.getCollections().asLiveData()

@ -100,6 +100,11 @@ class DefaultNavigationInteractor(
private val bookmarksUseCase: BookmarksUseCase,
private val tabsTrayStore: TabsTrayStore,
private val collectionStorage: TabCollectionStorage,
private val showCollectionSnackbar: (
tabSize: Int,
isNewCollection: Boolean,
collectionToSelect: Long?
) -> Unit,
private val accountManager: FxaAccountManager,
private val ioDispatcher: CoroutineContext
) : NavigationInteractor {
@ -170,18 +175,21 @@ class DefaultNavigationInteractor(
CollectionsDialog(
storage = collectionStorage,
onPositiveButtonClick = { existingCollection ->
sessionList = browserStore.getTabSessionState(tabs),
onPositiveButtonClick = { id, isNewCollection ->
tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode)
// If collection is null, a new one was created.
val event = if (existingCollection == null) {
val event = if (isNewCollection) {
Event.CollectionSaved(browserStore.state.normalTabs.size, tabs.size)
} else {
Event.CollectionTabsAdded(browserStore.state.normalTabs.size, tabs.size)
}
metrics.track(event)
id?.apply {
showCollectionSnackbar(tabs.size, isNewCollection, id)
}
browserStore.getTabSessionState(tabs)
metrics.track(event)
},
onNegativeButtonClick = {
tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode)

@ -48,8 +48,13 @@ import org.mozilla.fenix.tabstray.browser.SelectionHandleBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding
import org.mozilla.fenix.tabstray.browser.SelectionBannerBinding.VisibilityModifier
import org.mozilla.fenix.tabstray.ext.showWithTheme
import org.mozilla.fenix.tabstray.ext.anchorWithAction
import org.mozilla.fenix.utils.allowUndo
import kotlin.math.max
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.tabstray.ext.make
import org.mozilla.fenix.tabstray.ext.orDefault
import org.mozilla.fenix.tabstray.ext.message
@Suppress("TooManyFunctions", "LargeClass")
class TabsTrayFragment : AppCompatDialogFragment() {
@ -114,6 +119,7 @@ class TabsTrayFragment : AppCompatDialogFragment() {
dismissTabTrayAndNavigateHome = ::dismissTabsTrayAndNavigateHome,
bookmarksUseCase = requireComponents.useCases.bookmarksUseCases,
collectionStorage = requireComponents.core.tabCollectionStorage,
showCollectionSnackbar = ::showCollectionSnackbar,
accountManager = requireComponents.backgroundServices.accountManager,
ioDispatcher = Dispatchers.IO
)
@ -369,6 +375,32 @@ class TabsTrayFragment : AppCompatDialogFragment() {
dismissAllowingStateLoss()
}
@VisibleForTesting
internal fun showCollectionSnackbar(
tabSize: Int,
isNewCollection: Boolean = false,
collectionToSelect: Long?
) {
val anchor = if (requireComponents.settings.accessibilityServicesEnabled) {
null
} else {
new_tab_button
}
FenixSnackbar
.make(requireView())
.message(tabSize, isNewCollection)
.anchorWithAction(anchor) {
findNavController().navigateBlockingForAsyncNavGraph(
TabsTrayFragmentDirections.actionGlobalHome(
focusOnAddressBar = false,
focusOnCollection = collectionToSelect.orDefault()
)
)
dismissTabsTray()
}.show()
}
companion object {
// Minimum number of list items for which to show the tabs tray as expanded.
const val EXPAND_AT_LIST_SIZE = 4

@ -0,0 +1,49 @@
/* 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.ext
import android.view.View
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.tabstray.TabsTrayFragment.Companion.ELEVATION
internal fun FenixSnackbar.message(
tabSize: Int,
isNewCollection: Boolean = false
): FenixSnackbar {
val stringRes = when {
isNewCollection -> {
R.string.create_collection_tabs_saved_new_collection
}
tabSize > 1 -> {
R.string.create_collection_tabs_saved
}
else -> {
R.string.create_collection_tab_saved
}
}
setText(context.getString(stringRes))
return this
}
internal inline fun FenixSnackbar.anchorWithAction(
anchor: View?,
crossinline action: () -> Unit
): FenixSnackbar {
anchorView = anchor
view.elevation = ELEVATION
setAction(context.getString(R.string.create_collection_view)) {
action.invoke()
}
return this
}
internal fun FenixSnackbar.Companion.make(view: View) = make(
duration = LENGTH_LONG,
isDisplayedWithBrowserToolbar = true,
view = view
)

@ -0,0 +1,10 @@
/* 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.ext
/**
* A helper that will return the default value of -1L for the home fragment navigation if Long is null.
*/
internal fun Long?.orDefault() = this ?: -1L

@ -5,6 +5,7 @@
package org.mozilla.fenix.collections
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
@ -78,6 +79,9 @@ class DefaultCollectionCreationControllerTest {
TabListAction.AddMultipleTabsAction(listOf(tab1, tab2))
).joinBlocking()
coEvery { tabCollectionStorage.addTabsToCollection(any(), any()) } returns 1L
coEvery { tabCollectionStorage.createCollection(any(), any()) } returns 1L
val tabs = listOf(
Tab("session-1", "", "", ""),
Tab("null-session", "", "", "")
@ -166,7 +170,6 @@ class DefaultCollectionCreationControllerTest {
fun `WHEN selectCollection is called THEN add tabs should be added to collection`() {
val tab1 = createTab("https://www.mozilla.org", id = "session-1")
val tab2 = createTab("https://www.mozilla.org", id = "session-2")
browserStore.dispatch(
TabListAction.AddMultipleTabsAction(listOf(tab1, tab2))
).joinBlocking()
@ -175,6 +178,8 @@ class DefaultCollectionCreationControllerTest {
Tab("session-1", "", "", "")
)
val collection = mockk<TabCollection>()
coEvery { tabCollectionStorage.addTabsToCollection(any(), any()) } returns 1L
coEvery { tabCollectionStorage.createCollection(any(), any()) } returns 1L
controller.selectCollection(collection, tabs)

@ -54,6 +54,7 @@ class NavigationInteractorTest {
private val bookmarksUseCase: BookmarksUseCase = mockk(relaxed = true)
private val context: Context = mockk(relaxed = true)
private val collectionStorage: TabCollectionStorage = mockk(relaxed = true)
private val showCollectionSnackbar: (Int, Boolean, Long?) -> Unit = mockk(relaxed = true)
private val accountManager: FxaAccountManager = mockk(relaxed = true)
private val activity: HomeActivity = mockk(relaxed = true)
@ -80,6 +81,7 @@ class NavigationInteractorTest {
bookmarksUseCase,
tabsTrayStore,
collectionStorage,
showCollectionSnackbar,
accountManager,
testDispatcher
)
@ -247,6 +249,7 @@ class NavigationInteractorTest {
bookmarksUseCase,
tabsTrayStore,
collectionStorage,
showCollectionSnackbar,
accountManager,
coroutineContext
)

@ -0,0 +1,66 @@
package org.mozilla.fenix.tabstray.ext
import android.content.Context
import android.view.View
import io.mockk.every
import io.mockk.mockk
import io.mockk.verifyOrder
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.tabstray.TabsTrayFragment.Companion.ELEVATION
class FenixSnackbarKtTest {
@Test
fun `WHEN message is called with different parameters THEN correct text will be set`() {
val mockContext: Context = mockk {
every { getString(R.string.create_collection_tabs_saved_new_collection) }
.answers { "test1" }
every { getString(R.string.create_collection_tabs_saved) }
.answers { "test2" }
every { getString(R.string.create_collection_tab_saved) }
.answers { "test3" }
}
val snackbar: FenixSnackbar = mockk {
every { context }.answers { mockContext }
}
every { snackbar.setText(any()) }.answers { snackbar }
snackbar.message(1, true)
snackbar.message(2, false)
snackbar.message(1, false)
verifyOrder {
snackbar.setText("test1")
snackbar.setText("test2")
snackbar.setText("test3")
}
}
@Test
fun `WHEN anchorWithAction is called THEN correct text will be set`() {
val mockContext: Context = mockk {
every { getString(R.string.create_collection_view) }
.answers { "test1" }
}
val anchor: View = mockk(relaxed = true)
val view: View = mockk(relaxed = true)
val snackbar: FenixSnackbar = mockk {
every { context }.answers { mockContext }
}
every { snackbar.setAnchorView(anchor) }.answers { snackbar }
every { snackbar.view }.answers { view }
every { snackbar.setAction(any(), any()) }.answers { mockk(relaxed = true) }
every { snackbar.anchorView }.answers { anchor }
snackbar.anchorWithAction(anchor, mockk(relaxed = true))
verifyOrder {
snackbar.anchorView = anchor
view.elevation = ELEVATION
snackbar.setAction("test1", any())
}
}
}

@ -0,0 +1,21 @@
package org.mozilla.fenix.tabstray.ext
import org.junit.Assert.assertEquals
import org.junit.Test
class LongKtTest {
@Test
fun `WHEN value is null THEN default is returned`() {
val value: Long? = null
assertEquals(value.orDefault(), -1L)
}
@Test
fun `WHEN value is not null THEN value is returned`() {
val value: Long? = 100L
assertEquals(value.orDefault(), 100L)
}
}

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "91.0.20210603145049"
const val VERSION = "91.0.20210604143054"
}

Loading…
Cancel
Save