1
0
Fork 0

Closes #877 - Add Search Engine Settings

master
Emily Kager 2019-03-06 11:39:38 -08:00 committed by Emily Kager
parent a79134fdc3
commit d15e4bb3fa
16 changed files with 336 additions and 3 deletions

View File

@ -19,6 +19,7 @@ class Search(private val context: Context) {
*/
val searchEngineManager by lazy {
SearchEngineManager().apply {
registerForLocaleUpdates(context)
GlobalScope.launch {
load(context).await()
}

View File

@ -45,6 +45,7 @@ import org.mozilla.fenix.mvi.ActionBusFactory
import org.mozilla.fenix.mvi.getAutoDisposeObservable
import org.mozilla.fenix.mvi.getManagedEmitter
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings
import kotlin.math.roundToInt
fun SessionBundleStorage.archive(sessionManager: SessionManager) {
@ -110,7 +111,10 @@ class HomeFragment : Fragment() {
getManagedEmitter<SessionsChange>().onNext(SessionsChange.Changed(archivedSessions))
})
val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(requireContext()).let {
val searchIcon = requireComponents.search.searchEngineManager.getDefaultSearchEngine(
requireContext(),
Settings.getInstance(requireContext()).defaultSearchEngineName
).let {
BitmapDrawable(resources, it.icon)
}

View File

@ -1,4 +1,5 @@
package org.mozilla.fenix.search.awesomebar
/* 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/. */
@ -25,7 +26,11 @@ class AwesomeBarUIView(
actionEmitter: Observer<AwesomeBarAction>,
changesObservable: Observable<AwesomeBarChange>
) :
UIView<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(container, actionEmitter, changesObservable) {
UIView<AwesomeBarState, AwesomeBarAction, AwesomeBarChange>(
container,
actionEmitter,
changesObservable
) {
override val view: BrowserAwesomeBar = LayoutInflater.from(container.context)
.inflate(R.layout.component_awesomebar, container, true)
.findViewById(R.id.awesomeBar)

View File

@ -0,0 +1,50 @@
/* 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.widget.CompoundButton
import android.widget.RadioGroup
import androidx.preference.PreferenceViewHolder
import org.mozilla.fenix.R
import org.mozilla.fenix.utils.Settings
class RadioSearchEngineListPreference : SearchEngineListPreference,
RadioGroup.OnCheckedChangeListener {
override val itemResId: Int
get() = R.layout.search_engine_radio_button
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
searchEngineGroup!!.setOnCheckedChangeListener(this)
}
override fun updateDefaultItem(defaultButton: CompoundButton) {
defaultButton.isChecked = true
}
override fun onCheckedChanged(group: RadioGroup, checkedId: Int) {
/* onCheckedChanged is called intermittently before the search engine table is full, so we
must check these conditions to prevent crashes and inconsistent states. */
if (group.childCount != searchEngines.count() || group.getChildAt(checkedId) == null ||
!group.getChildAt(checkedId).isPressed
) {
return
}
val newDefaultEngine = searchEngines[checkedId]
Settings.getInstance(group.context).setDefaultSearchEngineByName(newDefaultEngine.name)
}
}

View File

@ -0,0 +1,23 @@
/* 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.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import org.mozilla.fenix.R
class SearchEngineFragment : PreferenceFragmentCompat() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as AppCompatActivity).title = getString(R.string.preferences_search_engine)
(activity as AppCompatActivity).supportActionBar?.show()
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.search_engine_preferences, rootKey)
}
}

View File

@ -0,0 +1,121 @@
/* 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.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.RadioGroup
import androidx.core.content.ContextCompat
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import mozilla.components.browser.search.SearchEngine
import org.mozilla.fenix.DefaultThemeManager
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.utils.Settings
import kotlin.coroutines.CoroutineContext
abstract class SearchEngineListPreference : Preference, CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
protected var searchEngines: List<SearchEngine> = emptyList()
protected var searchEngineGroup: RadioGroup? = null
protected abstract val itemResId: Int
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
layoutResource = R.layout.preference_search_engine_chooser
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
layoutResource = R.layout.preference_search_engine_chooser
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
searchEngineGroup = holder!!.itemView.findViewById(R.id.search_engine_group)
val context = searchEngineGroup!!.context
searchEngines = context.components.search.searchEngineManager.getSearchEngines(context)
.sortedBy { it.name }
refreshSearchEngineViews(context)
}
override fun onDetached() {
job.cancel()
super.onDetached()
}
protected abstract fun updateDefaultItem(defaultButton: CompoundButton)
private fun refreshSearchEngineViews(context: Context) {
if (searchEngineGroup == null) {
// We want to refresh the search engine list of this preference in onResume,
// but the first time this preference is created onResume is called before onCreateView
// so searchEngineGroup is not set yet.
return
}
val defaultSearchEngine =
context.components.search.searchEngineManager.getDefaultSearchEngine(
context,
Settings.getInstance(context).defaultSearchEngineName
).identifier
searchEngineGroup!!.removeAllViews()
val layoutInflater = LayoutInflater.from(context)
val layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
for (i in searchEngines.indices) {
val engine = searchEngines[i]
val engineId = engine.identifier
val engineItem = makeButtonFromSearchEngine(engine, layoutInflater, context.resources)
engineItem.id = i
engineItem.tag = engineId
if (engineId == defaultSearchEngine) {
updateDefaultItem(engineItem)
}
searchEngineGroup!!.addView(engineItem, layoutParams)
}
}
private fun makeButtonFromSearchEngine(
engine: SearchEngine,
layoutInflater: LayoutInflater,
res: Resources
): CompoundButton {
val buttonItem = layoutInflater.inflate(itemResId, null) as CompoundButton
buttonItem.text = engine.name
val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt()
val engineIcon = BitmapDrawable(res, engine.icon)
engineIcon.setBounds(0, 0, iconSize, iconSize)
val attr =
DefaultThemeManager.resolveAttribute(android.R.attr.listChoiceIndicatorSingle, context)
val buttonDrawable = ContextCompat.getDrawable(context, attr)
buttonDrawable.apply {
this?.setBounds(0, 0, this.intrinsicWidth, this.intrinsicHeight)
}
buttonItem.setCompoundDrawables(engineIcon, null, buttonDrawable, null)
return buttonItem
}
}

View File

@ -48,6 +48,7 @@ import org.mozilla.fenix.R.string.pref_key_about
import org.mozilla.fenix.R.string.pref_key_sign_in
import org.mozilla.fenix.R.string.pref_key_account
import org.mozilla.fenix.R.string.pref_key_account_category
import org.mozilla.fenix.R.string.pref_key_search_engine_settings
@SuppressWarnings("TooManyFunctions")
class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObserver {
@ -76,6 +77,9 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
@Suppress("ComplexMethod")
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
resources.getString(pref_key_search_engine_settings) -> {
navigateToSearchEngineSettings()
}
resources.getString(pref_key_site_permissions) -> {
navigateToSitePermissions()
}
@ -206,6 +210,11 @@ class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope, AccountObse
}
}
private fun navigateToSearchEngineSettings() {
val directions = SettingsFragmentDirections.actionSettingsFragmentToSearchEngineFragment()
Navigation.findNavController(view!!).navigate(directions)
}
private fun navigateToSitePermissions() {
val directions = SettingsFragmentDirections.actionSettingsFragmentToSitePermissionsFragment()
Navigation.findNavController(view!!).navigate(directions)

View File

@ -0,0 +1,52 @@
package org.mozilla.fenix.utils
/* 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/. */
import android.content.Context
import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
/**
* A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
*/
class Settings private constructor(context: Context) {
companion object {
var instance: Settings? = null
@JvmStatic
@Synchronized
fun getInstance(context: Context): Settings {
if (instance == null) {
instance = Settings(context.applicationContext)
}
return instance ?: throw AssertionError("Instance cleared")
}
}
private val appContext = context.applicationContext
private val preferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
val defaultSearchEngineName: String
get() = preferences.getString(
appContext.getPreferenceKey(R.string.pref_key_search_engine),
""
) ?: ""
fun setDefaultSearchEngineByName(name: String) {
preferences.edit()
.putString(appContext.getPreferenceKey(R.string.pref_key_search_engine), name)
.apply()
}
fun showSearchSuggestions(): Boolean = preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions),
true
)
}

View File

@ -0,0 +1,16 @@
<?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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioGroup android:id="@+id/search_engine_group"
android:paddingTop="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,20 @@
<?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/. -->
<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/radio_button"
android:layout_width="match_parent"
android:layout_height="@dimen/search_engine_radio_button_height"
android:layout_gravity="start"
android:background="?android:selectableItemBackground"
android:button="@null"
android:drawableStart="@drawable/ic_close"
android:drawableEnd="?android:attr/listChoiceIndicatorSingle"
android:drawablePadding="16dp"
android:paddingStart="@dimen/radio_button_padding_horizontal"
android:paddingTop="@dimen/radio_button_padding_vertical"
android:paddingEnd="@dimen/radio_button_padding_horizontal"
android:paddingBottom="@dimen/radio_button_padding_vertical"
android:textAlignment="textStart"
android:textAppearance="?android:attr/textAppearanceListItem" />

View File

@ -98,6 +98,9 @@
app:destination="@id/accessibilityFragment"/>
<action android:id="@+id/action_settingsFragment_to_accountSettingsFragment"
app:destination="@id/accountSettingsFragment"/>
<action
android:id="@+id/action_settingsFragment_to_searchEngineFragment"
app:destination="@id/searchEngineFragment" />
</fragment>
<fragment android:id="@+id/dataChoicesFragment" android:name="org.mozilla.fenix.settings.DataChoicesFragment"
android:label="DataChoicesFragment"/>
@ -108,4 +111,8 @@
android:label="AccessibilityFragment"/>
<fragment android:id="@+id/accountSettingsFragment" android:name="org.mozilla.fenix.settings.AccountSettingsFragment"
android:label="AccountSettingsFragment"/>
<fragment
android:id="@+id/searchEngineFragment"
android:name="org.mozilla.fenix.settings.SearchEngineFragment"
android:label="SearchEngineFragment" />
</navigation>

View File

@ -15,4 +15,9 @@
<dimen name="session_card_padding">16dp</dimen>
<dimen name="tab_border_width">2dp</dimen>
<dimen name="tab_corner_radius">8dp</dimen>
<dimen name="preference_icon_drawable_size">24dp</dimen>
<dimen name="search_engine_radio_button_height">48dp</dimen>
<dimen name="radio_button_drawable_padding">32dp</dimen>
<dimen name="radio_button_padding_horizontal">16dp</dimen>
<dimen name="radio_button_padding_vertical">12dp</dimen>
</resources>

View File

@ -3,6 +3,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<string name="pref_key_make_default_browser" translatable="false">pref_key_make_default_browser</string>
<string name="pref_key_search_engine_settings" translatable="false">pref_key_search_engine_settings</string>
<string name="pref_key_search_engine" translatable="false">pref_key_search_engine</string>
<string name="pref_key_passwords" translatable="false">pref_key_passwords</string>
<string name="pref_key_credit_cards_addresses" translatable="false">pref_key_credit_cards_addresses</string>
@ -32,4 +33,7 @@
<string name="pref_key_sync_history" translatable="false">pref_key_sync_history</string>
<string name="pref_key_sign_out" translatable="false">pref_key_sign_out</string>
<!-- Search Settings -->
<string name="pref_key_show_search_suggestions" translatable="false">pref_key_show_search_suggestions</string>
</resources>

View File

@ -102,6 +102,9 @@
<!-- Preference for developers -->
<string name="preference_leakcanary">Leak Canary</string>
<!-- Preference title for switch preference to show search suggestions -->
<string name="preferences_show_search_suggestions">Show search suggestions</string>
<!-- Preference for account settings -->
<string name="preferences_account_settings">Account Settings</string>

View File

@ -29,7 +29,7 @@
app:iconSpaceReserved="false">
<androidx.preference.Preference
android:icon="@drawable/ic_search"
android:key="@string/pref_key_search_engine"
android:key="@string/pref_key_search_engine_settings"
android:title="@string/preferences_search_engine" />
<androidx.preference.Preference

View File

@ -0,0 +1,13 @@
<?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/. -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.mozilla.fenix.settings.RadioSearchEngineListPreference />
<SwitchPreference
android:key="@string/pref_key_show_search_suggestions"
android:title="@string/preferences_show_search_suggestions"
app:iconSpaceReserved="false" />
</PreferenceScreen>