1
0
Fork 0

Closes #2593: FxA automatic sign-in integration

master
Grisha Kruglov 2019-07-25 18:25:34 -07:00 committed by Grisha Kruglov
parent 210864186b
commit ae33234bbc
10 changed files with 202 additions and 125 deletions

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.home
import android.animation.Animator
import android.content.Context
import android.content.DialogInterface
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
@ -158,7 +159,7 @@ class HomeFragment : Fragment(), AccountObserver {
): View? {
val view = inflater.inflate(R.layout.fragment_home, container, false)
val mode = currentMode()
val mode = currentMode(view.context)
sessionControlComponent = SessionControlComponent(
view.homeLayout,
@ -300,7 +301,7 @@ class HomeFragment : Fragment(), AccountObserver {
getManagedEmitter<SessionControlChange>().onNext(
SessionControlChange.Change(
tabs = getListOfSessions().toTabs(),
mode = currentMode(),
mode = currentMode(context!!),
collections = requireComponents.core.tabCollectionStorage.cachedTabCollections
)
)
@ -325,7 +326,7 @@ class HomeFragment : Fragment(), AccountObserver {
is OnboardingAction.Finish -> {
onboarding.finish()
val mode = currentMode()
val mode = currentMode(context!!)
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
}
}
@ -705,12 +706,18 @@ class HomeFragment : Fragment(), AccountObserver {
nav(R.id.homeFragment, directions)
}
private fun currentMode(): Mode = if (!onboarding.userHasBeenOnboarded()) {
val account = requireComponents.backgroundServices.accountManager.authenticatedAccount()
if (account == null) {
Mode.Onboarding(OnboardingState.SignedOut)
private fun currentMode(context: Context): Mode = if (!onboarding.userHasBeenOnboarded()) {
val accountManager = requireComponents.backgroundServices.accountManager
val account = accountManager.authenticatedAccount()
if (account != null) {
Mode.Onboarding(OnboardingState.SignedIn)
} else {
Mode.Onboarding(OnboardingState.ManuallySignedIn)
val availableAccounts = accountManager.shareableAccounts(context)
if (availableAccounts.isEmpty()) {
Mode.Onboarding(OnboardingState.SignedOutNoAutoSignIn)
} else {
Mode.Onboarding(OnboardingState.SignedOutCanAutoSignIn(availableAccounts[0]))
}
}
} else if ((activity as HomeActivity).browsingModeManager.isPrivate) {
Mode.Private
@ -719,12 +726,22 @@ class HomeFragment : Fragment(), AccountObserver {
}
private fun emitAccountChanges() {
val mode = currentMode()
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
context?.let {
val mode = currentMode(it)
getManagedEmitter<SessionControlChange>().onNext(SessionControlChange.ModeChange(mode))
}
}
override fun onAuthenticated(account: OAuthAccount) {
view?.let {
FenixSnackbar.make(it, Snackbar.LENGTH_SHORT).setText(
it.context.getString(R.string.onboarding_firefox_account_sync_is_on)
).show()
}
emitAccountChanges()
}
override fun onAuthenticationProblems() = emitAccountChanges()
override fun onAuthenticated(account: OAuthAccount) = emitAccountChanges()
override fun onLoggedOut() = emitAccountChanges()
override fun onProfileUpdated(profile: Profile) = emitAccountChanges()

View File

@ -22,9 +22,10 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.SaveTabGroupViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabInCollectionViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.TabViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingAutomaticSignInViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFinishViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingFirefoxAccountViewHolder
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.OnboardingPrivateBrowsingViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder
@ -67,9 +68,12 @@ sealed class AdapterItem(@LayoutRes val viewType: Int) {
) : AdapterItem(OnboardingSectionHeaderViewHolder.LAYOUT_ID) {
override fun sameAs(other: AdapterItem) = other is OnboardingSectionHeader && labelBuilder == other.labelBuilder
}
data class OnboardingFirefoxAccount(
data class OnboardingManualSignIn(
val state: OnboardingState
) : AdapterItem(OnboardingFirefoxAccountViewHolder.LAYOUT_ID)
) : AdapterItem(OnboardingManualSignInViewHolder.LAYOUT_ID)
data class OnboardingAutomaticSignIn(
val state: OnboardingState
) : AdapterItem(OnboardingAutomaticSignInViewHolder.LAYOUT_ID)
object OnboardingThemePicker : AdapterItem(OnboardingThemePickerViewHolder.LAYOUT_ID)
object OnboardingTrackingProtection : AdapterItem(OnboardingTrackingProtectionViewHolder.LAYOUT_ID)
object OnboardingPrivateBrowsing : AdapterItem(OnboardingPrivateBrowsingViewHolder.LAYOUT_ID)
@ -108,7 +112,8 @@ class SessionControlAdapter(
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter)
OnboardingHeaderViewHolder.LAYOUT_ID -> OnboardingHeaderViewHolder(view)
OnboardingSectionHeaderViewHolder.LAYOUT_ID -> OnboardingSectionHeaderViewHolder(view)
OnboardingFirefoxAccountViewHolder.LAYOUT_ID -> OnboardingFirefoxAccountViewHolder(view)
OnboardingAutomaticSignInViewHolder.LAYOUT_ID -> OnboardingAutomaticSignInViewHolder(view)
OnboardingManualSignInViewHolder.LAYOUT_ID -> OnboardingManualSignInViewHolder(view)
OnboardingThemePickerViewHolder.LAYOUT_ID -> OnboardingThemePickerViewHolder(view)
OnboardingTrackingProtectionViewHolder.LAYOUT_ID -> OnboardingTrackingProtectionViewHolder(view)
OnboardingPrivateBrowsingViewHolder.LAYOUT_ID -> OnboardingPrivateBrowsingViewHolder(view)
@ -145,8 +150,13 @@ class SessionControlAdapter(
is OnboardingSectionHeaderViewHolder -> holder.bind(
(item as AdapterItem.OnboardingSectionHeader).labelBuilder
)
is OnboardingFirefoxAccountViewHolder -> holder.bind(
(item as AdapterItem.OnboardingFirefoxAccount).state == OnboardingState.AutoSignedIn
is OnboardingManualSignInViewHolder -> holder.bind()
is OnboardingAutomaticSignInViewHolder -> holder.bind(
(
(
item as AdapterItem.OnboardingAutomaticSignIn
).state as OnboardingState.SignedOutCanAutoSignIn
).withAccount
)
}
}

View File

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer
import kotlinx.android.parcel.Parcelize
import mozilla.components.browser.session.Session
import mozilla.components.service.fxa.sharing.ShareableAccount
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.mvi.ViewState
import org.mozilla.fenix.mvi.Change
@ -69,13 +70,13 @@ fun List<Tab>.toSessionBundle(context: Context): MutableList<Session> {
/**
* Describes various onboarding states.
*/
enum class OnboardingState {
// Signed out, no account carried over from Fennec.
SignedOut,
// Auto-signed in, via a Fennec account.
AutoSignedIn,
// Manually signed in while in onboarding.
ManuallySignedIn
sealed class OnboardingState {
// Signed out, without an option to auto-login using a shared FxA account.
object SignedOutNoAutoSignIn : OnboardingState()
// Signed out, with an option to auto-login into a shared FxA account.
data class SignedOutCanAutoSignIn(val withAccount: ShareableAccount) : OnboardingState()
// Signed in.
object SignedIn : OnboardingState()
}
sealed class Mode {

View File

@ -79,17 +79,17 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List<Adapt
// Customize FxA items based on where we are with the account state:
items.addAll(when (onboardingState) {
OnboardingState.SignedOut -> {
OnboardingState.SignedOutNoAutoSignIn -> {
listOf(
AdapterItem.OnboardingFirefoxAccount(onboardingState)
AdapterItem.OnboardingManualSignIn(onboardingState)
)
}
OnboardingState.AutoSignedIn -> {
is OnboardingState.SignedOutCanAutoSignIn -> {
listOf(
AdapterItem.OnboardingFirefoxAccount(onboardingState)
AdapterItem.OnboardingAutomaticSignIn(onboardingState)
)
}
else -> listOf()
OnboardingState.SignedIn -> listOf()
})
items.addAll(listOf(

View File

@ -0,0 +1,59 @@
/* 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 com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.turn_on_sync_button
import kotlinx.android.synthetic.main.onboarding_automatic_signin.view.header_text
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.service.fxa.sharing.ShareableAccount
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.ext.components
class OnboardingAutomaticSignInViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
private lateinit var shareableAccount: ShareableAccount
init {
view.turn_on_sync_button.setOnClickListener {
it.turn_on_sync_button.text = it.context.getString(
R.string.onboarding_firefox_account_signing_in
)
it.turn_on_sync_button.isEnabled = false
CoroutineScope(Dispatchers.Main).launch {
val result = view.context.components.backgroundServices.accountManager
.signInWithShareableAccountAsync(shareableAccount).await()
if (result) {
// Success.
} else {
// Failed to sign-in (either network problem, or bad credentials). Allow to try again.
it.turn_on_sync_button.text = it.context.getString(
R.string.onboarding_firefox_account_auto_signin_confirm
)
it.turn_on_sync_button.isEnabled = true
FenixSnackbar.make(it, Snackbar.LENGTH_SHORT).setText(
it.context.getString(R.string.onboarding_firefox_account_automatic_signin_failed)
).show()
}
}
}
}
fun bind(account: ShareableAccount) {
shareableAccount = account
view.header_text.text = view.context.getString(
R.string.onboarding_firefox_account_auto_signin_header_2, account.email
)
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_automatic_signin
}
}

View File

@ -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.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.onboarding_firefox_account.view.*
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelativeWithIntrinsicBounds
import org.mozilla.fenix.R
import org.mozilla.fenix.home.HomeFragmentDirections
class OnboardingFirefoxAccountViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
private val avatarAnonymousDrawable by lazy {
AppCompatResources.getDrawable(view.context, R.drawable.ic_onboarding_avatar_anonymous)
}
private val firefoxAccountsDrawable by lazy {
AppCompatResources.getDrawable(view.context, R.drawable.ic_onboarding_firefox_accounts)
}
init {
view.turn_on_sync_button.setOnClickListener {
val directions = HomeFragmentDirections.actionHomeFragmentToTurnOnSyncFragment()
Navigation.findNavController(view).navigate(directions)
}
}
fun bind(autoSignedIn: Boolean) {
updateHeaderText(autoSignedIn)
updateButtonVisibility(autoSignedIn)
}
private fun updateButtonVisibility(autoSignedIn: Boolean) {
view.turn_on_sync_button.visibility = if (autoSignedIn) View.GONE else View.VISIBLE
view.stay_signed_in_button.visibility = if (autoSignedIn) View.VISIBLE else View.GONE
view.sign_out_button.visibility = if (autoSignedIn) View.VISIBLE else View.GONE
}
private fun updateHeaderText(autoSignedIn: Boolean) {
val icon = if (autoSignedIn) avatarAnonymousDrawable else firefoxAccountsDrawable
view.header_text.putCompoundDrawablesRelativeWithIntrinsicBounds(start = icon)
val appName = view.context.getString(R.string.app_name)
view.header_text.text =
if (autoSignedIn) view.context.getString(R.string.onboarding_firefox_account_auto_signin_header)
else view.context.getString(R.string.onboarding_firefox_account_header, appName)
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_firefox_account
}
}

View File

@ -0,0 +1,30 @@
/* 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 kotlinx.android.synthetic.main.onboarding_manual_signin.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.home.HomeFragmentDirections
class OnboardingManualSignInViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
init {
view.turn_on_sync_button.setOnClickListener {
val directions = HomeFragmentDirections.actionHomeFragmentToTurnOnSyncFragment()
Navigation.findNavController(view).navigate(directions)
}
}
fun bind() {
val appName = view.context.getString(R.string.app_name)
view.header_text.text = view.context.getString(R.string.onboarding_firefox_account_header, appName)
}
companion object {
const val LAYOUT_ID = R.layout.onboarding_manual_signin
}
}

View File

@ -0,0 +1,43 @@
<?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/. -->
<LinearLayout
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"
style="@style/OnboardingCardDark"
android:id="@+id/onboarding_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/header_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
tools:text="@string/onboarding_firefox_account_auto_signin_header_2"
android:drawableStart="@drawable/ic_onboarding_avatar_anonymous"
android:drawablePadding="12dp"
android:textAppearance="@style/Header16TextStyle"
android:textColor="@color/onboarding_card_primary_text_dark" />
<Button
android:id="@+id/turn_on_sync_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/button_background"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:padding="10dp"
android:text="@string/onboarding_firefox_account_auto_signin_confirm"
android:textAllCaps="false"
android:textColor="?neutral"
android:textSize="14sp"
android:textStyle="bold"
app:backgroundTint="@color/onboarding_card_button_background_dark" />
</LinearLayout>

View File

@ -18,7 +18,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
tools:text="@string/onboarding_firefox_account_header"
tools:drawableStart="@drawable/ic_onboarding_firefox_accounts"
android:drawableStart="@drawable/ic_onboarding_firefox_accounts"
android:drawablePadding="12dp"
android:textAppearance="@style/Header16TextStyle"
android:textColor="@color/onboarding_card_primary_text_dark" />
@ -39,38 +39,4 @@
android:textSize="14sp"
android:textStyle="bold"
app:backgroundTint="@color/onboarding_card_button_background_dark" />
<Button
android:id="@+id/stay_signed_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/button_background"
android:backgroundTint="@color/onboarding_card_button_background_dark"
android:clickable="true"
android:focusable="true"
android:gravity="center"
android:padding="10dp"
android:text="@string/onboarding_firefox_account_stay_signed_in"
android:textAllCaps="false"
android:textColor="?neutral"
android:textSize="14sp"
android:textStyle="bold"
android:visibility="gone" />
<Button
android:id="@+id/sign_out_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@drawable/button_background"
android:backgroundTint="@color/onboarding_card_button_background_dark"
android:clickable="true"
android:focusable="true"
android:padding="10dp"
android:text="@string/onboarding_firefox_account_sign_out"
android:textAllCaps="false"
android:textColor="?neutral"
android:textSize="12sp"
android:visibility="gone" />
</LinearLayout>

View File

@ -641,13 +641,19 @@
<string name="onboarding_firefox_account_header">Get the most out of %s.</string>
<!-- 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 -->
<string name="onboarding_firefox_account_auto_signin_header">You are signed in to another Firefox browser on this phone.</string>
<!-- text for the button to sign into your Firefox account. The word "Firefox" should not be translated -->
<string name="onboarding_firefox_account_auto_signin_header_2">You are signed in as %s on another Firefox browser on this phone. Would you like to sign in with this account?</string>
<!-- text for the button to confirm automatic sign-in -->
<string name="onboarding_firefox_account_auto_signin_confirm">Yes, sign me in</string>
<!-- text for the automatic sign-in button while signing in is in process -->
<string name="onboarding_firefox_account_signing_in">Signing in&#8230;</string>
<!-- text for the button to manually sign into Firefox account. The word "Firefox" should not be translated -->
<string name="onboarding_firefox_account_sign_in">Sign in to Firefox</string>
<!-- text for the button to stay signed into your Firefox account. -->
<string name="onboarding_firefox_account_stay_signed_in">Stay signed in</string>
<!-- text for the button to sign out of your Firefox account. -->
<string name="onboarding_firefox_account_sign_out">Sign out</string>
<!-- text for the button to stay signed out when presented with an option to automatically sign-in. -->
<string name="onboarding_firefox_account_stay_signed_out">Stay signed out</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 to display in the snackbar if automatic sign-in fails. user may try again -->
<string name="onboarding_firefox_account_automatic_signin_failed">Failed to sign-in</string>
<!-- text for the tracking protection onboarding card header -->
<string name="onboarding_tracking_protection_header">Protect yourself</string>
<!-- text for the tracking protection card description