1
0
Fork 0

Fix how we reflect FxA state in preference

This cleans up how we're displaying account state in the main preference UI.
Before when it worked, it worked mostly accidentally.
'launch' wrapper around "update ui" methods would trigger a race condition
between binding the account pref view holder and actually updating that view
with values. Sometimes the "update view with values" would happen after view
was bound, and the UI will be correct. Most of the time it would happen before,
and so there will be nothing to update and we'd get into an inconsistent state.

This also splits up the "accountpreference" into two: account is good,
and account needs re-auth. This greatly simplifies their management.
master
Grisha Kruglov 2019-06-03 18:58:39 -07:00 committed by Jeff Boek
parent 0e1d81126d
commit fe3c163a20
10 changed files with 202 additions and 163 deletions

View File

@ -116,7 +116,7 @@ class DefaultToolbarMenu(
highlight = if (hasAccountProblem) { highlight = if (hasAccountProblem) {
BrowserMenuHighlightableItem.Highlight( BrowserMenuHighlightableItem.Highlight(
imageResource = R.drawable.ic_alert, imageResource = R.drawable.ic_alert,
backgroundResource = R.color.sync_error_color backgroundResource = R.color.sync_error_background_color
) )
} else null } else null
) { ) {

View File

@ -0,0 +1,35 @@
/* 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
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R
class AccountAuthErrorPreference : Preference {
var email: String? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, attributeSetId: Int) : super(context, attrs, attributeSetId)
init {
layoutResource = R.layout.account_auth_error_preference
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val emailView = holder.findViewById(R.id.email) as TextView
emailView.text = email.orEmpty()
emailView.visibility = when (email.isNullOrEmpty()) {
true -> View.GONE
false -> View.VISIBLE
}
}
}

View File

@ -7,26 +7,18 @@ package org.mozilla.fenix.settings
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R import org.mozilla.fenix.R
class AccountPreference : Preference { class AccountPreference : Preference {
var displayName: String? = null
var title: TextView? = null var email: String? = null
var summary: TextView? = null
var errorIcon: ImageView? = null
var background: View? = null
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, attributeSetId: Int) : super( constructor(context: Context, attrs: AttributeSet?, attributeSetId: Int) : super(context, attrs, attributeSetId)
context,
attrs,
attributeSetId
)
init { init {
layoutResource = R.layout.account_preference layoutResource = R.layout.account_preference
@ -34,9 +26,18 @@ class AccountPreference : Preference {
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
title = holder.findViewById(R.id.title) as TextView val displayNameView = holder.findViewById(R.id.displayName) as TextView
summary = holder.findViewById(R.id.summary) as TextView val emailView = holder.findViewById(R.id.email) as TextView
errorIcon = holder.findViewById(R.id.error_icon) as ImageView
background = holder.itemView displayNameView.text = displayName.orEmpty()
displayNameView.visibility = when (displayName.isNullOrEmpty()) {
true -> View.GONE
false -> View.VISIBLE
}
// There is a potential for a race condition here. We might not have the user profile by the time we display
// this field, in which case we won't have the email address (or the display name, but that we may just not have
// at all even after fetching the profile). We don't hide the email field or change its text if email is missing
// because in the layout a default value ("Firefox Account") is specified, which will be displayed instead.
email?.let { emailView.text = it }
} }
} }

View File

@ -5,15 +5,14 @@
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.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.navigation.Navigation import androidx.navigation.Navigation
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceClickListener import androidx.preference.Preference.OnPreferenceClickListener
@ -21,12 +20,10 @@ import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile import mozilla.components.concept.sync.Profile
import mozilla.components.service.fxa.FxaUnauthorizedException
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.FenixApplication import org.mozilla.fenix.FenixApplication
@ -35,6 +32,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.R.string.pref_key_about import org.mozilla.fenix.R.string.pref_key_about
import org.mozilla.fenix.R.string.pref_key_accessibility import org.mozilla.fenix.R.string.pref_key_accessibility
import org.mozilla.fenix.R.string.pref_key_account import org.mozilla.fenix.R.string.pref_key_account
import org.mozilla.fenix.R.string.pref_key_account_auth_error
import org.mozilla.fenix.R.string.pref_key_account_category import org.mozilla.fenix.R.string.pref_key_account_category
import org.mozilla.fenix.R.string.pref_key_data_choices import org.mozilla.fenix.R.string.pref_key_data_choices
import org.mozilla.fenix.R.string.pref_key_delete_browsing_data import org.mozilla.fenix.R.string.pref_key_delete_browsing_data
@ -53,18 +51,12 @@ import org.mozilla.fenix.R.string.pref_key_tracking_protection_settings
import org.mozilla.fenix.R.string.pref_key_your_rights import org.mozilla.fenix.R.string.pref_key_your_rights
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.utils.ItsNotBrokenSnack import org.mozilla.fenix.utils.ItsNotBrokenSnack
import kotlin.coroutines.CoroutineContext
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObserver { class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
private val preferenceChangeListener = private val preferenceChangeListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key -> SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
try { try {
@ -83,10 +75,15 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
job = Job()
setupAccountUI() // Observe account changes to keep the UI up-to-date.
updateSignInVisibility() requireComponents.backgroundServices.accountManager.register(this, owner = this, autoPause = true)
displayAccountErrorIfNecessary()
// It's important to update the account UI state in onCreate, even though we also call it in onResume, since
// that ensures we'll never display an incorrect state in the UI. 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(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener) preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
} }
@ -131,9 +128,8 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
aboutPreference?.title = getString(R.string.preferences_about, appName) aboutPreference?.title = getString(R.string.preferences_about, appName)
setupPreferences() setupPreferences()
setupAccountUI()
updateSignInVisibility() updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile())
displayAccountErrorIfNecessary()
} }
@Suppress("ComplexMethod") @Suppress("ComplexMethod")
@ -182,11 +178,10 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
navigateToAbout() navigateToAbout()
} }
resources.getString(pref_key_account) -> { resources.getString(pref_key_account) -> {
if (requireComponents.backgroundServices.accountManager.accountNeedsReauth()) { navigateToAccountSettings()
navigateToAccountProblem() }
} else { resources.getString(pref_key_account_auth_error) -> {
navigateToAccountSettings() navigateToAccountProblem()
}
} }
resources.getString(pref_key_delete_browsing_data) -> { resources.getString(pref_key_delete_browsing_data) -> {
navigateToDeleteBrowsingData() navigateToDeleteBrowsingData()
@ -215,19 +210,9 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
job.cancel()
preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) preferenceManager.sharedPreferences.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
} }
private fun setupAccountUI() {
val accountManager = requireComponents.backgroundServices.accountManager
// Observe account changes to keep the UI up-to-date.
accountManager.register(this, owner = this)
updateAuthState(accountManager.authenticatedAccount())
accountManager.accountProfile()?.let { updateAccountProfile(it) }
}
private fun getClickListenerForSignIn(): OnPreferenceClickListener { private fun getClickListenerForSignIn(): OnPreferenceClickListener {
return OnPreferenceClickListener { return OnPreferenceClickListener {
val directions = SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment() val directions = SettingsFragmentDirections.actionSettingsFragmentToTurnOnSyncFragment()
@ -335,116 +320,85 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
} }
override fun onAuthenticated(account: OAuthAccount) { override fun onAuthenticated(account: OAuthAccount) {
updateAuthState(account) CoroutineScope(Dispatchers.Main).launch {
updateSignInVisibility() context?.let {
} updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
override fun onError(error: Exception) {
// TODO we could display some error states in this UI.
when (error) {
is FxaUnauthorizedException -> {
} }
} }
} }
override fun onError(error: Exception) {}
override fun onLoggedOut() { override fun onLoggedOut() {
updateAuthState() CoroutineScope(Dispatchers.Main).launch {
updateSignInVisibility() context?.let {
updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
}
}
} }
override fun onProfileUpdated(profile: Profile) { override fun onProfileUpdated(profile: Profile) {
updateAccountProfile(profile) CoroutineScope(Dispatchers.Main).launch {
context?.let {
updateAccountUIState(it, profile)
}
}
} }
override fun onAuthenticationProblems() { override fun onAuthenticationProblems() {
displayAccountErrorIfNecessary() CoroutineScope(Dispatchers.Main).launch {
context?.let {
updateAccountUIState(it, it.components.backgroundServices.accountManager.accountProfile())
}
}
} }
// --- Account UI helpers --- /**
private fun updateAuthState(account: OAuthAccount? = null) { * Updates the UI to reflect current account state.
// Cache the user's auth state to improve performance of sign in visibility * Possible conditions are logged-in without problems, logged-out, and logged-in but needs to re-authenticate.
org.mozilla.fenix.utils.Settings.getInstance(context!!).setHasCachedAccount(account != null) */
} private fun updateAccountUIState(context: Context, profile: Profile?) {
private fun updateSignInVisibility() {
val hasCachedAccount = org.mozilla.fenix.utils.Settings.getInstance(context!!).hasCachedAccount
val preferenceSignIn = val preferenceSignIn =
findPreference<Preference>(context!!.getPreferenceKey(pref_key_sign_in)) findPreference<Preference>(context.getPreferenceKey(pref_key_sign_in))
val preferenceFirefoxAccount = val preferenceFirefoxAccount =
findPreference<Preference>(context!!.getPreferenceKey(pref_key_account)) findPreference<AccountPreference>(context.getPreferenceKey(pref_key_account))
val preferenceFirefoxAccountAuthError =
findPreference<AccountAuthErrorPreference>(context.getPreferenceKey(pref_key_account_auth_error))
val accountPreferenceCategory = val accountPreferenceCategory =
findPreference<PreferenceCategory>(context!!.getPreferenceKey(pref_key_account_category)) findPreference<PreferenceCategory>(context.getPreferenceKey(pref_key_account_category))
if (hasCachedAccount) { val accountManager = requireComponents.backgroundServices.accountManager
val account = accountManager.authenticatedAccount()
// Signed-in, no problems.
if (account != null && !accountManager.accountNeedsReauth()) {
preferenceSignIn?.isVisible = false preferenceSignIn?.isVisible = false
preferenceSignIn?.onPreferenceClickListener = null preferenceSignIn?.onPreferenceClickListener = null
preferenceFirefoxAccountAuthError?.isVisible = false
preferenceFirefoxAccount?.isVisible = true preferenceFirefoxAccount?.isVisible = true
accountPreferenceCategory?.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 { } else {
preferenceSignIn?.isVisible = true preferenceSignIn?.isVisible = true
preferenceSignIn?.onPreferenceClickListener = getClickListenerForSignIn() preferenceSignIn?.onPreferenceClickListener = getClickListenerForSignIn()
preferenceFirefoxAccount?.isVisible = false preferenceFirefoxAccount?.isVisible = false
preferenceFirefoxAccountAuthError?.isVisible = false
accountPreferenceCategory?.isVisible = false accountPreferenceCategory?.isVisible = false
} }
} }
private fun updateAccountProfile(profile: Profile) {
launch {
context?.let { context ->
val preferenceFirefoxAccount =
findPreference<AccountPreference>(context.getPreferenceKey(pref_key_account))
preferenceFirefoxAccount?.title?.setTextColor(
R.attr.primaryText.getColorFromAttr(context)
)
preferenceFirefoxAccount?.title?.text = profile.displayName.orEmpty()
preferenceFirefoxAccount?.title?.visibility =
if (preferenceFirefoxAccount?.title?.text.isNullOrEmpty()) View.GONE else View.VISIBLE
preferenceFirefoxAccount?.summary?.setTextColor(
R.attr.primaryText.getColorFromAttr(context)
)
preferenceFirefoxAccount?.summary?.text = profile.email.orEmpty()
preferenceFirefoxAccount?.icon = ContextCompat.getDrawable(context, R.drawable.ic_shortcuts)
preferenceFirefoxAccount?.errorIcon?.visibility = View.GONE
preferenceFirefoxAccount?.background?.background = null
}
}
}
private fun displayAccountErrorIfNecessary() {
launch {
context?.let { context ->
if (!context.components.backgroundServices.accountManager.accountNeedsReauth()) { return@launch }
val preferenceFirefoxAccount =
findPreference<AccountPreference>(context.getPreferenceKey(pref_key_account))
preferenceFirefoxAccount?.title?.setTextColor(
ContextCompat.getColor(
context,
R.color.sync_error_text_color
)
)
preferenceFirefoxAccount?.title?.text = context.getString(R.string.preferences_account_sync_error)
preferenceFirefoxAccount?.title?.visibility =
if (preferenceFirefoxAccount?.title?.text.isNullOrEmpty()) View.GONE else View.VISIBLE
preferenceFirefoxAccount?.summary?.setTextColor(
ContextCompat.getColor(
context,
R.color.sync_error_text_color
)
)
preferenceFirefoxAccount?.summary?.text =
context.components.backgroundServices.accountManager.accountProfile()?.email.orEmpty()
preferenceFirefoxAccount?.icon = ContextCompat.getDrawable(context, R.drawable.ic_account_warning)
preferenceFirefoxAccount?.errorIcon?.visibility = View.VISIBLE
preferenceFirefoxAccount?.background?.background =
ContextCompat.getDrawable(context, R.color.sync_error_color)
}
}
}
} }

