1
0
Fork 0

FNX-14498 ⁃ For #9487: improve fxa onboarding manual sign in card (#13317)

* Replace strings, add learn more, hook up button

* Constrain learn more and icon to the text

* View holder tests

* Lint

* Update homescreen ui test
master
Elise Richards 2020-08-20 11:05:11 -05:00 committed by GitHub
parent 499ff83b18
commit feae7fff2f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 135 additions and 35 deletions

View File

@ -553,11 +553,11 @@ private fun assertWelcomeHeader() =
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertGetTheMostHeader() = private fun assertGetTheMostHeader() =
onView(allOf(withText("Get the most out of Firefox Preview."))) onView(allOf(withText("Start syncing bookmarks, passwords, and more with your Firefox account.")))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertAccountsSignInButton() = private fun assertAccountsSignInButton() =
onView(ViewMatchers.withResourceName("turn_on_sync_button")) onView(ViewMatchers.withResourceName("fxa_sign_in_button"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE))) .check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
private fun assertGetToKnowHeader() = private fun assertGetToKnowHeader() =

View File

@ -31,9 +31,9 @@ class OnboardingAutomaticSignInViewHolder(
private val headerText = view.header_text private val headerText = view.header_text
init { init {
view.turn_on_sync_button.setOnClickListener { view.fxa_sign_in_button.setOnClickListener {
scope.launch { scope.launch {
onClick(it.turn_on_sync_button) onClick(it.fxa_sign_in_button)
} }
} }
} }

View File

@ -5,40 +5,40 @@
package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.View import android.view.View
import androidx.core.content.ContextCompat
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_manual_signin.view.* import kotlinx.android.synthetic.main.onboarding_manual_signin.view.*
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.addUnderline
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.onboarding.OnboardingController
import org.mozilla.fenix.onboarding.OnboardingInteractor
class OnboardingManualSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) { class OnboardingManualSignInViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val headerText = view.header_text private val headerText = view.header_text
init { init {
view.turn_on_sync_button.setOnClickListener { val interactor = OnboardingInteractor(OnboardingController(itemView.context))
view.fxa_sign_in_button.setOnClickListener {
it.context.components.analytics.metrics.track(Event.OnboardingManualSignIn) it.context.components.analytics.metrics.track(Event.OnboardingManualSignIn)
val directions = HomeFragmentDirections.actionGlobalTurnOnSync() val directions = HomeFragmentDirections.actionGlobalTurnOnSync()
Navigation.findNavController(view).navigate(directions) Navigation.findNavController(view).navigate(directions)
} }
view.learn_more.addUnderline()
view.learn_more.setOnClickListener {
interactor.onLearnMoreClicked()
}
} }
fun bind() { fun bind() {
val context = itemView.context val context = itemView.context
headerText.text = context.getString(R.string.onboarding_firefox_account_header)
val appName = context.getString(R.string.app_name)
headerText.text = context.getString(R.string.onboarding_firefox_account_header, appName)
val icon = context.getDrawableWithTint(
R.drawable.ic_onboarding_firefox_accounts,
ContextCompat.getColor(context, R.color.white_color)
)
headerText.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon)
} }
companion object { companion object {

View File

@ -0,0 +1,22 @@
/* 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 org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.settings.SupportUtils
class OnboardingController(
private val context: Context
) {
fun handleLearnMoreClicked() {
(context as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getFirefoxAccountSumoUrl(),
newTab = true,
from = BrowserDirection.FromHome
)
}
}

View File

@ -0,0 +1,14 @@
/* 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
class OnboardingInteractor(private val onboardingController: OnboardingController) {
/**
* Called when the user clicks the learn more link
* @param url the url the suggestion was providing
*/
fun onLearnMoreClicked() = onboardingController.handleLearnMoreClicked()
}

View File

@ -74,6 +74,10 @@ object SupportUtils {
return "https://support.mozilla.org/$langTag/kb/$escapedTopic" return "https://support.mozilla.org/$langTag/kb/$escapedTopic"
} }
fun getFirefoxAccountSumoUrl(): String {
return "https://support.mozilla.org/kb/access-mozilla-services-firefox-account"
}
fun getMozillaPageUrl(page: MozillaPage, locale: Locale = Locale.getDefault()): String { fun getMozillaPageUrl(page: MozillaPage, locale: Locale = Locale.getDefault()): String {
val path = page.path val path = page.path
val langTag = getLanguageTag(locale) val langTag = getLanguageTag(locale)

View File

@ -0,0 +1,16 @@
<?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="@android:color/white"
android:pathData="M17.6655,14.2092C16.1843,12.4998 14.1333,14.8 11.9997,14.8C9.8675,14.8 7.8151,12.4998 6.3339,14.2092C5.9139,14.6936 5.8803,15.3992 6.2401,15.9284C7.4959,17.7764 9.5959,19 11.9997,19C14.4035,19 16.5035,17.7764 17.7593,15.9284C18.1205,15.3992 18.0855,14.6936 17.6655,14.2092M16.25,9.25C16.25,6.9026 14.3474,5 12,5C9.6526,5 7.75,6.9026 7.75,9.25C7.75,11.5974 9.6526,13.5 12,13.5C14.3474,13.5 16.25,11.5974 16.25,9.25Z" />
<path
android:fillColor="@android:color/white"
android:pathData="M12,22C6.4772,22 2,17.5228 2,12C2,6.4772 6.4772,2 12,2C17.5228,2 22,6.4772 22,12C21.9939,17.5203 17.5203,21.9939 12,22L12,22ZM12,4C7.5817,4 4,7.5817 4,12C4,16.4183 7.5817,20 12,20C16.4183,20 20,16.4183 20,12C19.995,7.5838 16.4162,4.005 12,4Z" />
</vector>

View File

@ -22,7 +22,7 @@
tools:text="@string/onboarding_firefox_account_auto_signin_header_2" /> tools:text="@string/onboarding_firefox_account_auto_signin_header_2" />
<Button <Button
android:id="@+id/turn_on_sync_button" android:id="@+id/fxa_sign_in_button"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"

View File

@ -2,7 +2,8 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public <!-- 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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/onboarding_card" android:id="@+id/onboarding_card"
@ -11,22 +12,51 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> 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 <TextView
android:id="@+id/header_text" android:id="@+id/header_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
android:drawablePadding="12dp"
android:textAppearance="@style/Header16TextStyle" android:textAppearance="@style/Header16TextStyle"
android:lineSpacingExtra="8sp"
android:textColor="@color/neutral_text" android:textColor="@color/neutral_text"
app:drawableTint="@color/white_color" app:drawableTint="@color/white_color"
tools:text="@string/onboarding_firefox_account_header" /> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/avatar_icon"
android:layout_marginStart="52dp"
tools:text="@string/onboarding_firefox_account_header"
/>
<org.mozilla.fenix.utils.LinkTextView
android:id="@+id/learn_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/onboarding_manual_sign_in_learn_more"
android:textAppearance="@style/Header16TextStyle"
android:textColor="@color/neutral_text"
app:layout_constraintStart_toStartOf="@id/header_text"
app:layout_constraintTop_toBottomOf="@id/header_text"
tools:textColor="@color/neutral_text" />
<Button <Button
style="@style/NeutralButton" style="@style/NeutralButton"
android:id="@+id/turn_on_sync_button" android:id="@+id/fxa_sign_in_button"
android:background="@drawable/onboarding_padded_background" android:background="@drawable/onboarding_padded_background"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:text="@string/onboarding_firefox_account_sign_in" android:text="@string/onboarding_firefox_account_sign_in"
app:layout_constraintTop_toBottomOf="@id/learn_more"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="parent"
app:backgroundTint="@color/foundation_light_theme" /> app:backgroundTint="@color/foundation_light_theme" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -974,9 +974,10 @@
<string name="onboarding_whats_new_description">Have questions about the redesigned %s? Want to know whats changed?</string> <string name="onboarding_whats_new_description">Have questions about the redesigned %s? Want to know whats changed?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ --> <!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Get answers here</string> <string name="onboarding_whats_new_description_linktext">Get answers here</string>
<!-- text for the firefox account onboarding card header <!-- text for the firefox account onboarding card header -->
The first parameter is the name of the app (e.g. Firefox Preview) --> <string name="onboarding_firefox_account_header">Start syncing bookmarks, passwords, and more with your Firefox account.</string>
<string name="onboarding_firefox_account_header">Get the most out of %s.</string> <!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Learn more</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to <!-- text for the firefox account onboarding card header when we detect you're already signed in to
another Firefox browser. (The word `Firefox` should not be translated) another Firefox browser. (The word `Firefox` should not be translated)
The first parameter is the email of the detected user's account --> The first parameter is the email of the detected user's account -->

View File

@ -65,7 +65,7 @@ class OnboardingAutomaticSignInViewHolderTest {
"You are signed in as email@example.com on another Firefox browser on this phone. Would you like to sign in with this account?", "You are signed in as email@example.com on another Firefox browser on this phone. Would you like to sign in with this account?",
view.header_text.text view.header_text.text
) )
assertTrue(view.turn_on_sync_button.isEnabled) assertTrue(view.fxa_sign_in_button.isEnabled)
} }
@Test @Test
@ -79,10 +79,10 @@ class OnboardingAutomaticSignInViewHolderTest {
val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) val holder = OnboardingAutomaticSignInViewHolder(view, scope = this)
holder.bind(account) holder.bind(account)
holder.onClick(view.turn_on_sync_button) holder.onClick(view.fxa_sign_in_button)
assertEquals("Signing in…", view.turn_on_sync_button.text) assertEquals("Signing in…", view.fxa_sign_in_button.text)
assertFalse(view.turn_on_sync_button.isEnabled) assertFalse(view.fxa_sign_in_button.isEnabled)
} }
@Test @Test
@ -96,10 +96,10 @@ class OnboardingAutomaticSignInViewHolderTest {
val holder = OnboardingAutomaticSignInViewHolder(view, scope = this) val holder = OnboardingAutomaticSignInViewHolder(view, scope = this)
holder.bind(account) holder.bind(account)
holder.onClick(view.turn_on_sync_button) holder.onClick(view.fxa_sign_in_button)
assertEquals("Yes, sign me in", view.turn_on_sync_button.text) assertEquals("Yes, sign me in", view.fxa_sign_in_button.text)
assertTrue(view.turn_on_sync_button.isEnabled) assertTrue(view.fxa_sign_in_button.isEnabled)
verify { snackbar.setText("Failed to sign-in") } verify { snackbar.setText("Failed to sign-in") }
} }
} }

