Bug 1845357 - Remove the first run onboarding

fenix/118.0
Gabriel Luong 10 months ago committed by mergify[bot]
parent 06ca0e760a
commit 0d2dfcb85a

@ -819,150 +819,6 @@ onboarding:
metadata:
tags:
- Onboarding
fxa_manual_signin:
type: event
description:
The onboarding manual sign in card was tapped.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
privacy_notice:
type: event
description:
The onboarding privacy notice card was tapped.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
pref_toggled_toolbar_position:
type: event
description:
The toolbar position preference was chosen from the onboarding card.
extra_keys:
position:
type: string
description: |
A string that indicates the position of the toolbar TOP or BOTTOM.
Default: BOTTOM
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
pref_toggled_tracking_prot:
type: event
description:
The tracking protection preference was chosen from the onboarding card.
extra_keys:
setting:
type: string
description: |
A string that indicates the Tracking Protection policy STANDARD
or STRICT. Default: Toggle ON, STANDARD
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
pref_toggled_theme_picker:
type: event
description:
The device theme was chosen using the theme picker onboarding card.
extra_keys:
theme:
type: string
description: |
A string that indicates the theme LIGHT, DARK, or FOLLOW DEVICE.
Default: FOLLOW DEVICE
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
finish:
type: event
description:
The user taps starts browsing and ends the onboarding experience.
bugs:
- https://github.com/mozilla-mobile/fenix/issues/10824
data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/11867
- https://github.com/mozilla-mobile/fenix/pull/15713#issuecomment-703972068
- https://github.com/mozilla-mobile/fenix/pull/19924#issuecomment-861423789
- https://github.com/mozilla-mobile/fenix/pull/20517#pullrequestreview-718069041
- https://github.com/mozilla-mobile/fenix/pull/21038#issuecomment-906757301
data_sensitivity:
- interaction
notification_emails:
- android-probes@mozilla.com
- cgordon@mozilla.com
expires: never
metadata:
tags:
- Onboarding
welcome_card_impression:
type: event
description:

@ -33,7 +33,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
*
* Say no to main thread IO! 🙅
*/
private const val EXPECTED_SUPPRESSION_COUNT = 17
private const val EXPECTED_SUPPRESSION_COUNT = 16
/**
* The number of times we call the `runBlocking` coroutine method on the main thread during this

@ -43,7 +43,7 @@ class DefaultHomeScreenTest : ScreenshotTest() {
@Test
fun showDefaultHomeScreen() {
homeScreen {
verifyAccountsSignInButton()
verifyHomeScreen()
Screengrab.screenshot("HomeScreenRobot_home-screen-scroll")
TestAssetHelper.waitingTime
}

@ -1,245 +0,0 @@
/* 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.ui
import android.content.res.Configuration
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.verifyDarkThemeApplied
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.helpers.TestHelper.verifyLightThemeApplied
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class OnboardingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val privacyNoticeLink = "mozilla.org/en-US/privacy/firefox"
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
private fun getUITheme(): Boolean {
val mode =
activityTestRule.activity.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)
return when (mode) {
Configuration.UI_MODE_NIGHT_YES -> true // dark theme is set
Configuration.UI_MODE_NIGHT_NO -> false // dark theme is not set, using light theme
else -> false // default option is light theme
}
}
// Verifies the first run onboarding screen
@SmokeTest
@Test
fun firstRunScreenTest() {
homeScreen {
verifyHomeScreenAppBarItems()
verifyHomeScreenWelcomeItems()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
verifySignInToSyncCard()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
verifyPrivacyNoticeCard()
verifyStartBrowsingSection()
verifyNavigationToolbarItems("0")
}
}
// Verifies the functionality of the onboarding Start Browsing button
@SmokeTest
@Test
fun startBrowsingButtonTest() {
homeScreen {
verifyStartBrowsingButton()
}.clickStartBrowsingButton {
verifySearchView()
}
}
@Test
fun dismissOnboardingUsingSettingsTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openSettings {
verifyGeneralHeading()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingBookmarksTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
navigateUp()
}
homeScreen {
verifyExistingTopSitesList()
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun dismissOnboardingUsingHelpTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun toolbarTapDoesntDismissOnboardingTest() {
homeScreen {
verifyStartBrowsingButton()
}.openSearch {
verifySearchView()
verifyKeyboardVisibility()
}.dismissSearchBar {
verifyStartBrowsingButton()
}
}
@Test
fun dismissOnboardingWithPageLoadTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
verifyStartBrowsingButton()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
verifyHomeScreen()
}
}
@Test
fun chooseYourThemeCardTest() {
homeScreen {
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
clickLightThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = true,
isAutomaticThemeChecked = false,
)
verifyLightThemeApplied(getUITheme())
clickDarkThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = true,
isLightThemeChecked = false,
isAutomaticThemeChecked = false,
)
verifyDarkThemeApplied(getUITheme())
clickAutomaticThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyLightThemeApplied(getUITheme())
}
}
@Test
fun pickYourToolbarPlacementCardTest() {
homeScreen {
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
clickTopToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = false)
clickBottomToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = true)
}
}
@Test
fun privacyProtectionByDefaultCardTest() {
homeScreen {
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
clickStrictTrackingProtectionButton()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = false,
isStrictChecked = true,
)
clickStandardTrackingProtectionButton()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
}
}
@Test
fun pickUpWhereYouLeftOffCardTest() {
homeScreen {
verifySignInToSyncCard()
}.clickSignInButton {
verifyTurnOnSyncMenu()
}
}
@Test
fun youControlYourDataCardTest() {
homeScreen {
verifyPrivacyNoticeCard()
}.clickPrivacyNoticeButton {
verifyCustomTabToolbarTitle("Firefox Privacy Notice")
}.goBackToOnboardingScreen {
verifyPrivacyNoticeCard()
}
}
}

@ -38,7 +38,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
@ -54,13 +53,10 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
@ -76,7 +72,6 @@ import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.tabstray.TabsTrayTestTag
import org.mozilla.fenix.utils.Settings
/**
* Implementation of Robot Pattern for the home screen menu.
@ -102,92 +97,6 @@ class HomeScreenRobot {
fun verifyHomeScreenAppBarItems() =
assertItemWithResIdExists(homeScreen, privateBrowsingButton, homepageWordmark)
fun verifyHomeScreenWelcomeItems() =
assertItemContainingTextExists(welcomeHeader, welcomeSubHeader)
fun verifyChooseYourThemeCard(
isDarkThemeChecked: Boolean,
isLightThemeChecked: Boolean,
isAutomaticThemeChecked: Boolean,
) {
scrollToElementByText(getStringResource(R.string.onboarding_theme_picker_header))
assertItemContainingTextExists(
chooseThemeHeader,
chooseThemeText,
darkThemeDescription,
lightThemeDescription,
)
assertCheckedItemWithResIdExists(
darkThemeToggle(isDarkThemeChecked),
lightThemeToggle(isLightThemeChecked),
automaticThemeToggle(isAutomaticThemeChecked),
)
assertItemWithResIdAndDescriptionExists(automaticThemeDescription)
}
fun clickLightThemeButton() =
itemWithResId("$packageName:id/theme_light_radio_button").click()
fun clickDarkThemeButton() =
itemWithResId("$packageName:id/theme_dark_radio_button").click()
fun clickAutomaticThemeButton() =
itemWithResId("$packageName:id/theme_automatic_radio_button").click()
fun verifyToolbarPlacementCard(isBottomChecked: Boolean, isTopChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
assertItemContainingTextExists(toolbarPlacementHeader, toolbarPlacementDescription)
assertCheckedItemWithResIdExists(
toolbarPlacementBottomRadioButton(isBottomChecked),
toolbarPlacementTopRadioButton(isTopChecked),
)
assertItemWithResIdExists(
toolbarPlacementBottomImage,
toolbarPlacementBottomTitle,
toolbarPlacementTopImage,
toolbarPlacementTopTitle,
)
}
fun clickTopToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_top_radio_button").click()
fun clickBottomToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_bottom_radio_button").click()
fun verifySignInToSyncCard() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertItemContainingTextExists(startSyncHeader, startSyncDescription)
assertItemWithResIdExists(signInButton)
}
fun verifyPrivacyProtectionCard(settings: Settings, isStandardChecked: Boolean, isStrictChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1))
assertItemContainingTextExists(privacyProtectionHeader, privacyProtectionDescription(settings))
assertCheckedItemWithResIdExists(
standardTrackingProtectionToggle(isStandardChecked),
strictTrackingProtectionToggle(isStrictChecked),
)
}
fun clickStandardTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_standard_option").click()
fun clickStrictTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_strict_default").click()
fun verifyPrivacyNoticeCard() {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_read_button))
assertItemContainingTextExists(privacyNoticeHeader, privacyNoticeDescription)
assertItemWithResIdExists(privacyNoticeButton)
}
fun verifyStartBrowsingSection() {
scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertItemWithResIdExists(startBrowsingButton)
assertItemContainingTextExists(conclusionHeader)
}
fun verifyNavigationToolbarItems(numberOfOpenTabs: String = "0") {
assertItemWithResIdExists(navigationToolbar, menuButton)
assertItemWithResIdAndTextExists(tabCounter(numberOfOpenTabs))
@ -225,17 +134,6 @@ class HomeScreenRobot {
mDevice.findObject(UiSelector())
}
// First Run elements
fun verifyWelcomeHeader() = assertItemContainingTextExists(welcomeHeader)
fun verifyAccountsSignInButton() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertItemWithResIdExists(signInButton)
}
fun verifyStartBrowsingButton() {
scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertItemWithResIdExists(startBrowsingButton)
}
// Upgrading users onboarding dialog
fun verifyUpgradingUserOnboardingFirstScreen(testRule: ComposeTestRule) {
testRule.also {
@ -600,13 +498,6 @@ class HomeScreenRobot {
openThreeDotMenu { }.openSettings { }.goBack { }
}
fun clickStartBrowsingButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
startBrowsingButton.click()
SearchRobot().interact()
return SearchRobot.Transition()
}
fun clickUpgradingUserOnboardingSignInButton(
testRule: ComposeTestRule,
interact: SyncSignInRobot.() -> Unit,
@ -886,20 +777,6 @@ class HomeScreenRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickSignInButton(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
signInButton.clickAndWaitForNewWindow(waitingTimeShort)
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun clickPrivacyNoticeButton(interact: CustomTabRobot.() -> Unit): CustomTabRobot.Transition {
privacyNoticeButton.clickAndWaitForNewWindow(waitingTimeShort)
CustomTabRobot().interact()
return CustomTabRobot.Transition()
}
}
}
@ -1088,82 +965,13 @@ private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
private fun privacyProtectionDescription(settings: Settings): UiObject {
val isTCPPublic = settings.enabledTotalCookieProtectionCFR
val descriptionText = when (isTCPPublic) {
true -> R.string.onboarding_tracking_protection_description
false -> R.string.onboarding_tracking_protection_description_old
}
return itemContainingText(getStringResource(descriptionText))
}
private val homeScreen =
itemWithResId("$packageName:id/homeLayout")
private val privateBrowsingButton =
itemWithResId("$packageName:id/privateBrowsingButton")
private val homepageWordmark =
itemWithResId("$packageName:id/wordmark")
private val welcomeHeader = itemContainingText(getStringResource(R.string.onboarding_header_2))
private val welcomeSubHeader =
itemContainingText(getStringResource(R.string.onboarding_message))
private val chooseThemeHeader =
itemContainingText(getStringResource(R.string.onboarding_theme_picker_header))
private val chooseThemeText =
itemContainingText(getStringResource(R.string.onboarding_theme_picker_description_2))
private val darkThemeDescription =
itemContainingText(getStringResource(R.string.onboarding_theme_dark_title))
private val lightThemeDescription =
itemContainingText(getStringResource(R.string.onboarding_theme_light_title))
private val automaticThemeDescription =
itemWithResIdAndDescription(
"$packageName:id/clickable_region_automatic",
"${getStringResource(R.string.onboarding_theme_automatic_title)} ${getStringResource(R.string.onboarding_theme_automatic_summary)}",
)
private fun darkThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_dark_radio_button", isChecked)
private fun lightThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_light_radio_button", isChecked)
private fun automaticThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_automatic_radio_button", isChecked)
private val toolbarPlacementHeader =
itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
private val toolbarPlacementDescription =
itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_description))
private fun toolbarPlacementBottomRadioButton(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/toolbar_bottom_radio_button", isChecked)
private fun toolbarPlacementTopRadioButton(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/toolbar_top_radio_button", isChecked)
private val toolbarPlacementBottomImage =
itemWithResId("$packageName:id/toolbar_bottom_image")
private val toolbarPlacementBottomTitle =
itemWithResId("$packageName:id/toolbar_bottom_title")
private val toolbarPlacementTopTitle =
itemWithResId("$packageName:id/toolbar_top_title")
private val toolbarPlacementTopImage =
itemWithResId("$packageName:id/toolbar_top_image")
private val startSyncHeader =
itemContainingText(getStringResource(R.string.onboarding_account_sign_in_header))
private val startSyncDescription =
itemContainingText(getStringResource(R.string.onboarding_manual_sign_in_description))
private val signInButton =
itemWithResId("$packageName:id/fxa_sign_in_button")
private val privacyProtectionHeader =
itemContainingText(getStringResource(R.string.onboarding_tracking_protection_header))
private fun standardTrackingProtectionToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/tracking_protection_standard_option", isChecked)
private fun strictTrackingProtectionToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/tracking_protection_strict_default", isChecked)
private val privacyNoticeHeader =
itemContainingText(getStringResource(R.string.onboarding_privacy_notice_header_1))
private val privacyNoticeDescription =
itemContainingText(getStringResource(R.string.onboarding_privacy_notice_description))
private val privacyNoticeButton =
itemWithResId("$packageName:id/read_button")
private val startBrowsingButton =
itemWithResId("$packageName:id/finish_button")
private val conclusionHeader =
itemContainingText(getStringResource(R.string.onboarding_conclusion_header))
private val navigationToolbar =
itemWithResId("$packageName:id/toolbar")
private val menuButton =

@ -1119,11 +1119,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
open fun navigateToHome() {
if (components.fenixOnboarding.userHasBeenOnboarded()) {
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
} else {
navHost.navController.navigate(NavGraphDirections.actionStartupOnboarding())
}
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
}
override fun attachBaseContext(base: Context) {

@ -560,10 +560,7 @@ class HomeFragment : Fragment() {
toolbarView?.build()
PrivateBrowsingButtonView(binding.privateBrowsingButton, browsingModeManager) { newMode ->
sessionControlInteractor.onPrivateModeButtonClicked(
newMode,
userHasBeenOnboarded = true,
)
sessionControlInteractor.onPrivateModeButtonClicked(newMode)
Homepage.privateModeIconTapped.record(mozilla.telemetry.glean.private.NoExtras())
}

@ -43,7 +43,6 @@ import org.mozilla.fenix.GleanMetrics.HomeMenu as HomeMenuMetrics
* @property navController [NavController] used for navigation.
* @property menuButton The [MenuButton] that will be used to create a menu when the button is
* clicked.
* @property hideOnboardingIfNeeded Lambda invoked to dismiss onboarding.
*/
@Suppress("LongParameterList")
class HomeMenuView(
@ -53,7 +52,6 @@ class HomeMenuView(
private val homeActivity: HomeActivity,
private val navController: NavController,
private val menuButton: WeakReference<MenuButton>,
private val hideOnboardingIfNeeded: () -> Unit = {},
private val fxaEntrypoint: FxAEntryPoint = FenixFxAEntryPoint.HomeMenu,
) {
@ -90,10 +88,6 @@ class HomeMenuView(
@Suppress("LongMethod", "ComplexMethod")
@VisibleForTesting(otherwise = PRIVATE)
internal fun onItemTapped(item: HomeMenu.Item) {
if (item !is HomeMenu.Item.DesktopMode) {
hideOnboardingIfNeeded()
}
when (item) {
HomeMenu.Item.Settings -> {
HomeMenuMetrics.settingsItemClicked.record(NoExtras())

@ -29,7 +29,7 @@ interface PrivateBrowsingController {
/**
* @see [PrivateBrowsingInteractor.onPrivateModeButtonClicked]
*/
fun handlePrivateModeButtonClicked(newMode: BrowsingMode, userHasBeenOnboarded: Boolean)
fun handlePrivateModeButtonClicked(newMode: BrowsingMode)
}
/**
@ -49,26 +49,21 @@ class DefaultPrivateBrowsingController(
)
}
override fun handlePrivateModeButtonClicked(
newMode: BrowsingMode,
userHasBeenOnboarded: Boolean,
) {
override fun handlePrivateModeButtonClicked(newMode: BrowsingMode) {
if (newMode == BrowsingMode.Private) {
activity.settings().incrementNumTimesPrivateModeOpened()
}
if (userHasBeenOnboarded) {
appStore.dispatch(
AppAction.ModeChange(Mode.fromBrowsingMode(newMode)),
)
appStore.dispatch(
AppAction.ModeChange(Mode.fromBrowsingMode(newMode)),
)
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigate(
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
),
)
}
if (navController.currentDestination?.id == R.id.searchDialogFragment) {
navController.navigate(
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = null,
),
)
}
}
}

@ -19,5 +19,5 @@ interface PrivateBrowsingInteractor {
/**
* Called when a user clicks on the Private Mode button on the homescreen.
*/
fun onPrivateModeButtonClicked(newMode: BrowsingMode, userHasBeenOnboarded: Boolean)
fun onPrivateModeButtonClicked(newMode: BrowsingMode)
}

@ -321,8 +321,8 @@ class SessionControlInteractor(
privateBrowsingController.handleLearnMoreClicked()
}
override fun onPrivateModeButtonClicked(newMode: BrowsingMode, userHasBeenOnboarded: Boolean) {
privateBrowsingController.handlePrivateModeButtonClicked(newMode, userHasBeenOnboarded)
override fun onPrivateModeButtonClicked(newMode: BrowsingMode) {
privateBrowsingController.handlePrivateModeButtonClicked(newMode)
}
override fun onPasteAndGo(clipboardText: String) {

@ -1,31 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingFinishBinding
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
class OnboardingFinishViewHolder(
view: View,
private val interactor: OnboardingInteractor,
) : RecyclerView.ViewHolder(view) {
init {
val binding = OnboardingFinishBinding.bind(view)
binding.finishButton.setOnClickListener {
interactor.onFinishOnboarding(focusOnAddressBar = true)
Onboarding.finish.record(NoExtras())
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_finish
}
}

@ -1,16 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
class OnboardingHeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
companion object {
const val LAYOUT_ID = R.layout.onboarding_header
}
}

@ -1,24 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.widget.TextView
import androidx.annotation.DrawableRes
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.setBounds
/**
* Sets the drawableStart of a header in an onboarding card.
*/
fun TextView.setOnboardingIcon(@DrawableRes id: Int) {
val icon = context.getDrawableWithTint(id, context.getColorFromAttr(R.attr.iconActive))?.apply {
val size = context.resources.getDimensionPixelSize(R.dimen.onboarding_header_icon_height_width)
setBounds(size)
}
putCompoundDrawablesRelative(start = icon)
}

@ -1,34 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint
import org.mozilla.fenix.databinding.OnboardingManualSigninBinding
import org.mozilla.fenix.home.HomeFragmentDirections
class OnboardingManualSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private var binding: OnboardingManualSigninBinding = OnboardingManualSigninBinding.bind(view)
init {
binding.fxaSignInButton.setOnClickListener {
Onboarding.fxaManualSignin.record(NoExtras())
val directions = HomeFragmentDirections.actionGlobalTurnOnSync(
entrypoint = FenixFxAEntryPoint.OnboardingManualSignIn,
)
Navigation.findNavController(view).navigate(directions)
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_manual_signin
}
}

@ -1,32 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingPrivacyNoticeBinding
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
class OnboardingPrivacyNoticeViewHolder(
view: View,
private val interactor: OnboardingInteractor,
) : RecyclerView.ViewHolder(view) {
init {
val binding = OnboardingPrivacyNoticeBinding.bind(view)
binding.readButton.setOnClickListener {
Onboarding.privacyNotice.record(NoExtras())
interactor.onReadPrivacyNoticeClicked()
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_privacy_notice
}
}

@ -1,24 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingSectionHeaderBinding
class OnboardingSectionHeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val binding = OnboardingSectionHeaderBinding.bind(view)
private val sectionHeader = binding.sectionHeaderText
fun bind(labelBuilder: (Context) -> String) {
sectionHeader.text = labelBuilder(itemView.context)
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_section_header
}
}

@ -1,135 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingThemePickerBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingThemePickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
val binding = OnboardingThemePickerBinding.bind(view)
val radioLightTheme = binding.themeLightRadioButton
val radioDarkTheme = binding.themeDarkRadioButton
val radioFollowDeviceTheme = binding.themeAutomaticRadioButton
radioFollowDeviceTheme.key = if (SDK_INT >= Build.VERSION_CODES.P) {
R.string.pref_key_follow_device_theme
} else {
R.string.pref_key_auto_battery_theme
}
addToRadioGroup(
radioLightTheme,
radioDarkTheme,
radioFollowDeviceTheme,
)
radioLightTheme.addIllustration(binding.themeLightImage)
radioDarkTheme.addIllustration(binding.themeDarkImage)
binding.themeDarkImage.setOnClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.DARK.name,
),
)
radioDarkTheme.performClick()
}
binding.themeLightImage.setOnClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.LIGHT.name,
),
)
radioLightTheme.performClick()
}
val automaticTitle = view.context.getString(R.string.onboarding_theme_automatic_title)
val automaticSummary = view.context.getString(R.string.onboarding_theme_automatic_summary)
binding.clickableRegionAutomatic.contentDescription = "$automaticTitle $automaticSummary"
binding.clickableRegionAutomatic.setOnClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.FOLLOW_DEVICE.name,
),
)
radioFollowDeviceTheme.performClick()
}
radioLightTheme.onClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.LIGHT.name,
),
)
setNewTheme(AppCompatDelegate.MODE_NIGHT_NO)
}
radioDarkTheme.onClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.DARK.name,
),
)
setNewTheme(AppCompatDelegate.MODE_NIGHT_YES)
}
radioFollowDeviceTheme.onClickListener {
Onboarding.prefToggledThemePicker.record(
Onboarding.PrefToggledThemePickerExtra(
Theme.FOLLOW_DEVICE.name,
),
)
if (SDK_INT >= Build.VERSION_CODES.P) {
setNewTheme(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
} else {
setNewTheme(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
}
}
with(view.context.settings()) {
val radio: OnboardingRadioButton = when {
shouldUseLightTheme -> {
radioLightTheme
}
shouldUseDarkTheme -> {
radioDarkTheme
}
else -> {
radioFollowDeviceTheme
}
}
radio.updateRadioValue(true)
}
}
private fun setNewTheme(mode: Int) {
if (AppCompatDelegate.getDefaultNightMode() == mode) return
AppCompatDelegate.setDefaultNightMode(mode)
with(itemView.context.components) {
core.engine.settings.preferredColorScheme = core.getPreferredColorScheme()
useCases.sessionUseCases.reload.invoke()
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_theme_picker
// The theme used for telemetry
enum class Theme { LIGHT, DARK, FOLLOW_DEVICE }
}
}

@ -1,83 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.databinding.OnboardingToolbarPositionPickerBinding
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingToolbarPositionPickerViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
val binding = OnboardingToolbarPositionPickerBinding.bind(view)
val radioTopToolbar = binding.toolbarTopRadioButton
val radioBottomToolbar = binding.toolbarBottomRadioButton
val radio: OnboardingRadioButton
addToRadioGroup(radioTopToolbar, radioBottomToolbar)
radioTopToolbar.addIllustration(binding.toolbarTopImage)
radioBottomToolbar.addIllustration(binding.toolbarBottomImage)
val settings = view.context.components.settings
radio = when (settings.toolbarPosition) {
ToolbarPosition.BOTTOM -> radioBottomToolbar
ToolbarPosition.TOP -> radioTopToolbar
}
radio.updateRadioValue(true)
radioBottomToolbar.onClickListener {
Onboarding.prefToggledToolbarPosition.record(
Onboarding.PrefToggledToolbarPositionExtra(
Position.BOTTOM.name,
),
)
itemView.context.asActivity()?.recreate()
}
binding.toolbarBottomImage.setOnClickListener {
Onboarding.prefToggledToolbarPosition.record(
Onboarding.PrefToggledToolbarPositionExtra(
Position.BOTTOM.name,
),
)
radioBottomToolbar.performClick()
}
radioTopToolbar.onClickListener {
Onboarding.prefToggledToolbarPosition.record(
Onboarding.PrefToggledToolbarPositionExtra(
Position.TOP.name,
),
)
itemView.context.asActivity()?.recreate()
}
binding.toolbarTopImage.setOnClickListener {
Onboarding.prefToggledToolbarPosition.record(
Onboarding.PrefToggledToolbarPositionExtra(
Position.TOP.name,
),
)
radioTopToolbar.performClick()
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_toolbar_position_picker
// Position of the toolbar used for telemetry
enum class Position { TOP, BOTTOM }
}
}

