1
0
Fork 0
fenix/app/src/main/java/org/mozilla/fenix/settings/search/SearchEngineListPreference.kt

216 lines
8.6 KiB
Kotlin

/* 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.content.Context
import android.content.res.Resources
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.RadioGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.navigation.Navigation
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import kotlinx.android.synthetic.main.search_engine_radio_button.view.*
import kotlinx.coroutines.MainScope
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.search.provider.SearchEngineList
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.allowUndo
import java.util.Locale
abstract class SearchEngineListPreference @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.preferenceStyle
) : Preference(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener {
protected lateinit var searchEngineList: SearchEngineList
protected var searchEngineGroup: RadioGroup? = null
protected abstract val itemResId: Int
init {
layoutResource = R.layout.preference_search_engine_chooser
}
override fun onBindViewHolder(holder: PreferenceViewHolder?) {
super.onBindViewHolder(holder)
searchEngineGroup = holder!!.itemView.findViewById(R.id.search_engine_group)
reload(searchEngineGroup!!.context)
}
fun reload(context: Context) {
searchEngineList = context.components.search.provider.installedSearchEngines(context)
refreshSearchEngineViews(context)
}
protected abstract fun onSearchEngineSelected(searchEngine: SearchEngine)
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 defaultEngine = context.components.search.provider.getDefaultEngine(context).identifier
val selectedEngine = (searchEngineList.list.find {
it.identifier == defaultEngine
} ?: searchEngineList.list.first()).identifier
searchEngineGroup!!.removeAllViews()
val layoutInflater = LayoutInflater.from(context)
val layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
val setupSearchEngineItem: (Int, SearchEngine) -> Unit = { index, engine ->
val engineId = engine.identifier
val engineItem = makeButtonFromSearchEngine(
engine = engine,
layoutInflater = layoutInflater,
res = context.resources,
allowDeletion = searchEngineList.list.size > 1
)
engineItem.id = index + (searchEngineList.default?.let { 1 } ?: 0)
engineItem.tag = engineId
if (engineId == selectedEngine) {
updateDefaultItem(engineItem.radio_button)
}
searchEngineGroup!!.addView(engineItem, layoutParams)
}
searchEngineList.default?.apply {
setupSearchEngineItem(0, this)
}
searchEngineList.list
.filter { it.identifier != searchEngineList.default?.identifier }
.sortedBy { it.name.toLowerCase(Locale.getDefault()) }
.forEachIndexed(setupSearchEngineItem)
}
private fun makeButtonFromSearchEngine(
engine: SearchEngine,
layoutInflater: LayoutInflater,
res: Resources,
allowDeletion: Boolean
): View {
val isCustomSearchEngine = CustomSearchEngineStore.isCustomSearchEngine(context, engine.identifier)
val wrapper = layoutInflater.inflate(itemResId, null) as ConstraintLayout
wrapper.setOnClickListener { wrapper.radio_button.isChecked = true }
wrapper.radio_button.setOnCheckedChangeListener(this)
wrapper.engine_text.text = engine.name
wrapper.overflow_menu.isVisible = allowDeletion || isCustomSearchEngine
wrapper.overflow_menu.setOnClickListener {
SearchEngineMenu(
context = context,
allowDeletion = allowDeletion,
isCustomSearchEngine = isCustomSearchEngine,
onItemTapped = {
when (it) {
is SearchEngineMenu.Item.Edit -> editCustomSearchEngine(engine)
is SearchEngineMenu.Item.Delete -> deleteSearchEngine(context, engine, isCustomSearchEngine)
}
}
).menuBuilder.build(context).show(wrapper.overflow_menu)
}
val iconSize = res.getDimension(R.dimen.preference_icon_drawable_size).toInt()
val engineIcon = BitmapDrawable(res, engine.icon)
engineIcon.setBounds(0, 0, iconSize, iconSize)
wrapper.engine_icon.setImageDrawable(engineIcon)
return wrapper
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
searchEngineList.list.forEach { engine ->
val wrapper: ConstraintLayout = searchEngineGroup?.findViewWithTag(engine.identifier) ?: return
when (wrapper.radio_button == buttonView) {
true -> onSearchEngineSelected(engine)
false -> {
wrapper.radio_button.setOnCheckedChangeListener(null)
wrapper.radio_button.isChecked = false
wrapper.radio_button.setOnCheckedChangeListener(this)
}
}
}
}
private fun editCustomSearchEngine(engine: SearchEngine) {
val directions = SearchEngineFragmentDirections
.actionSearchEngineFragmentToEditCustomSearchEngineFragment(engine.identifier)
Navigation.findNavController(searchEngineGroup!!).navigate(directions)
}
private fun deleteSearchEngine(context: Context, engine: SearchEngine, isCustomSearchEngine: Boolean) {
val isDefaultEngine = engine == context.components.search.provider.getDefaultEngine(context)
val initialEngineList = searchEngineList.copy()
val initialDefaultEngine = searchEngineList.default
context.components.search.provider.uninstallSearchEngine(context, engine)
MainScope().allowUndo(
view = context.getRootView()!!,
message = context
.getString(R.string.search_delete_search_engine_success_message, engine.name),
undoActionTitle = context.getString(R.string.snackbar_deleted_undo),
onCancel = {
context.components.search.provider.installSearchEngine(context, engine)
searchEngineList = initialEngineList.copy(
default = initialDefaultEngine
)
refreshSearchEngineViews(context)
},
operation = {
if (isDefaultEngine) {
context.settings().defaultSearchEngineName = context
.components
.search
.provider
.getDefaultEngine(context)
.name
}
if (isCustomSearchEngine) {
context.components.analytics.metrics.track(Event.CustomEngineDeleted)
}
refreshSearchEngineViews(context)
}
)
searchEngineList = searchEngineList.copy(
list = searchEngineList.list.filter {
it.identifier != engine.identifier
},
default = if (searchEngineList.default?.identifier == engine.identifier) {
null
} else {
searchEngineList.default
}
)
refreshSearchEngineViews(context)
}
}