Commit f3224435 authored by Leonardo Aramaki's avatar Leonardo Aramaki

Showing unlimeted completion suggestions for commands and properly displaying its descriptions

parent f14e01f3
...@@ -11,7 +11,7 @@ import chat.rocket.android.widget.autocompletion.model.SuggestionModel ...@@ -11,7 +11,7 @@ import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder import chat.rocket.android.widget.autocompletion.ui.BaseSuggestionViewHolder
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>("/") { class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolder>("/", UNLIMITED_RESULT_COUNT) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommandSuggestionsViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommandSuggestionsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.suggestion_command_item, parent, val view = LayoutInflater.from(parent.context).inflate(R.layout.suggestion_command_item, parent,
...@@ -27,7 +27,7 @@ class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolde ...@@ -27,7 +27,7 @@ class CommandSuggestionsAdapter : SuggestionsAdapter<CommandSuggestionsViewHolde
val nameTextView = itemView.findViewById<TextView>(R.id.text_command_name) val nameTextView = itemView.findViewById<TextView>(R.id.text_command_name)
val descriptionTextView = itemView.findViewById<TextView>(R.id.text_command_description) val descriptionTextView = itemView.findViewById<TextView>(R.id.text_command_description)
nameTextView.text = item.text nameTextView.text = item.text
descriptionTextView.text = item.description descriptionTextView.text = item.description.replace("_", " ")
setOnClickListener { setOnClickListener {
itemClickListener?.onClick(item) itemClickListener?.onClick(item)
} }
......
...@@ -495,9 +495,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR ...@@ -495,9 +495,9 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
private fun setupSuggestionsView() { private fun setupSuggestionsView() {
suggestions_view.anchor(text_message) suggestions_view.anchor(text_message)
.bindTokenAdapter(PeopleSuggestionsAdapter()) .registerTokenAdapter(PeopleSuggestionsAdapter())
.bindTokenAdapter(CommandSuggestionsAdapter()) .registerTokenAdapter(CommandSuggestionsAdapter())
.bindTokenAdapter(RoomSuggestionsAdapter()) .registerTokenAdapter(RoomSuggestionsAdapter())
.addSuggestionProviderAction("@") { query -> .addSuggestionProviderAction("@") { query ->
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
presenter.spotlight(query, PEOPLE, true) presenter.spotlight(query, PEOPLE, true)
......
...@@ -2,24 +2,25 @@ package chat.rocket.android.widget.autocompletion.strategy.regex ...@@ -2,24 +2,25 @@ package chat.rocket.android.widget.autocompletion.strategy.regex
import chat.rocket.android.widget.autocompletion.model.SuggestionModel import chat.rocket.android.widget.autocompletion.model.SuggestionModel
import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy import chat.rocket.android.widget.autocompletion.strategy.CompletionStrategy
import chat.rocket.android.widget.autocompletion.ui.SuggestionsAdapter
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
internal class StringMatchingCompletionStrategy : CompletionStrategy { internal class StringMatchingCompletionStrategy(private val threshold: Int = -1) : CompletionStrategy {
private val list = CopyOnWriteArrayList<SuggestionModel>() private val list = CopyOnWriteArrayList<SuggestionModel>()
override fun autocompleteItems(prefix: String): List<SuggestionModel> { override fun autocompleteItems(prefix: String): List<SuggestionModel> {
return list.filter { val result = list.filter {
it.searchList.forEach { word -> it.searchList.forEach { word ->
if (word.contains(prefix, ignoreCase = true)) { if (word.contains(prefix, ignoreCase = true)) {
return@filter true return@filter true
} }
} }
false false
}.sortedByDescending { it.pinned }.take(5) }.sortedByDescending { it.pinned }
return if (threshold == SuggestionsAdapter.UNLIMITED_RESULT_COUNT) result else result.take(threshold)
} }
override fun addAll(list: List<SuggestionModel>) { override fun addAll(list: List<SuggestionModel>) {
// this.list.removeAll { !it.pinned }
this.list.addAllAbsent(list) this.list.addAllAbsent(list)
} }
......
...@@ -7,14 +7,23 @@ import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCo ...@@ -7,14 +7,23 @@ import chat.rocket.android.widget.autocompletion.strategy.regex.StringMatchingCo
import java.lang.reflect.Type import java.lang.reflect.Type
import kotlin.properties.Delegates import kotlin.properties.Delegates
abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: String) : RecyclerView.Adapter<VH>() { abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: String, threshold: Int = MAX_RESULT_COUNT) : RecyclerView.Adapter<VH>() {
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy() companion object {
const val UNLIMITED_RESULT_COUNT = -1
private const val MAX_RESULT_COUNT = 5
}
private var itemType: Type? = null private var itemType: Type? = null
private var itemClickListener: ItemClickListener? = null private var itemClickListener: ItemClickListener? = null
// Called to gather results when no results have previously matched.
private var providerExternal: ((query: String) -> Unit)? = null private var providerExternal: ((query: String) -> Unit)? = null
private var prefix: String by Delegates.observable("", { _, _, _ -> // Maximum number of results/suggestions to display.
strategy.autocompleteItems(prefix) private var resultsThreshold: Int = if (threshold > 0) threshold else UNLIMITED_RESULT_COUNT
notifyItemRangeChanged(0, 5) // The strategy used for suggesting completions.
private val strategy: CompletionStrategy = StringMatchingCompletionStrategy(resultsThreshold)
// Current input term to look up for suggestions.
private var currentTerm: String by Delegates.observable("", { _, _, newTerm ->
val items = strategy.autocompleteItems(newTerm)
notifyDataSetChanged()
}) })
init { init {
...@@ -29,21 +38,21 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri ...@@ -29,21 +38,21 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri
holder.bind(getItem(position), itemClickListener) holder.bind(getItem(position), itemClickListener)
} }
override fun getItemCount() = strategy.autocompleteItems(prefix).size override fun getItemCount() = strategy.autocompleteItems(currentTerm).size
private fun getItem(position: Int): SuggestionModel { private fun getItem(position: Int): SuggestionModel {
return strategy.autocompleteItems(prefix)[position] return strategy.autocompleteItems(currentTerm)[position]
} }
fun autocomplete(prefix: String) { fun autocomplete(newTerm: String) {
this.prefix = prefix.toLowerCase().trim() this.currentTerm = newTerm.toLowerCase().trim()
} }
fun addItems(list: List<SuggestionModel>) { fun addItems(list: List<SuggestionModel>) {
strategy.addAll(list) strategy.addAll(list)
// Since we've just added new items we should check for possible new completion suggestions. // Since we've just added new items we should check for possible new completion suggestions.
strategy.autocompleteItems(prefix) strategy.autocompleteItems(currentTerm)
notifyItemRangeChanged(0, 5) notifyDataSetChanged()
} }
fun setOnClickListener(clickListener: ItemClickListener) { fun setOnClickListener(clickListener: ItemClickListener) {
...@@ -52,11 +61,24 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri ...@@ -52,11 +61,24 @@ abstract class SuggestionsAdapter<VH : BaseSuggestionViewHolder>(val token: Stri
fun hasItemClickListener() = itemClickListener != null fun hasItemClickListener() = itemClickListener != null
fun prefix() = prefix /**
* Return the current searched term.
*/
fun term() = this.currentTerm
/**
* Set the maximum number of results to show.
*
* @param threshold The maximum number of suggestions to display.
*/
fun setResultsThreshold(threshold: Int) {
check(threshold > 0)
resultsThreshold = threshold
}
fun cancel() { fun cancel() {
strategy.addAll(emptyList()) strategy.addAll(emptyList())
strategy.autocompleteItems(prefix) strategy.autocompleteItems(currentTerm)
notifyDataSetChanged() notifyDataSetChanged()
} }
......
...@@ -132,7 +132,7 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -132,7 +132,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
return this return this
} }
fun bindTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView { fun registerTokenAdapter(adapter: SuggestionsAdapter<*>): SuggestionsView {
adaptersByToken.getOrPut(adapter.token, { adapter }) adaptersByToken.getOrPut(adapter.token, { adapter })
return this return this
} }
...@@ -141,7 +141,7 @@ class SuggestionsView : FrameLayout, TextWatcher { ...@@ -141,7 +141,7 @@ class SuggestionsView : FrameLayout, TextWatcher {
if (list.isNotEmpty()) { if (list.isNotEmpty()) {
val adapter = adapter(token) val adapter = adapter(token)
localProvidersByToken.getOrPut(token, { hashMapOf() }) localProvidersByToken.getOrPut(token, { hashMapOf() })
.put(adapter.prefix(), list) .put(adapter.term(), list)
if (completionStartIndex.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand() if (completionStartIndex.get() > NO_STATE_INDEX && adapter.itemCount == 0) expand()
adapter.addItems(list) adapter.addItems(list)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment