Add autocomplete tag search in filters for E/Exhentai
This commit is contained in:
parent
61e4ff548c
commit
50eef307f4
@ -29,6 +29,8 @@ sealed class Filter<T>(val name: String, var state: T) {
|
|||||||
data class Selection(val index: Int, val ascending: Boolean)
|
data class Selection(val index: Int, val ascending: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class AutoComplete(name: String, val hint: String, val values: List<String>, val skipAutoFillTags: List<String> = emptyList(), val excludePrefix: String? = null, state: List<String>) : Filter<List<String>>(name, state)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other !is Filter<*>) return false
|
if (other !is Filter<*>) return false
|
||||||
|
@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.source.online.LewdSource
|
|||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
|
import exh.eh.EHTags
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
import exh.eh.EHentaiUpdateWorkerConstants
|
import exh.eh.EHentaiUpdateWorkerConstants
|
||||||
import exh.eh.GalleryEntry
|
import exh.eh.GalleryEntry
|
||||||
@ -280,7 +281,8 @@ class EHentai(
|
|||||||
|
|
||||||
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
|
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
|
||||||
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
|
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
|
||||||
uri.appendQueryParameter("f_search", query)
|
|
||||||
|
uri.appendQueryParameter("f_search", (query + " " + combineQuery(filters)).trim())
|
||||||
filters.forEach {
|
filters.forEach {
|
||||||
if (it is UriFilter) it.addToUri(uri)
|
if (it is UriFilter) it.addToUri(uri)
|
||||||
}
|
}
|
||||||
@ -614,7 +616,14 @@ class EHentai(
|
|||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList(): FilterList {
|
||||||
|
val excludePrefix = "-"
|
||||||
|
|
||||||
|
return FilterList(
|
||||||
|
AutoCompleteTags(
|
||||||
|
EHTags.getNameSpaces().map { "$it:" } + EHTags.getAllTags(),
|
||||||
|
EHTags.getNameSpaces().map { "$it:" }, excludePrefix
|
||||||
|
),
|
||||||
if (prefs.eh_watchedListDefaultState().get()) {
|
if (prefs.eh_watchedListDefaultState().get()) {
|
||||||
Watched(isEnabled = true)
|
Watched(isEnabled = true)
|
||||||
} else {
|
} else {
|
||||||
@ -624,6 +633,7 @@ class EHentai(
|
|||||||
AdvancedGroup(),
|
AdvancedGroup(),
|
||||||
ReverseFilter()
|
ReverseFilter()
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
class Watched(val isEnabled: Boolean) : Filter.CheckBox("Watched List", isEnabled), UriFilter {
|
class Watched(val isEnabled: Boolean) : Filter.CheckBox("Watched List", isEnabled), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
@ -679,6 +689,41 @@ class EHentai(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun combineQuery(filters: FilterList): String {
|
||||||
|
val stringBuilder = StringBuilder()
|
||||||
|
val advSearch = filters.filterIsInstance<Filter.AutoComplete>().flatMap { filter ->
|
||||||
|
val splitState = filter.state.map(String::trim).filterNot(String::isBlank)
|
||||||
|
splitState.mapNotNull { tag ->
|
||||||
|
val split = tag.split(":").filterNot { it.isBlank() }.toMutableList()
|
||||||
|
if (split.size > 1) {
|
||||||
|
val namespace = split[0].removePrefix("-")
|
||||||
|
val exclude = split[0].startsWith("-")
|
||||||
|
split -= namespace
|
||||||
|
AdvSearchEntry(Pair(namespace, split.joinToString(":")), exclude)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advSearch.forEach { entry ->
|
||||||
|
if (entry.exclude) stringBuilder.append("-")
|
||||||
|
if (entry.search.second.contains(" ")) {
|
||||||
|
stringBuilder.append(("${entry.search.first}:\"${entry.search.second}$\""))
|
||||||
|
} else {
|
||||||
|
stringBuilder.append("${entry.search.first}:${entry.search.second}$")
|
||||||
|
}
|
||||||
|
stringBuilder.append(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
XLog.d(stringBuilder.toString())
|
||||||
|
return stringBuilder.toString().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class AdvSearchEntry(val search: Pair<String, String>, val exclude: Boolean)
|
||||||
|
|
||||||
|
class AutoCompleteTags(tags: List<String>, skipAutoFillTags: List<String>, excludePrefix: String) : Filter.AutoComplete(name = "Tags", hint = "Search tags here (limit of 8)", values = tags, skipAutoFillTags = skipAutoFillTags, excludePrefix = excludePrefix, state = emptyList())
|
||||||
|
|
||||||
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
||||||
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ import eu.kanade.tachiyomi.source.model.Filter
|
|||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.filter.AutoComplete
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.filter.AutoCompleteSectionItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
|
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem
|
import eu.kanade.tachiyomi.ui.browse.source.filter.CheckboxSectionItem
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
|
import eu.kanade.tachiyomi.ui.browse.source.filter.GroupItem
|
||||||
@ -309,6 +311,7 @@ open class BrowseSourcePresenter(
|
|||||||
is Filter.Header -> HeaderItem(filter)
|
is Filter.Header -> HeaderItem(filter)
|
||||||
// --> EXH
|
// --> EXH
|
||||||
is Filter.HelpDialog -> HelpDialogItem(filter)
|
is Filter.HelpDialog -> HelpDialogItem(filter)
|
||||||
|
is Filter.AutoComplete -> AutoComplete(filter)
|
||||||
// <-- EXH
|
// <-- EXH
|
||||||
is Filter.Separator -> SeparatorItem(filter)
|
is Filter.Separator -> SeparatorItem(filter)
|
||||||
is Filter.CheckBox -> CheckboxItem(filter)
|
is Filter.CheckBox -> CheckboxItem(filter)
|
||||||
@ -323,6 +326,9 @@ open class BrowseSourcePresenter(
|
|||||||
is Filter.TriState -> TriStateSectionItem(it)
|
is Filter.TriState -> TriStateSectionItem(it)
|
||||||
is Filter.Text -> TextSectionItem(it)
|
is Filter.Text -> TextSectionItem(it)
|
||||||
is Filter.Select<*> -> SelectSectionItem(it)
|
is Filter.Select<*> -> SelectSectionItem(it)
|
||||||
|
// SY -->
|
||||||
|
is Filter.AutoComplete -> AutoCompleteSectionItem(it)
|
||||||
|
// SY <--
|
||||||
else -> null
|
else -> null
|
||||||
} as? ISectionable<*, *>
|
} as? ISectionable<*, *>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.source.filter
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.AutoCompleteTextView
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.widget.AutoCompleteAdapter
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
open class AutoComplete(val filter: Filter.AutoComplete) : AbstractFlexibleItem<AutoComplete.Holder>() {
|
||||||
|
|
||||||
|
override fun getLayoutRes(): Int {
|
||||||
|
return R.layout.navigation_view_autocomplete
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||||
|
return Holder(view, adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: Holder, position: Int, payloads: List<Any?>?) {
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
|
holder.text.text = "${filter.name}: "
|
||||||
|
|
||||||
|
holder.autoComplete.hint = filter.hint
|
||||||
|
holder.autoComplete.setAdapter(
|
||||||
|
AutoCompleteAdapter(
|
||||||
|
holder.itemView.context,
|
||||||
|
android.R.layout.simple_dropdown_item_1line,
|
||||||
|
filter.values,
|
||||||
|
filter.excludePrefix
|
||||||
|
)
|
||||||
|
)
|
||||||
|
holder.autoComplete.threshold = 3
|
||||||
|
|
||||||
|
var text: String = ""
|
||||||
|
|
||||||
|
// select from auto complete
|
||||||
|
holder.autoComplete.setOnItemClickListener { adapterView, _, chipPosition, _ ->
|
||||||
|
val name = adapterView.getItemAtPosition(chipPosition) as String
|
||||||
|
if (name !in if (filter.excludePrefix != null && name.startsWith(filter.excludePrefix)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
|
||||||
|
holder.autoComplete.text = null
|
||||||
|
addTag(name, holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// done keyboard button is pressed
|
||||||
|
holder.autoComplete.setOnEditorActionListener { textView, actionId, keyEvent ->
|
||||||
|
if (actionId == EditorInfo.IME_ACTION_DONE && textView.text.toString() !in if (filter.excludePrefix != null && textView.text.toString().startsWith(filter.excludePrefix)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
|
||||||
|
textView.text = null
|
||||||
|
addTag(textView.text.toString(), holder)
|
||||||
|
return@setOnEditorActionListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// space or comma is detected
|
||||||
|
holder.autoComplete.addTextChangedListener {
|
||||||
|
if (it == null || it.isEmpty()) {
|
||||||
|
return@addTextChangedListener
|
||||||
|
}
|
||||||
|
text = it.toString()
|
||||||
|
|
||||||
|
if (it.last() == ',') {
|
||||||
|
val name = it.substring(0, it.length - 1)
|
||||||
|
addTag(name, holder)
|
||||||
|
|
||||||
|
holder.autoComplete.text = null
|
||||||
|
// mainTagAutoCompleteTextView.removeTextChangedListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.mainTagChipGroup.removeAllViews()
|
||||||
|
filter.state.forEach {
|
||||||
|
addChipToGroup(it, holder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
return filter == (other as SelectItem).filter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addTag(name: String, holder: Holder) {
|
||||||
|
if (name.isNotEmpty() && !filter.state.contains(name)) {
|
||||||
|
addChipToGroup(name, holder)
|
||||||
|
filter.state += name
|
||||||
|
} else {
|
||||||
|
Timber.d("Invalid tag: $name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addChipToGroup(name: String, holder: Holder) {
|
||||||
|
val chip = Chip(holder.itemView.context)
|
||||||
|
chip.text = name
|
||||||
|
|
||||||
|
chip.isClickable = true
|
||||||
|
chip.isCheckable = false
|
||||||
|
chip.isCloseIconVisible = true
|
||||||
|
|
||||||
|
holder.mainTagChipGroup.addView(chip)
|
||||||
|
|
||||||
|
chip.setOnCloseIconClickListener {
|
||||||
|
holder.mainTagChipGroup.removeView(chip)
|
||||||
|
filter.state -= name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
|
val text: TextView = itemView.findViewById(R.id.nav_view_item_text)
|
||||||
|
val autoComplete: AutoCompleteTextView = itemView.findViewById(R.id.nav_view_item)
|
||||||
|
val mainTagChipGroup: ChipGroup = itemView.findViewById(R.id.chip_group)
|
||||||
|
}
|
||||||
|
}
|
@ -86,3 +86,26 @@ class SelectSectionItem(filter: Filter.Select<*>) : SelectItem(filter), ISection
|
|||||||
return filter.hashCode()
|
return filter.hashCode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SY -->
|
||||||
|
class AutoCompleteSectionItem(filter: Filter.AutoComplete) : AutoComplete(filter), ISectionable<AutoComplete.Holder, GroupItem> {
|
||||||
|
|
||||||
|
private var head: GroupItem? = null
|
||||||
|
|
||||||
|
override fun getHeader(): GroupItem? = head
|
||||||
|
|
||||||
|
override fun setHeader(header: GroupItem?) {
|
||||||
|
head = header
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
return filter == (other as AutoCompleteSectionItem).filter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return filter.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SY <--
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Filter
|
||||||
|
import android.widget.Filterable
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class AutoCompleteAdapter(context: Context, resource: Int, var objects: List<String>, val excludePrefix: String?) :
|
||||||
|
ArrayAdapter<String>(context, resource, objects),
|
||||||
|
Filterable {
|
||||||
|
|
||||||
|
private var mOriginalValues: List<String>? = objects
|
||||||
|
private var mFilter: ListFilter? = null
|
||||||
|
|
||||||
|
override fun getCount(): Int {
|
||||||
|
return objects.size
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItem(position: Int): String {
|
||||||
|
return objects[position]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilter(): Filter {
|
||||||
|
if (mFilter == null) {
|
||||||
|
mFilter = ListFilter()
|
||||||
|
}
|
||||||
|
return mFilter!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ListFilter : Filter() {
|
||||||
|
override fun performFiltering(prefix: CharSequence?): FilterResults {
|
||||||
|
val results = FilterResults()
|
||||||
|
if (mOriginalValues == null) {
|
||||||
|
mOriginalValues = objects
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("$prefix ")
|
||||||
|
|
||||||
|
if (prefix == null || prefix.isEmpty()) {
|
||||||
|
val list = mOriginalValues!!
|
||||||
|
results.values = list
|
||||||
|
results.count = list.size
|
||||||
|
} else {
|
||||||
|
val prefixString = prefix.toString()
|
||||||
|
val containsPrefix: Boolean = excludePrefix?.let { prefixString.startsWith(it) } ?: false
|
||||||
|
Timber.d(prefixString)
|
||||||
|
val filterResults = mOriginalValues!!.filter { it.contains(if (excludePrefix != null) prefixString.removePrefix(excludePrefix) else prefixString, true) }
|
||||||
|
results.values = if (containsPrefix) filterResults.map { excludePrefix + it } else filterResults
|
||||||
|
results.count = filterResults.size
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun publishResults(constraint: CharSequence?, results: FilterResults) {
|
||||||
|
objects = if (results.values != null) {
|
||||||
|
results.values as List<String>? ?: emptyList()
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.count > 0) {
|
||||||
|
notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
notifyDataSetInvalidated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21989
app/src/main/java/exh/eh/EHTags.kt
Normal file
21989
app/src/main/java/exh/eh/EHTags.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,7 @@ class FilterSerializer {
|
|||||||
val serializers = listOf<Serializer<*>>(
|
val serializers = listOf<Serializer<*>>(
|
||||||
// EXH -->
|
// EXH -->
|
||||||
HelpDialogSerializer(this),
|
HelpDialogSerializer(this),
|
||||||
|
AutoCompleteSerializer(this),
|
||||||
// EXH <--
|
// EXH <--
|
||||||
HeaderSerializer(this),
|
HeaderSerializer(this),
|
||||||
SeparatorSerializer(this),
|
SeparatorSerializer(this),
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package xyz.nulldev.ts.api.http.serializer
|
package xyz.nulldev.ts.api.http.serializer
|
||||||
|
|
||||||
import com.github.salomonbrys.kotson.bool
|
import com.github.salomonbrys.kotson.bool
|
||||||
|
import com.github.salomonbrys.kotson.forEach
|
||||||
import com.github.salomonbrys.kotson.int
|
import com.github.salomonbrys.kotson.int
|
||||||
|
import com.github.salomonbrys.kotson.nullArray
|
||||||
import com.github.salomonbrys.kotson.nullObj
|
import com.github.salomonbrys.kotson.nullObj
|
||||||
import com.github.salomonbrys.kotson.set
|
import com.github.salomonbrys.kotson.set
|
||||||
|
import com.github.salomonbrys.kotson.string
|
||||||
import com.google.gson.JsonArray
|
import com.google.gson.JsonArray
|
||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
@ -218,3 +221,33 @@ class SortSerializer(override val serializer: FilterSerializer) : Serializer<Fil
|
|||||||
const val STATE_ASCENDING = "ascending"
|
const val STATE_ASCENDING = "ascending"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AutoCompleteSerializer(override val serializer: FilterSerializer) : Serializer<Filter.AutoComplete> {
|
||||||
|
override val type = "AUTOCOMPLETE"
|
||||||
|
override val clazz = Filter.AutoComplete::class
|
||||||
|
|
||||||
|
override fun serialize(json: JsonObject, filter: Filter.AutoComplete) {
|
||||||
|
// Serialize values to JSON
|
||||||
|
json[STATE] = JsonArray().apply {
|
||||||
|
filter.state.forEach { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(json: JsonObject, filter: Filter.AutoComplete) {
|
||||||
|
// Deserialize state
|
||||||
|
json[STATE].nullArray?.let { array ->
|
||||||
|
filter.state = array.map {
|
||||||
|
it.string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mappings() = listOf(
|
||||||
|
Pair(NAME, Filter.AutoComplete::name)
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val NAME = "name"
|
||||||
|
const val STATE = "state"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
43
app/src/main/res/layout/navigation_view_autocomplete.xml
Normal file
43
app/src/main/res/layout/navigation_view_autocomplete.xml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="?attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?attr/listPreferredItemPaddingEnd">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/listPreferredItemHeightSmall">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/nav_view_item_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
tools:text="Filter:" />
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/nav_view_item"
|
||||||
|
style="@style/Theme.Widget.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:completionThreshold="1"
|
||||||
|
android:gravity="center_vertical|start"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textCapWords" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/chip_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:chipSpacingVertical="4dp"
|
||||||
|
style="@style/Theme.Widget.Chip"/>
|
||||||
|
</LinearLayout>
|
Loading…
x
Reference in New Issue
Block a user