1
0
Fork 0

No issue: Clean up preferences code (#4699)

master
Tiger Oakes 2019-08-21 11:38:14 -04:00 committed by Sawyer Blatz
parent c1b1c2f332
commit aab357845b
27 changed files with 381 additions and 550 deletions

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.ext
import androidx.annotation.IdRes
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import androidx.navigation.NavDirections
import androidx.navigation.NavOptions
@ -29,3 +30,5 @@ fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, extras: Navigator.E
fun Fragment.nav(@IdRes id: Int?, directions: NavDirections, options: NavOptions) {
findNavController(this).nav(id, directions, options)
}
fun Fragment.getPreferenceKey(@StringRes resourceId: Int): String = getString(resourceId)

View File

@ -21,12 +21,18 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.geckoview.BuildConfig as GeckoViewBuildConfig
/**
* Displays the logo and information about the app, including library versions.
*/
class AboutFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_about, container, false)
}
/**
* Sets the activity title, displays library version strings, and sets up the [view_licenses_button].
*/
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -53,8 +59,8 @@ class AboutFragment : Fragment() {
""
}
val buildDate = BuildConfig.BUILD_DATE
val content = getString(R.string.about_content, appName)
val buildDate = BuildConfig.BUILD_DATE
about_text.text = aboutText
about_content.text = content
@ -62,12 +68,7 @@ class AboutFragment : Fragment() {
view_licenses_button.setOnClickListener {
startActivity(Intent(context, OssLicensesMenuActivity::class.java))
OssLicensesMenuActivity.setActivityTitle(
getString(
R.string.open_source_licenses_title,
getString(R.string.app_name)
)
)
OssLicensesMenuActivity.setActivityTitle(getString(R.string.open_source_licenses_title, appName))
}
}

View File

@ -6,44 +6,64 @@ package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.utils.Settings
/**
* Displays font size controls for accessibility.
*
* Includes an automatic font sizing toggle. When turned on, font sizing follows the Android device settings.
* When turned off, the font sizing can be controlled manually within the app.
*/
class AccessibilityFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).title = getString(R.string.preferences_accessibility)
(activity as AppCompatActivity).supportActionBar?.show()
val textSizePreference =
findPreference<TextPercentageSeekBarPreference>(getString(R.string.pref_key_accessibility_font_scale))
textSizePreference?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
(newValue as? Int).let {
// Value is mapped from 0->30 in steps of 1 so let's convert to float in range 0.5->2.0
val newTextScale = ((newValue as Int * STEP_SIZE) + MIN_SCALE_VALUE).toFloat() / PERCENT_TO_DECIMAL
Settings.getInstance(context!!).fontSizeFactor = newTextScale
requireComponents.core.engine.settings.fontSizeFactor = newTextScale
requireComponents.useCases.sessionUseCases.reload.invoke()
}
true
}
val textSizePreference = findPreference<TextPercentageSeekBarPreference>(
getPreferenceKey(R.string.pref_key_accessibility_font_scale)
)
textSizePreference?.setOnPreferenceChangeListener<Int> { preference, newTextSize ->
val settings = Settings.getInstance(preference.context)
val components = preference.context.components
// Value is mapped from 0->30 in steps of 1 so let's convert to float in range 0.5->2.0
val newTextScale = ((newTextSize * STEP_SIZE) + MIN_SCALE_VALUE).toFloat() / PERCENT_TO_DECIMAL
// Save new text scale value. We assume auto sizing is off if this change listener was called.
settings.fontSizeFactor = newTextScale
components.core.engine.settings.fontSizeFactor = newTextScale
// Reload the current session to reflect the new text scale
components.useCases.sessionUseCases.reload()
true
}
textSizePreference?.isVisible = !Settings.getInstance(context!!).shouldUseAutoSize
val useAutoSizePreference =
findPreference<SwitchPreference>(getString(R.string.pref_key_accessibility_auto_size))
useAutoSizePreference?.setOnPreferenceChangeListener { _, newValue ->
Settings.getInstance(context!!).shouldUseAutoSize = newValue as Boolean
requireComponents.core.engine.settings.automaticFontSizeAdjustment = newValue
if (!newValue) {
requireComponents.core.engine.settings.fontSizeFactor = Settings.getInstance(context!!).fontSizeFactor
findPreference<SwitchPreference>(getPreferenceKey(R.string.pref_key_accessibility_auto_size))
useAutoSizePreference?.setOnPreferenceChangeListener<Boolean> { preference, useAutoSize ->
val settings = Settings.getInstance(preference.context)
val components = preference.context.components
// Save the new setting value
settings.shouldUseAutoSize = useAutoSize
components.core.engine.settings.automaticFontSizeAdjustment = useAutoSize
// If using manual sizing, update the engine settings with the local saved setting
if (!useAutoSize) {
components.core.engine.settings.fontSizeFactor = settings.fontSizeFactor
}
textSizePreference?.isVisible = !newValue
requireComponents.useCases.sessionUseCases.reload.invoke()
// Show the manual sizing controls if automatic sizing is turned off.
textSizePreference?.isVisible = !useAutoSize
// Reload the current session to reflect the new text scale
components.useCases.sessionUseCases.reload()
true
}
}

View File

@ -6,8 +6,8 @@ package org.mozilla.fenix.settings
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.core.view.isGone
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R
@ -32,10 +32,7 @@ class AccountAuthErrorPreference @JvmOverloads constructor(
}
private fun updateEmailView(email: String?) {
emailView?.text = email.orEmpty()
emailView?.visibility = when (email.isNullOrEmpty()) {
true -> View.GONE
false -> View.VISIBLE
}
emailView?.text = email
emailView?.isGone = email.isNullOrEmpty()
}
}

View File

@ -6,8 +6,8 @@ package org.mozilla.fenix.settings
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.TextView
import androidx.core.view.isGone
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R
@ -49,10 +49,7 @@ class AccountPreference @JvmOverloads constructor(
}
private fun updateDisplayName(name: String?) {
displayNameView?.text = name.orEmpty()
displayNameView?.visibility = when (displayName.isNullOrEmpty()) {
true -> View.GONE
false -> View.VISIBLE
}
displayNameView?.text = name
displayNameView?.isGone = displayName.isNullOrEmpty()
}
}

View File

@ -7,7 +7,7 @@ package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.launch
@ -20,66 +20,60 @@ import org.mozilla.fenix.ext.requireComponents
class AccountProblemFragment : PreferenceFragmentCompat(), AccountObserver {
private val signInClickListener = Preference.OnPreferenceClickListener {
requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext())
// TODO The sign-in web content populates session history,
// so pressing "back" after signing in won't take us back into the settings screen, but rather up the
// session history stack.
// We could auto-close this tab once we get to the end of the authentication process?
// Via an interceptor, perhaps.
true
}
private val signOutClickListener = Preference.OnPreferenceClickListener {
nav(
R.id.accountProblemFragment,
AccountProblemFragmentDirections.actionAccountProblemFragmentToSignOutFragment()
)
true
}
override fun onResume() {
super.onResume()
(activity as AppCompatActivity).title = getString(R.string.sync_reconnect)
(activity as AppCompatActivity).supportActionBar?.show()
val accountManager = requireComponents.backgroundServices.accountManager
// We may have fixed our auth problem, in which case close this fragment.
if (requireComponents.backgroundServices.accountManager.authenticatedAccount() != null &&
!requireComponents.backgroundServices.accountManager.accountNeedsReauth()
) {
NavHostFragment.findNavController(this).popBackStack()
if (accountManager.authenticatedAccount() != null && !accountManager.accountNeedsReauth()) {
findNavController().popBackStack()
return
}
requireComponents.backgroundServices.accountManager.register(this, owner = this)
accountManager.register(this, owner = this)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sync_problem, rootKey)
val preferenceSignIn =
findPreference<Preference>(context!!.getPreferenceKey(R.string.pref_key_sync_sign_in))
findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_sign_in))
val preferenceSignOut =
findPreference<Preference>(context!!.getPreferenceKey(R.string.pref_key_sign_out))
preferenceSignIn?.onPreferenceClickListener = getClickListenerForSignIn()
preferenceSignOut?.onPreferenceClickListener = getClickListenerForSignOut()
}
private fun getClickListenerForSignIn(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener {
requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext())
// TODO The sign-in web content populates session history,
// so pressing "back" after signing in won't take us back into the settings screen, but rather up the
// session history stack.
// We could auto-close this tab once we get to the end of the authentication process?
// Via an interceptor, perhaps.
true
}
}
private fun getClickListenerForSignOut(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener {
nav(
R.id.accountProblemFragment,
AccountProblemFragmentDirections.actionAccountProblemFragmentToSignOutFragment()
)
true
}
findPreference<Preference>(getPreferenceKey(R.string.pref_key_sign_out))
preferenceSignIn?.onPreferenceClickListener = signInClickListener
preferenceSignOut?.onPreferenceClickListener = signOutClickListener
}
// We're told our auth problems have been fixed; close this fragment.
override fun onAuthenticated(account: OAuthAccount, newAccount: Boolean) {
lifecycleScope.launch {
NavHostFragment.findNavController(this@AccountProblemFragment).popBackStack()
}
}
override fun onAuthenticated(account: OAuthAccount, newAccount: Boolean) = closeFragment()
// We're told there are no more auth problems since there is no more account; close this fragment.
override fun onLoggedOut() {
override fun onLoggedOut() = closeFragment()
private fun closeFragment() {
lifecycleScope.launch {
NavHostFragment.findNavController(this@AccountProblemFragment).popBackStack()
findNavController().popBackStack()
}
}
}

View File

@ -11,14 +11,18 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.utils.Settings
/**
* Lets the user toggle telemetry on/off.
*/
class DataChoicesFragment : PreferenceFragmentCompat() {
private val preferenceChangeListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
when (key) {
getString(R.string.pref_key_telemetry) -> {
getPreferenceKey(R.string.pref_key_telemetry) -> {
if (sharedPreferences.getBoolean(key, Settings.getInstance(requireContext()).isTelemetryEnabled)) {
context?.components?.analytics?.metrics?.start()
} else {
@ -51,16 +55,13 @@ class DataChoicesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.data_choices_preferences, rootKey)
val telemetryPreference = findPreference<SwitchPreference>(getString(R.string.pref_key_telemetry))?.apply {
findPreference<SwitchPreference>(getPreferenceKey(R.string.pref_key_telemetry))?.apply {
isChecked = Settings.getInstance(context).isTelemetryEnabled
val appName = context.getString(R.string.app_name)
summary = context.getString(R.string.preferences_usage_data_description, appName)
}
telemetryPreference?.setOnPreferenceChangeListener { preference, newValue ->
Settings.getInstance(preference.context).preferences.edit().putBoolean(preference.key, newValue as Boolean)
.apply()
true
onPreferenceChangeListener = SharedPreferenceUpdater()
}
}
}

View File

@ -4,82 +4,22 @@
package org.mozilla.fenix.settings
import android.content.Context
import android.view.View
import android.widget.RadioButton
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.preference.Preference
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager
internal fun SitePermissionsRules.Action.toString(context: Context): String {
return when (this) {
SitePermissionsRules.Action.ASK_TO_ALLOW -> {
context.getString(R.string.preference_option_phone_feature_ask_to_allow)
}
SitePermissionsRules.Action.BLOCKED -> {
context.getString(R.string.preference_option_phone_feature_blocked)
}
}
}
internal fun SitePermissions.Status.toString(context: Context): String {
return when (this) {
SitePermissions.Status.BLOCKED -> {
context.getString(R.string.preference_option_phone_feature_blocked)
}
SitePermissions.Status.NO_DECISION -> {
context.getString(R.string.preference_option_phone_feature_ask_to_allow)
}
SitePermissions.Status.ALLOWED -> {
context.getString(R.string.preference_option_phone_feature_allowed)
}
}
}
fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions {
return when (featurePhone) {
PhoneFeature.CAMERA -> {
copy(
camera = camera.toggle()
)
}
PhoneFeature.LOCATION -> {
copy(
location = location.toggle()
)
}
PhoneFeature.MICROPHONE -> {
copy(
microphone = microphone.toggle()
)
}
PhoneFeature.NOTIFICATION -> {
copy(
notification = notification.toggle()
)
}
}
}
fun PhoneFeature.getLabel(context: Context): String {
return when (this) {
PhoneFeature.CAMERA -> context.getString(R.string.preference_phone_feature_camera)
PhoneFeature.LOCATION -> context.getString(R.string.preference_phone_feature_location)
PhoneFeature.MICROPHONE -> context.getString(R.string.preference_phone_feature_microphone)
PhoneFeature.NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification)
}
}
fun PhoneFeature.getPreferenceKey(context: Context): String {
return when (this) {
PhoneFeature.CAMERA -> context.getString(R.string.pref_key_phone_feature_camera)
PhoneFeature.LOCATION -> context.getString(R.string.pref_key_phone_feature_location)
PhoneFeature.MICROPHONE -> context.getString(R.string.pref_key_phone_feature_microphone)
PhoneFeature.NOTIFICATION -> context.getString(R.string.pref_key_phone_feature_notification)
PhoneFeature.CAMERA -> copy(camera = camera.toggle())
PhoneFeature.LOCATION -> copy(location = location.toggle())
PhoneFeature.MICROPHONE -> copy(microphone = microphone.toggle())
PhoneFeature.NOTIFICATION -> copy(notification = notification.toggle())
}
}
@ -111,3 +51,18 @@ fun initBlockedByAndroidView(phoneFeature: PhoneFeature, blockedByAndroidView: V
blockedByAndroidView.visibility = View.GONE
}
}
/**
* Sets the callback to be invoked when this preference is changed by the user (but before
* the internal state has been updated). Allows the type of the preference to be specified.
* If the new value doesn't match the preference type the listener isn't called.
*
* @param onPreferenceChangeListener The callback to be invoked
*/
inline fun <reified T> Preference.setOnPreferenceChangeListener(
crossinline onPreferenceChangeListener: (Preference, T) -> Boolean
) {
setOnPreferenceChangeListener { preference: Preference, newValue: Any ->
(newValue as? T)?.let { onPreferenceChangeListener(preference, it) } ?: false
}
}

View File

@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_pair.*
import mozilla.components.feature.qr.QrFeature
import mozilla.components.support.base.feature.BackHandler
@ -68,8 +69,7 @@ class PairFragment : Fragment(), BackHandler {
override fun onBackPressed(): Boolean {
qrFeature.onBackPressed()
findNavController(this@PairFragment)
.popBackStack(R.id.turnOnSyncFragment, false)
findNavController().popBackStack(R.id.turnOnSyncFragment, false)
return true
}

View File

@ -8,8 +8,12 @@ import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.RECORD_AUDIO
import android.content.Context
import androidx.annotation.StringRes
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.support.ktx.android.content.isPermissionGranted
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.utils.Settings
import android.Manifest.permission.CAMERA as CAMERA_PERMISSION
@ -32,57 +36,50 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
}
fun getActionLabel(context: Context, sitePermissions: SitePermissions? = null, settings: Settings? = null): String {
val label = when (this) {
CAMERA -> {
sitePermissions?.camera?.toString(context) ?: settings
?.sitePermissionsPhoneFeatureCameraAction
?.toString(context)
}
LOCATION -> {
sitePermissions?.location?.toString(context) ?: settings
?.sitePermissionsPhoneFeatureLocation
?.toString(context)
}
MICROPHONE -> {
sitePermissions?.microphone?.toString(context) ?: settings
?.sitePermissionsPhoneFeatureMicrophoneAction
?.toString(context)
}
NOTIFICATION -> {
sitePermissions?.notification?.toString(context) ?: settings
?.sitePermissionsPhoneFeatureNotificationAction
?.toString(context)
}
@StringRes val stringRes = when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
}
return requireNotNull(label)
return context.getString(stringRes)
}
fun getStatus(sitePermissions: SitePermissions? = null, settings: Settings? = null): SitePermissions.Status {
val status = when (this) {
CAMERA -> {
sitePermissions?.camera ?: settings
?.sitePermissionsPhoneFeatureCameraAction
?.toStatus()
}
LOCATION -> {
sitePermissions?.location ?: settings
?.sitePermissionsPhoneFeatureLocation
?.toStatus()
}
MICROPHONE -> {
sitePermissions?.microphone ?: settings
?.sitePermissionsPhoneFeatureMicrophoneAction
?.toStatus()
}
NOTIFICATION -> {
sitePermissions?.notification ?: settings
?.sitePermissionsPhoneFeatureNotificationAction
?.toStatus()
}
}
val status = getStatus(sitePermissions) ?: settings?.let(::getAction)?.toStatus()
return requireNotNull(status)
}
fun getLabel(context: Context): String {
return when (this) {
CAMERA -> context.getString(R.string.preference_phone_feature_camera)
LOCATION -> context.getString(R.string.preference_phone_feature_location)
MICROPHONE -> context.getString(R.string.preference_phone_feature_microphone)
NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification)
}
}
fun getPreferenceKey(context: Context): String {
return when (this) {
CAMERA -> context.getPreferenceKey(R.string.pref_key_phone_feature_camera)
LOCATION -> context.getPreferenceKey(R.string.pref_key_phone_feature_location)
MICROPHONE -> context.getPreferenceKey(R.string.pref_key_phone_feature_microphone)
NOTIFICATION -> context.getPreferenceKey(R.string.pref_key_phone_feature_notification)
}
}
fun getAction(settings: Settings): SitePermissionsRules.Action =
settings.getSitePermissionsPhoneFeatureAction(this)
private fun getStatus(sitePermissions: SitePermissions?): SitePermissions.Status? {
sitePermissions ?: return null
return when (this) {
CAMERA -> sitePermissions.camera
LOCATION -> sitePermissions.location
MICROPHONE -> sitePermissions.microphone
NOTIFICATION -> sitePermissions.notification
}
}
companion object {
fun findFeatureBy(permissions: Array<out String>): PhoneFeature? {
return PhoneFeature.values().find { feature ->

View File

@ -9,6 +9,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.utils.Settings
class SearchEngineFragment : PreferenceFragmentCompat() {
@ -23,25 +24,17 @@ class SearchEngineFragment : PreferenceFragmentCompat() {
(activity as AppCompatActivity).supportActionBar?.show()
val searchSuggestionsPreference =
findPreference<SwitchPreference>(getString(R.string.pref_key_show_search_suggestions))?.apply {
findPreference<SwitchPreference>(getPreferenceKey(R.string.pref_key_show_search_suggestions))?.apply {
isChecked = Settings.getInstance(context).showSearchSuggestions
}
searchSuggestionsPreference?.setOnPreferenceChangeListener { preference, newValue ->
Settings.getInstance(preference.context).preferences.edit().putBoolean(preference.key, newValue as Boolean)
.apply()
true
}
searchSuggestionsPreference?.onPreferenceChangeListener = SharedPreferenceUpdater()
val showVisitedSitesBookmarks =
findPreference<SwitchPreference>(getString(R.string.pref_key_show_visited_sites_bookmarks))?.apply {
findPreference<SwitchPreference>(getPreferenceKey(R.string.pref_key_show_visited_sites_bookmarks))?.apply {
isChecked = Settings.getInstance(context).shouldShowVisitedSitesBookmarks
}
showVisitedSitesBookmarks?.setOnPreferenceChangeListener { preference, newValue ->
Settings.getInstance(preference.context).preferences.edit().putBoolean(preference.key, newValue as Boolean)
.apply()
true
}
showVisitedSitesBookmarks?.onPreferenceChangeListener = SharedPreferenceUpdater()
}
}

View File

@ -90,7 +90,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
if (SDK_INT <= Build.VERSION_CODES.M) {
findPreference<DefaultBrowserPreference>(getString(R.string.pref_key_make_default_browser))?.apply {
findPreference<DefaultBrowserPreference>(getPreferenceKey(R.string.pref_key_make_default_browser))?.apply {
isVisible = false
}
}
@ -106,17 +106,17 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
(activity as AppCompatActivity).title = getString(R.string.settings_title)
(activity as AppCompatActivity).supportActionBar?.show()
val defaultBrowserPreference =
findPreference<DefaultBrowserPreference>(getString(R.string.pref_key_make_default_browser))
findPreference<DefaultBrowserPreference>(getPreferenceKey(R.string.pref_key_make_default_browser))
defaultBrowserPreference?.updateSwitch()
val searchEnginePreference =
findPreference<Preference>(getString(R.string.pref_key_search_engine_settings))
findPreference<Preference>(getPreferenceKey(R.string.pref_key_search_engine_settings))
searchEnginePreference?.summary = context?.let {
requireComponents.search.searchEngineManager.getDefaultSearchEngine(it).name
}
val trackingProtectionPreference =
findPreference<Preference>(getString(R.string.pref_key_tracking_protection_settings))
findPreference<Preference>(getPreferenceKey(R.string.pref_key_tracking_protection_settings))
trackingProtectionPreference?.summary = context?.let {
if (org.mozilla.fenix.utils.Settings.getInstance(it).shouldUseTrackingProtection) {
getString(R.string.tracking_protection_on)
@ -126,12 +126,12 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
}
val themesPreference =
findPreference<Preference>(getString(R.string.pref_key_theme))
findPreference<Preference>(getPreferenceKey(R.string.pref_key_theme))
themesPreference?.summary = context?.let {
org.mozilla.fenix.utils.Settings.getInstance(it).themeSettingString
}
val aboutPreference = findPreference<Preference>(getString(R.string.pref_key_about))
val aboutPreference = findPreference<Preference>(getPreferenceKey(R.string.pref_key_about))
val appName = getString(R.string.app_name)
aboutPreference?.title = getString(R.string.preferences_about, appName)
@ -198,16 +198,16 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
navigateToThemeSettings()
}
resources.getString(pref_key_privacy_link) -> {
requireContext().apply {
val intent = SupportUtils.createCustomTabIntent(this, SupportUtils.PRIVACY_NOTICE_URL)
requireContext().let { context ->
val intent = SupportUtils.createCustomTabIntent(context, SupportUtils.PRIVACY_NOTICE_URL)
startActivity(intent)
}
}
resources.getString(pref_key_your_rights) -> {
requireContext().apply {
requireContext().let { context ->
val intent = SupportUtils.createCustomTabIntent(
this,
SupportUtils.getSumoURLForTopic(context!!, SupportUtils.SumoTopic.YOUR_RIGHTS)
context,
SupportUtils.getSumoURLForTopic(context, SupportUtils.SumoTopic.YOUR_RIGHTS)
)
startActivity(intent)
}
@ -229,9 +229,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
}
private fun setupPreferences() {
val makeDefaultBrowserKey = context!!.getPreferenceKey(pref_key_make_default_browser)
val leakKey = context!!.getPreferenceKey(pref_key_leakcanary)
val debuggingKey = context!!.getPreferenceKey(pref_key_remote_debugging)
val makeDefaultBrowserKey = getPreferenceKey(pref_key_make_default_browser)
val leakKey = getPreferenceKey(pref_key_leakcanary)
val debuggingKey = getPreferenceKey(pref_key_remote_debugging)
val preferenceMakeDefaultBrowser = findPreference<Preference>(makeDefaultBrowserKey)
val preferenceLeakCanary = findPreference<Preference>(leakKey)

View File

@ -0,0 +1,20 @@
package org.mozilla.fenix.settings
import androidx.core.content.edit
import androidx.preference.Preference
import org.mozilla.fenix.utils.Settings
/**
* Updates the corresponding [android.content.SharedPreferences] when the boolean [Preference] is changed.
* The preference key is used as the shared preference key.
*/
class SharedPreferenceUpdater : Preference.OnPreferenceChangeListener {
override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
val newBooleanValue = newValue as? Boolean ?: return false
Settings.getInstance(preference.context).preferences.edit {
putBoolean(preference.key, newBooleanValue)
}
return true
}
}

View File

@ -19,6 +19,7 @@ import org.jetbrains.anko.noButton
import org.jetbrains.anko.yesButton
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.settings.PhoneFeature.CAMERA
import org.mozilla.fenix.settings.PhoneFeature.LOCATION
import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE
@ -83,7 +84,7 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat() {
}
private fun bindClearPermissionsButton() {
val keyPreference = getString(R.string.pref_key_exceptions_clear_site_permissions)
val keyPreference = getPreferenceKey(R.string.pref_key_exceptions_clear_site_permissions)
val button: Preference = requireNotNull(findPreference(keyPreference))
button.onPreferenceClickListener = Preference.OnPreferenceClickListener {

View File

@ -11,10 +11,7 @@ import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceClickListener
import androidx.preference.PreferenceFragmentCompat
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.PhoneFeature.CAMERA
import org.mozilla.fenix.settings.PhoneFeature.LOCATION
import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE
import org.mozilla.fenix.settings.PhoneFeature.NOTIFICATION
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.utils.Settings
@SuppressWarnings("TooManyFunctions")
@ -36,13 +33,12 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
}
private fun setupPreferences() {
bindCategoryPhoneFeatures()
bindExceptions()
}
private fun bindExceptions() {
val keyExceptions = getString(R.string.pref_key_show_site_exceptions)
val keyExceptions = getPreferenceKey(R.string.pref_key_show_site_exceptions)
val exceptionsCategory = requireNotNull<Preference>(findPreference(keyExceptions))
exceptionsCategory.onPreferenceClickListener = OnPreferenceClickListener {
@ -53,33 +49,17 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
}
private fun bindCategoryPhoneFeatures() {
val settings = Settings.getInstance(requireContext())
val cameraAction = settings
.sitePermissionsPhoneFeatureCameraAction
.toString(requireContext())
val locationAction = settings
.sitePermissionsPhoneFeatureLocation
.toString(requireContext())
val microPhoneAction = settings
.sitePermissionsPhoneFeatureMicrophoneAction
.toString(requireContext())
val notificationAction = settings
.sitePermissionsPhoneFeatureNotificationAction
.toString(requireContext())
initPhoneFeature(CAMERA, cameraAction)
initPhoneFeature(LOCATION, locationAction)
initPhoneFeature(MICROPHONE, microPhoneAction)
initPhoneFeature(NOTIFICATION, notificationAction)
PhoneFeature.values().forEach(::initPhoneFeature)
}
private fun initPhoneFeature(phoneFeature: PhoneFeature, summary: String) {
val keyPreference = phoneFeature.getPreferenceKey(requireContext())
val cameraPhoneFeatures: Preference = requireNotNull(findPreference(keyPreference))
private fun initPhoneFeature(phoneFeature: PhoneFeature) {
val context = requireContext()
val settings = Settings.getInstance(context)
val summary = phoneFeature.getActionLabel(context, settings = settings)
val preferenceKey = phoneFeature.getPreferenceKey(context)
val cameraPhoneFeatures: Preference = requireNotNull(findPreference(preferenceKey))
cameraPhoneFeatures.summary = summary
cameraPhoneFeatures.onPreferenceClickListener = OnPreferenceClickListener {

View File

@ -92,7 +92,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
private fun RadioButton.restoreState(action: SitePermissionsRules.Action) {
if (phoneFeature.action == action) {
if (phoneFeature.getAction(settings) == action) {
this.isChecked = true
this.setStartCheckedIndicator()
}
@ -119,16 +119,6 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
}
private val PhoneFeature.action: SitePermissionsRules.Action
get() {
return when (phoneFeature) {
PhoneFeature.CAMERA -> settings.sitePermissionsPhoneFeatureCameraAction
PhoneFeature.LOCATION -> settings.sitePermissionsPhoneFeatureLocation
PhoneFeature.MICROPHONE -> settings.sitePermissionsPhoneFeatureMicrophoneAction
PhoneFeature.NOTIFICATION -> settings.sitePermissionsPhoneFeatureNotificationAction
}
}
private fun initSettingsButton(rootView: View) {
val button = rootView.findViewById<Button>(R.id.settings_button)
button.setOnClickListener {
@ -144,11 +134,6 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
private fun saveActionInSettings(action: SitePermissionsRules.Action) {
when (phoneFeature) {
PhoneFeature.CAMERA -> settings.sitePermissionsPhoneFeatureCameraAction = action
PhoneFeature.LOCATION -> settings.sitePermissionsPhoneFeatureLocation = action
PhoneFeature.MICROPHONE -> settings.sitePermissionsPhoneFeatureMicrophoneAction = action
PhoneFeature.NOTIFICATION -> settings.sitePermissionsPhoneFeatureNotificationAction = action
}
settings.setSitePermissionsPhoneFeatureAction(phoneFeature, action)
}
}

View File

@ -39,23 +39,19 @@ import org.mozilla.fenix.R
import java.text.NumberFormat
/**
* Preference based on android.preference.SeekBarPreference but uses support preference as a base
* . It contains a title and a [SeekBar] and a SeekBar value [TextView] and an Example [TextView].
* Preference based on android.preference.SeekBarPreference but uses support preference as a base.
* It contains a title and a [SeekBar] and a SeekBar value [TextView] and an Example [TextView].
* The actual preference layout is customizable by setting `android:layout` on the
* preference widget layout or `seekBarPreferenceStyle` attribute.
*
*
* The [SeekBar] within the preference can be defined adjustable or not by setting `adjustable` attribute.
* If adjustable, the preference will be responsive to DPAD left/right keys.
* Otherwise, it skips those keys.
*
*
* The [SeekBar] value view can be shown or disabled by setting `showSeekBarValue`
* attribute to true or false, respectively.
*
*
* Other [SeekBar] specific attributes (e.g. `title, summary, defaultValue, min,
* max`)
* Other [SeekBar] specific attributes (e.g. `title, summary, defaultValue, min, max`)
* can be set directly on the preference widget layout.
*/
class TextPercentageSeekBarPreference @JvmOverloads constructor(
@ -64,21 +60,32 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
defStyleAttr: Int = R.attr.seekBarPreferenceStyle,
defStyleRes: Int = 0
) : Preference(context, attrs, defStyleAttr, defStyleRes) {
internal /* synthetic access */ var mSeekBarValue: Int = 0
internal /* synthetic access */ var mMin: Int = 0
/* synthetic access */
internal var mSeekBarValue: Int = 0
/* synthetic access */
internal var mMin: Int = 0
private var mMax: Int = 0
private var mSeekBarIncrement: Int = 0
internal /* synthetic access */ var mTrackingTouch: Boolean = false
internal /* synthetic access */ var mSeekBar: SeekBar? = null
/* synthetic access */
internal var mTrackingTouch: Boolean = false
/* synthetic access */
internal var mSeekBar: SeekBar? = null
private var mSeekBarValueTextView: TextView? = null
private var mExampleTextTextView: TextView? = null
// Whether the SeekBar should respond to the left/right keys
/* synthetic access */ var isAdjustable: Boolean = false
// Whether to show the SeekBar value TextView next to the bar
/**
* Whether the SeekBar should respond to the left/right keys
*/
/* synthetic access */
var isAdjustable: Boolean = false
/**
* Whether to show the SeekBar value TextView next to the bar
*/
private var mShowSeekBarValue: Boolean = false
// Whether the SeekBarPreference should continuously save the Seekbar value while it is being
// dragged.
/* synthetic access */ var updatesContinuously: Boolean = false
/**
* Whether the SeekBarPreference should continuously save the Seekbar value while it is being dragged.
*/
/* synthetic access */
var updatesContinuously: Boolean = false
/**
* Listener reacting to the [SeekBar] changing value by the user
*/
@ -111,26 +118,21 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
* to be handled accordingly.
*/
private val mSeekBarKeyListener = View.OnKeyListener { _, keyCode, event ->
if (event.action != KeyEvent.ACTION_DOWN) {
return@OnKeyListener false
}
if (!isAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
return@OnKeyListener if (event.action != KeyEvent.ACTION_DOWN) {
false
} else if (!isAdjustable && (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT)) {
// Right or left keys are pressed when in non-adjustable mode; Skip the keys.
return@OnKeyListener false
}
// We don't want to propagate the click keys down to the SeekBar view since it will
// create the ripple effect for the thumb.
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
return@OnKeyListener false
}
if (mSeekBar == null) {
false
} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
// We don't want to propagate the click keys down to the SeekBar view since it will
// create the ripple effect for the thumb.
false
} else if (mSeekBar == null) {
Log.e(TAG, "SeekBar view is null and hence cannot be adjusted.")
return@OnKeyListener false
false
} else {
mSeekBar!!.onKeyDown(keyCode, event)
}
mSeekBar!!.onKeyDown(keyCode, event)
}
/**
@ -158,13 +160,14 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
* from the default mKeyProgressIncrement value in [android.widget.AbsSeekBar].
* @return The amount of increment on the [SeekBar] performed after each user's arrow
* key press
*
* Sets the increment amount on the [SeekBar] for each arrow key press.
* @param seekBarIncrement The amount to increment or decrement when the user presses an
* arrow key.
*/
var seekBarIncrement: Int
get() = mSeekBarIncrement
/**
* Sets the increment amount on the [SeekBar] for each arrow key press.
* @param seekBarIncrement The amount to increment or decrement when the user presses an
* arrow key.
*/
set(seekBarIncrement) {
if (seekBarIncrement != mSeekBarIncrement) {
mSeekBarIncrement = Math.min(mMax - mMin, Math.abs(seekBarIncrement))
@ -173,10 +176,7 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
}
/**
* Gets the upper bound set on the [SeekBar].
* @return The upper bound set
* Sets the upper bound on the [SeekBar].
* @param max The upper bound to set
* Gets/Sets the upper bound set on the [SeekBar].
*/
var max: Int
get() = mMax
@ -195,29 +195,27 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
* Gets whether the current [SeekBar] value is displayed to the user.
* @return Whether the current [SeekBar] value is displayed to the user
* @see .setShowSeekBarValue
* Sets whether the current [SeekBar] value is displayed to the user.
* @param showSeekBarValue Whether the current [SeekBar] value is displayed to the user
* @see .getShowSeekBarValue
*/
var showSeekBarValue: Boolean
get() = mShowSeekBarValue
/**
* Sets whether the current [SeekBar] value is displayed to the user.
* @param showSeekBarValue Whether the current [SeekBar] value is displayed to the user
* @see .getShowSeekBarValue
*/
set(showSeekBarValue) {
mShowSeekBarValue = showSeekBarValue
notifyChanged()
}
/**
* Gets the current progress of the [SeekBar].
* @return The current progress of the [SeekBar]
* Sets the current progress of the [SeekBar].
* @param seekBarValue The current progress of the [SeekBar]
* Gets/Sets the current progress of the [SeekBar].
*/
var value: Int
get() = mSeekBarValue
set(seekBarValue) = setValueInternal(seekBarValue, true)
init {
val a = context.obtainStyledAttributes(
attrs, R.styleable.SeekBarPreference, defStyleAttr, defStyleRes
)
@ -308,7 +306,8 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
* Persist the [SeekBar]'s SeekBar value if callChangeListener returns true, otherwise
* set the [SeekBar]'s value to the stored value.
*/
internal /* synthetic access */ fun syncValueInternal(seekBar: SeekBar) {
/* synthetic access */
internal fun syncValueInternal(seekBar: SeekBar) {
val seekBarValue = mMin + seekBar.progress
if (seekBarValue != mSeekBarValue) {
if (callChangeListener(seekBarValue)) {
@ -324,9 +323,10 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
/**
* Attempts to update the TextView label that displays the current value.
*
* @param value the value to display next to the [SeekBar]
* @param labelValue the value to display next to the [SeekBar]
*/
internal /* synthetic access */ fun updateLabelValue(labelValue: Int) {
/* synthetic access */
internal fun updateLabelValue(labelValue: Int) {
var value = labelValue
if (mSeekBarValueTextView != null) {
value = value * STEP_SIZE + MIN_VALUE
@ -339,9 +339,9 @@ class TextPercentageSeekBarPreference @JvmOverloads constructor(
/**
* Attempts to update the example TextView text with text scale size.
*
* @param value the value of text size
* @param textValue the value of text size
*/
internal /* synthetic access */ fun updateExampleTextValue(textValue: Int) {
internal fun updateExampleTextValue(textValue: Int) {
var value = textValue
if (mExampleTextTextView != null) {
value = value * STEP_SIZE + MIN_VALUE

View File

@ -12,6 +12,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceFragmentCompat
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
class ThemeFragment : PreferenceFragmentCompat() {
@ -60,7 +61,7 @@ class ThemeFragment : PreferenceFragmentCompat() {
}
private fun bindLightTheme() {
val keyLightTheme = getString(R.string.pref_key_light_theme)
val keyLightTheme = getPreferenceKey(R.string.pref_key_light_theme)
radioLightTheme = requireNotNull(findPreference(keyLightTheme))
radioLightTheme.onClickListener {
setNewTheme(AppCompatDelegate.MODE_NIGHT_NO)
@ -70,7 +71,7 @@ class ThemeFragment : PreferenceFragmentCompat() {
@SuppressLint("WrongConstant")
// Suppressing erroneous lint warning about using MODE_NIGHT_AUTO_BATTERY, a likely library bug
private fun bindAutoBatteryTheme() {
val keyBatteryTheme = getString(R.string.pref_key_auto_battery_theme)
val keyBatteryTheme = getPreferenceKey(R.string.pref_key_auto_battery_theme)
radioAutoBatteryTheme = requireNotNull(findPreference(keyBatteryTheme))
radioAutoBatteryTheme.onClickListener {
setNewTheme(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
@ -78,7 +79,7 @@ class ThemeFragment : PreferenceFragmentCompat() {
}
private fun bindDarkTheme() {
val keyDarkTheme = getString(R.string.pref_key_dark_theme)
val keyDarkTheme = getPreferenceKey(R.string.pref_key_dark_theme)
radioDarkTheme = requireNotNull(findPreference(keyDarkTheme))
radioDarkTheme.onClickListener {
setNewTheme(AppCompatDelegate.MODE_NIGHT_YES)
@ -86,7 +87,7 @@ class ThemeFragment : PreferenceFragmentCompat() {
}
private fun bindFollowDeviceTheme() {
val keyDeviceTheme = getString(R.string.pref_key_follow_device_theme)
val keyDeviceTheme = getPreferenceKey(R.string.pref_key_follow_device_theme)
radioFollowDeviceTheme = requireNotNull(findPreference(keyDeviceTheme))
if (SDK_INT >= Build.VERSION_CODES.P) {
radioFollowDeviceTheme.onClickListener {

View File

@ -6,16 +6,27 @@ package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.Navigation
import androidx.navigation.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.utils.Settings
/**
* Displays the toggle for tracking protection and a button to open
* the tracking protection [org.mozilla.fenix.exceptions.ExceptionsFragment].
*/
class TrackingProtectionFragment : PreferenceFragmentCompat() {
private val exceptionsClickListener = Preference.OnPreferenceClickListener {
val directions = TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment()
view!!.findNavController().navigate(directions)
true
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.tracking_protection_preferences, rootKey)
}
@ -26,34 +37,22 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
(activity as AppCompatActivity).supportActionBar?.show()
// Tracking Protection Switch
val trackingProtectionKey =
context!!.getPreferenceKey(R.string.pref_key_tracking_protection)
val trackingProtectionKey = getPreferenceKey(R.string.pref_key_tracking_protection)
val preferenceTP = findPreference<SwitchPreference>(trackingProtectionKey)
preferenceTP?.isChecked = Settings.getInstance(context!!).shouldUseTrackingProtection
preferenceTP?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
Settings.getInstance(requireContext()).shouldUseTrackingProtection = newValue as Boolean
with(requireComponents) {
val policy = core.createTrackingProtectionPolicy(newValue)
useCases.settingsUseCases.updateTrackingProtection.invoke(policy)
useCases.sessionUseCases.reload.invoke()
}
true
preferenceTP?.setOnPreferenceChangeListener<Boolean> { preference, trackingProtectionOn ->
Settings.getInstance(preference.context).shouldUseTrackingProtection = trackingProtectionOn
with(preference.context.components) {
val policy = core.createTrackingProtectionPolicy(trackingProtectionOn)
useCases.settingsUseCases.updateTrackingProtection(policy)
useCases.sessionUseCases.reload()
}
val exceptions =
context!!.getPreferenceKey(R.string.pref_key_tracking_protection_exceptions)
val preferenceExceptions = findPreference<Preference>(exceptions)
preferenceExceptions?.onPreferenceClickListener = getClickListenerForExceptions()
}
private fun getClickListenerForExceptions(): Preference.OnPreferenceClickListener {
return Preference.OnPreferenceClickListener {
val directions =
TrackingProtectionFragmentDirections.actionTrackingProtectionFragmentToExceptionsFragment()
Navigation.findNavController(view!!).navigate(directions)
true
}
val exceptions = getPreferenceKey(R.string.pref_key_tracking_protection_exceptions)
val preferenceExceptions = findPreference<Preference>(exceptions)
preferenceExceptions?.onPreferenceClickListener = exceptionsClickListener
}
}

View File

@ -11,8 +11,8 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import kotlinx.android.synthetic.main.fragment_turn_on_sync.view.*
import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.OAuthAccount
@ -23,6 +23,22 @@ import org.mozilla.fenix.ext.requireComponents
@SuppressWarnings("TooManyFunctions")
class TurnOnSyncFragment : Fragment(), AccountObserver {
private val signInClickListener = View.OnClickListener {
requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext())
// TODO The sign-in web content populates session history,
// so pressing "back" after signing in won't take us back into the settings screen, but rather up the
// session history stack.
// We could auto-close this tab once we get to the end of the authentication process?
// Via an interceptor, perhaps.
}
private val paringClickListener = View.OnClickListener {
val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment()
view!!.findNavController().navigate(directions)
requireComponents.analytics.metrics.track(Event.SyncAuthScanPairing)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireComponents.analytics.metrics.track(Event.SyncAuthOpened)
@ -36,7 +52,7 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
override fun onResume() {
super.onResume()
if (requireComponents.backgroundServices.accountManager.authenticatedAccount() != null) {
findNavController(this).popBackStack()
findNavController().popBackStack()
return
}
@ -47,8 +63,8 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_turn_on_sync, container, false)
view.signInScanButton.setOnClickListener(getClickListenerForPairing())
view.signInEmailButton.setOnClickListener(getClickListenerForSignIn())
view.signInScanButton.setOnClickListener(paringClickListener)
view.signInEmailButton.setOnClickListener(signInClickListener)
view.signInInstructions.text = HtmlCompat.fromHtml(
getString(R.string.sign_in_instructions),
HtmlCompat.FROM_HTML_MODE_LEGACY
@ -56,25 +72,6 @@ class TurnOnSyncFragment : Fragment(), AccountObserver {
return view
}
private fun getClickListenerForSignIn(): View.OnClickListener {
return View.OnClickListener {
requireComponents.services.accountsAuthFeature.beginAuthentication(requireContext())
// TODO The sign-in web content populates session history,
// so pressing "back" after signing in won't take us back into the settings screen, but rather up the
// session history stack.
// We could auto-close this tab once we get to the end of the authentication process?
// Via an interceptor, perhaps.
}
}
private fun getClickListenerForPairing(): View.OnClickListener {
return View.OnClickListener {
val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment()
Navigation.findNavController(view!!).navigate(directions)
requireComponents.analytics.metrics.track(Event.SyncAuthScanPairing)
}
}
override fun onAuthenticated(account: OAuthAccount, newAccount: Boolean) {
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_SHORT)
.setText(requireContext().getString(R.string.sync_syncing_in_progress))

View File

@ -113,12 +113,12 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
accountManager.register(accountStateObserver, this, true)
// Sign out
val signOut = context!!.getPreferenceKey(R.string.pref_key_sign_out)
val signOut = getPreferenceKey(R.string.pref_key_sign_out)
val preferenceSignOut = findPreference<Preference>(signOut)
preferenceSignOut?.onPreferenceClickListener = getClickListenerForSignOut()
// Sync now
val syncNow = context!!.getPreferenceKey(R.string.pref_key_sync_now)
val syncNow = getPreferenceKey(R.string.pref_key_sync_now)
val preferenceSyncNow = findPreference<Preference>(syncNow)
preferenceSyncNow?.let {
it.onPreferenceClickListener = getClickListenerForSyncNow()
@ -134,7 +134,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
// Device Name
val deviceConstellation = accountManager.authenticatedAccount()?.deviceConstellation()
val deviceNameKey = context!!.getPreferenceKey(R.string.pref_key_sync_device_name)
val deviceNameKey = getPreferenceKey(R.string.pref_key_sync_device_name)
findPreference<EditTextPreference>(deviceNameKey)?.apply {
onPreferenceChangeListener = getChangeListenerForDeviceName()
deviceConstellation?.state()?.currentDevice?.let { device ->
@ -211,7 +211,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
private val syncStatusObserver = object : SyncStatusObserver {
override fun onStarted() {
lifecycleScope.launch {
val pref = findPreference<Preference>(context!!.getPreferenceKey(R.string.pref_key_sync_now))
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
view?.announceForAccessibility(getString(R.string.sync_syncing_in_progress))
pref?.title = getString(R.string.sync_syncing_in_progress)
pref?.isEnabled = false
@ -221,7 +221,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
// Sync stopped successfully.
override fun onIdle() {
lifecycleScope.launch {
val pref = findPreference<Preference>(context!!.getPreferenceKey(R.string.pref_key_sync_now))
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
pref?.let {
pref.title = getString(R.string.preferences_sync_now)
pref.isEnabled = true
@ -235,7 +235,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
// Sync stopped after encountering a problem.
override fun onError(error: Exception?) {
lifecycleScope.launch {
val pref = findPreference<Preference>(context!!.getPreferenceKey(R.string.pref_key_sync_now))
val pref = findPreference<Preference>(getPreferenceKey(R.string.pref_key_sync_now))
pref?.let {
pref.title = getString(R.string.preferences_sync_now)
pref.isEnabled = true
@ -256,7 +256,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
}
private fun updateDeviceName(state: AccountSettingsState) {
val deviceNameKey = context!!.getPreferenceKey(R.string.pref_key_sync_device_name)
val deviceNameKey = getPreferenceKey(R.string.pref_key_sync_device_name)
val preferenceDeviceName = findPreference<Preference>(deviceNameKey)
preferenceDeviceName?.summary = state.deviceName
}
@ -280,7 +280,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
)
}
val syncNow = context!!.getPreferenceKey(R.string.pref_key_sync_now)
val syncNow = getPreferenceKey(R.string.pref_key_sync_now)
findPreference<Preference>(syncNow)?.summary = value
}

View File

@ -48,10 +48,10 @@ class QuickSettingsComponent(
return if (sitePermissions == null) {
val settings = Settings.getInstance(context)
val origin = requireNotNull(url.toUri().host)
var location = settings.sitePermissionsPhoneFeatureLocation.toStatus()
var camera = settings.sitePermissionsPhoneFeatureCameraAction.toStatus()
var microphone = settings.sitePermissionsPhoneFeatureMicrophoneAction.toStatus()
var notification = settings.sitePermissionsPhoneFeatureNotificationAction.toStatus()
var location = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION).toStatus()
var camera = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA).toStatus()
var microphone = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE).toStatus()
var notification = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION).toStatus()
when (featurePhone) {
PhoneFeature.CAMERA -> camera = camera.toggle()

View File

@ -1,45 +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.settings.sharedpreferences
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import java.security.InvalidParameterException
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private class SitePermissionsRulesActionPreference(
private val key: String
) : ReadWriteProperty<PreferencesHolder, SitePermissionsRules.Action> {
override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>): SitePermissionsRules.Action =
intToAction(thisRef.preferences.getInt(key, ASK_TO_ALLOW_INT))
override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: SitePermissionsRules.Action) {
thisRef.preferences.edit().putInt(key, actionToInt(value)).apply()
}
companion object {
private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1
private fun actionToInt(action: SitePermissionsRules.Action) = when (action) {
SitePermissionsRules.Action.BLOCKED -> BLOCKED_INT
SitePermissionsRules.Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT
}
private fun intToAction(action: Int) = when (action) {
BLOCKED_INT -> SitePermissionsRules.Action.BLOCKED
ASK_TO_ALLOW_INT -> SitePermissionsRules.Action.ASK_TO_ALLOW
else -> throw InvalidParameterException("$action is not a valid SitePermissionsRules.Action")
}
}
}
/**
* Property delegate for getting and setting a [SitePermissionsRules.Action] preference.
*/
fun sitePermissionsRulesActionPreference(
key: String
): ReadWriteProperty<PreferencesHolder, SitePermissionsRules.Action> = SitePermissionsRulesActionPreference(key)

View File

@ -14,9 +14,10 @@ import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.Config
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.sharedpreferences.PreferencesHolder
import org.mozilla.fenix.settings.sharedpreferences.booleanPreference
import org.mozilla.fenix.settings.sharedpreferences.sitePermissionsRulesActionPreference
import java.security.InvalidParameterException
/**
* A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
@ -29,6 +30,19 @@ class Settings private constructor(
companion object {
const val autoBounceMaximumCount = 2
const val FENIX_PREFERENCES = "fenix_preferences"
private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1
private fun actionToInt(action: SitePermissionsRules.Action) = when (action) {
SitePermissionsRules.Action.BLOCKED -> BLOCKED_INT
SitePermissionsRules.Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT
}
private fun intToAction(action: Int) = when (action) {
BLOCKED_INT -> SitePermissionsRules.Action.BLOCKED
ASK_TO_ALLOW_INT -> SitePermissionsRules.Action.ASK_TO_ALLOW
else -> throw InvalidParameterException("$action is not a valid SitePermissionsRules.Action")
}
var instance: Settings? = null
@ -147,28 +161,19 @@ class Settings private constructor(
default = true
)
var sitePermissionsPhoneFeatureCameraAction by sitePermissionsRulesActionPreference(
appContext.getPreferenceKey(R.string.pref_key_phone_feature_camera)
)
fun getSitePermissionsPhoneFeatureAction(feature: PhoneFeature) =
intToAction(preferences.getInt(feature.getPreferenceKey(appContext), ASK_TO_ALLOW_INT))
var sitePermissionsPhoneFeatureMicrophoneAction by sitePermissionsRulesActionPreference(
appContext.getPreferenceKey(R.string.pref_key_phone_feature_microphone)
)
var sitePermissionsPhoneFeatureNotificationAction by sitePermissionsRulesActionPreference(
appContext.getPreferenceKey(R.string.pref_key_phone_feature_notification)
)
var sitePermissionsPhoneFeatureLocation by sitePermissionsRulesActionPreference(
appContext.getPreferenceKey(R.string.pref_key_phone_feature_location)
)
fun setSitePermissionsPhoneFeatureAction(feature: PhoneFeature, value: SitePermissionsRules.Action) {
preferences.edit().putInt(feature.getPreferenceKey(appContext), actionToInt(value)).apply()
}
fun getSitePermissionsCustomSettingsRules(): SitePermissionsRules {
return SitePermissionsRules(
notification = sitePermissionsPhoneFeatureNotificationAction,
microphone = sitePermissionsPhoneFeatureMicrophoneAction,
location = sitePermissionsPhoneFeatureLocation,
camera = sitePermissionsPhoneFeatureCameraAction
notification = getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION),
microphone = getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE),
location = getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION),
camera = getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA)
)
}

View File

@ -2,10 +2,12 @@
<!-- 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/. -->
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView
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="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
@ -45,7 +47,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/sign_in_instructions"
tools:text="@string/sign_in_instructions"
android:textColor="?primaryText"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
@ -84,4 +86,4 @@
app:layout_constraintTop_toBottomOf="@id/signInScanButton" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.core.widget.NestedScrollView>

View File

@ -1,73 +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.settings.sharedpreferences
import android.content.SharedPreferences
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
class SitePermissionsRulesActionPreferenceTest {
private lateinit var sharedPrefs: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
sharedPrefs = mockk(relaxed = true)
editor = mockk()
every { sharedPrefs.edit() } returns editor
every { editor.putInt(any(), any()) } returns editor
every { editor.apply() } just runs
}
@Test
fun `getter returns action from shared preferences`() {
val holder = object : PreferencesHolder {
override val preferences = sharedPrefs
val test by sitePermissionsRulesActionPreference("test_preference_key")
}
every { sharedPrefs.getInt("test_preference_key", 1) } returns 0
assertEquals(SitePermissionsRules.Action.BLOCKED, holder.test)
verify { sharedPrefs.getInt("test_preference_key", 1) }
}
@Test
fun `setter applies boolean to shared preferences`() {
val holder = object : PreferencesHolder {
override val preferences = sharedPrefs
var test by sitePermissionsRulesActionPreference("pref")
}
holder.test = SitePermissionsRules.Action.BLOCKED
verify { editor.putInt("pref", 0) }
verify { editor.apply() }
holder.test = SitePermissionsRules.Action.ASK_TO_ALLOW
verify { editor.putInt("pref", 1) }
verify { editor.apply() }
}
@Test
fun `getter defaults to ASK_TO_ALLOW`() {
every { sharedPrefs.getInt("key", 1) } returns 1
val holder = object : PreferencesHolder {
override val preferences = sharedPrefs
val action by sitePermissionsRulesActionPreference("key")
}
assertEquals(SitePermissionsRules.Action.ASK_TO_ALLOW, holder.action)
verify { sharedPrefs.getInt("key", 1) }
}
}

View File

@ -18,6 +18,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.TestApplication
import org.mozilla.fenix.ext.clearAndCommit
import org.mozilla.fenix.settings.PhoneFeature
import org.robolectric.annotation.Config
@ObsoleteCoroutinesApi
@ -231,52 +232,52 @@ class SettingsTest {
fun sitePermissionsPhoneFeatureCameraAction() {
// When just created
// Then
assertEquals(ASK_TO_ALLOW, settings.sitePermissionsPhoneFeatureCameraAction)
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA))
// When
settings.sitePermissionsPhoneFeatureCameraAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA, BLOCKED)
// Then
assertEquals(BLOCKED, settings.sitePermissionsPhoneFeatureCameraAction)
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA))
}
@Test
fun sitePermissionsPhoneFeatureMicrophoneAction() {
// When just created
// Then
assertEquals(ASK_TO_ALLOW, settings.sitePermissionsPhoneFeatureMicrophoneAction)
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE))
// When
settings.sitePermissionsPhoneFeatureMicrophoneAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE, BLOCKED)
// Then
assertEquals(BLOCKED, settings.sitePermissionsPhoneFeatureMicrophoneAction)
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE))
}
@Test
fun sitePermissionsPhoneFeatureNotificationAction() {
// When just created
// Then
assertEquals(ASK_TO_ALLOW, settings.sitePermissionsPhoneFeatureNotificationAction)
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION))
// When
settings.sitePermissionsPhoneFeatureNotificationAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION, BLOCKED)
// Then
assertEquals(BLOCKED, settings.sitePermissionsPhoneFeatureNotificationAction)
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION))
}
@Test
fun sitePermissionsPhoneFeatureLocation() {
// When just created
// Then
assertEquals(ASK_TO_ALLOW, settings.sitePermissionsPhoneFeatureLocation)
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION))
// When
settings.sitePermissionsPhoneFeatureLocation = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION, BLOCKED)
// Then
assertEquals(BLOCKED, settings.sitePermissionsPhoneFeatureLocation)
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION))
}
@Test
@ -292,7 +293,7 @@ class SettingsTest {
@Test
fun getSitePermissionsCustomSettingsRules_camera() {
// When
settings.sitePermissionsPhoneFeatureCameraAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA, BLOCKED)
// Then
assertEquals(
@ -304,7 +305,7 @@ class SettingsTest {
@Test
fun getSitePermissionsCustomSettingsRules_notification() {
// When
settings.sitePermissionsPhoneFeatureNotificationAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION, BLOCKED)
// Then
assertEquals(
@ -316,7 +317,7 @@ class SettingsTest {
@Test
fun getSitePermissionsCustomSettingsRules_location() {
// When
settings.sitePermissionsPhoneFeatureLocation = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION, BLOCKED)
// Then
assertEquals(
@ -328,7 +329,7 @@ class SettingsTest {
@Test
fun getSitePermissionsCustomSettingsRules_microphone() {
// When
settings.sitePermissionsPhoneFeatureMicrophoneAction = BLOCKED
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE, BLOCKED)
// Then
assertEquals(