@ -1,100 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingTrackingProtectionBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.onboarding.OnboardingRadioButton
import org.mozilla.fenix.utils.view.addToRadioGroup
class OnboardingTrackingProtectionViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private var standardTrackingProtection: OnboardingRadioButton
private var strictTrackingProtection: OnboardingRadioButton
private var descriptionText: TextView
init {
val binding = OnboardingTrackingProtectionBinding.bind(view)
binding.headerText.setOnboardingIcon(R.drawable.ic_onboarding_tracking_protection)
standardTrackingProtection = binding.trackingProtectionStandardOption
strictTrackingProtection = binding.trackingProtectionStrictDefault
descriptionText = binding.descriptionText
val isTCPPublic = view.context.settings().enabledTotalCookieProtectionCFR
setupDescriptionText(view.context, isTCPPublic)
val isTrackingProtectionEnabled = view.context.settings().shouldUseTrackingProtection
setupRadioGroup(isTrackingProtectionEnabled)
updateRadioGroupState(isTrackingProtectionEnabled)
}
private fun setupDescriptionText(context: Context, shuldUseNewDescription: Boolean) {
if (!shuldUseNewDescription) {
val appName = context.getString(R.string.app_name)
descriptionText.text = context.getString(
R.string.onboarding_tracking_protection_description_old,
appName,
)
}
}
private fun setupRadioGroup(isChecked: Boolean) {
updateRadioGroupState(isChecked)
addToRadioGroup(standardTrackingProtection, strictTrackingProtection)
strictTrackingProtection.isChecked =
itemView.context.settings().useStrictTrackingProtection
standardTrackingProtection.isChecked =
!itemView.context.settings().useStrictTrackingProtection
standardTrackingProtection.onClickListener {
updateTrackingProtectionPolicy()
Onboarding.prefToggledTrackingProt.record(
Onboarding.PrefToggledTrackingProtExtra(
Settings.STANDARD.name,
),
)
}
strictTrackingProtection.onClickListener {
updateTrackingProtectionPolicy()
Onboarding.prefToggledTrackingProt.record(
Onboarding.PrefToggledTrackingProtExtra(
Settings.STRICT.name,
),
)
}
}
private fun updateRadioGroupState(isChecked: Boolean) {
standardTrackingProtection.isEnabled = isChecked
strictTrackingProtection.isEnabled = isChecked
}
private fun updateTrackingProtectionPolicy() {
itemView.context?.components?.let {
val policy = it.core.trackingProtectionPolicyFactory
.createTrackingProtectionPolicy()
it.useCases.settingsUseCases.updateTrackingProtection.invoke(policy)
it.useCases.sessionUseCases.reload.invoke()
}
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_tracking_protection
// Tracking protection policy types used for telemetry
enum class Settings { STRICT, STANDARD }
}
}

