For #1146: Extract AccountUiView from settings
parent
5a4c391b52
commit
eab9660146
|
@ -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').
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue