diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt new file mode 100644 index 0000000000..e0d7bdd888 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiController.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.riotx.features.autocomplete.emoji + +import android.graphics.Typeface +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.TypedEpoxyController +import im.vector.riotx.EmojiCompatFontProvider +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import im.vector.riotx.features.reactions.ReactionClickListener +import im.vector.riotx.features.reactions.data.EmojiItem +import im.vector.riotx.features.reactions.emojiSearchResultItem +import javax.inject.Inject + +class AutocompleteEmojiController @Inject constructor( + private val fontProvider: EmojiCompatFontProvider +) : TypedEpoxyController>() { + + var emojiTypeface: Typeface? = fontProvider.typeface + + private val fontProviderListener = object : EmojiCompatFontProvider.FontProviderListener { + override fun compatibilityFontUpdate(typeface: Typeface?) { + emojiTypeface = typeface + } + } + + init { + fontProvider.addListener(fontProviderListener) + } + + var listener: AutocompleteClickListener? = null + + override fun buildModels(data: List?) { + if (data.isNullOrEmpty()) { + return + } + data.forEach { emojiItem -> + emojiSearchResultItem { + id(emojiItem.name) + emojiItem(emojiItem) + emojiTypeFace(emojiTypeface) + //currentQuery(data.query) + onClickListener(object : ReactionClickListener { + override fun onReactionSelected(reaction: String) { + listener?.onItemClick(reaction) + } + } + ) + } + } + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + fontProvider.removeListener(fontProviderListener) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiItem.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiItem.kt new file mode 100644 index 0000000000..e5c53315ab --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiItem.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.riotx.features.autocomplete.emoji + +import android.view.View +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.riotx.R +import im.vector.riotx.core.epoxy.VectorEpoxyHolder +import im.vector.riotx.core.epoxy.VectorEpoxyModel + +//@EpoxyModelClass(layout = R.layout.item_autocomplete_emoji) +//abstract class AutocompleteEmojiItem : VectorEpoxyModel() { +// +// @EpoxyAttribute +// var name: CharSequence? = null +// @EpoxyAttribute +// var parameters: CharSequence? = null +// @EpoxyAttribute +// var description: CharSequence? = null +// @EpoxyAttribute +// var clickListener: View.OnClickListener? = null +// +// override fun bind(holder: Holder) { +// holder.view.setOnClickListener(clickListener) +// +// holder.nameView.text = name +// holder.parametersView.text = parameters +// holder.descriptionView.text = description +// } +// +// class Holder : VectorEpoxyHolder() { +// val nameView by bind(R.id.commandName) +// val parametersView by bind(R.id.commandParameter) +// val descriptionView by bind(R.id.commandDescription) +// } +//} diff --git a/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiPresenter.kt b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiPresenter.kt new file mode 100644 index 0000000000..731b48af86 --- /dev/null +++ b/vector/src/main/java/im/vector/riotx/features/autocomplete/emoji/AutocompleteEmojiPresenter.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2019 New Vector Ltd + * + * 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 im.vector.riotx.features.autocomplete.emoji + +import android.content.Context +import androidx.recyclerview.widget.RecyclerView +import com.otaliastudios.autocomplete.RecyclerViewPresenter +import im.vector.riotx.features.autocomplete.AutocompleteClickListener +import im.vector.riotx.features.reactions.data.EmojiDataSource +import javax.inject.Inject + +class AutocompleteEmojiPresenter @Inject constructor(context: Context, + private val emojiDataSource: EmojiDataSource, + private val controller: AutocompleteEmojiController) : + RecyclerViewPresenter(context), AutocompleteClickListener { + + init { + controller.listener = this + } + + override fun instantiateAdapter(): RecyclerView.Adapter<*> { + // Also remove animation + recyclerView?.itemAnimator = null + return controller.adapter + } + + override fun onItemClick(t: String) { + dispatchClick(t) + } + + override fun onQuery(query: CharSequence?) { + val data = if (query.isNullOrBlank()) { + // Return common emojis + emojiDataSource.getQuickReactions() + } else { + emojiDataSource.filterWith(query.toString()) + } + controller.setData(data) + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt index ba643e2d7d..9fe2249450 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/AutoCompleter.kt @@ -21,7 +21,6 @@ import android.graphics.drawable.Drawable import android.text.Editable import android.text.Spannable import android.widget.EditText -import androidx.fragment.app.Fragment import com.otaliastudios.autocomplete.Autocomplete import com.otaliastudios.autocomplete.AutocompleteCallback import com.otaliastudios.autocomplete.CharPolicy @@ -35,6 +34,7 @@ import im.vector.riotx.R import im.vector.riotx.core.glide.GlideApp import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy +import im.vector.riotx.features.autocomplete.emoji.AutocompleteEmojiPresenter import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.autocomplete.user.AutocompleteUserPresenter @@ -51,7 +51,8 @@ class AutoCompleter @Inject constructor( private val autocompleteCommandPresenter: AutocompleteCommandPresenter, private val autocompleteUserPresenter: AutocompleteUserPresenter, private val autocompleteRoomPresenter: AutocompleteRoomPresenter, - private val autocompleteGroupPresenter: AutocompleteGroupPresenter + private val autocompleteGroupPresenter: AutocompleteGroupPresenter, + private val autocompleteEmojiPresenter: AutocompleteEmojiPresenter ) { private lateinit var editText: EditText @@ -76,6 +77,7 @@ class AutoCompleter @Inject constructor( setupUsers(backgroundDrawable, editText, listener) setupRooms(backgroundDrawable, editText, listener) setupGroups(backgroundDrawable, editText, listener) + setupEmojis(backgroundDrawable, editText) } fun render(state: TextComposerViewState) { @@ -162,6 +164,27 @@ class AutoCompleter @Inject constructor( .build() } + private fun setupEmojis(backgroundDrawable: Drawable, editText: EditText) { + Autocomplete.on(editText) + .with(CharPolicy(':', true)) + .with(autocompleteEmojiPresenter) + .with(ELEVATION) + .with(backgroundDrawable) + .with(object : AutocompleteCallback { + override fun onPopupItemClicked(editable: Editable, item: String): Boolean { + editable.clear() + editable + .append(item) + .append(" ") + return true + } + + override fun onPopupVisibilityChanged(shown: Boolean) { + } + }) + .build() + } + private fun insertMatrixItem(editText: EditText, editable: Editable, firstChar: String, matrixItem: MatrixItem) { // Detect last firstChar and remove it var startIndex = editable.lastIndexOf(firstChar) diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt index 01debac5ed..aa5e79ed29 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/EmojiSearchResultViewModel.kt @@ -56,26 +56,12 @@ class EmojiSearchResultViewModel @AssistedInject constructor( } private fun updateQuery(action: EmojiSearchAction.UpdateQuery) { - val words = action.queryString.split("\\s".toRegex()) setState { copy( query = action.queryString, // First add emojis with name matching query, sorted by name // Then emojis with keyword matching any of the word in the query, sorted by name - results = dataSource.rawData.emojis - .values - .filter { emojiItem -> - emojiItem.name.contains(action.queryString, true) - } - .sortedBy { it.name } - + dataSource.rawData.emojis - .values - .filter { emojiItem -> - words.fold(true, { prev, word -> - prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) } - }) - } - .sortedBy { it.name } + results = dataSource.filterWith(action.queryString) ) } } diff --git a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt index a326828112..873ab90254 100644 --- a/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt +++ b/vector/src/main/java/im/vector/riotx/features/reactions/data/EmojiDataSource.kt @@ -33,4 +33,25 @@ class EmojiDataSource @Inject constructor( .fromJson(input.bufferedReader().use { it.readText() }) } ?: EmojiData(emptyList(), emptyMap(), emptyMap()) + + fun filterWith(query: String): List { + val words = query.split("\\s".toRegex()) + + return rawData.emojis.values + .filter { emojiItem -> + emojiItem.name.contains(query, true) + } + .sortedBy { it.name } + + rawData.emojis.values + .filter { emojiItem -> + words.fold(true, { prev, word -> + prev && emojiItem.keywords.any { keyword -> keyword.contains(word, true) } + }) + } + .sortedBy { it.name } + } + + fun getQuickReactions(): List { + return listOf("👍", "👎", "😄", "🎉", "😕", "❤️", "🚀", "👀").mapNotNull { rawData.emojis[it] } + } }