@ -1,48 +0,0 @@
/* 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.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.ext.components
/**
* Observes various account-related events and dispatches the [OnboardingState] based on whether
* or not the account is authenticated.
*/
class OnboardingAccountObserver(
private val context: Context,
private val dispatchChanges: (state: OnboardingState) -> Unit,
) : AccountObserver {
private val accountManager by lazy { context.components.backgroundServices.accountManager }
/**
* Returns the current [OnboardingState] based on the account state.
*/
fun getOnboardingState(): OnboardingState {
val account = accountManager.authenticatedAccount()
return if (account != null) {
OnboardingState.SignedIn
} else {
OnboardingState.SignedOutNoAutoSignIn
}
}
@VisibleForTesting
internal fun emitChanges() {
dispatchChanges(getOnboardingState())
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) = emitChanges()
override fun onAuthenticationProblems() = emitChanges()
override fun onLoggedOut() = emitChanges()
override fun onProfileUpdated(profile: Profile) = emitChanges()
}

@ -1,245 +0,0 @@
/* 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.content.res.Configuration
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.databinding.FragmentHomeBinding
import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.home.HomeMenuView
import org.mozilla.fenix.home.PrivateBrowsingButtonView
import org.mozilla.fenix.home.TabCounterView
import org.mozilla.fenix.home.ToolbarView
import org.mozilla.fenix.home.privatebrowsing.controller.DefaultPrivateBrowsingController
import org.mozilla.fenix.home.toolbar.DefaultToolbarController
import org.mozilla.fenix.home.toolbar.SearchSelectorBinding
import org.mozilla.fenix.home.toolbar.SearchSelectorMenuBinding
import org.mozilla.fenix.onboarding.controller.DefaultOnboardingController
import org.mozilla.fenix.onboarding.interactor.DefaultOnboardingInteractor
import org.mozilla.fenix.onboarding.view.OnboardingView
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
import java.lang.ref.WeakReference
/**
* Displays the first run onboarding screen.
*/
class OnboardingFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private val searchSelectorMenu by lazy {
SearchSelectorMenu(
context = requireContext(),
interactor = interactor,
)
}
private val store: BrowserStore
get() = requireComponents.core.store
private val browsingModeManager
get() = (activity as HomeActivity).browsingModeManager
private var _interactor: DefaultOnboardingInteractor? = null
private val interactor: DefaultOnboardingInteractor
get() = _interactor!!
private var onboardingView: OnboardingView? = null
private var homeMenuView: HomeMenuView? = null
private var tabCounterView: TabCounterView? = null
private var toolbarView: ToolbarView? = null
private lateinit var onboardingStore: OnboardingStore
private lateinit var onboardingAccountObserver: OnboardingAccountObserver
private val searchSelectorBinding = ViewBoundFeatureWrapper<SearchSelectorBinding>()
private val searchSelectorMenuBinding = ViewBoundFeatureWrapper<SearchSelectorMenuBinding>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val activity = activity as HomeActivity
onboardingAccountObserver = OnboardingAccountObserver(
context = requireContext(),
dispatchChanges = ::dispatchOnboardingStateChanges,
)
onboardingStore = StoreProvider.get(this) {
OnboardingStore(
initialState = OnboardingFragmentState(
onboardingState = onboardingAccountObserver.getOnboardingState(),
),
)
}
_interactor = DefaultOnboardingInteractor(
controller = DefaultOnboardingController(
activity = activity,
navController = findNavController(),
onboarding = requireComponents.fenixOnboarding,
crashReporter = requireComponents.analytics.crashReporter,
),
privateBrowsingController = DefaultPrivateBrowsingController(
activity = activity,
appStore = requireComponents.appStore,
navController = findNavController(),
),
searchSelectorController = DefaultSearchSelectorController(
activity = activity,
navController = findNavController(),
),
toolbarController = DefaultToolbarController(
activity = activity,
store = store,
navController = findNavController(),
),
)
toolbarView = ToolbarView(
binding = binding,
context = requireContext(),
interactor = interactor,
)
onboardingView = OnboardingView(
containerView = binding.sessionControlRecyclerView,
interactor = interactor,
)
activity.themeManager.applyStatusBarTheme(activity)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
consumeFrom(onboardingStore) { state ->
onboardingView?.update(state.onboardingState, requireComponents.fenixOnboarding.config)
}
homeMenuView = HomeMenuView(
view = view,
context = view.context,
lifecycleOwner = viewLifecycleOwner,
homeActivity = activity as HomeActivity,
navController = findNavController(),
menuButton = WeakReference(binding.menuButton),
hideOnboardingIfNeeded = { interactor.onFinishOnboarding(focusOnAddressBar = false) },
).also { it.build() }
tabCounterView = TabCounterView(
context = requireContext(),
browsingModeManager = browsingModeManager,
navController = findNavController(),
tabCounter = binding.tabButton,
)
consumeFrom(store) {
tabCounterView?.update(it)
}
toolbarView?.build()
PrivateBrowsingButtonView(
button = binding.privateBrowsingButton,
browsingModeManager = browsingModeManager,
onClick = { mode ->
interactor.onPrivateModeButtonClicked(mode, userHasBeenOnboarded = false)
},
)
searchSelectorBinding.set(
feature = SearchSelectorBinding(
context = view.context,
binding = binding,
browserStore = store,
searchSelectorMenu = searchSelectorMenu,
),
owner = viewLifecycleOwner,
view = binding.root,
)
searchSelectorMenuBinding.set(
feature = SearchSelectorMenuBinding(
context = view.context,
interactor = interactor,
searchSelectorMenu = searchSelectorMenu,
browserStore = store,
),
owner = viewLifecycleOwner,
view = view,
)
}
override fun onResume() {
super.onResume()
if (browsingModeManager.mode == BrowsingMode.Private) {
activity?.window?.setBackgroundDrawableResource(R.drawable.private_home_background_gradient)
}
hideToolbar()
}
override fun onPause() {
super.onPause()
if (browsingModeManager.mode == BrowsingMode.Private) {
activity?.window?.setBackgroundDrawable(
ColorDrawable(
ContextCompat.getColor(
requireContext(),
R.color.fx_mobile_private_layer_color_1,
),
),
)
}
}
override fun onDestroyView() {
super.onDestroyView()
onboardingView = null
homeMenuView = null
tabCounterView = null
toolbarView = null
_interactor = null
_binding = null
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
homeMenuView?.dismissMenu()
}
private fun dispatchOnboardingStateChanges(state: OnboardingState) {
if (state != onboardingStore.state.onboardingState) {
onboardingStore.dispatch(OnboardingAction.UpdateState(state))
}
}
}

@ -1,114 +0,0 @@
/* 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.content.Context
import android.text.SpannableString
import android.text.SpannableStringBuilder
import android.util.AttributeSet
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatRadioButton
import androidx.core.content.edit
import androidx.core.content.withStyledAttributes
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.setTextColor
import org.mozilla.fenix.ext.setTextSize
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.view.GroupableRadioButton
import org.mozilla.fenix.utils.view.uncheckAll
class OnboardingRadioButton(
context: Context,
attrs: AttributeSet,
) : AppCompatRadioButton(context, attrs), GroupableRadioButton {
private val radioGroups = mutableListOf<GroupableRadioButton>()
private var illustration: ImageView? = null
private var clickListener: (() -> Unit)? = null
var key: Int = 0
var title: Int = 0
var description: Int = 0
init {
context.withStyledAttributes(
attrs,
R.styleable.OnboardingRadioButton,
0,
0,
) {
key = getResourceId(R.styleable.OnboardingRadioButton_onboardingKey, 0)
title = getResourceId(R.styleable.OnboardingRadioButton_onboardingKeyTitle, 0)
description =
getResourceId(R.styleable.OnboardingRadioButton_onboardingKeyDescription, 0)
}
}
override fun addToRadioGroup(radioButton: GroupableRadioButton) {
radioGroups.add(radioButton)
}
fun addIllustration(illustration: ImageView) {
this.illustration = illustration
}
fun onClickListener(listener: () -> Unit) {
clickListener = listener
}
init {
setOnClickListener {
updateRadioValue(true)
toggleRadioGroups()
clickListener?.invoke()
}
if (title != 0) {
setRadioButtonText(context)
}
}
private fun setRadioButtonText(context: Context) {
val builder = SpannableStringBuilder()
val spannableTitle = SpannableString(resources.getString(title))
spannableTitle.setTextSize(context, TITLE_TEXT_SIZE)
spannableTitle.setTextColor(context, R.attr.textPrimary)
builder.append(spannableTitle)
if (description != 0) {
val spannableDescription = SpannableString(resources.getString(description))
spannableDescription.setTextSize(context, DESCRIPTION_TEXT_SIZE)
spannableDescription.setTextColor(context, R.attr.textSecondary)
builder.append("\n")
builder.append(spannableDescription)
}
this.text = builder
}
override fun updateRadioValue(isChecked: Boolean) {
this.isChecked = isChecked
illustration?.let {
it.isSelected = isChecked
}
CoroutineScope(Dispatchers.IO).launch {
context.settings().preferences.edit {
putBoolean(context.getString(key), isChecked)
}
}
}
private fun toggleRadioGroups() {
if (isChecked) {
radioGroups.uncheckAll()
}
}
companion object {
private const val TITLE_TEXT_SIZE = 16
private const val DESCRIPTION_TEXT_SIZE = 14
}
}

@ -1,20 +0,0 @@
/* 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
/**
* Describes various onboarding states.
*/
sealed class OnboardingState {
/**
* Signed out, without an option to auto-login using a shared FxA account.
*/
object SignedOutNoAutoSignIn : OnboardingState()
/**
* Signed in.
*/
object SignedIn : OnboardingState()
}

@ -1,57 +0,0 @@
/* 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 mozilla.components.lib.state.Action
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
/**
* The [Store] for holding the [OnboardingFragmentState] and applying [OnboardingAction]s.
*/
class OnboardingStore(
initialState: OnboardingFragmentState = OnboardingFragmentState(),
) : Store<OnboardingFragmentState, OnboardingAction>(
initialState = initialState,
reducer = ::onboardingFragmentStateReducer,
)
/**
* The state used for managing [OnboardingFragment].
*
* @property onboardingState Describes the onboarding account state.
*/
data class OnboardingFragmentState(
val onboardingState: OnboardingState = OnboardingState.SignedOutNoAutoSignIn,
) : State
/**
* Actions to dispatch through the [OnboardingStore] to modify the [OnboardingFragmentState]
* through the [onboardingFragmentStateReducer].
*/
sealed class OnboardingAction : Action {
/**
* Updates the onboarding account state.
*
* @param state The onboarding account state to display.
*/
data class UpdateState(val state: OnboardingState) : OnboardingAction()
}
/**
* Reduces the onboarding state from the current state with the provided [action] to be performed.
*
* @param state The current onboarding state.
* @param action The action to be performed on the state.
* @return the new [OnboardingState] with the [action] executed.
*/
private fun onboardingFragmentStateReducer(
state: OnboardingFragmentState,
action: OnboardingAction,
): OnboardingFragmentState {
return when (action) {
is OnboardingAction.UpdateState -> state.copy(onboardingState = action.state)
}
}

@ -1,65 +0,0 @@
/* 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.controller
import androidx.navigation.NavController
import mozilla.components.lib.crash.CrashReporter
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.navigateWithBreadcrumb
import org.mozilla.fenix.onboarding.FenixOnboarding
import org.mozilla.fenix.onboarding.OnboardingFragmentDirections
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
import org.mozilla.fenix.settings.SupportUtils
/**
* An interface that handles the view manipulation of the first run onboarding.
*/
interface OnboardingController {
/**
* @see [OnboardingInteractor.onFinishOnboarding]
*/
fun handleFinishOnboarding(focusOnAddressBar: Boolean)
/**
* @see [OnboardingInteractor.onReadPrivacyNoticeClicked]
*/
fun handleReadPrivacyNoticeClicked()
}
/**
* The default implementation of [OnboardingController].
*/
class DefaultOnboardingController(
private val activity: HomeActivity,
private val navController: NavController,
private val onboarding: FenixOnboarding,
private val crashReporter: CrashReporter,
) : OnboardingController {
override fun handleFinishOnboarding(focusOnAddressBar: Boolean) {
onboarding.finish()
navController.navigateWithBreadcrumb(
directions = OnboardingFragmentDirections.actionHome(focusOnAddressBar = focusOnAddressBar),
navigateFrom = "OnboardingFragment",
navigateTo = "ActionHome",
crashReporter = crashReporter,
)
if (focusOnAddressBar) {
Events.searchBarTapped.record(Events.SearchBarTappedExtra("HOME"))
}
}
override fun handleReadPrivacyNoticeClicked() {
activity.startActivity(
SupportUtils.createCustomTabIntent(
activity,
SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
),
)
}
}

@ -1,85 +0,0 @@
/* 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.interactor
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeMenu
import org.mozilla.fenix.home.privatebrowsing.controller.PrivateBrowsingController
import org.mozilla.fenix.home.privatebrowsing.interactor.PrivateBrowsingInteractor
import org.mozilla.fenix.home.toolbar.ToolbarController
import org.mozilla.fenix.home.toolbar.ToolbarInteractor
import org.mozilla.fenix.onboarding.controller.OnboardingController
import org.mozilla.fenix.search.toolbar.SearchSelectorController
import org.mozilla.fenix.search.toolbar.SearchSelectorInteractor
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
/**
* Interface for onboarding related actions.
*/
interface OnboardingInteractor {
/**
* Finishes the onboarding and navigates to the [HomeFragment]. Called when a user clicks on the
* "Start Browsing" button or a menu item in the [HomeMenu].
*
* @param focusOnAddressBar Whether or not to focus the address bar when navigating to the
* [HomeFragment].
*/
fun onFinishOnboarding(focusOnAddressBar: Boolean)
/**
* Opens a custom tab to privacy notice url. Called when a user clicks on the "read our privacy notice" button.
*/
fun onReadPrivacyNoticeClicked()
}
/**
* The default implementation of [OnboardingInteractor].
*
* @param controller An instance of [OnboardingController] which will be delegated for all user
* interactions.
*/
class DefaultOnboardingInteractor(
private val controller: OnboardingController,
private val privateBrowsingController: PrivateBrowsingController,
private val searchSelectorController: SearchSelectorController,
private val toolbarController: ToolbarController,
) : OnboardingInteractor,
PrivateBrowsingInteractor,
SearchSelectorInteractor,
ToolbarInteractor {
override fun onFinishOnboarding(focusOnAddressBar: Boolean) {
controller.handleFinishOnboarding(focusOnAddressBar)
}
override fun onReadPrivacyNoticeClicked() {
controller.handleReadPrivacyNoticeClicked()
}
override fun onMenuItemTapped(item: SearchSelectorMenu.Item) {
searchSelectorController.handleMenuItemTapped(item)
}
override fun onLearnMoreClicked() {
privateBrowsingController.handleLearnMoreClicked()
}
override fun onPrivateModeButtonClicked(newMode: BrowsingMode, userHasBeenOnboarded: Boolean) {
privateBrowsingController.handlePrivateModeButtonClicked(newMode, userHasBeenOnboarded)
}
override fun onNavigateSearch() {
toolbarController.handleNavigateSearch()
}
override fun onPaste(clipboardText: String) {
toolbarController.handlePaste(clipboardText)
}
override fun onPasteAndGo(clipboardText: String) {
toolbarController.handlePasteAndGo(clipboardText)
}
}

