From ae33234bbc08a4d6ca50c48caf7f778bbfb4a10a Mon Sep 17 00:00:00 2001 From: Grisha Kruglov Date: Thu, 25 Jul 2019 18:25:34 -0700 Subject: [PATCH] Closes #2593: FxA automatic sign-in integration --- .../org/mozilla/fenix/home/HomeFragment.kt | 39 ++++++++---- .../sessioncontrol/SessionControlAdapter.kt | 22 +++++-- .../sessioncontrol/SessionControlComponent.kt | 15 ++--- .../sessioncontrol/SessionControlUIView.kt | 10 ++-- .../OnboardingAutomaticSignInViewHolder.kt | 59 +++++++++++++++++++ .../OnboardingFirefoxAccountViewHolder.kt | 55 ----------------- .../OnboardingManualSignInViewHolder.kt | 30 ++++++++++ .../layout/onboarding_automatic_signin.xml | 43 ++++++++++++++ ...count.xml => onboarding_manual_signin.xml} | 36 +---------- app/src/main/res/values/strings.xml | 18 ++++-- 10 files changed, 202 insertions(+), 125 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt delete mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFirefoxAccountViewHolder.kt create mode 100644 app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt create mode 100644 app/src/main/res/layout/onboarding_automatic_signin.xml rename app/src/main/res/layout/{onboarding_firefox_account.xml => onboarding_manual_signin.xml} (56%) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 019570b20..4f37a5972 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -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().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().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().onNext(SessionControlChange.ModeChange(mode)) + context?.let { + val mode = currentMode(it) + getManagedEmitter().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() diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt index e220276ac..0a1f3ee77 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlAdapter.kt @@ -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 ) } } diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt index da21da6e1..687a380a8 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlComponent.kt @@ -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.toSessionBundle(context: Context): MutableList { /** * 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 { diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt index 92b6b5a6c..f249a28d7 100644 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/SessionControlUIView.kt @@ -79,17 +79,17 @@ private fun onboardingAdapterItems(onboardingState: OnboardingState): List { + 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( diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt new file mode 100644 index 000000000..67c5ae2df --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingAutomaticSignInViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFirefoxAccountViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFirefoxAccountViewHolder.kt deleted file mode 100644 index 3e1915aa6..000000000 --- a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingFirefoxAccountViewHolder.kt +++ /dev/null @@ -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 - } -} diff --git a/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt new file mode 100644 index 000000000..cfda858c7 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/sessioncontrol/viewholders/onboarding/OnboardingManualSignInViewHolder.kt @@ -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 + } +} diff --git a/app/src/main/res/layout/onboarding_automatic_signin.xml b/app/src/main/res/layout/onboarding_automatic_signin.xml new file mode 100644 index 000000000..b3579afa6 --- /dev/null +++ b/app/src/main/res/layout/onboarding_automatic_signin.xml @@ -0,0 +1,43 @@ + + + + + + +