For #21493 show onboarding dialog for home sections

upstream-sync
Arturo Mejia 3 years ago committed by mergify[bot]
parent f338d64a4c
commit f15291757b

@ -16,6 +16,7 @@ import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
@ -344,6 +345,8 @@ class BookmarksTest {
@Test
fun openSelectionInNewTabTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {

@ -11,6 +11,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -52,6 +53,8 @@ class CollectionTest {
@Test
// open a webpage, and add currently opened tab to existing collection
fun mainMenuSaveToExistingCollection() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -78,6 +81,8 @@ class CollectionTest {
@Test
fun verifyAddTabButtonOfCollectionMenu() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)
@ -104,6 +109,8 @@ class CollectionTest {
@Test
fun renameCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -125,6 +132,8 @@ class CollectionTest {
@Test
fun createSecondCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val webPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -204,6 +213,8 @@ class CollectionTest {
@Test
fun selectTabOnLongTapTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val firstWebPage = getGenericAsset(mockWebServer, 1)
val secondWebPage = getGenericAsset(mockWebServer, 2)

@ -7,6 +7,7 @@ package org.mozilla.fenix.ui
import android.content.Context
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.IdlingRegistry
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import okhttp3.mockwebserver.MockWebServer
@ -15,6 +16,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
@ -219,6 +221,8 @@ class HistoryTest {
fun deleteMultipleSelectionTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
val settings = InstrumentationRegistry.getInstrumentation().targetContext.settings()
settings.hasShownHomeOnboardingDialog = true
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {

@ -10,6 +10,7 @@ import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.ext.waitNotNull
@ -117,6 +118,8 @@ class HomeScreenTest {
@Test
fun dismissOnboardingUsingHelpTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {

@ -5,10 +5,12 @@
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
@ -53,6 +55,8 @@ class NoNetworkAccessStartupTests {
// Based on STR from https://github.com/mozilla-mobile/fenix/issues/16886
fun networkInterruptedFromBrowserToHomeTest() {
val url = "example.com"
val settings = InstrumentationRegistry.getInstrumentation().targetContext.settings()
settings.hasShownHomeOnboardingDialog = true
activityTestRule.launchActivity(null)

@ -12,6 +12,7 @@ import org.junit.Rule
import org.junit.Before
import org.junit.After
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.robots.clickRateButtonGooglePlay
@ -75,6 +76,8 @@ class SettingsAboutTest {
@Test
fun verifyAboutFirefoxPreview() {
val settings = activityIntentTestRule.activity.settings()
settings.hasShownHomeOnboardingDialog = true
homeScreen {
}.openThreeDotMenu {
}.openSettings {

@ -14,6 +14,7 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -155,6 +156,8 @@ class SettingsBasicsTest {
// Goes through the settings and changes the default text on a webpage, then verifies if the text has changed.
val fenixApp = activityIntentTestRule.activity.applicationContext as FenixApplication
val webpage = getLoremIpsumAsset(mockWebServer).url
val settings = fenixApp.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
// This value will represent the text size percentage the webpage will scale to. The default value is 100%.
val textSizePercentage = 180

@ -12,6 +12,7 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -218,6 +219,8 @@ class SettingsPrivacyTest {
@Test
fun neverSaveLoginFromPromptTest() {
val saveLoginTest = TestAssetHelper.getSaveLoginAsset(mockWebServer)
val settings = activityTestRule.activity.settings()
settings.hasShownHomeOnboardingDialog = true
navigationToolbar {
}.enterURLAndEnterToBrowser(saveLoginTest.url) {
@ -327,6 +330,8 @@ class SettingsPrivacyTest {
@Test
fun launchLinksInPrivateToggleOffStateDoesntChangeTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
setOpenLinksInPrivateOn()

@ -312,6 +312,9 @@ class SmokeTest {
@Test
// Verifies the Add to top sites option in a tab's 3 dot menu
fun openMainMenuAddTopSiteTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -837,6 +840,8 @@ class SmokeTest {
@Test
fun createFirstCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
@ -868,6 +873,8 @@ class SmokeTest {
@Test
fun verifyExpandedCollectionItemsTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -920,6 +927,9 @@ class SmokeTest {
@Test
fun shareCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -943,6 +953,8 @@ class SmokeTest {
// Test running on beta/release builds in CI:
// caution when making changes to it, so they don't block the builds
fun deleteCollectionTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val webPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -1360,6 +1372,8 @@ class SmokeTest {
@Test
fun goToHomeScreenBottomToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
@ -1372,6 +1386,9 @@ class SmokeTest {
@Test
fun goToHomeScreenTopToolbarTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
@ -1439,6 +1456,8 @@ class SmokeTest {
@Test
fun alwaysStartOnHomeTest() {
val settings = activityTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val genericURL = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {

@ -11,6 +11,7 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -48,6 +49,8 @@ class TopSitesTest {
@Test
fun verifyAddToFirefoxHome() {
val settings = activityIntentTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val defaultWebPageTitle = "Test_Page_1"
@ -68,6 +71,8 @@ class TopSitesTest {
@Test
fun verifyOpenTopSiteNormalTab() {
val settings = activityIntentTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val defaultWebPageTitle = "Test_Page_1"
@ -100,6 +105,8 @@ class TopSitesTest {
@Test
fun verifyOpenTopSitePrivateTab() {
val settings = activityIntentTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val defaultWebPageTitle = "Test_Page_1"
@ -124,6 +131,8 @@ class TopSitesTest {
@Test
fun verifyRenameTopSite() {
val settings = activityIntentTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val defaultWebPageTitle = "Test_Page_1"
val defaultWebPageTitleNew = "Test_Page_2"
@ -150,6 +159,8 @@ class TopSitesTest {
@Test
fun verifyRemoveTopSite() {
val settings = activityIntentTestRule.activity.applicationContext.settings()
settings.hasShownHomeOnboardingDialog = true
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val defaultWebPageTitle = "Test_Page_1"

@ -182,6 +182,11 @@ interface SessionControlController {
* @see [CustomizeHomeIteractor.openCustomizeHomePage]
*/
fun handleCustomizeHomeTapped()
/**
* @see [OnboardingInteractor.showOnboardingDialog]
*/
fun handleShowOnboardingDialog()
}
@Suppress("TooManyFunctions", "LargeClass")
@ -448,6 +453,13 @@ class DefaultSessionControlController(
metrics.track(Event.HomeScreenCustomizedHomeClicked)
}
override fun handleShowOnboardingDialog() {
navController.nav(
R.id.homeFragment,
HomeFragmentDirections.actionGlobalHomeOnboardingDialog()
)
}
override fun handleReadPrivacyNoticeClicked() {
activity.openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),

@ -148,6 +148,12 @@ interface OnboardingInteractor {
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
fun onReadPrivacyNoticeClicked()
/**
* Show the onboarding dialog to onboard users about recentTabs,recentBookmarks,
* historyMetadata and pocketArticles sections.
*/
fun showOnboardingDialog()
}
interface TipInteractor {
@ -293,6 +299,10 @@ class SessionControlInteractor(
controller.handleReadPrivacyNoticeClicked()
}
override fun showOnboardingDialog() {
controller.handleShowOnboardingDialog()
}
override fun onToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
controller.handleToggleCollectionExpanded(collection, expand)
}

@ -25,6 +25,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.utils.Settings
// This method got a little complex with the addition of the tab tray feature flag
// When we remove the tabs from the home screen this will get much simpler again.
@ -167,6 +168,13 @@ private fun HomeFragmentState.toAdapterList(context: Context): List<AdapterItem>
is Mode.Onboarding -> onboardingAdapterItems(mode.state)
}
@VisibleForTesting
internal fun HomeFragmentState.shouldShowHomeOnboardingDialog(settings: Settings): Boolean {
val isAnySectionsVisible = recentTabs.isNotEmpty() || recentBookmarks.isNotEmpty() ||
historyMetadata.isNotEmpty() || pocketStories.isNotEmpty()
return isAnySectionsVisible && !settings.hasShownHomeOnboardingDialog
}
private fun collectionTabItems(collection: TabCollection) =
collection.tabs.mapIndexed { index, tab ->
AdapterItem.TabInCollectionItem(collection, tab, index == collection.tabs.lastIndex)
@ -176,7 +184,7 @@ class SessionControlView(
store: HomeFragmentStore,
val containerView: View,
viewLifecycleOwner: LifecycleOwner,
interactor: SessionControlInteractor,
internal val interactor: SessionControlInteractor,
private var homeScreenViewModel: HomeScreenViewModel
) {
@ -204,6 +212,10 @@ class SessionControlView(
}
fun update(state: HomeFragmentState) {
if (state.shouldShowHomeOnboardingDialog(view.context.settings())) {
interactor.showOnboardingDialog()
}
val stateAdapterList = state.toAdapterList(view.context)
if (homeScreenViewModel.shouldScrollToTopSites) {
sessionControlAdapter.submitList(stateAdapterList) {

@ -0,0 +1,43 @@
/* 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.onboarding
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.FragmentOnboardingHomeDialogBinding
import org.mozilla.fenix.ext.settings
/**
* Dialog displayed once when one or multiples of these sections are shown in the home screen
* recentTabs,recentBookmarks,historyMetadata or pocketArticles.
*/
class HomeOnboardingDialogFragment : DialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.HomeOnboardingDialogStyle)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_onboarding_home_dialog, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentOnboardingHomeDialogBinding.bind(view)
binding.finishButton.setOnClickListener {
context?.settings()?.let { settings ->
settings.hasShownHomeOnboardingDialog = true
}
dismiss()
}
}
}

@ -757,6 +757,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false
)
/**
* Indicates if the home onboarding dialog has already shown before.
*/
var hasShownHomeOnboardingDialog by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_has_shown_home_onboarding),
default = false
)
fun incrementVisitedInstallableCount() = pwaInstallableVisitCount.increment()
@VisibleForTesting(otherwise = PRIVATE)

@ -0,0 +1,222 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- 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/. -->
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never"
android:fitsSystemWindows="true"
android:fillViewport="true"
android:background="@drawable/scrim_background"
android:scrollbars="none">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/dialog_background"
android:layout_marginTop="16dp" >
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/firefox_logo"
android:layout_width="59dp"
android:layout_height="60dp"
android:layout_marginTop="30dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_firefox" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/welcome_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="@dimen/oboarding_home_dialog_margin_horizontal"
android:lineHeight="24sp"
android:text="@string/onboarding_home_screen_title"
android:textAppearance="@style/Header20TextStyle"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/firefox_logo" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_marginHorizontal="@dimen/oboarding_home_dialog_margin_horizontal"
android:lineHeight="24sp"
android:textAppearance="@style/Body14TextStyle"
android:text="@string/onboarding_home_screen_description"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/welcome_title" />
<ImageView
android:id="@+id/home_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="26dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/home_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description"
app:srcCompat="@drawable/mozac_ic_home"
app:tint="?primaryText" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/home_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:layout_marginTop="26dp"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_home_title"
android:textAppearance="@style/Header14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/home_icon"
app:layout_constraintTop_toBottomOf="@id/description" />
<View
android:id="@+id/home_placeholder"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/home_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/home_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/home_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_home_description"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/home_placeholder"
app:layout_constraintTop_toBottomOf="@+id/home_title" />
<ImageView
android:id="@+id/cleaner_tab_tray_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="26dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/cleaner_tab_tray_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/home_description"
app:srcCompat="@drawable/ic_multiple_tabs"
app:tint="?primaryText" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/cleaner_tab_tray_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:layout_marginTop="26dp"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_cleaner_tab_tray_title"
android:textAppearance="@style/Header14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cleaner_tab_tray_icon"
app:layout_constraintTop_toBottomOf="@id/home_description" />
<View
android:id="@+id/cleaner_tab_tray_placeholder"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/cleaner_tab_tray_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cleaner_tab_tray_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/cleaner_tab_tray_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_cleaner_tab_tray_description"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/cleaner_tab_tray_placeholder"
app:layout_constraintTop_toBottomOf="@+id/cleaner_tab_tray_title" />
<ImageView
android:id="@+id/useful_history_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginTop="26dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/useful_history_title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cleaner_tab_tray_description"
app:srcCompat="@drawable/ic_history"
app:tint="?primaryText" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/useful_history_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:layout_marginTop="26dp"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_useful_history_title"
android:textAppearance="@style/Header14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/useful_history_icon"
app:layout_constraintTop_toBottomOf="@id/cleaner_tab_tray_description" />
<View
android:id="@+id/useful_history_placeholder"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginHorizontal="20dp"
android:importantForAccessibility="no"
app:layout_constraintEnd_toStartOf="@id/useful_history_description"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/useful_history_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/useful_history_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginEnd="@dimen/oboarding_home_dialog_margin_horizontal"
android:lineHeight="20sp"
android:text="@string/onboarding_home_screen_section_useful_history_description"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/useful_history_placeholder"
app:layout_constraintTop_toBottomOf="@+id/useful_history_title" />
<Button
android:id="@+id/finish_button"
style="@style/PositiveButton"
android:layout_marginHorizontal="64dp"
android:layout_marginTop="42dp"
android:background="@drawable/onboarding_padded_background"
android:backgroundTint="?accent"
android:text="@string/onboarding_finish"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/useful_history_description" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

@ -23,6 +23,11 @@
app:popUpTo="@id/homeFragment"
app:popUpToInclusive="false" />
<action
android:id="@+id/action_global_home_onboarding_dialog"
app:destination="@id/homeOnboardingDialogFragment"
app:popUpTo="@id/homeFragment" />
<action
android:id="@+id/action_global_search_dialog"
app:destination="@id/searchDialogFragment"
@ -160,6 +165,10 @@
android:defaultValue="-1L"
app:argType="long" />
</fragment>
<dialog
android:id="@+id/homeOnboardingDialogFragment"
android:name="org.mozilla.fenix.onboarding.HomeOnboardingDialogFragment"
tools:layout="@layout/fragment_onboarding_home_dialog"/>
<dialog
android:id="@+id/searchDialogFragment"

@ -174,6 +174,9 @@
<dimen name="onboarding_dual_pane_radio_button_translation_x">-7dp</dimen>
<dimen name="onboarding_dual_pane_radio_button_translation_y">-8dp</dimen>
<!--Onboarding home dialog-->
<dimen name="oboarding_home_dialog_margin_horizontal">24dp</dimen>
<!-- Addon Details Fragment -->
<dimen name="addon_details_rating_view_margin_start">6dp</dimen>
<dimen name="addon_details_rating_view_margin_end">6dp</dimen>

@ -70,6 +70,7 @@
<string name="pref_key_last_cfr_shown_time" translatable="false">pref_key_last_cfr_shown_time</string>
<string name="pref_key_should_show_default_browser_notification" translatable="false">pref_key_should_show_default_browser_notification</string>
<string name="pref_key_is_first_run" translatable="false">pref_key_is_first_run</string>
<string name="pref_key_has_shown_home_onboarding" translatable="false">pref_key_has_shown_home_onboarding</string>
<!-- Data Choices -->
<string name="pref_key_telemetry" translatable="false">pref_key_telemetry</string>

@ -247,6 +247,22 @@
<string name="search_engine_suggestions_title">Search %s</string>
<!-- Search engine suggestion description text -->
<string name="search_engine_suggestions_description">Search directly from the address bar</string>
<!-- Onboarding home screen dialog title text. Firefox is intentionally hardcoded. -->
<string name="onboarding_home_screen_title">Whats new in Firefox!</string>
<!-- Onboarding home screen dialog description text. -->
<string name="onboarding_home_screen_description">Its now easier to pick up where you left off so you can stay in the flow.</string>
<!-- Onboarding home screen dialog title text for the home section. Firefox is intentionally hardcoded. -->
<string name="onboarding_home_screen_section_home_title">Personalized Firefox home</string>
<!-- Onboarding home screen dialog description text for the home section. -->
<string name="onboarding_home_screen_section_home_description">Access your open tabs, bookmarks, and history directly from your home screen.</string>
<!-- Onboarding home screen dialog description text for the tab tray section. -->
<string name="onboarding_home_screen_section_cleaner_tab_tray_title">Cleaner tab tray</string>
<!-- Onboarding home screen dialog description text for the tab tray section. Firefox is intentionally hardcoded. -->
<string name="onboarding_home_screen_section_cleaner_tab_tray_description">Firefox helps you to manage your tabs and quickly find what youre looking for.</string>
<!-- Onboarding home screen dialog description text for the history section. -->
<string name="onboarding_home_screen_section_useful_history_title">Useful history</string>
<!-- Onboarding home screen dialog description text for the history section. -->
<string name="onboarding_home_screen_section_useful_history_description">Get a better overview of your browsing history.</string>
<!-- Search Widget -->
<!-- Content description for searching with a widget. Firefox is intentionally hardcoded.-->

@ -555,6 +555,7 @@
<style name="CreateCollectionDialogStyle" parent="DialogStyleBase"/>
<style name="CreateShortcutDialogStyle" parent="DialogStyleBase"/>
<style name="HomeOnboardingDialogStyle" parent="DialogStyleBase"/>
<style name="CreateShortcutDialogButton" parent="Widget.MaterialComponents.Button.TextButton.Dialog">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>

@ -177,6 +177,20 @@ class DefaultSessionControlControllerTest {
}
}
@Test
fun handleShowOnboardingDialog() {
createController().handleShowOnboardingDialog()
verify {
navController.navigate(
match<NavDirections> {
it.actionId == R.id.action_global_home_onboarding_dialog
},
null
)
}
}
@Test
fun `handleCollectionOpenTabClicked onFailure`() {
val tab = mockk<ComponentTab> {

@ -212,6 +212,12 @@ class SessionControlInteractorTest {
verify { controller.handleCustomizeHomeTapped() }
}
@Test
fun `WHEN calling showOnboardingDialog THEN handleShowOnboardingDialog`() {
interactor.showOnboardingDialog()
verify { controller.handleShowOnboardingDialog() }
}
@Test
fun `WHEN Show All recently saved bookmarks button is clicked THEN the click is handled`() {
interactor.onShowAllBookmarksClicked()

@ -4,9 +4,11 @@
package org.mozilla.fenix.home.sessioncontrol
import androidx.recyclerview.widget.RecyclerView
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType
@ -15,15 +17,122 @@ import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.pocket.PocketRecommendedStory
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertTrue
import org.junit.Assert.assertFalse
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.historymetadata.HistoryMetadataGroup
import org.mozilla.fenix.home.HomeFragmentState
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class SessionControlViewTest {
@Test
fun `GIVEN recent Bookmarks WHEN calling shouldShowHomeOnboardingDialog THEN show the dialog `() {
val recentBookmarks =
listOf(BookmarkNode(BookmarkNodeType.ITEM, "guid", null, null, null, null, 0, null))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns false
val state = HomeFragmentState(recentBookmarks = recentBookmarks)
assertTrue(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVEN recentTabs WHEN calling shouldShowHomeOnboardingDialog THEN show the dialog `() {
val recentTabs = listOf<TabSessionState>(mockk())
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns false
val state = HomeFragmentState(recentTabs = recentTabs)
assertTrue(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVEN historyMetadata WHEN calling shouldShowHomeOnboardingDialog THEN show the dialog `() {
val historyMetadata = listOf(HistoryMetadataGroup("title", emptyList()))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns false
val state = HomeFragmentState(historyMetadata = historyMetadata)
assertTrue(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVEN pocketArticles WHEN calling shouldShowHomeOnboardingDialog THEN show the dialog `() {
val pocketArticles = listOf(PocketRecommendedStory("", "", "", "", 0, ""))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns false
val state = HomeFragmentState(pocketStories = pocketArticles)
assertTrue(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVEN the home onboading dialog has been shown before WHEN calling shouldShowHomeOnboardingDialog THEN DO NOT showthe dialog `() {
val pocketArticles = listOf(PocketRecommendedStory("", "", "", "", 0, ""))
val settings: Settings = mockk()
every { settings.hasShownHomeOnboardingDialog } returns true
val state = HomeFragmentState(pocketStories = pocketArticles)
assertFalse(state.shouldShowHomeOnboardingDialog(settings))
}
@Test
fun `GIVENs updates WHEN sections recentTabs, recentBookmarks, historyMetadata or pocketArticles are available THEN show the dialog`() {
val interactor = mockk<SessionControlInteractor>(relaxed = true)
val view = RecyclerView(testContext)
val controller = SessionControlView(
mockk(relaxed = true),
view,
mockk(relaxed = true),
interactor,
mockk(relaxed = true)
)
val recentTabs = listOf<TabSessionState>(mockk(relaxed = true))
val state = HomeFragmentState(recentTabs = recentTabs)
controller.update(state)
verify {
interactor.showOnboardingDialog()
}
}
@Test
fun `GIVENs updates WHEN sections recentTabs, recentBookmarks, historyMetadata or pocketArticles are NOT available THEN DO NOT show the dialog`() {
val interactor = mockk<SessionControlInteractor>(relaxed = true)
val view = RecyclerView(testContext)
val controller = SessionControlView(
mockk(relaxed = true),
view,
mockk(relaxed = true),
interactor,
mockk(relaxed = true)
)
val state = HomeFragmentState()
controller.update(state)
verify(exactly = 0) {
interactor.showOnboardingDialog()
}
}
@Test
fun `GIVEN recent Bookmarks WHEN normalModeAdapterItems is called THEN add a customize home button`() {
val topSites = emptyList<TopSite>()

Loading…
Cancel
Save