@ -1,159 +0,0 @@
/* 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.view
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.home.BottomSpacerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingManualSignInViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingPrivacyNoticeViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingToolbarPositionPickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
/**
* Adapter for a list of onboarding views to be displayed.
*/
class OnboardingAdapter(
private val interactor: OnboardingInteractor,
) : ListAdapter<OnboardingAdapterItem, RecyclerView.ViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) {
OnboardingHeaderViewHolder.LAYOUT_ID -> OnboardingHeaderViewHolder(view)
OnboardingSectionHeaderViewHolder.LAYOUT_ID -> OnboardingSectionHeaderViewHolder(view)
OnboardingManualSignInViewHolder.LAYOUT_ID -> OnboardingManualSignInViewHolder(view)
OnboardingThemePickerViewHolder.LAYOUT_ID -> OnboardingThemePickerViewHolder(view)
OnboardingTrackingProtectionViewHolder.LAYOUT_ID -> OnboardingTrackingProtectionViewHolder(
view,
)
OnboardingPrivacyNoticeViewHolder.LAYOUT_ID -> OnboardingPrivacyNoticeViewHolder(
view,
interactor,
)
OnboardingFinishViewHolder.LAYOUT_ID -> OnboardingFinishViewHolder(view, interactor)
OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID -> OnboardingToolbarPositionPickerViewHolder(
view,
)
BottomSpacerViewHolder.LAYOUT_ID -> BottomSpacerViewHolder(view)
else -> throw IllegalStateException("ViewType $viewType does not match a ViewHolder")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (holder) {
is OnboardingSectionHeaderViewHolder -> holder.bind(
(item as OnboardingAdapterItem.OnboardingSectionHeader).labelBuilder,
)
}
}
override fun getItemViewType(position: Int) = getItem(position).viewType
internal object DiffCallback : DiffUtil.ItemCallback<OnboardingAdapterItem>() {
override fun areItemsTheSame(oldItem: OnboardingAdapterItem, newItem: OnboardingAdapterItem) =
oldItem.sameAs(newItem)
@Suppress("DiffUtilEquals")
override fun areContentsTheSame(
oldItem: OnboardingAdapterItem,
newItem: OnboardingAdapterItem,
) = oldItem.contentsSameAs(newItem)
override fun getChangePayload(
oldItem: OnboardingAdapterItem,
newItem: OnboardingAdapterItem,
): Any? {
return oldItem.getChangePayload(newItem) ?: return super.getChangePayload(oldItem, newItem)
}
}
}
/**
* Enum of the various onboarding views.
*/
sealed class OnboardingAdapterItem(@LayoutRes val viewType: Int) {
/**
* Onboarding top header.
*/
object OnboardingHeader : OnboardingAdapterItem(OnboardingHeaderViewHolder.LAYOUT_ID)
/**
* Onboarding section header.
*/
data class OnboardingSectionHeader(
val labelBuilder: (Context) -> String,
) : OnboardingAdapterItem(OnboardingSectionHeaderViewHolder.LAYOUT_ID) {
override fun sameAs(other: OnboardingAdapterItem) =
other is OnboardingSectionHeader && labelBuilder == other.labelBuilder
}
/**
* Onboarding sign into sync card.
*/
object OnboardingManualSignIn :
OnboardingAdapterItem(OnboardingManualSignInViewHolder.LAYOUT_ID)
/**
* Onboarding theme picker card.
*/
object OnboardingThemePicker : OnboardingAdapterItem(OnboardingThemePickerViewHolder.LAYOUT_ID)
/**
* Onboarding tracking protection card.
*/
object OnboardingTrackingProtection :
OnboardingAdapterItem(OnboardingTrackingProtectionViewHolder.LAYOUT_ID)
/**
* Onboarding privacy card.
*/
object OnboardingPrivacyNotice :
OnboardingAdapterItem(OnboardingPrivacyNoticeViewHolder.LAYOUT_ID)
/**
* Onboarding start browsing button.
*/
object OnboardingFinish : OnboardingAdapterItem(OnboardingFinishViewHolder.LAYOUT_ID)
/**
* Onboarding toolbar placement picker.
*/
object OnboardingToolbarPositionPicker :
OnboardingAdapterItem(OnboardingToolbarPositionPickerViewHolder.LAYOUT_ID)
/**
* Spacer.
*/
object BottomSpacer : OnboardingAdapterItem(BottomSpacerViewHolder.LAYOUT_ID)
/**
* Returns true if this item represents the same value as other.
*/
open fun sameAs(other: OnboardingAdapterItem) = this::class == other::class
/**
* Returns a payload if there's been a change or null if not.
*/
open fun getChangePayload(newItem: OnboardingAdapterItem): Any? = null
/**
* Returns true if this item represents the same value as the other.
*/
open fun contentsSameAs(other: OnboardingAdapterItem) = this::class == other::class
}

@ -1,74 +0,0 @@
/* 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.view
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.nimbus.OnboardingPanel
import org.mozilla.fenix.onboarding.OnboardingState
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
import org.mozilla.fenix.nimbus.Onboarding as OnboardingConfig
/**
* Shows a list of onboarding cards.
*/
class OnboardingView(
containerView: RecyclerView,
interactor: OnboardingInteractor,
) {
private val onboardingAdapter = OnboardingAdapter(interactor)
init {
containerView.apply {
adapter = onboardingAdapter
layoutManager = LinearLayoutManager(containerView.context)
}
}
/**
* Updates the display of the onboarding cards based on the given [OnboardingState].
*
* @param onboardingState The new user onboarding page state.
* @param onboardingConfig The new user onboarding page configuration.
*/
fun update(
onboardingState: OnboardingState,
onboardingConfig: OnboardingConfig,
) {
onboardingAdapter.submitList(onboardingAdapterItems(onboardingState, onboardingConfig))
}
private fun onboardingAdapterItems(
onboardingState: OnboardingState,
onboardingConfig: OnboardingConfig,
): List<OnboardingAdapterItem> {
val items: MutableList<OnboardingAdapterItem> =
mutableListOf(OnboardingAdapterItem.OnboardingHeader)
onboardingConfig.order.forEach {
when (it) {
OnboardingPanel.THEMES -> items.add(OnboardingAdapterItem.OnboardingThemePicker)
OnboardingPanel.TOOLBAR_PLACEMENT -> items.add(OnboardingAdapterItem.OnboardingToolbarPositionPicker)
// Customize FxA items based on where we are with the account state:
OnboardingPanel.SYNC -> if (onboardingState == OnboardingState.SignedOutNoAutoSignIn) {
items.add(OnboardingAdapterItem.OnboardingManualSignIn)
}
OnboardingPanel.TCP -> items.add(OnboardingAdapterItem.OnboardingTrackingProtection)
OnboardingPanel.PRIVACY_NOTICE -> items.add(OnboardingAdapterItem.OnboardingPrivacyNotice)
}
}
items.addAll(
listOf(
OnboardingAdapterItem.OnboardingFinish,
OnboardingAdapterItem.BottomSpacer,
),
)
return items
}
}

