[go: nahoru, domu]

blob: d30928066c2c716f088725737d856eb1de9fb051 [file] [log] [blame]
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.ui.androidview.adapters
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.Filter
import android.widget.Filterable
import android.widget.FrameLayout
import android.widget.TextView
import androidx.compose.runtime.Composable
import androidx.ui.core.setViewContent
// TODO(lmr): This should be moved to a separate module, but needs to be one that is not IR-compiled
class ArrayAdapter<T> : BaseAdapter(), Filterable {
var composable: (@Composable (T) -> Unit)? = null
var items: MutableList<T>? = null
var itemLayoutResourceId: Int = android.R.layout.simple_list_item_1
var itemFieldId: Int = 0
private val lock = object {}
private var originalValues: ArrayList<T>? = null
private val filter: ArrayFilter by lazy { ArrayFilter() }
private var inflater: LayoutInflater? = null
override fun getFilter(): Filter = filter
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
if (parent == null) error("Expected non-null parent")
return if (composable != null)
getViewFromComposable(position, convertView, parent)
else
getViewFromLayout(position, convertView, parent)
}
private fun getViewFromLayout(position: Int, convertView: View?, parent: ViewGroup): View {
var inflater = inflater
if (inflater == null) {
inflater = LayoutInflater.from(parent.context)!!
this.inflater = inflater
}
return createViewFromResource(inflater, position, convertView, parent, itemLayoutResourceId)
}
private fun createViewFromResource(
inflater: LayoutInflater,
position: Int,
convertView: View?,
parent: ViewGroup,
resource: Int
): View {
val context = parent.context
val text: TextView?
val view = convertView ?: inflater.inflate(resource, parent, false)
try {
if (itemFieldId == 0) {
// If no custom field is assigned, assume the whole resource is a TextView
text = view as TextView
} else {
// Otherwise, find the TextView field within the layout
text = view.findViewById(itemFieldId) as? TextView
if (text == null) {
throw RuntimeException(
"Failed to find view with ID ${
context.resources.getResourceName(itemFieldId)
} in item layout"
)
}
}
} catch (e: ClassCastException) {
Log.e("ArrayAdapter", "You must supply a resource ID for a TextView")
throw IllegalStateException(
"ArrayAdapter requires the resource ID to be a TextView", e
)
}
val item = getItem(position)
if (item is CharSequence) {
text.text = item
} else {
text.text = item.toString()
}
return view
}
@Suppress("PLUGIN_WARNING")
private fun getViewFromComposable(position: Int, convertView: View?, parent: ViewGroup): View {
if (composable == null) error("Expected composable to be non-null")
val items = items ?: error("Expected non-null items array")
val item = items[position]
val view = convertView ?: FrameLayout(parent.context)
val group = view as ViewGroup
// TODO(lmr): we should use the variant of this that passes through ambients if we can
group.setViewContent {
composable!!(item)
}
return view
}
override fun getItem(position: Int): Any {
val items = items ?: error("Expected non-null items array")
return items[position] as Any
}
override fun getItemId(position: Int): Long = position.toLong()
override fun getCount(): Int {
val items = items ?: return 0
return items.size
}
private inner class ArrayFilter : Filter() {
override fun performFiltering(prefix: CharSequence?): Filter.FilterResults {
val results = Filter.FilterResults()
if (originalValues == null) {
originalValues = synchronized(lock) { ArrayList<T>(items!!) }
}
if (prefix == null || prefix.isEmpty()) {
val list = synchronized(lock) { ArrayList<T>(originalValues!!) }
results.values = list
results.count = list.size
} else {
val prefixString = prefix.toString().toLowerCase()
val values = synchronized(lock) { ArrayList<T>(originalValues!!) }
val count = values.size
val newValues = ArrayList<T>()
for (i in 0 until count) {
val value = values[i]
val valueText = value.toString().toLowerCase()
// First match against the whole, non-splitted value
if (valueText.startsWith(prefixString)) {
newValues.add(value)
} else {
val words = valueText.split(" ".toRegex()).dropLastWhile({
it.isEmpty()
}).toTypedArray()
for (word in words) {
if (word.startsWith(prefixString)) {
newValues.add(value)
break
}
}
}
}
results.values = newValues
results.count = newValues.size
}
return results
}
override fun publishResults(constraint: CharSequence?, results: Filter.FilterResults) {
@Suppress("UNCHECKED_CAST")
items = results.values as MutableList<T>
if (results.count > 0) {
notifyDataSetChanged()
} else {
notifyDataSetInvalidated()
}
}
}
}