View File

@ -152,9 +152,6 @@ class Settings private constructor(context: Context) {
else -> appContext.getString(R.string.preference_light_theme) else -> appContext.getString(R.string.preference_light_theme)
} }
val hasCachedAccount: Boolean
get() = preferences.getBoolean(appContext.getPreferenceKey(R.string.pref_key_cached_account), false)
private val autoBounceQuickActionSheetCount: Int private val autoBounceQuickActionSheetCount: Int
get() = (preferences.getInt(appContext.getPreferenceKey(R.string.pref_key_bounce_quick_action), 0)) get() = (preferences.getInt(appContext.getPreferenceKey(R.string.pref_key_bounce_quick_action), 0))
@ -229,12 +226,6 @@ class Settings private constructor(context: Context) {
) )
} }
fun setHasCachedAccount(isCached: Boolean) {
preferences.edit()
.putBoolean(appContext.getPreferenceKey(R.string.pref_key_cached_account), isCached)
.apply()
}
private val SitePermissionsRules.Action.id: Int private val SitePermissionsRules.Action.id: Int
get() { get() {
return when (this) { return when (this) {

View File

@ -0,0 +1,67 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:background="@color/sync_error_background_color">
<FrameLayout
android:id="@+id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.preference.internal.PreferenceImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:maxWidth="48dp"
app:maxHeight="48dp"/>
</FrameLayout>
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="15dp"
android:layout_marginEnd="6dp"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:layout_weight="1">
<TextView
android:id="@+id/errorSummary"
android:text="@string/preferences_account_sync_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/sync_error_text_color"
android:textSize="16sp"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:visibility="visible"/>
<TextView
android:id="@+id/email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/errorSummary"
android:layout_alignStart="@id/errorSummary"
android:textColor="@color/sync_error_text_color"
android:maxLines="4"
android:visibility="gone"/>
</RelativeLayout>
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"/>
</LinearLayout>

View File

@ -35,21 +35,20 @@
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:layout_weight="1"> android:layout_weight="1">
<TextView android:id="@+id/title" <TextView android:id="@+id/displayName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="?primaryText" android:textColor="?primaryText"
android:singleLine="true"
android:textSize="16sp" android:textSize="16sp"
android:ellipsize="marquee" android:ellipsize="marquee"
android:fadingEdge="horizontal" android:fadingEdge="horizontal"
android:visibility="gone"/> android:visibility="gone"/>
<TextView android:id="@+id/summary" <TextView android:id="@+id/email"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/title" android:layout_below="@id/displayName"
android:layout_alignStart="@id/title" android:layout_alignStart="@id/displayName"
android:textColor="?primaryText" android:textColor="?primaryText"
android:text="@string/preferences_account_default_name" android:text="@string/preferences_account_default_name"
android:maxLines="4"/> android:maxLines="4"/>
@ -62,17 +61,4 @@
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="vertical"/> android:orientation="vertical"/>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/error_icon"
android:src="@drawable/ic_alert"
android:padding="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:gravity="center_vertical"
android:orientation="vertical"
android:visibility="gone"/>
</LinearLayout> </LinearLayout>

View File

@ -139,7 +139,7 @@
<color name="disabled_text">#cccccc</color> <color name="disabled_text">#cccccc</color>
<color name="violet_05">#E7DFFF</color> <color name="violet_05">#E7DFFF</color>
<color name="text_scale_example_text_color">#232749</color> <color name="text_scale_example_text_color">#232749</color>
<color name="sync_error_color">#FFF36E</color> <color name="sync_error_background_color">#FFF36E</color>
<color name="sync_error_text_color">#960E18</color> <color name="sync_error_text_color">#960E18</color>
<!-- Reader View colors --> <!-- Reader View colors -->

View File

@ -22,6 +22,7 @@
<string name="pref_key_your_rights" translatable="false">pref_key_your_rights</string> <string name="pref_key_your_rights" translatable="false">pref_key_your_rights</string>
<string name="pref_key_account" translatable="false">pref_key_account</string> <string name="pref_key_account" translatable="false">pref_key_account</string>
<string name="pref_key_sign_in" translatable="false">pref_key_sign_in</string> <string name="pref_key_sign_in" translatable="false">pref_key_sign_in</string>
<string name="pref_key_account_auth_error" translatable="false">pref_key_account_auth_error</string>
<string name="pref_key_private_mode" translatable="false">pref_key_private_mode</string> <string name="pref_key_private_mode" translatable="false">pref_key_private_mode</string>
<string name="pref_key_theme" translatable="false">pref_key_theme</string> <string name="pref_key_theme" translatable="false">pref_key_theme</string>
<string name="pref_key_leakcanary" translatable="false">pref_key_leakcanary</string> <string name="pref_key_leakcanary" translatable="false">pref_key_leakcanary</string>

View File

@ -14,14 +14,18 @@
android:title="@string/preferences_sign_in" /> android:title="@string/preferences_sign_in" />
<androidx.preference.PreferenceCategory <androidx.preference.PreferenceCategory
android:key = "@string/pref_key_account_category" android:key="@string/pref_key_account_category"
android:title="@string/preferences_category_account" android:title="@string/preferences_category_account"
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:isPreferenceVisible="false"> app:isPreferenceVisible="false">
<org.mozilla.fenix.settings.AccountPreference <org.mozilla.fenix.settings.AccountPreference
android:icon="@drawable/ic_shortcuts" android:icon="@drawable/ic_shortcuts"
android:key="@string/pref_key_account"/> android:key="@string/pref_key_account" />
<org.mozilla.fenix.settings.AccountAuthErrorPreference
android:icon="@drawable/ic_account_warning"
android:key="@string/pref_key_account_auth_error"/>
</androidx.preference.PreferenceCategory> </androidx.preference.PreferenceCategory>
<androidx.preference.PreferenceCategory <androidx.preference.PreferenceCategory