@ -233,8 +233,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
)
val fromHomeFragment =
getPreviousDestination()?.destination?.id == R.id.homeFragment ||
getPreviousDestination()?.destination?.id == R.id.onboardingFragment
getPreviousDestination()?.destination?.id == R.id.homeFragment
toolbarView = ToolbarView(
requireContext(),
@ -364,12 +363,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
false
}
}
R.id.onboardingFragment -> {
binding.searchWrapper.setOnTouchListener { _, _ ->
dismissAllowingStateLoss()
true
}
}
R.id.historyFragment, R.id.bookmarkFragment -> {
binding.searchWrapper.setOnTouchListener { _, _ ->
dismissAllowingStateLoss()

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/onboardingSelected" android:state_selected="true" />
<item android:color="?attr/onboardingDeselected" />
</selector>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="?attr/textDisabled" />
<item android:color="?attr/textPrimary"/>
</selector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,74 +0,0 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="136dp"
android:height="80dp"
android:viewportWidth="136"
android:viewportHeight="80">
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M8,4L128,4A4,4 0,0 1,132 8L132,72A4,4 0,0 1,128 76L8,76A4,4 0,0 1,4 72L4,8A4,4 0,0 1,8 4z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="16.32"
android:startX="-61.12"
android:endY="53.76"
android:endX="143.68"
android:type="linear">
<item android:offset="0" android:color="#FFFF9100"/>
<item android:offset="0.41" android:color="#FFF10366"/>
<item android:offset="1" android:color="#FF6173FF"/>
</gradient>
</aapt:attr>
</path>
</group>
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M14,68h108a2,2 0,0 0,2 -2V4H12v62a2,2 0,0 0,2 2z"
android:fillColor="#f9f9fb"
android:fillType="evenOdd"/>
</group>
<path
android:pathData="M22,24h92a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V25.88A1.93,1.93 0,0 1,22 24z"
android:fillColor="#fff"
android:fillType="evenOdd"/>
<path
android:pathData="M114,24a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V25.88A1.93,1.93 0,0 1,22 24h92m0,-1H22a2.94,2.94 0,0 0,-3 2.88v12.24A2.94,2.94 0,0 0,22 41h92a2.94,2.94 0,0 0,3 -2.88V25.88a2.94,2.94 0,0 0,-3 -2.88z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M111.67,36.14l-3,-3a3.78,3.78 0,1 0,-0.71 0.72l3,3a0.51,0.51 0,0 0,0.71 -0.72zM105.67,33.6a2.78,2.78 0,1 1,2.78 -2.78,2.79 2.79,0 0,1 -2.82,2.78z"
android:fillColor="#20123a"
android:fillType="evenOdd"/>
<path
android:pathData="M114.35,5A13,13 0,0 0,127 21a12.88,12.88 0,0 0,4 -0.63V72a3,3 0,0 1,-3 3H8a3,3 0,0 1,-3 -3V8a3,3 0,0 1,3 -3z"
android:strokeAlpha=".08"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#20123a"/>
<path
android:pathData="M124,49H12v-1h112z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M96,63v-9l7,4.5zM68.5,55a3.5,3.5 0,1 1,-3.5 3.5,3.5 3.5,0 0,1 3.5,-3.5zM42,55v7h-7v-7z"
android:strokeAlpha="0.36"
android:fillColor="#15141a"
android:fillType="evenOdd"
android:fillAlpha="0.36"/>
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M8,2L128,2A6,6 0,0 1,134 8L134,72A6,6 0,0 1,128 78L8,78A6,6 0,0 1,2 72L2,8A6,6 0,0 1,8 2z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/onboarding_illustration_color"/>
</group>
</vector>

@ -1,82 +0,0 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="136dp"
android:height="80dp"
android:viewportWidth="136"
android:viewportHeight="80">
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M8,4L128,4A4,4 0,0 1,132 8L132,72A4,4 0,0 1,128 76L8,76A4,4 0,0 1,4 72L4,8A4,4 0,0 1,8 4z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="16.32"
android:startX="-60.4"
android:endY="53.76"
android:endX="143.12"
android:type="linear">
<item android:offset="0" android:color="#FFFF9100"/>
<item android:offset="0.41" android:color="#FFF10366"/>
<item android:offset="1" android:color="#FF6173FF"/>
</gradient>
</aapt:attr>
</path>
</group>
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M122,12H14a2,2 0,0 0,-2 2v62h112V14a2,2 0,0 0,-2 -2z"
android:fillColor="#f9f9fb"
android:fillType="evenOdd"/>
</group>
<path
android:pathData="M124,52H12v-1h112z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M114,26H22a1.93,1.93 0,0 0,-2 1.88v12.24A1.93,1.93 0,0 0,22 42h92a1.93,1.93 0,0 0,2 -1.88V27.88a1.93,1.93 0,0 0,-2 -1.88z"
android:fillColor="#fff"
android:fillType="evenOdd"/>
<path
android:pathData="M22,26a1.93,1.93 0,0 0,-2 1.88v12.24A1.93,1.93 0,0 0,22 42h92a1.93,1.93 0,0 0,2 -1.88V27.88a1.93,1.93 0,0 0,-2 -1.88H22m0,-1h92a2.94,2.94 0,0 1,3 2.88v12.24a2.94,2.94 0,0 1,-3 2.88H22a2.94,2.94 0,0 1,-3 -2.88V27.88A2.94,2.94 0,0 1,22 25z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M112.86,38.14l-3,-3a3.81,3.81 0,1 0,-0.72 0.72l3,3a0.51,0.51 0,0 0,0.72 -0.72zM106.86,35.6a2.78,2.78 0,1 1,2.78 -2.78,2.78 2.78,0 0,1 -2.82,2.78z"
android:fillColor="#20123a"
android:fillType="evenOdd"/>
<path
android:pathData="M25,15a2,2 0,1 0,2 2,2 2,0 0,0 -2,-2zM16,15l2.5,4 2.5,-4zM30,15v4h4v-4z"
android:strokeAlpha="0.36"
android:fillColor="#15141a"
android:fillType="evenOdd"
android:fillAlpha="0.36"/>
<path
android:pathData="M114.35,5A13,13 0,0 0,127 21a12.88,12.88 0,0 0,4 -0.63V72a3,3 0,0 1,-3 3H8a3,3 0,0 1,-3 -3V8a3,3 0,0 1,3 -3z"
android:strokeAlpha=".08"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#20123a"/>
<path
android:pathData="M116.5,61L25.5,61A1.5,1.5 0,0 1,24 59.5L24,59.5A1.5,1.5 0,0 1,25.5 58L116.5,58A1.5,1.5 0,0 1,118 59.5L118,59.5A1.5,1.5 0,0 1,116.5 61z"
android:fillColor="#15141a"
android:fillAlpha=".16"/>
<path
android:pathData="M116.5,69L42.5,69A1.5,1.5 0,0 1,41 67.5L41,67.5A1.5,1.5 0,0 1,42.5 66L116.5,66A1.5,1.5 0,0 1,118 67.5L118,67.5A1.5,1.5 0,0 1,116.5 69z"
android:fillColor="#15141a"
android:fillAlpha=".16"/>
<group>
<clip-path
android:pathData="M127,20a12,12 0,0 1,0 -24H8A12,12 0,0 0,-4 8v64A12,12 0,0 0,8 84h119a12,12 0,0 0,12 -12V8a12,12 0,0 1,-12 12z"/>
<path
android:pathData="M8,2L128,2A6,6 0,0 1,134 8L134,72A6,6 0,0 1,128 78L8,78A6,6 0,0 1,2 72L2,8A6,6 0,0 1,8 2z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/onboarding_illustration_color"/>
</group>
</vector>

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?iconActive"
android:pathData="M12 6.5a3 3 0 1 0 0 6 3 3 0 0 0 0-6z" />
<path
android:fillColor="?iconActive"
android:pathData="M12 21.5c-5.238 0-9.5-4.262-9.5-9.5S6.762 2.5 12 2.5s9.5 4.262 9.5 9.5-4.262 9.5-9.5 9.5zM12 4c-4.411 0-8 3.589-8 8s3.589 8 8 8 8-3.589 8-8-3.589-8-8-8z" />
<path
android:fillColor="?iconActive"
android:pathData="M8.314 14c-0.652 0-1.232 0.269-1.672 0.684A6.014 6.014 0 0 0 12 18a6.355 6.355 0 0 0 1.084-0.104c0.083-0.014 0.165-0.033 0.247-0.051a6.014 6.014 0 0 0 4.027-3.162A2.43 2.43 0 0 0 15.686 14H8.314z" />
</vector>

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12.0002,22C11.5342,22 11.0662,21.885 10.6462,21.668C8.4962,20.547 6.5242,18.735 5.0942,16.566C4.3852,15.493 3.8882,14.198 3.6162,12.716L2.9092,8.872C2.6692,7.563 3.2942,6.259 4.4632,5.625L10.5652,2.363C11.4632,1.88 12.5362,1.88 13.4322,2.359L19.5342,5.682C20.7082,6.313 21.3352,7.621 21.0912,8.936L20.3862,12.728C20.1152,14.201 19.6172,15.49 18.9112,16.56C17.4822,18.729 15.5102,20.542 13.3562,21.666C12.9372,21.885 12.4682,22 12.0002,22ZM11.5152,3.556L4.8452,7.13L4.3382,8.368L5.0922,12.445C5.3272,13.727 5.7502,14.836 6.3472,15.74C7.6392,17.7 9.4122,19.332 11.3392,20.337H12.6632C14.5932,19.329 16.3672,17.694 17.6582,15.735C18.2532,14.833 18.6752,13.729 18.9102,12.455L19.6632,8.368L19.0532,7.129L12.4942,3.557H11.5152V3.556Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="3"
android:endY="22"
android:startX="21"
android:startY="2"
android:type="linear">
<item
android:color="#FFAB71FF"
android:offset="0" />
<item
android:color="#FF0250BB"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</vector>

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="?neutralFaded" />
<gradient
android:angle="45"
android:endColor="?onboardingDarkGradientEndBackground"
android:startColor="?onboardingDarkGradientStartBackground" />
<corners android:radius="8dp" />
</shape>

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke android:width="1dp" android:color="?neutralFaded" />
<solid android:color="?attr/layer2" />
<corners android:radius="8dp" />
</shape>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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/. -->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#1F000000">
<item
android:bottom="6dp"
android:top="6dp">
<shape android:shape="rectangle">
<corners android:radius="4dp" />
</shape>
</item>
</ripple>

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:attr/colorControlHighlight">
<item android:id="@android:id/mask">
<shape>
<solid android:color="#000000" />
<corners
android:bottomLeftRadius="@dimen/tab_corner_radius"
android:bottomRightRadius="@dimen/tab_corner_radius" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<stroke
android:width="1dp"
android:color="?neutralFaded" />
<solid android:color="?attr/layer2" />
<corners
android:bottomLeftRadius="@dimen/tab_corner_radius"
android:bottomRightRadius="@dimen/tab_corner_radius" />
</shape>
</item>
</ripple>

@ -1,70 +0,0 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="136dp"
android:height="80dp"
android:viewportWidth="136"
android:viewportHeight="80">
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M8,4L128,4A4,4 0,0 1,132 8L132,72A4,4 0,0 1,128 76L8,76A4,4 0,0 1,4 72L4,8A4,4 0,0 1,8 4z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="16.64"
android:startX="197.12"
android:endY="54.08"
android:endX="-7.68"
android:type="linear">
<item android:offset="0" android:color="#FFFF9100"/>
<item android:offset="0.41" android:color="#FFF10366"/>
<item android:offset="1" android:color="#FF6173FF"/>
</gradient>
</aapt:attr>
</path>
</group>
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M122,68H14a2,2 0,0 1,-2 -2V4h112v62a2,2 0,0 1,-2 2z"
android:fillColor="#f9f9fb"/>
</group>
<path
android:pathData="M22,24h92a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V25.88A1.93,1.93 0,0 1,22 24z"
android:fillColor="#fff"/>
<path
android:pathData="M114,24a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V25.88A1.93,1.93 0,0 1,22 24h92m0,-1H22a2.94,2.94 0,0 0,-3 2.88v12.24A2.94,2.94 0,0 0,22 41h92a2.94,2.94 0,0 0,3 -2.88V25.88a2.94,2.94 0,0 0,-3 -2.88z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M32.86,36.14l-3,-3a3.81,3.81 0,1 0,-0.72 0.72l3,3a0.51,0.51 0,0 0,0.72 -0.72zM26.86,33.6a2.78,2.78 0,1 1,2.78 -2.78,2.78 2.78,0 0,1 -2.82,2.78z"
android:fillColor="#20123a"/>
<path
android:pathData="M21.65,5a13,13 0,0 1,-3.46 12.19A13,13 0,0 1,5 20.37V72a3,3 0,0 0,3 3h120a3,3 0,0 0,3 -3V8a3,3 0,0 0,-3 -3z"
android:strokeAlpha=".16"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#20123a"/>
<path
android:pathData="M12,48h112v1H12z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M40,54v9l-7,-4.5zM67.5,55a3.5,3.5 0,1 1,-3.5 3.5,3.5 3.5,0 0,1 3.5,-3.5zM101,55v7h-7v-7z"
android:strokeAlpha="0.36"
android:fillColor="#15141a"
android:fillAlpha="0.36"/>
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M8,2L128,2A6,6 0,0 1,134 8L134,72A6,6 0,0 1,128 78L8,78A6,6 0,0 1,2 72L2,8A6,6 0,0 1,8 2z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/onboarding_illustration_color"/>
</group>
</vector>

@ -1,78 +0,0 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="136dp"
android:height="80dp"
android:viewportWidth="136"
android:viewportHeight="80">
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M8,4L128,4A4,4 0,0 1,132 8L132,72A4,4 0,0 1,128 76L8,76A4,4 0,0 1,4 72L4,8A4,4 0,0 1,8 4z">
<aapt:attr name="android:fillColor">
<gradient
android:startY="16.2"
android:startX="196.4"
android:endY="53.64"
android:endX="-7.12"
android:type="linear">
<item android:offset="0" android:color="#FFFF9100"/>
<item android:offset="0.41" android:color="#FFF10366"/>
<item android:offset="1" android:color="#FF6173FF"/>
</gradient>
</aapt:attr>
</path>
</group>
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M14,12h108a2,2 0,0 1,2 2v62H12V14a2,2 0,0 1,2 -2z"
android:fillColor="#f9f9fb"/>
</group>
<path
android:pathData="M12,51h112v1H12z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M22,26h92a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V27.88A1.93,1.93 0,0 1,22 26z"
android:fillColor="#fff"/>
<path
android:pathData="M114,26a1.93,1.93 0,0 1,2 1.88v12.24a1.93,1.93 0,0 1,-2 1.88H22a1.93,1.93 0,0 1,-2 -1.88V27.88A1.93,1.93 0,0 1,22 26h92m0,-1H22a2.94,2.94 0,0 0,-3 2.88v12.24A2.94,2.94 0,0 0,22 43h92a2.94,2.94 0,0 0,3 -2.88V27.88a2.94,2.94 0,0 0,-3 -2.88z"
android:fillColor="#15141a"
android:fillAlpha=".2"/>
<path
android:pathData="M32.86,38.14l-3,-3a3.81,3.81 0,1 0,-0.72 0.72l3,3a0.51,0.51 0,0 0,0.72 -0.72zM26.86,35.6a2.78,2.78 0,1 1,2.78 -2.78,2.78 2.78,0 0,1 -2.82,2.78z"
android:fillColor="#20123a"/>
<path
android:pathData="M111,15a2,2 0,1 1,-2 2,2 2,0 0,1 2,-2zM120,15l-2.5,4 -2.5,-4zM106,15v4h-4v-4z"
android:strokeAlpha="0.36"
android:fillColor="#15141a"
android:fillAlpha="0.36"/>
<path
android:pathData="M21.65,5a13,13 0,0 1,-3.46 12.19A13,13 0,0 1,5 20.37V72a3,3 0,0 0,3 3h120a3,3 0,0 0,3 -3V8a3,3 0,0 0,-3 -3z"
android:strokeAlpha=".16"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#20123a"/>
<path
android:pathData="M19.5,58L110.5,58A1.5,1.5 0,0 1,112 59.5L112,59.5A1.5,1.5 0,0 1,110.5 61L19.5,61A1.5,1.5 0,0 1,18 59.5L18,59.5A1.5,1.5 0,0 1,19.5 58z"
android:fillColor="#15141a"
android:fillAlpha=".16"/>
<path
android:pathData="M19.5,66L93.5,66A1.5,1.5 0,0 1,95 67.5L95,67.5A1.5,1.5 0,0 1,93.5 69L19.5,69A1.5,1.5 0,0 1,18 67.5L18,67.5A1.5,1.5 0,0 1,19.5 66z"
android:fillColor="#15141a"
android:fillAlpha=".16"/>
<group>
<clip-path
android:pathData="M9,20A12,12 0,0 0,9 -4h119a12,12 0,0 1,12 12v64a12,12 0,0 1,-12 12H9A12,12 0,0 1,-3 72V8A12,12 0,0 0,9 20z"/>
<path
android:pathData="M8,2L128,2A6,6 0,0 1,134 8L134,72A6,6 0,0 1,128 78L8,78A6,6 0,0 1,2 72L2,8A6,6 0,0 1,8 2z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="@color/onboarding_illustration_color"/>
</group>
</vector>

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/conclusion"
android:layout_marginTop="32dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin">
<TextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Header20TextStyle"
android:text="@string/onboarding_conclusion_header"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/finish_button"
style="@style/PositiveButton"
android:layout_marginTop="16dp"
android:layout_marginBottom="10dp"
android:background="@drawable/onboarding_padded_background"
android:backgroundTint="?actionPrimary"
android:text="@string/onboarding_finish"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/conclusion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin">
<TextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Header20TextStyle"
android:text="@string/onboarding_header_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/subheader_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Body14TextStyle"
android:layout_marginTop="4dp"
android:text="@string/onboarding_message"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/avatar_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="12dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_onboarding_avatar_anonymous_large"
tools:ignore="RtlSymmetry" />
<TextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/HeaderTextStyle"
app:layout_constraintTop_toTopOf="@id/avatar_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/avatar_icon"
app:layout_constraintBottom_toBottomOf="@id/avatar_icon"
android:layout_marginStart="52dp"
android:text="@string/onboarding_account_sign_in_header" />
<TextView
android:id="@+id/description_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:text="@string/onboarding_manual_sign_in_description"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintStart_toStartOf="@id/avatar_icon"
app:layout_constraintTop_toBottomOf="@id/header_text" />
<Button
android:id="@+id/fxa_sign_in_button"
style="@style/NeutralOnboardingButton"
android:layout_marginTop="10dp"
android:text="@string/onboarding_firefox_account_sign_in"
app:layout_constraintTop_toBottomOf="@id/description_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin">
<TextView
android:id="@+id/header_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/onboarding_privacy_notice_header_1"
android:drawablePadding="12dp"
android:textAppearance="@style/HeaderTextStyle"
android:gravity="center_vertical"
android:lines="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/description_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Body14TextStyle"
android:layout_marginTop="14dp"
android:text="@string/onboarding_privacy_notice_description"
app:layout_constraintTop_toBottomOf="@id/header_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
style="@style/NeutralOnboardingButton"
android:id="@+id/read_button"
android:text="@string/onboarding_privacy_notice_read_button"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="@id/description_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/section_header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/HeaderTextStyle"
tools:text="@tools:sample/lorem"
android:layout_marginBottom="16dp"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin"/>

@ -1,159 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/OnboardingCardLight"
android:paddingTop="16dp"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin">
<TextView
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="14dp"
android:text="@string/onboarding_theme_picker_header"
android:textAppearance="@style/HeaderTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/description_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/onboarding_theme_picker_description_2"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text" />
<TextView
android:id="@+id/light_theme_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/onboarding_theme_light_title"
android:textColor="?attr/textPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@+id/theme_light_image"
app:layout_constraintStart_toStartOf="@id/theme_light_image"
app:layout_constraintTop_toBottomOf="@id/theme_light_image" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/theme_light_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/onboarding_theme_light_title"
android:elevation="1dp"
android:theme="@style/Checkable.Colored"
android:translationX="@dimen/onboarding_dual_pane_radio_button_translation_x"
android:translationY="@dimen/onboarding_dual_pane_radio_button_translation_y"
app:layout_constraintStart_toStartOf="@+id/theme_light_image"
app:layout_constraintTop_toTopOf="@+id/theme_light_image"
app:onboardingKey="@string/pref_key_light_theme" />
<ImageView
android:id="@+id/theme_light_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@string/onboarding_theme_light_title"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintEnd_toStartOf="@+id/theme_dark_image"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:srcCompat="@drawable/onboarding_light_theme" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/theme_dark_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/onboarding_theme_dark_title"
android:elevation="1dp"
android:theme="@style/Checkable.Colored"
android:translationX="@dimen/onboarding_dual_pane_radio_button_translation_x"
android:translationY="@dimen/onboarding_dual_pane_radio_button_translation_y"
app:layout_constraintStart_toStartOf="@+id/theme_dark_image"
app:layout_constraintTop_toTopOf="@+id/theme_dark_image"
app:onboardingKey="@string/pref_key_dark_theme" />
<ImageView
android:id="@+id/theme_dark_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/onboarding_theme_dark_title"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/theme_light_image"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:srcCompat="@drawable/onboarding_dark_theme" />
<TextView
android:id="@+id/dark_theme_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/onboarding_theme_dark_title"
android:textColor="?attr/textPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@+id/theme_dark_image"
app:layout_constraintStart_toStartOf="@id/theme_dark_image"
app:layout_constraintTop_toBottomOf="@id/theme_dark_image" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="1dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="1dp"
android:layout_marginBottom="16dp"
android:background="?attr/borderPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/dark_theme_title" />
<View
android:id="@+id/clickable_region_automatic"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@drawable/onboarding_rounded_bottom_corners_ripple"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="@+id/divider" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/theme_automatic_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:contentDescription="@string/onboarding_theme_automatic_title"
android:foreground="@drawable/rounded_ripple"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:theme="@style/Checkable.Colored"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider"
app:onboardingKey="@string/pref_key_follow_device_theme"
app:onboardingKeyDescription="@string/onboarding_theme_automatic_summary"
app:onboardingKeyTitle="@string/onboarding_theme_automatic_title"
tools:text="Automatic" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/onboarding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/OnboardingCardLight"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="14dp"
android:text="@string/onboarding_toolbar_placement_header_1"
android:textAppearance="@style/HeaderTextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/description_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/onboarding_toolbar_placement_description"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/toolbar_bottom_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/preference_bottom_toolbar"
android:elevation="1dp"
android:theme="@style/Checkable.Colored"
android:translationX="@dimen/onboarding_dual_pane_radio_button_translation_x"
android:translationY="@dimen/onboarding_dual_pane_radio_button_translation_y"
app:layout_constraintStart_toStartOf="@+id/toolbar_bottom_image"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:layout_constraintTop_toTopOf="@+id/toolbar_bottom_image"
app:onboardingKey="@string/pref_key_toolbar_bottom" />
<ImageView
android:id="@+id/toolbar_bottom_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/preference_bottom_toolbar"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toStartOf="@+id/toolbar_top_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:srcCompat="@drawable/onboarding_toolbar_bottom" />
<TextView
android:id="@+id/toolbar_bottom_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/preference_bottom_toolbar"
android:textColor="?attr/textPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@+id/toolbar_bottom_image"
app:layout_constraintStart_toStartOf="@id/toolbar_bottom_image"
app:layout_constraintTop_toBottomOf="@id/toolbar_bottom_image"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/toolbar_top_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/preference_top_toolbar"
android:textColor="?attr/textPrimary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@+id/toolbar_top_image"
app:layout_constraintStart_toStartOf="@id/toolbar_top_image"
app:layout_constraintTop_toBottomOf="@id/toolbar_top_image"
app:layout_constraintBottom_toBottomOf="parent"/>
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/toolbar_top_radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/preference_top_toolbar"
android:elevation="1dp"
android:theme="@style/Checkable.Colored"
android:translationX="@dimen/onboarding_dual_pane_radio_button_translation_x"
android:translationY="@dimen/onboarding_dual_pane_radio_button_translation_y"
app:layout_constraintStart_toStartOf="@+id/toolbar_top_image"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:layout_constraintTop_toTopOf="@+id/toolbar_top_image"
app:onboardingKey="@string/pref_key_toolbar_top" />
<ImageView
android:id="@+id/toolbar_top_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:contentDescription="@string/preference_top_toolbar"
android:foreground="@drawable/rounded_ripple"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/toolbar_bottom_image"
app:layout_constraintTop_toBottomOf="@+id/description_text"
app:srcCompat="@drawable/onboarding_toolbar_top" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,86 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- 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.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card"
style="@style/OnboardingCardLightWithPadding"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/home_item_horizontal_margin"
android:clipChildren="false"
android:clipToPadding="false">
<TextView
android:id="@+id/header_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawablePadding="12dp"
android:gravity="center_vertical"
android:lines="1"
android:text="@string/onboarding_tracking_protection_header"
android:textAppearance="@style/HeaderTextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:drawableStart="@drawable/ic_onboarding_tracking_protection" />
<TextView
android:id="@+id/description_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:textAppearance="@style/Body14TextStyle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/header_text"
android:text="@string/onboarding_tracking_protection_description" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/tracking_protection_standard_option"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/accessibility_min_height"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:checked="true"
android:foreground="@drawable/rounded_ripple"
android:gravity="top"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:theme="@style/Checkable.Colored"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/description_text"
app:onboardingKey="@string/pref_key_tracking_protection_standard_option"
app:onboardingKeyDescription="@string/onboarding_tracking_protection_standard_button_description_3"
app:onboardingKeyTitle="@string/onboarding_tracking_protection_standard_button_2"
tools:text="Standard" />
<org.mozilla.fenix.onboarding.OnboardingRadioButton
android:id="@+id/tracking_protection_strict_default"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/accessibility_min_height"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:checked="false"
android:foreground="@drawable/rounded_ripple"
android:gravity="top"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:textColor="@color/primary_state_list_text_color"
android:theme="@style/Checkable.Colored"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tracking_protection_standard_option"
app:onboardingKey="@string/pref_key_tracking_protection_strict_default"
app:onboardingKeyDescription="@string/onboarding_tracking_protection_strict_button_description_3"
app:onboardingKeyTitle="@string/onboarding_tracking_protection_strict_option"
tools:text="Strict" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -15,12 +15,6 @@
app:popUpTo="@id/startupFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_startup_onboarding"
app:destination="@id/onboardingFragment"
app:popUpTo="@id/startupFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_global_home"
app:destination="@id/homeFragment"
@ -223,17 +217,6 @@
app:argType="boolean" />
</fragment>
<fragment
android:id="@+id/onboardingFragment"
android:name="org.mozilla.fenix.onboarding.OnboardingFragment"
tools:layout="@layout/fragment_home">
<action
android:id="@+id/action_home"
app:destination="@id/homeFragment"
app:popUpTo="@id/onboardingFragment"
app:popUpToInclusive="true" />
</fragment>
<dialog
android:id="@+id/homeOnboardingDialogFragment"
android:name="org.mozilla.fenix.onboarding.HomeOnboardingDialogFragment" />

@ -3,6 +3,4 @@
- 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/. -->
<resources>
<!-- Onboarding -->
<dimen name="onboarding_dual_pane_radio_button_translation_x">7dp</dimen>
</resources>

@ -138,8 +138,6 @@
<color name="fill_link_from_clipboard_normal_theme">@color/photonViolet40</color>
<color name="sync_disconnected_icon_fill_normal_theme">#FFF36E</color>
<color name="sync_disconnected_background_normal_theme">#5B5846</color>
<color name="onboarding_illustration_selected_normal_theme">@color/accent_normal_theme</color>
<color name="onboarding_illustration_deselected_normal_theme">#99FBFBFE</color>
<color name="add_on_private_browsing_exterior_circle_background_normal_theme">@color/accent_normal_theme</color>
<color name="prompt_login_edit_text_cursor_color_normal_theme">@color/photonViolet50</color>
<color name="search_suggestion_indicator_icon_color_normal_theme">@color/photonGreen60</color>

@ -77,10 +77,6 @@
<attr name="fillLinkFromClipboard" format="reference"/>
<attr name="syncDisconnected" format="reference" />
<attr name="syncDisconnectedBackground" format="reference" />
<attr name="onboardingDarkGradientStartBackground" format="reference" />
<attr name="onboardingDarkGradientEndBackground" format="reference" />
<attr name="onboardingSelected" format="reference"/>
<attr name="onboardingDeselected" format="reference"/>
<attr name="addOnPrivateBrowsingExteriorCircleBackground" format="reference"/>
<attr name="awesomeBarIndicatorColor" format="reference|color"/>
<attr name="awesomeBarIndicatorBookmarkColor" format="reference|color"/>
@ -105,12 +101,6 @@
<attr name="deleteBrowsingDataItemIcon" format="reference" />
</declare-styleable>
<declare-styleable name="OnboardingRadioButton">
<attr name="onboardingKey" format="reference" />
<attr name="onboardingKeyTitle" format="reference" />
<attr name="onboardingKeyDescription" format="reference" />
</declare-styleable>
<attr name="selectPromptHeaderTextColor" format="reference"/>
<declare-styleable name="TextPercentageSeekBarPreference">

@ -271,7 +271,6 @@
<color name="neutral_faded_private_theme">#1FFBFBFE</color>
<color name="sync_disconnected_icon_fill_private_theme">@color/photonYellow70</color>
<color name="sync_disconnected_background_private_theme">#5B5846</color>
<color name="onboarding_illustration_deselected_private_theme">#99FBFBFE</color>
<color name="prompt_login_edit_text_cursor_color_private_theme">@color/photonViolet50</color>
<!-- Normal theme colors for light mode -->
@ -282,8 +281,6 @@
<color name="fill_link_from_clipboard_normal_theme">@color/photonInk20</color>
<color name="sync_disconnected_icon_fill_normal_theme">@color/photonYellow70</color>
<color name="sync_disconnected_background_normal_theme">#FFFDE2</color>
<color name="onboarding_illustration_selected_normal_theme">@color/photonViolet70</color>
<color name="onboarding_illustration_deselected_normal_theme">@color/photonDarkGrey90A60</color>
<color name="add_on_private_browsing_exterior_circle_background_normal_theme">@color/photonViolet70</color>
<color name="prompt_login_edit_text_cursor_color_normal_theme">@color/photonInk20</color>
<color name="search_suggestion_indicator_icon_color_normal_theme">@color/photonGreen70</color>

@ -60,14 +60,11 @@
<dimen name="tracking_protection_item_margin_end">16dp</dimen>
<dimen name="tracking_protection_item_margin_bottom">18dp</dimen>
<dimen name="onboarding_header_icon_height_width">32dp</dimen>
<!-- Bottom Sheet Fragment card -->
<dimen name="bottom_sheet_corner_radius">8dp</dimen>
<dimen name="bottom_sheet_handle_height">3dp</dimen>
<dimen name="bottom_sheet_handle_top_margin">8dp</dimen>
<!-- Browser Toolbar -->
<dimen name="browser_toolbar_height">56dp</dimen>
@ -137,10 +134,6 @@
<dimen name="share_header_text_size">12sp</dimen>
<dimen name="share_progress_bar_margin">16dp</dimen>
<!-- Onboarding -->
<dimen name="onboarding_dual_pane_radio_button_translation_x">-7dp</dimen>
<dimen name="onboarding_dual_pane_radio_button_translation_y">-8dp</dimen>
<!-- Addon Details Fragment -->
<dimen name="addon_details_rating_view_margin_start">6dp</dimen>
<dimen name="addon_details_rating_view_margin_end">6dp</dimen>

@ -1367,59 +1367,59 @@
<!-- Onboarding -->
<!-- Text for onboarding welcome header. -->
<string name="onboarding_header_2">Welcome to a better internet</string>
<string name="onboarding_header_2" moz:RemovedIn="118" tools:ignore="UnusedResources">Welcome to a better internet</string>
<!-- Text for the onboarding welcome message. -->
<string name="onboarding_message">A browser built for people, not profits.</string>
<string name="onboarding_message" moz:RemovedIn="118" tools:ignore="UnusedResources">A browser built for people, not profits.</string>
<!-- Text for the Firefox account onboarding sign in card header. -->
<string name="onboarding_account_sign_in_header">Pick up where you left off</string>
<string name="onboarding_account_sign_in_header" moz:RemovedIn="118" tools:ignore="UnusedResources">Pick up where you left off</string>
<!-- Text for the button to learn more about signing in to your Firefox account. -->
<string name="onboarding_manual_sign_in_description">Sync tabs and passwords across devices for seamless screen-switching.</string>
<string name="onboarding_manual_sign_in_description" moz:RemovedIn="118" tools:ignore="UnusedResources">Sync tabs and passwords across devices for seamless screen-switching.</string>
<!-- Text for the button to manually sign into Firefox account. -->
<string name="onboarding_firefox_account_sign_in">Sign in</string>
<string name="onboarding_firefox_account_sign_in" moz:RemovedIn="118" tools:ignore="UnusedResources">Sign in</string>
<!-- text to display in the snackbar once account is signed-in -->
<string name="onboarding_firefox_account_sync_is_on">Sync is on</string>
<!-- Text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header">Privacy protection by default</string>
<string name="onboarding_tracking_protection_header" moz:RemovedIn="118" tools:ignore="UnusedResources">Privacy protection by default</string>
<!-- Text for the tracking protection card description. The first parameter is the name of the application.-->
<string name="onboarding_tracking_protection_description_old">%1$s automatically stops companies from secretly following you around the web.</string>
<string name="onboarding_tracking_protection_description_old" moz:RemovedIn="118" tools:ignore="UnusedResources">%1$s automatically stops companies from secretly following you around the web.</string>
<!-- Text for the tracking protection card description. -->
<string name="onboarding_tracking_protection_description">Featuring Total Cookie Protection to stop trackers from using cookies to stalk you across sites.</string>
<string name="onboarding_tracking_protection_description" moz:RemovedIn="118" tools:ignore="UnusedResources">Featuring Total Cookie Protection to stop trackers from using cookies to stalk you across sites.</string>
<!-- text for tracking protection radio button option for standard level of blocking -->
<string name="onboarding_tracking_protection_standard_button_2">Standard (default)</string>
<string name="onboarding_tracking_protection_standard_button_2" moz:RemovedIn="118" tools:ignore="UnusedResources">Standard (default)</string>
<!-- text for standard blocking option button description -->
<string name="onboarding_tracking_protection_standard_button_description_3">Balanced for privacy and performance. Pages load normally.</string>
<string name="onboarding_tracking_protection_standard_button_description_3" moz:RemovedIn="118" tools:ignore="UnusedResources">Balanced for privacy and performance. Pages load normally.</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">Strict</string>
<string name="onboarding_tracking_protection_strict_option" moz:RemovedIn="118" tools:ignore="UnusedResources">Strict</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description_3">Blocks more trackers so pages load faster, but some on-page functionality may break.</string>
<string name="onboarding_tracking_protection_strict_button_description_3" moz:RemovedIn="118" tools:ignore="UnusedResources">Blocks more trackers so pages load faster, but some on-page functionality may break.</string>
<!-- text for the toolbar position card header -->
<string name="onboarding_toolbar_placement_header_1">Pick your toolbar placement</string>
<string name="onboarding_toolbar_placement_header_1" moz:RemovedIn="118" tools:ignore="UnusedResources">Pick your toolbar placement</string>
<!-- Text for the toolbar position card description -->
<string name="onboarding_toolbar_placement_description">Keep it on the bottom, or move it to the top.</string>
<string name="onboarding_toolbar_placement_description" moz:RemovedIn="118" tools:ignore="UnusedResources">Keep it on the bottom, or move it to the top.</string>
<!-- Text for the privacy notice onboarding card header -->
<string name="onboarding_privacy_notice_header_1">You control your data</string>
<string name="onboarding_privacy_notice_header_1" moz:RemovedIn="118" tools:ignore="UnusedResources">You control your data</string>
<!-- Text for the privacy notice onboarding card description. -->
<string name="onboarding_privacy_notice_description">Firefox gives you control over what you share online and what you share with us.</string>
<string name="onboarding_privacy_notice_description" moz:RemovedIn="118" tools:ignore="UnusedResources">Firefox gives you control over what you share online and what you share with us.</string>
<!-- Text for the button to read the privacy notice -->
<string name="onboarding_privacy_notice_read_button">Read our privacy notice</string>
<string name="onboarding_privacy_notice_read_button" moz:RemovedIn="118" tools:ignore="UnusedResources">Read our privacy notice</string>
<!-- Text for the conclusion onboarding message -->
<string name="onboarding_conclusion_header">Ready to open up an amazing internet?</string>
<string name="onboarding_conclusion_header" moz:RemovedIn="118" tools:ignore="UnusedResources">Ready to open up an amazing internet?</string>
<!-- text for the button to finish onboarding -->
<string name="onboarding_finish">Start browsing</string>
<string name="onboarding_finish" moz:RemovedIn="118" tools:ignore="UnusedResources">Start browsing</string>
<!-- Onboarding theme -->
<!-- text for the theme picker onboarding card header -->
<string name="onboarding_theme_picker_header">Choose your theme</string>
<string name="onboarding_theme_picker_header" moz:RemovedIn="118" tools:ignore="UnusedResources">Choose your theme</string>
<!-- text for the theme picker onboarding card description -->
<string name="onboarding_theme_picker_description_2">Save some battery and your eyesight with dark mode.</string>
<string name="onboarding_theme_picker_description_2" moz:RemovedIn="118" tools:ignore="UnusedResources">Save some battery and your eyesight with dark mode.</string>
<!-- Automatic theme setting (will follow device setting) -->
<string name="onboarding_theme_automatic_title">Automatic</string>
<string name="onboarding_theme_automatic_title" moz:RemovedIn="118" tools:ignore="UnusedResources">Automatic</string>
<!-- Summary of automatic theme setting (will follow device setting) -->
<string name="onboarding_theme_automatic_summary">Adapts to your device settings</string>
<string name="onboarding_theme_automatic_summary" moz:RemovedIn="118" tools:ignore="UnusedResources">Adapts to your device settings</string>
<!-- Theme setting for dark mode -->
<string name="onboarding_theme_dark_title">Dark theme</string>
<string name="onboarding_theme_dark_title" moz:RemovedIn="118" tools:ignore="UnusedResources">Dark theme</string>
<!-- Theme setting for light mode -->
<string name="onboarding_theme_light_title">Light theme</string>
<string name="onboarding_theme_light_title" moz:RemovedIn="118" tools:ignore="UnusedResources">Light theme</string>
<!-- Text shown in snackbar when multiple tabs have been sent to device -->
<string name="sync_sent_tabs_snackbar">Tabs sent!</string>

@ -108,10 +108,6 @@
<item name="fillLinkFromClipboard">@color/fill_link_from_clipboard_normal_theme</item>
<item name="syncDisconnected">@color/sync_disconnected_icon_fill_normal_theme</item>
<item name="syncDisconnectedBackground">@color/sync_disconnected_background_normal_theme</item>
<item name="onboardingDarkGradientStartBackground">@color/photonViolet70</item>
<item name="onboardingDarkGradientEndBackground">@color/photonViolet40</item>
<item name="onboardingSelected">@color/onboarding_illustration_selected_normal_theme</item>
<item name="onboardingDeselected">@color/onboarding_illustration_deselected_normal_theme</item>
<item name="addOnPrivateBrowsingExteriorCircleBackground">@color/add_on_private_browsing_exterior_circle_background_normal_theme</item>
<item name="mozacPromptLoginEditTextCursorColor">@color/prompt_login_edit_text_cursor_color_normal_theme</item>
<item name="awesomeBarIndicatorColor">@color/search_suggestion_indicator_icon_color_normal_theme</item>
@ -191,11 +187,6 @@
<item name="drawableTint">?accentHighContrast</item>
</style>
<style name="Checkable.Colored" parent="Theme.AppCompat">
<item name="colorControlActivated">?attr/onboardingSelected</item>
<item name="colorControlNormal">?attr/onboardingDeselected</item>
</style>
<style name="DialogStyleDark" parent="BaseDialogStyle">
<item name="buttonBarNegativeButtonStyle">@style/DialogButtonStyleDark</item>
<item name="buttonBarPositiveButtonStyle">@style/DialogButtonStyleDark</item>
@ -310,10 +301,6 @@
<item name="fillLinkFromClipboard">@color/accent_high_contrast_private_theme</item>
<item name="syncDisconnected">@color/sync_disconnected_icon_fill_private_theme</item>
<item name="syncDisconnectedBackground">@color/sync_disconnected_background_private_theme</item>
<item name="onboardingDarkGradientStartBackground">@color/photonViolet60</item>
<item name="onboardingDarkGradientEndBackground">@color/photonPurple50</item>
<item name="onboardingSelected">@color/accent_private_theme</item>
<item name="onboardingDeselected">@color/onboarding_illustration_deselected_private_theme</item>
<item name="addOnPrivateBrowsingExteriorCircleBackground">@color/accent_private_theme</item>
<item name="mozacPromptLoginEditTextCursorColor">@color/prompt_login_edit_text_cursor_color_private_theme</item>
<item name="awesomeBarIndicatorColor">@color/photonGreen60</item>
@ -390,13 +377,6 @@
<item name="android:fontWeight" tools:ignore="NewApi">@integer/font_weight_medium</item>
</style>
<!-- Ideally we should consolidate this with NeutralButton in the future -->
<style name="NeutralOnboardingButton" parent="NeutralButton">
<item name="android:background">@drawable/onboarding_padded_background</item>
<item name="backgroundTint">?neutralFaded</item>
<item name="android:textColor">?attr/textPrimary</item>
</style>
<style name="DestructiveButton" parent="NeutralButton">
<item name="iconTint">@color/fx_mobile_icon_color_warning_button</item>
<item name="android:textColor">@color/fx_mobile_text_color_warning_button</item>
@ -564,21 +544,6 @@
<item name="android:textColor">?accentUsedOnDarkBackground</item>
</style>
<style name="OnboardingCardLight">
<item name="android:background">@drawable/onboarding_card_background_light</item>
<item name="android:layout_marginBottom">16dp</item>
<item name="android:elevation">5dp</item>
</style>
<style name="OnboardingCardLightWithPadding" parent="OnboardingCardLight">
<item name="android:padding">16dp</item>
</style>
<style name="OnboardingCardDark" parent="OnboardingCardLightWithPadding" tools:ignore="UnusedResources">
<item name="android:background">@drawable/onboarding_card_background_dark</item>
<item name="android:elevation">0dp</item>
</style>
<style name="SearchClipboardStyle">
<item name="android:ellipsize">end</item>
<item name="android:maxLines">1</item>

@ -204,10 +204,9 @@ class SessionControlInteractorTest {
@Test
fun `WHEN private mode button is clicked THEN the click is handled`() {
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
interactor.onPrivateModeButtonClicked(newMode, hasBeenOnboarded)
verify { privateBrowsingController.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded) }
interactor.onPrivateModeButtonClicked(newMode)
verify { privateBrowsingController.handlePrivateModeButtonClicked(newMode) }
}
@Test

@ -82,9 +82,8 @@ class DefaultPrivateBrowsingControllerTest {
every { settings.incrementNumTimesPrivateModeOpened() } just Runs
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
controller.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
controller.handlePrivateModeButtonClicked(newMode)
verify {
settings.incrementNumTimesPrivateModeOpened()
@ -110,9 +109,8 @@ class DefaultPrivateBrowsingControllerTest {
store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking()
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
controller.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
controller.handlePrivateModeButtonClicked(newMode)
verify {
settings.incrementNumTimesPrivateModeOpened()
@ -141,9 +139,8 @@ class DefaultPrivateBrowsingControllerTest {
store.dispatch(TabListAction.AddTabAction(tab, select = true)).joinBlocking()
val newMode = BrowsingMode.Normal
val hasBeenOnboarded = true
controller.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded)
controller.handlePrivateModeButtonClicked(newMode)
verify(exactly = 0) {
settings.incrementNumTimesPrivateModeOpened()

@ -1,53 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.databinding.OnboardingFinishBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingFinishViewHolderTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var binding: OnboardingFinishBinding
private lateinit var interactor: OnboardingInteractor
@Before
fun setup() {
binding = OnboardingFinishBinding.inflate(LayoutInflater.from(testContext))
interactor = mockk(relaxed = true)
}
@Test
fun `call interactor on click`() {
every { testContext.components.analytics } returns mockk(relaxed = true)
OnboardingFinishViewHolder(binding.root, interactor)
binding.finishButton.performClick()
verify { interactor.onFinishOnboarding(focusOnAddressBar = true) }
// Check if the event was recorded
assertNotNull(Onboarding.finish.testGetValue())
assertEquals(1, Onboarding.finish.testGetValue()!!.size)
assertNull(Onboarding.finish.testGetValue()!!.single().extra)
}
}

@ -1,76 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.navigation.NavController
import androidx.navigation.Navigation
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint
import org.mozilla.fenix.databinding.OnboardingManualSigninBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.HomeFragmentDirections
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingManualSignInViewHolderTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var binding: OnboardingManualSigninBinding
private lateinit var navController: NavController
private lateinit var itemView: ViewGroup
@Before
fun setup() {
binding = OnboardingManualSigninBinding.inflate(LayoutInflater.from(testContext))
navController = mockk(relaxed = true)
itemView = mockk(relaxed = true)
mockkStatic(Navigation::class)
every { itemView.context } returns testContext
every { Navigation.findNavController(binding.root) } returns navController
}
@After
fun teardown() {
unmockkStatic(Navigation::class)
}
@Test
fun `navigate on click`() {
every { testContext.components.analytics } returns mockk(relaxed = true)
OnboardingManualSignInViewHolder(binding.root)
binding.fxaSignInButton.performClick()
verify {
navController.navigate(
HomeFragmentDirections.actionGlobalTurnOnSync(
entrypoint = FenixFxAEntryPoint.OnboardingManualSignIn,
),
)
}
// Check if the event was recorded
Assert.assertNotNull(Onboarding.fxaManualSignin.testGetValue())
assertEquals(1, Onboarding.fxaManualSignin.testGetValue()!!.size)
Assert.assertNull(Onboarding.fxaManualSignin.testGetValue()!!.single().extra)
}
}

@ -1,56 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingPrivacyNoticeBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.onboarding.interactor.OnboardingInteractor
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingPrivacyNoticeViewHolderTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var binding: OnboardingPrivacyNoticeBinding
private lateinit var interactor: OnboardingInteractor
@Before
fun setup() {
val context = ContextThemeWrapper(testContext, R.style.NormalTheme)
binding = OnboardingPrivacyNoticeBinding.inflate(LayoutInflater.from(context))
interactor = mockk(relaxed = true)
}
@Test
fun `call interactor on click`() {
every { testContext.components.analytics } returns mockk(relaxed = true)
OnboardingPrivacyNoticeViewHolder(binding.root, interactor)
binding.readButton.performClick()
verify { interactor.onReadPrivacyNoticeClicked() }
// Check if the event was recorded
assertNotNull(Onboarding.privacyNotice.testGetValue())
assertEquals(1, Onboarding.privacyNotice.testGetValue()!!.size)
assertNull(Onboarding.privacyNotice.testGetValue()!!.single().extra)
}
}

@ -1,36 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.databinding.OnboardingSectionHeaderBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingSectionHeaderViewHolderTest {
private lateinit var binding: OnboardingSectionHeaderBinding
@Before
fun setup() {
binding = OnboardingSectionHeaderBinding.inflate(LayoutInflater.from(testContext))
}
@Test
fun `bind text`() {
val holder = OnboardingSectionHeaderViewHolder(binding.root)
holder.bind { "Hello world" }
assertEquals(
"Hello world",
binding.sectionHeaderText.text,
)
}
}

@ -1,97 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater
import io.mockk.every
import io.mockk.mockk
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Onboarding
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.databinding.OnboardingToolbarPositionPickerBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingToolbarPositionPickerViewHolderTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
private lateinit var binding: OnboardingToolbarPositionPickerBinding
private lateinit var settings: Settings
@Before
fun setup() {
binding = OnboardingToolbarPositionPickerBinding.inflate(LayoutInflater.from(testContext))
settings = mockk(relaxed = true)
every { testContext.components.settings } returns settings
every { testContext.components.analytics } returns mockk(relaxed = true)
}
@Test
fun `bottom illustration should select corresponding radio button`() {
every { settings.toolbarPosition } returns ToolbarPosition.TOP
OnboardingToolbarPositionPickerViewHolder(binding.root)
assertTrue(binding.toolbarTopRadioButton.isChecked)
assertFalse(binding.toolbarBottomRadioButton.isChecked)
binding.toolbarBottomImage.performClick()
assertFalse(binding.toolbarTopRadioButton.isChecked)
assertTrue(binding.toolbarBottomRadioButton.isChecked)
}
@Test
fun `top illustration should select corresponding radio button`() {
every { settings.toolbarPosition } returns ToolbarPosition.BOTTOM
OnboardingToolbarPositionPickerViewHolder(binding.root)
assertFalse(binding.toolbarTopRadioButton.isChecked)
assertTrue(binding.toolbarBottomRadioButton.isChecked)
binding.toolbarTopImage.performClick()
assertTrue(binding.toolbarTopRadioButton.isChecked)
assertFalse(binding.toolbarBottomRadioButton.isChecked)
}
@Test
fun `WHEN the top radio button is clicked THEN the proper event is recorded`() {
every { settings.toolbarPosition } returns ToolbarPosition.BOTTOM
OnboardingToolbarPositionPickerViewHolder(binding.root)
binding.toolbarTopImage.performClick()
assertNotNull(Onboarding.prefToggledToolbarPosition.testGetValue())
assertEquals(
OnboardingToolbarPositionPickerViewHolder.Companion.Position.TOP.name,
Onboarding.prefToggledToolbarPosition.testGetValue()!!
.last().extra?.get("position"),
)
}
@Test
fun `WHEN the bottom radio button is clicked THEN the proper event is recorded`() {
every { settings.toolbarPosition } returns ToolbarPosition.TOP
OnboardingToolbarPositionPickerViewHolder(binding.root)
binding.toolbarBottomImage.performClick()
assertNotNull(Onboarding.prefToggledToolbarPosition.testGetValue())
assertEquals(
OnboardingToolbarPositionPickerViewHolder.Companion.Position.BOTTOM.name,
Onboarding.prefToggledToolbarPosition.testGetValue()!!
.last().extra?.get("position"),
)
}
}

@ -1,55 +0,0 @@
/* 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.home.sessioncontrol.viewholders.onboarding
import android.content.Context
import android.view.LayoutInflater
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.OnboardingTrackingProtectionBinding
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class OnboardingTrackingProtectionViewHolderTest {
@Test
fun `GIVEN the TCP feature is public WHEN this ViewHolder is initialized THEN set an appropriate description`() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true) {
every { enabledTotalCookieProtectionCFR } returns true
}
val expectedDescription = testContext.getString(R.string.onboarding_tracking_protection_description)
val binding = OnboardingTrackingProtectionBinding.inflate(LayoutInflater.from(testContext))
OnboardingTrackingProtectionViewHolder(binding.root)
assertEquals(expectedDescription, binding.descriptionText.text)
}
}
@Test
fun `GIVEN the TCP feature is not public WHEN this ViewHolder is initialized THEN set an appropriate description`() {
mockkStatic("org.mozilla.fenix.ext.ContextKt") {
every { any<Context>().settings() } returns mockk(relaxed = true) {
every { enabledTotalCookieProtectionCFR } returns false
}
val expectedDescription = testContext.getString(
R.string.onboarding_tracking_protection_description_old,
testContext.getString(R.string.app_name),
)
val binding = OnboardingTrackingProtectionBinding.inflate(LayoutInflater.from(testContext))
OnboardingTrackingProtectionViewHolder(binding.root)
assertEquals(expectedDescription, binding.descriptionText.text)
}
}
}

@ -1,122 +0,0 @@
/* 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 androidx.navigation.NavController
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.slot
import io.mockk.unmockkObject
import io.mockk.verify
import mozilla.components.support.test.robolectric.testContext
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.onboarding.controller.DefaultOnboardingController
import org.mozilla.fenix.settings.SupportUtils
@RunWith(FenixRobolectricTestRunner::class)
class DefaultOnboardingControllerTest {
@get:Rule
val gleanTestRule = GleanTestRule(testContext)
@RelaxedMockK
private lateinit var activity: HomeActivity
@MockK(relaxUnitFun = true)
private lateinit var navController: NavController
@RelaxedMockK
private lateinit var onboarding: FenixOnboarding
private lateinit var controller: DefaultOnboardingController
@Before
fun setup() {
MockKAnnotations.init(this)
controller = DefaultOnboardingController(
activity = activity,
navController = navController,
onboarding = onboarding,
crashReporter = mockk(relaxed = true),
)
}
@Test
fun `GIVEN focus on address bar is true WHEN onboarding is finished THEN navigate to home and log search bar tapped`() {
assertNull(Events.searchBarTapped.testGetValue())
val focusOnAddressBar = true
controller.handleFinishOnboarding(focusOnAddressBar)
assertNotNull(Events.searchBarTapped.testGetValue())
val recordedEvents = Events.searchBarTapped.testGetValue()!!
assertEquals(1, recordedEvents.size)
assertEquals("HOME", recordedEvents.single().extra?.getValue("source"))
verify {
onboarding.finish()
navController.navigate(
OnboardingFragmentDirections.actionHome(focusOnAddressBar = focusOnAddressBar),
)
}
}
@Test
fun `GIVEN focus on address bar is false WHEN onboarding is finished THEN navigate to home`() {
assertNull(Events.searchBarTapped.testGetValue())
val focusOnAddressBar = false
controller.handleFinishOnboarding(focusOnAddressBar)
assertNull(Events.searchBarTapped.testGetValue())
verify {
onboarding.finish()
navController.navigate(
OnboardingFragmentDirections.actionHome(focusOnAddressBar = focusOnAddressBar),
)
}
}
@Test
fun `WHEN read privacy notice is clicked THEN open the privacy notice support page`() {
mockkObject(SupportUtils)
val urlCaptor = slot<String>()
every { SupportUtils.createCustomTabIntent(any(), capture(urlCaptor)) } returns mockk()
controller.handleReadPrivacyNoticeClicked()
verify {
activity.startActivity(
any(),
)
}
assertEquals(
SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE),
urlCaptor.captured,
)
unmockkObject(SupportUtils)
}
}

@ -1,92 +0,0 @@
/* 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 io.mockk.mockk
import io.mockk.verify
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.home.privatebrowsing.controller.PrivateBrowsingController
import org.mozilla.fenix.home.toolbar.ToolbarController
import org.mozilla.fenix.onboarding.controller.OnboardingController
import org.mozilla.fenix.onboarding.interactor.DefaultOnboardingInteractor
import org.mozilla.fenix.search.toolbar.SearchSelectorController
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
class DefaultOnboardingInteractorTest {
private val controller: OnboardingController = mockk(relaxed = true)
private val privateBrowsingController: PrivateBrowsingController = mockk(relaxed = true)
private val searchSelectorController: SearchSelectorController = mockk(relaxed = true)
private val toolbarController: ToolbarController = mockk(relaxed = true)
private lateinit var interactor: DefaultOnboardingInteractor
@Before
fun setup() {
interactor = DefaultOnboardingInteractor(
controller = controller,
privateBrowsingController = privateBrowsingController,
searchSelectorController = searchSelectorController,
toolbarController = toolbarController,
)
}
@Test
fun `WHEN the onboarding is finished THEN forward to controller handler`() {
val focusOnAddressBar = true
interactor.onFinishOnboarding(focusOnAddressBar)
verify { controller.handleFinishOnboarding(focusOnAddressBar) }
}
@Test
fun `WHEN the privacy notice clicked THEN forward to controller handler`() {
interactor.onReadPrivacyNoticeClicked()
verify { controller.handleReadPrivacyNoticeClicked() }
}
@Test
fun onMenuItemTapped() {
val item = SearchSelectorMenu.Item.SearchSettings
interactor.onMenuItemTapped(item)
verify { searchSelectorController.handleMenuItemTapped(item) }
}
@Test
fun onLearnMoreClicked() {
interactor.onLearnMoreClicked()
verify { privateBrowsingController.handleLearnMoreClicked() }
}
@Test
fun onPrivateModeButtonClicked() {
val newMode = BrowsingMode.Private
val hasBeenOnboarded = true
interactor.onPrivateModeButtonClicked(newMode, hasBeenOnboarded)
verify { privateBrowsingController.handlePrivateModeButtonClicked(newMode, hasBeenOnboarded) }
}
@Test
fun onNavigateSearch() {
interactor.onNavigateSearch()
verify { toolbarController.handleNavigateSearch() }
}
@Test
fun onPaste() {
val clipboardText = "text"
interactor.onPaste(clipboardText)
verify { toolbarController.handlePaste(clipboardText) }
}
@Test
fun onPasteAndGo() {
val clipboardText = "text"
interactor.onPasteAndGo(clipboardText)
verify { toolbarController.handlePasteAndGo(clipboardText) }
}
}

@ -1,70 +0,0 @@
/* 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.content.Context
import io.mockk.every
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import mozilla.components.service.fxa.manager.FxaAccountManager
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.ext.components
class OnboardingAccountObserverTest {
private lateinit var context: Context
private lateinit var accountManager: FxaAccountManager
private lateinit var observer: OnboardingAccountObserver
private lateinit var dispatchChanges: (mode: OnboardingState) -> Unit
private var dispatchChangesResult: OnboardingState? = null
@Before
fun setup() {
context = mockk(relaxed = true)
accountManager = mockk(relaxed = true)
dispatchChangesResult = null
dispatchChanges = {
dispatchChangesResult = it
}
every { context.components.backgroundServices.accountManager } returns accountManager
observer = spyk(
OnboardingAccountObserver(
context = context,
dispatchChanges = dispatchChanges,
),
)
}
@Test
fun `WHEN account is authenticated THEN return signed in state`() {
every { accountManager.authenticatedAccount() } returns mockk()
assertEquals(OnboardingState.SignedIn, observer.getOnboardingState())
}
@Test
fun `WHEN account is not authenticated THEN return signed out state`() {
every { accountManager.authenticatedAccount() } returns null
assertEquals(OnboardingState.SignedOutNoAutoSignIn, observer.getOnboardingState())
}
@Test
fun `WHEN account observer receives a state change THEN emit state changes`() {
observer.onAuthenticated(mockk(), mockk())
observer.onAuthenticationProblems()
observer.onLoggedOut()
observer.onProfileUpdated(mockk())
verify(exactly = 4) { observer.emitChanges() }
}
}

@ -10,31 +10,24 @@ import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.mock
import mozilla.components.support.test.rule.MainCoroutineRule
import mozilla.components.support.test.whenever
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
class ReviewQualityCheckFeatureTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
private val shoppingExperienceFeature = mock<ShoppingExperienceFeature>()
@Test
fun `WHEN feature is not enabled THEN callback returns false`() {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(false)
var availability: Boolean? = null
val tested = ReviewQualityCheckFeature(
browserStore = BrowserStore(),
shoppingExperienceFeature = shoppingExperienceFeature,
shoppingExperienceFeature = FakeShoppingExperienceFeature(enabled = false),
onAvailabilityChange = {
availability = it
},
@ -48,8 +41,6 @@ class ReviewQualityCheckFeatureTest {
@Test
fun `WHEN feature is enabled and selected tab is not a product page THEN callback returns false`() =
runTest {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(true)
var availability: Boolean? = null
val tab = createTab(
url = "https://www.mozilla.org",
@ -64,7 +55,7 @@ class ReviewQualityCheckFeatureTest {
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = shoppingExperienceFeature,
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {
availability = it
},
@ -78,8 +69,6 @@ class ReviewQualityCheckFeatureTest {
@Test
fun `WHEN feature is enabled and selected tab is a product page THEN callback returns true`() =
runTest {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(true)
var availability: Boolean? = null
val tab = createTab(
url = "https://www.mozilla.org",
@ -94,7 +83,7 @@ class ReviewQualityCheckFeatureTest {
browserStore = BrowserStore(
initialState = browserState,
),
shoppingExperienceFeature = shoppingExperienceFeature,
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {
availability = it
},
@ -108,8 +97,6 @@ class ReviewQualityCheckFeatureTest {
@Test
fun `WHEN feature is enabled and selected tab is switched to a product page THEN callback returns true`() =
runTest {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(true)
var availability: Boolean? = null
val tab1 = createTab(
url = "https://www.mozilla.org",
@ -129,7 +116,7 @@ class ReviewQualityCheckFeatureTest {
)
val tested = ReviewQualityCheckFeature(
browserStore = browserStore,
shoppingExperienceFeature = shoppingExperienceFeature,
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {
availability = it
},
@ -146,8 +133,6 @@ class ReviewQualityCheckFeatureTest {
@Test
fun `WHEN feature is enabled and selected tab is switched to not a product page THEN callback returns false`() =
runTest {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(true)
var availability: Boolean? = null
val tab1 = createTab(
url = "https://www.shopping.org",
@ -167,7 +152,7 @@ class ReviewQualityCheckFeatureTest {
)
val tested = ReviewQualityCheckFeature(
browserStore = browserStore,
shoppingExperienceFeature = shoppingExperienceFeature,
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {
availability = it
},
@ -184,9 +169,8 @@ class ReviewQualityCheckFeatureTest {
@Test
fun `WHEN feature is enabled and selected tab is switched to a product page after stop is called THEN callback is only called once with false`() =
runTest {
whenever(shoppingExperienceFeature.isEnabled).thenReturn(true)
val onAvailabilityChange = mock<(isAvailable: Boolean) -> Unit>()
var availability: Boolean? = null
var availabilityCount = 0
val tab1 = createTab(
url = "https://www.mozilla.org",
id = "tab1",
@ -206,8 +190,11 @@ class ReviewQualityCheckFeatureTest {
val tested = ReviewQualityCheckFeature(
browserStore = browserStore,
shoppingExperienceFeature = shoppingExperienceFeature,
onAvailabilityChange = onAvailabilityChange,
shoppingExperienceFeature = FakeShoppingExperienceFeature(),
onAvailabilityChange = {
availability = it
availabilityCount++
},
)
tested.start()
@ -215,6 +202,15 @@ class ReviewQualityCheckFeatureTest {
tested.stop()
browserStore.dispatch(TabListAction.SelectTabAction(tab2.id)).joinBlocking()
verify(onAvailabilityChange, times(1)).invoke(false)
assertEquals(1, availabilityCount)
assertFalse(availability!!)
}
}
class FakeShoppingExperienceFeature(
private val enabled: Boolean = true,
) : ShoppingExperienceFeature {
override val isEnabled: Boolean
get() = enabled
}

Loading…
Cancel
Save