View File

@ -6,9 +6,12 @@ package org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.Navigation import androidx.navigation.Navigation
import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkStatic import io.mockk.unmockkStatic
@ -22,20 +25,27 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.home.HomeFragmentDirections import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.onboarding.OnboardingInteractor
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class OnboardingManualSignInViewHolderTest { class OnboardingManualSignInViewHolderTest {
private lateinit var view: View private lateinit var view: View
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var interactor: OnboardingInteractor
private lateinit var itemView: ViewGroup
@Before @Before
fun setup() { fun setup() {
view = LayoutInflater.from(testContext) view = LayoutInflater.from(testContext)
.inflate(OnboardingManualSignInViewHolder.LAYOUT_ID, null) .inflate(OnboardingManualSignInViewHolder.LAYOUT_ID, null)
navController = mockk(relaxed = true) navController = mockk(relaxed = true)
interactor = mockk(relaxUnitFun = true)
itemView = mockk(relaxed = true)
mockkStatic(Navigation::class) mockkStatic(Navigation::class)
every { itemView.context } returns testContext
every { interactor.onLearnMoreClicked() } just Runs
every { Navigation.findNavController(view) } returns navController every { Navigation.findNavController(view) } returns navController
} }
@ -48,13 +58,16 @@ class OnboardingManualSignInViewHolderTest {
fun `bind header text`() { fun `bind header text`() {
OnboardingManualSignInViewHolder(view).bind() OnboardingManualSignInViewHolder(view).bind()
assertEquals("Get the most out of Firefox Preview.", view.header_text.text) assertEquals(
"Start syncing bookmarks, passwords, and more with your Firefox account.",
view.header_text.text
)
} }
@Test @Test
fun `navigate on click`() { fun `navigate on click`() {
OnboardingManualSignInViewHolder(view) OnboardingManualSignInViewHolder(view)
view.turn_on_sync_button.performClick() view.fxa_sign_in_button.performClick()
verify { navController.navigate(HomeFragmentDirections.actionGlobalTurnOnSync()) } verify { navController.navigate(HomeFragmentDirections.actionGlobalTurnOnSync()) }
} }