1
0
Fork 0

For #1146: Extract AccountUiView from settings

master
Tiger Oakes 2020-07-24 12:34:27 -07:00 committed by Mihai Branescu
parent 5a4c391b52
commit eab9660146
3 changed files with 118 additions and 86 deletions

View File

@ -4,13 +4,11 @@
package org.mozilla.fenix.ext package org.mozilla.fenix.ext
import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.util.Patterns import android.util.Patterns
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.net.toUri import androidx.core.net.toUri
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -115,16 +113,6 @@ fun String.simplifiedUrl(): String {
return afterScheme 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) { 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, // 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'). // we will be able to display this avatar as long as the cache isn't purged (e.g. via 'clear user data').

View File

@ -5,7 +5,6 @@
package org.mozilla.fenix.settings package org.mozilla.fenix.settings
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@ -13,16 +12,13 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.provider.Settings import android.provider.Settings
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver 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.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.ext.toRoundedDrawable import org.mozilla.fenix.settings.account.AccountUiView
import org.mozilla.fenix.settings.account.AccountAuthErrorPreference
import org.mozilla.fenix.settings.account.AccountPreference
import kotlin.system.exitProcess import kotlin.system.exitProcess
@Suppress("LargeClass", "TooManyFunctions") @Suppress("LargeClass", "TooManyFunctions")
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var accountUiView: AccountUiView
private val accountObserver = object : AccountObserver { private val accountObserver = object : AccountObserver {
private fun updateAccountUi(profile: Profile? = null) { private fun updateAccountUi(profile: Profile? = null) {
val context = context ?: return val context = context ?: return
lifecycleScope.launch { lifecycleScope.launch {
updateAccountUIState( accountUiView.updateAccountUIState(
context = context, context = context,
profile = profile profile = profile
?: context.components.backgroundServices.accountManager.accountProfile() ?: context.components.backgroundServices.accountManager.accountProfile()
@ -75,6 +71,13 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) 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. // Observe account changes to keep the UI up-to-date.
requireComponents.backgroundServices.accountManager.register( requireComponents.backgroundServices.accountManager.register(
accountObserver, 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 // 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 // display a "Sign In" preference, which will then get replaced by the correct account information
// once this call is ran in onResume shortly after. // once this call is ran in onResume shortly after.
updateAccountUIState( accountUiView.updateAccountUIState(
requireContext(), requireContext(),
requireComponents.backgroundServices.accountManager.accountProfile() requireComponents.backgroundServices.accountManager.accountProfile()
) )
@ -162,7 +165,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
setupPreferences() setupPreferences()
if (shouldUpdateAccountUIState) { if (shouldUpdateAccountUIState) {
updateAccountUIState( accountUiView.updateAccountUIState(
requireContext(), requireContext(),
requireComponents.backgroundServices.accountManager.accountProfile() requireComponents.backgroundServices.accountManager.accountProfile()
) )
@ -295,9 +298,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
} }
} }
preferenceRemoteDebugging?.setOnPreferenceChangeListener { preference, newValue -> preferenceRemoteDebugging?.setOnPreferenceChangeListener<Boolean> { preference, newValue ->
preference.context.settings().preferences.edit() preference.context.settings().preferences.edit()
.putBoolean(preference.key, newValue as Boolean).apply() .putBoolean(preference.key, newValue).apply()
requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue requireComponents.core.engine.settings.remoteDebuggingEnabled = newValue
true 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<Preference>(R.string.pref_key_sign_in)
val preferenceFirefoxAccount =
requirePreference<AccountPreference>(R.string.pref_key_account)
val preferenceFirefoxAccountAuthError =
requirePreference<AccountAuthErrorPreference>(R.string.pref_key_account_auth_error)
val accountPreferenceCategory =
requirePreference<PreferenceCategory>(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() { private fun updateFxASyncOverrideMenu() {
val preferenceFxAOverride = val preferenceFxAOverride =
findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_fxa_server)) findPreference<Preference>(getPreferenceKey(R.string.pref_key_override_fxa_server))

View File

@ -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<Preference>(R.string.pref_key_sign_in)
private val preferenceFirefoxAccount =
fragment.requirePreference<AccountPreference>(R.string.pref_key_account)
private val preferenceFirefoxAccountAuthError =
fragment.requirePreference<AccountAuthErrorPreference>(R.string.pref_key_account_auth_error)
private val accountPreferenceCategory =
fragment.requirePreference<PreferenceCategory>(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)
}
}
}