Bug 1849073 - Part 5: Remove the Add Search Engine fragment
parent
0860b29b6b
commit
aba4d5510e
@ -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}")
|
||||
}
|
@ -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>
|
@ -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>
|
Loading…
Reference in New Issue