Bug 1849073 - Part 5: Remove the Add Search Engine fragment

fenix/120.0
Gabriel Luong 9 months ago committed by mergify[bot]
parent 0860b29b6b
commit aba4d5510e

@ -30,7 +30,6 @@ enum class BrowserDirection(@IdRes val fragmentId: Int) {
FromTrackingProtectionDialog(R.id.trackingProtectionPanelDialogFragment),
FromSavedLoginsFragment(R.id.savedLoginsFragment),
FromAddNewDeviceFragment(R.id.addNewDeviceFragment),
FromAddSearchEngineFragment(R.id.addSearchEngineFragment),
FromSearchEngineFragment(R.id.searchEngineFragment),
FromSaveSearchEngineFragment(R.id.saveSearchEngineFragment),
FromAddonDetailsFragment(R.id.addonDetailsFragment),

@ -148,7 +148,6 @@ import org.mozilla.fenix.settings.about.AboutFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.LoginDetailFragmentDirections
import org.mozilla.fenix.settings.logins.fragment.SavedLoginsAuthFragmentDirections
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.dialog.CookieBannerReEngagementDialogUtils
import org.mozilla.fenix.settings.search.AddSearchEngineFragmentDirections
import org.mozilla.fenix.settings.search.SaveSearchEngineFragmentDirections
import org.mozilla.fenix.settings.search.SearchEngineFragmentDirections
import org.mozilla.fenix.settings.studies.StudiesFragmentDirections
@ -1042,8 +1041,6 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
SavedLoginsAuthFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAddNewDeviceFragment ->
AddNewDeviceFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromAddSearchEngineFragment ->
AddSearchEngineFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSearchEngineFragment ->
SearchEngineFragmentDirections.actionGlobalBrowser(customTabSessionId)
BrowserDirection.FromSaveSearchEngineFragment ->

@ -1,296 +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.search
import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.LinearLayout
import android.widget.RadioButton
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.state.search.SearchEngine
import mozilla.components.browser.state.state.availableSearchEngines
import mozilla.components.feature.search.ext.createSearchEngine
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.databinding.CustomSearchEngineBinding
import org.mozilla.fenix.databinding.CustomSearchEngineRadioButtonBinding
import org.mozilla.fenix.databinding.FragmentAddSearchEngineBinding
import org.mozilla.fenix.databinding.SearchEngineRadioButtonBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.SupportUtils
@SuppressWarnings("LargeClass", "TooManyFunctions")
class AddSearchEngineFragment :
Fragment(R.layout.fragment_add_search_engine),
CompoundButton.OnCheckedChangeListener,
MenuProvider {
private var availableEngines: List<SearchEngine> = listOf()
private var selectedIndex: Int = -1
private val engineViews = mutableListOf<View>()
private var _binding: FragmentAddSearchEngineBinding? = null
private val binding get() = _binding!!
private lateinit var customSearchEngine: CustomSearchEngineBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
availableEngines = requireContext()
.components
.core
.store
.state
.search
.availableSearchEngines
selectedIndex = if (availableEngines.isEmpty()) CUSTOM_INDEX else FIRST_INDEX
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
val layoutInflater = LayoutInflater.from(context)
val layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
_binding = FragmentAddSearchEngineBinding.bind(view)
customSearchEngine = binding.customSearchEngine
val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine ->
val engineId = engine.id
val engineItem = makeButtonFromSearchEngine(
engine = engine,
layoutInflater = layoutInflater,
res = requireContext().resources,
)
engineItem.root.id = index
engineItem.root.tag = engineId
engineItem.radioButton.isChecked = selectedIndex == index
engineViews.add(engineItem.root)
binding.searchEngineGroup.addView(engineItem.root, layoutParams)
}
availableEngines.forEachIndexed(setupSearchEngineItem)
val engineItem = makeCustomButton(layoutInflater)
engineItem.root.id = CUSTOM_INDEX
engineItem.radioButton.isChecked = selectedIndex == CUSTOM_INDEX
engineViews.add(engineItem.root)
binding.searchEngineGroup.addView(engineItem.root, layoutParams)
toggleCustomForm(selectedIndex == CUSTOM_INDEX)
customSearchEngine.customSearchEnginesLearnMore.setOnClickListener {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = SupportUtils.getSumoURLForTopic(
requireContext(),
SupportUtils.SumoTopic.CUSTOM_SEARCH_ENGINES,
),
newTab = true,
from = BrowserDirection.FromAddSearchEngineFragment,
)
}
}
override fun onResume() {
super.onResume()
showToolbar(getString(R.string.search_engine_add_custom_search_engine_title))
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.add_custom_searchengine_menu, menu)
}
override fun onMenuItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.add_search_engine -> {
when (selectedIndex) {
CUSTOM_INDEX -> createCustomEngine()
else -> {
val engine = availableEngines[selectedIndex]
requireComponents.useCases.searchUseCases.addSearchEngine(engine)
findNavController().popBackStack()
}
}
true
}
// other options are not handled by this menu provider
else -> false
}
}
@Suppress("ComplexMethod")
private fun createCustomEngine() {
customSearchEngine.customSearchEngineNameField.error = ""
customSearchEngine.customSearchEngineSearchStringField.error = ""
val name = customSearchEngine.editEngineName.text?.toString()?.trim() ?: ""
val searchString = customSearchEngine.editSearchString.text?.toString() ?: ""
if (checkForErrors(name, searchString)) {
return
}
viewLifecycleOwner.lifecycleScope.launch(Main) {
val result = withContext(IO) {
SearchStringValidator.isSearchStringValid(
requireComponents.core.client,
searchString,
)
}
when (result) {
SearchStringValidator.Result.CannotReach -> {
customSearchEngine.customSearchEngineSearchStringField.error = resources
.getString(R.string.search_add_custom_engine_error_cannot_reach, name)
}
SearchStringValidator.Result.Success -> {
val searchEngine = createSearchEngine(
name,
searchString.toSearchUrl(),
requireComponents.core.icons.loadIcon(IconRequest(searchString)).await().bitmap,
isGeneral = true,
)
requireComponents.useCases.searchUseCases.addSearchEngine(searchEngine)
val successMessage = resources
.getString(R.string.search_add_custom_engine_success_message, name)
view?.also {
FenixSnackbar.make(
view = it,
duration = FenixSnackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = false,
)
.setText(successMessage)
.show()
}
findNavController().popBackStack()
}
}
}
}
private fun checkForErrors(name: String, searchString: String): Boolean {
return when {
name.isEmpty() -> {
customSearchEngine.customSearchEngineNameField.error = resources
.getString(R.string.search_add_custom_engine_error_empty_name)
true
}
searchString.isEmpty() -> {
customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_empty_search_string)
true
}
!searchString.contains("%s") -> {
customSearchEngine.customSearchEngineSearchStringField.error =
resources.getString(R.string.search_add_custom_engine_error_missing_template)
true
}
else -> false
}
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
engineViews.forEach {
when (it.findViewById<RadioButton>(R.id.radio_button) == buttonView) {
true -> {
selectedIndex = it.id
}
false -> {
it.findViewById<RadioButton>(R.id.radio_button).also { radioButton ->
radioButton.setOnCheckedChangeListener(null)
radioButton.isChecked = false
radioButton.setOnCheckedChangeListener(this)
}
}
}
}
toggleCustomForm(selectedIndex == -1)
}
@SuppressLint("InflateParams")
private fun makeCustomButton(layoutInflater: LayoutInflater): CustomSearchEngineRadioButtonBinding {
val wrapper = layoutInflater
.inflate(R.layout.custom_search_engine_radio_button, null) as ConstraintLayout
val customSearchEngineRadioButtonBinding = CustomSearchEngineRadioButtonBinding.bind(wrapper)
wrapper.setOnClickListener { customSearchEngineRadioButtonBinding.radioButton.isChecked = true }
customSearchEngineRadioButtonBinding.radioButton.setOnCheckedChangeListener(this)
return customSearchEngineRadioButtonBinding
}
private fun toggleCustomForm(isEnabled: Boolean) {
customSearchEngine.customSearchEngineForm.alpha = if (isEnabled) ENABLED_ALPHA else DISABLED_ALPHA
customSearchEngine.editSearchString.isEnabled = isEnabled
customSearchEngine.editEngineName.isEnabled = isEnabled
customSearchEngine.customSearchEnginesLearnMore.isEnabled = isEnabled
}
@SuppressLint("InflateParams")
private fun makeButtonFromSearchEngine(
engine: SearchEngine,
layoutInflater: LayoutInflater,
res: Resources,
): SearchEngineRadioButtonBinding {
val wrapper = layoutInflater
.inflate(R.layout.search_engine_radio_button, null) as LinearLayout
val searchEngineRadioButtonBinding = SearchEngineRadioButtonBinding.bind(wrapper)
wrapper.setOnClickListener { searchEngineRadioButtonBinding.radioButton.isChecked = true }
searchEngineRadioButtonBinding.radioButton.setOnCheckedChangeListener(this)
searchEngineRadioButtonBinding.engineText.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)
searchEngineRadioButtonBinding.engineIcon.setImageDrawable(engineIcon)
searchEngineRadioButtonBinding.overflowMenu.visibility = View.GONE
return searchEngineRadioButtonBinding
}
companion object {
private const val ENABLED_ALPHA = 1.0f
private const val DISABLED_ALPHA = 0.2f
private const val CUSTOM_INDEX = -1
private const val FIRST_INDEX = 0
}
}
private fun String.toSearchUrl(): String {
return replace("%s", "{searchTerms}")
}

@ -148,18 +148,6 @@ class SearchEngineFragment : PreferenceFragmentCompat() {
override fun onPreferenceTreeClick(preference: Preference): Boolean {
when (preference.key) {
getPreferenceKey(R.string.pref_key_add_search_engine) -> {
val directions = SearchEngineFragmentDirections
.actionSearchEngineFragmentToAddSearchEngineFragment()
context?.let {
findNavController().navigateWithBreadcrumb(
directions = directions,
navigateFrom = "SearchEngineFragment",
navigateTo = "ActionSearchEngineFragmentToAddSearchEngineFragment",
it.components.analytics.crashReporter,
)
}
}
getPreferenceKey(R.string.pref_key_default_search_engine) -> {
val directions = SearchEngineFragmentDirections
.actionSearchEngineFragmentToDefaultEngineFragment()

@ -1,80 +0,0 @@
<?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:id="@+id/custom_search_engine_form"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:contentDescription="@string/search_add_custom_engine_form_description"
android:importantForAutofill="noExcludeDescendants">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/custom_search_engine_name_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintTextColor="?attr/textSecondary"
android:textColorHint="?attr/textSecondary"
app:hintTextAppearance="@style/EngineTextField"
android:paddingBottom="8dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_engine_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/accessibility_min_height"
android:hint="@string/search_add_custom_engine_name_hint"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/custom_search_engine_search_string_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:hintTextColor="?attr/textSecondary"
android:textColorHint="?attr/textSecondary"
app:hintTextAppearance="@style/EngineTextField"
android:paddingBottom="8dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/edit_search_string"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/accessibility_min_height"
android:hint="@string/search_add_custom_engine_search_string_hint"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search_add_custom_engine_search_string_example"
app:lineHeight="18sp"
android:labelFor="@id/edit_search_string"
android:textColor="@android:color/tertiary_text_dark" />
<org.mozilla.fenix.utils.LinkTextView
android:id="@+id/custom_search_engines_learn_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/exceptions_empty_message_learn_more_link"
android:minHeight="@dimen/accessibility_min_height"
android:textColor="?accent"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/exceptions_empty_message" />
<ProgressBar
android:id="@+id/progress"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:visibility="gone"/>
</LinearLayout>

@ -1,39 +0,0 @@
<?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/. -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="@dimen/search_engine_radio_button_height"
android:minHeight="@dimen/radio_button_preference_height"
android:layout_width="match_parent"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true">
<RadioButton
android:id="@+id/radio_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:textAlignment="textStart"
android:textAppearance="?android:attr/textAppearanceListItem"
android:layout_marginStart="@dimen/search_bar_search_engine_icon_padding"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/engine_text"
android:textColor="?attr/textPrimary"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/search_bar_search_icon_margin"
android:layout_marginEnd="@dimen/radio_button_padding_horizontal"
android:text="@string/search_add_custom_engine_label_other"
android:textSize="16sp"
app:layout_constraintStart_toEndOf="@id/radio_button"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -1,26 +0,0 @@
<?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/. -->
<ScrollView
android:id="@+id/search_engine_scrollview"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.mozilla.fenix.settings.search.AddSearchEngineFragment">
<LinearLayout
android:id="@+id/search_engine_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioGroup
android:id="@+id/search_engine_group"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
android:id="@+id/custom_search_engine"
layout="@layout/custom_search_engine" />
</LinearLayout>
</ScrollView>

@ -8,7 +8,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.mozilla.fenix.settings.search.AddSearchEngineFragment"
tools:context="org.mozilla.fenix.settings.search.SaveSearchEngineFragment"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:id="@+id/search_engine_wrapper"

@ -1,13 +0,0 @@
<?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/. -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/add_search_engine"
android:icon="@drawable/mozac_ic_checkmark_24"
app:iconTint="?attr/textPrimary"
android:title="@string/search_engine_add_button_content_description"
app:showAsAction="always" />
</menu>

@ -1234,9 +1234,6 @@
android:id="@+id/searchEngineFragment"
android:name="org.mozilla.fenix.settings.search.SearchEngineFragment"
android:label="@string/preferences_search">
<action
android:id="@+id/action_searchEngineFragment_to_addSearchEngineFragment"
app:destination="@+id/addSearchEngineFragment" />
<action
android:id="@+id/action_searchEngineFragment_to_defaultEngineFragment"
app:destination="@+id/defaultEngineFragment" />
@ -1267,10 +1264,6 @@
app:argType="string"
app:nullable="true"/>
</fragment>
<fragment
android:id="@+id/addSearchEngineFragment"
android:name="org.mozilla.fenix.settings.search.AddSearchEngineFragment"
tools:layout="@layout/fragment_add_search_engine" />
</navigation>
<navigation

@ -21,7 +21,6 @@
<dimen name="tab_corner_radius">8dp</dimen>
<dimen name="preference_icon_drawable_size">24dp</dimen>
<dimen name="search_bar_search_engine_icon_padding">12dp</dimen>
<dimen name="search_bar_search_icon_margin">28dp</dimen>
<dimen name="search_engine_engine_icon_margin">12dp</dimen>
<dimen name="search_engine_engine_icon_top_margin">14dp</dimen>
<dimen name="search_engine_radio_button_height">48dp</dimen>

@ -1892,7 +1892,7 @@
<!-- Title of the Edit search engine screen -->
<string name="search_engine_edit_custom_search_engine_title">Edit search engine</string>
<!-- Content description (not visible, for screen readers etc.): Title for the button to add a search engine in the action bar -->
<string name="search_engine_add_button_content_description">Add</string>
<string name="search_engine_add_button_content_description" moz:RemovedIn="120" tools:ignore="UnusedResources">Add</string>
<!-- Content description (not visible, for screen readers etc.): Title for the button to save a search engine in the action bar -->
<string name="search_engine_add_custom_search_engine_edit_button_content_description" moz:RemovedIn="120" tools:ignore="UnusedResources">Save</string>
<!-- Text for the menu button to edit a search engine -->
@ -1901,17 +1901,17 @@
<string name="search_engine_delete">Delete</string>
<!-- Text for the button to create a custom search engine on the Add search engine screen -->
<string name="search_add_custom_engine_label_other">Other</string>
<string name="search_add_custom_engine_label_other" moz:RemovedIn="120" tools:ignore="UnusedResources">Other</string>
<!-- Label for the TextField in which user enters custom search engine name -->
<string name="search_add_custom_engine_name_label">Name</string>
<!-- Placeholder text shown in the Search Engine Name TextField before a user enters text -->
<string name="search_add_custom_engine_name_hint">Name</string>
<string name="search_add_custom_engine_name_hint" moz:RemovedIn="120" tools:ignore="UnusedResources">Name</string>
<!-- Placeholder text shown in the Search Engine Name text field before a user enters text -->
<string name="search_add_custom_engine_name_hint_2">Search engine name</string>
<!-- Label for the TextField in which user enters custom search engine URL -->
<string name="search_add_custom_engine_url_label">Search string URL</string>
<!-- Placeholder text shown in the Search String TextField before a user enters text -->
<string name="search_add_custom_engine_search_string_hint">Search string to use</string>
<string name="search_add_custom_engine_search_string_hint" moz:RemovedIn="120" tools:ignore="UnusedResources">Search string to use</string>
<!-- Placeholder text shown in the Search String TextField before a user enters text -->
<string name="search_add_custom_engine_search_string_hint_2">URL to use for search</string>
<!-- Description text for the Search String TextField. The %s is part of the string -->

Loading…
Cancel
Save