From eab9660146704f557472e2cde33fafd8a99d9887 Mon Sep 17 00:00:00 2001 From: Tiger Oakes Date: Fri, 24 Jul 2020 12:34:27 -0700 Subject: [PATCH] For #1146: Extract AccountUiView from settings --- .../main/java/org/mozilla/fenix/ext/String.kt | 12 -- .../fenix/settings/SettingsFragment.kt | 89 +++------------ .../fenix/settings/account/AccountUiView.kt | 103 ++++++++++++++++++ 3 files changed, 118 insertions(+), 86 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt diff --git a/app/src/main/java/org/mozilla/fenix/ext/String.kt b/app/src/main/java/org/mozilla/fenix/ext/String.kt index af5656dfe..5bf8bae38 100644 --- a/app/src/main/java/org/mozilla/fenix/ext/String.kt +++ b/app/src/main/java/org/mozilla/fenix/ext/String.kt @@ -4,13 +4,11 @@ package org.mozilla.fenix.ext -import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri import android.util.Patterns import android.webkit.URLUtil -import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory import androidx.core.net.toUri import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -115,16 +113,6 @@ fun String.simplifiedUrl(): String { return afterScheme } -/** - * Gets a rounded drawable from a URL if possible, else null. - */ -suspend fun String.toRoundedDrawable(context: Context, client: Client) = bitmapForUrl(this, client)?.let { bitmap -> - RoundedBitmapDrawableFactory.create(context.resources, bitmap).also { - it.isCircular = true - it.setAntiAlias(true) - } -} - suspend fun bitmapForUrl(url: String, client: Client): Bitmap? = withContext(Dispatchers.IO) { // Code below will cache it in Gecko's cache, which ensures that as long as we've fetched it once, // we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data'). diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 1a1a2cc2f..4b8a25e88 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.settings import android.content.ActivityNotFoundException -import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build @@ -13,16 +12,13 @@ import android.os.Bundle import android.os.Handler import android.provider.Settings import android.widget.Toast -import androidx.appcompat.content.res.AppCompatResources import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.findNavController import androidx.preference.Preference -import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.delay import kotlinx.coroutines.launch import mozilla.components.concept.sync.AccountObserver @@ -41,19 +37,19 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar -import org.mozilla.fenix.ext.toRoundedDrawable -import org.mozilla.fenix.settings.account.AccountAuthErrorPreference -import org.mozilla.fenix.settings.account.AccountPreference +import org.mozilla.fenix.settings.account.AccountUiView import kotlin.system.exitProcess @Suppress("LargeClass", "TooManyFunctions") class SettingsFragment : PreferenceFragmentCompat() { + private lateinit var accountUiView: AccountUiView + private val accountObserver = object : AccountObserver { private fun updateAccountUi(profile: Profile? = null) { val context = context ?: return lifecycleScope.launch { - updateAccountUIState( + accountUiView.updateAccountUIState( context = context, profile = profile ?: context.components.backgroundServices.accountManager.accountProfile() @@ -75,6 +71,13 @@ class SettingsFragment : PreferenceFragmentCompat() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + accountUiView = AccountUiView( + fragment = this, + accountManager = requireComponents.backgroundServices.accountManager, + httpClient = requireComponents.core.client, + updateFxASyncOverrideMenu = ::updateFxASyncOverrideMenu + ) + // Observe account changes to keep the UI up-to-date. requireComponents.backgroundServices.accountManager.register( accountObserver, @@ -88,7 +91,7 @@ class SettingsFragment : PreferenceFragmentCompat() { // For example, if user is signed-in, and we don't perform this call in onCreate, we'll briefly // display a "Sign In" preference, which will then get replaced by the correct account information // once this call is ran in onResume shortly after. - updateAccountUIState( + accountUiView.updateAccountUIState( requireContext(), requireComponents.backgroundServices.accountManager.accountProfile() ) @@ -162,7 +165,7 @@ class SettingsFragment : PreferenceFragmentCompat() { setupPreferences() if (shouldUpdateAccountUIState) { - updateAccountUIState( + accountUiView.updateAccountUIState( requireContext(), requireComponents.backgroundServices.accountManager.accountProfile() ) @@ -295,9 +298,9 @@ class SettingsFragment : PreferenceFragmentCompat() { } } - preferenceRemoteDebugging?.setOnPreferenceChangeListener { preference, newValue -> + preferenceRemoteDebugging?.setOnPreferenceChangeListener { preference, newValue -> preference.context.settings().preferences.edit() - .putBoolean(preference.key, newValue as Boolean).apply() + .putBoolean(preference.key, newValue).apply() requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue true } @@ -378,68 +381,6 @@ class SettingsFragment : PreferenceFragmentCompat() { } } - /** - * Updates the UI to reflect current account state. - * Possible conditions are logged-in without problems, logged-out, and logged-in but needs to re-authenticate. - */ - private fun updateAccountUIState(context: Context, profile: Profile?) { - val preferenceSignIn = - requirePreference(R.string.pref_key_sign_in) - val preferenceFirefoxAccount = - requirePreference(R.string.pref_key_account) - val preferenceFirefoxAccountAuthError = - requirePreference(R.string.pref_key_account_auth_error) - val accountPreferenceCategory = - requirePreference(R.string.pref_key_account_category) - - val accountManager = requireComponents.backgroundServices.accountManager - val account = accountManager.authenticatedAccount() - - updateFxASyncOverrideMenu() - - // Signed-in, no problems. - if (account != null && !accountManager.accountNeedsReauth()) { - preferenceSignIn.isVisible = false - - profile?.avatar?.url?.let { avatarUrl -> - lifecycleScope.launch(Main) { - val roundedDrawable = - avatarUrl.toRoundedDrawable(context, requireComponents.core.client) - preferenceFirefoxAccount.icon = - roundedDrawable ?: AppCompatResources.getDrawable( - context, - R.drawable.ic_account - ) - } - } - preferenceSignIn.onPreferenceClickListener = null - preferenceFirefoxAccountAuthError.isVisible = false - preferenceFirefoxAccount.isVisible = true - accountPreferenceCategory.isVisible = true - - preferenceFirefoxAccount.displayName = profile?.displayName - preferenceFirefoxAccount.email = profile?.email - - // Signed-in, need to re-authenticate. - } else if (account != null && accountManager.accountNeedsReauth()) { - preferenceFirefoxAccount.isVisible = false - preferenceFirefoxAccountAuthError.isVisible = true - accountPreferenceCategory.isVisible = true - - preferenceSignIn.isVisible = false - preferenceSignIn.onPreferenceClickListener = null - - preferenceFirefoxAccountAuthError.email = profile?.email - - // Signed-out. - } else { - preferenceSignIn.isVisible = true - preferenceFirefoxAccount.isVisible = false - preferenceFirefoxAccountAuthError.isVisible = false - accountPreferenceCategory.isVisible = false - } - } - private fun updateFxASyncOverrideMenu() { val preferenceFxAOverride = findPreference(getPreferenceKey(R.string.pref_key_override_fxa_server)) diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt new file mode 100644 index 000000000..bb5fa64c9 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/account/AccountUiView.kt @@ -0,0 +1,103 @@ +/* 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.settings.account + +import android.content.Context +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceFragmentCompat +import kotlinx.coroutines.launch +import mozilla.components.concept.fetch.Client +import mozilla.components.concept.sync.Profile +import mozilla.components.service.fxa.manager.FxaAccountManager +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.bitmapForUrl +import org.mozilla.fenix.settings.requirePreference + +class AccountUiView( + fragment: PreferenceFragmentCompat, + private val accountManager: FxaAccountManager, + private val httpClient: Client, + private val updateFxASyncOverrideMenu: () -> Unit +) { + + private val lifecycleScope = fragment.viewLifecycleOwner.lifecycleScope + private val preferenceSignIn = + fragment.requirePreference(R.string.pref_key_sign_in) + private val preferenceFirefoxAccount = + fragment.requirePreference(R.string.pref_key_account) + private val preferenceFirefoxAccountAuthError = + fragment.requirePreference(R.string.pref_key_account_auth_error) + private val accountPreferenceCategory = + fragment.requirePreference(R.string.pref_key_account_category) + + /** + * Updates the UI to reflect current account state. + * Possible conditions are logged-in without problems, logged-out, and logged-in but needs to re-authenticate. + */ + fun updateAccountUIState(context: Context, profile: Profile?) { + val account = accountManager.authenticatedAccount() + + updateFxASyncOverrideMenu() + + // Signed-in, no problems. + if (account != null && !accountManager.accountNeedsReauth()) { + preferenceSignIn.isVisible = false + + profile?.avatar?.url?.let { avatarUrl -> + lifecycleScope.launch { + val roundedDrawable = toRoundedDrawable(avatarUrl, context) + preferenceFirefoxAccount.icon = + roundedDrawable ?: AppCompatResources.getDrawable( + context, + R.drawable.ic_account + ) + } + } + + preferenceSignIn.onPreferenceClickListener = null + preferenceFirefoxAccountAuthError.isVisible = false + preferenceFirefoxAccount.isVisible = true + accountPreferenceCategory.isVisible = true + + preferenceFirefoxAccount.displayName = profile?.displayName + preferenceFirefoxAccount.email = profile?.email + + // Signed-in, need to re-authenticate. + } else if (account != null && accountManager.accountNeedsReauth()) { + preferenceFirefoxAccount.isVisible = false + preferenceFirefoxAccountAuthError.isVisible = true + accountPreferenceCategory.isVisible = true + + preferenceSignIn.isVisible = false + preferenceSignIn.onPreferenceClickListener = null + + preferenceFirefoxAccountAuthError.email = profile?.email + + // Signed-out. + } else { + preferenceSignIn.isVisible = true + preferenceFirefoxAccount.isVisible = false + preferenceFirefoxAccountAuthError.isVisible = false + accountPreferenceCategory.isVisible = false + } + } + + /** + * Gets a rounded drawable from a URL if possible, else null. + */ + private suspend fun toRoundedDrawable( + url: String, + context: Context + ) = bitmapForUrl(url, httpClient)?.let { bitmap -> + RoundedBitmapDrawableFactory.create(context.resources, bitmap).apply { + isCircular = true + setAntiAlias(true) + } + } +}