Easier access to more custom emotes
- Expand button - More emotes by default Change-Id: Id18f0b36099465d83156fcee2d3b016f299402f4
This commit is contained in:
parent
1004bd19e3
commit
236c44a5a5
|
@ -16,10 +16,16 @@
|
|||
|
||||
package im.vector.app.features.autocomplete
|
||||
|
||||
import im.vector.app.features.autocomplete.member.AutocompleteEmojiDataItem
|
||||
|
||||
/**
|
||||
* Simple generic listener interface.
|
||||
*/
|
||||
interface AutocompleteClickListener<T> {
|
||||
|
||||
fun onItemClick(t: T)
|
||||
|
||||
fun onLoadMoreClick(item: AutocompleteEmojiDataItem.Expand) {}
|
||||
|
||||
fun maxShowSizeOverride(): Int? = null
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import im.vector.app.EmojiCompatFontProvider
|
|||
import im.vector.app.features.autocomplete.AutocompleteClickListener
|
||||
import im.vector.app.features.autocomplete.autocompleteHeaderItem
|
||||
import im.vector.app.features.autocomplete.member.AutocompleteEmojiDataItem
|
||||
import im.vector.app.features.autocomplete.member.AutocompleteMemberItem
|
||||
import im.vector.app.features.home.AvatarRenderer
|
||||
import im.vector.app.features.reactions.data.EmojiItem
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -49,16 +48,18 @@ class AutocompleteEmojiController @Inject constructor(
|
|||
if (data.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
val max = listener?.maxShowSizeOverride() ?: MAX
|
||||
data
|
||||
.take(MAX)
|
||||
.take(max)
|
||||
.forEach { item ->
|
||||
when (item) {
|
||||
is AutocompleteEmojiDataItem.Header -> buildHeaderItem(item)
|
||||
is AutocompleteEmojiDataItem.Emoji -> buildEmojiItem(item.emojiItem)
|
||||
is AutocompleteEmojiDataItem.Expand -> buildExpandItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.size > MAX) {
|
||||
if (data.size > max) {
|
||||
autocompleteMoreResultItem {
|
||||
id("more_result")
|
||||
}
|
||||
|
@ -89,6 +90,15 @@ class AutocompleteEmojiController @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildExpandItem(item: AutocompleteEmojiDataItem.Expand) {
|
||||
val host = this
|
||||
autocompleteExpandItem {
|
||||
id(item.loadMoreKey + "/" + item.loadMoreKeySecondary)
|
||||
count(item.count)
|
||||
onClickListener { host.listener?.onLoadMoreClick(item) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
fontProvider.addListener(fontProviderListener)
|
||||
|
@ -103,12 +113,16 @@ class AutocompleteEmojiController @Inject constructor(
|
|||
// Count of emojis for the current room's image pack
|
||||
const val CUSTOM_THIS_ROOM_MAX = 10
|
||||
// Count of emojis per other image pack
|
||||
const val CUSTOM_OTHER_ROOM_MAX = 3
|
||||
const val CUSTOM_OTHER_ROOM_MAX = 5
|
||||
// Count of emojis for global account data
|
||||
const val CUSTOM_ACCOUNT_MAX = 5
|
||||
// Count of other image packs
|
||||
const val MAX_CUSTOM_OTHER_ROOMS = 3
|
||||
const val MAX_CUSTOM_OTHER_ROOMS = 15
|
||||
// Total max
|
||||
const val MAX = 50
|
||||
// Total max after expanding a section
|
||||
const val MAX_EXPAND = 10000
|
||||
// Internal ID
|
||||
const val ACCOUNT_DATA_EMOTE_ID = "de.spiritcroc.riotx.ACCOUNT_DATA_EMOTES"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package im.vector.app.features.autocomplete.emoji
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Typeface
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
|
@ -30,6 +31,7 @@ import im.vector.app.core.epoxy.onClick
|
|||
import im.vector.app.core.extensions.setTextOrHide
|
||||
import im.vector.app.core.glide.GlideApp
|
||||
import im.vector.app.features.reactions.data.EmojiItem
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
|
||||
@EpoxyModelClass
|
||||
|
@ -52,6 +54,7 @@ abstract class AutocompleteEmojiItem : VectorEpoxyModel<AutocompleteEmojiItem.Ho
|
|||
if (emoteUrl?.isNotEmpty().orFalse()) {
|
||||
holder.emojiText.isVisible = false
|
||||
holder.emoteImage.isVisible = true
|
||||
holder.emoteImage.imageTintList = null
|
||||
GlideApp.with(holder.emoteImage)
|
||||
.load(emoteUrl)
|
||||
.centerCrop()
|
||||
|
|
|
@ -59,6 +59,9 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
|
||||
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
||||
|
||||
private val expandedSections = HashSet<Pair<String, String?>>()
|
||||
private var lastQuery: CharSequence? = null
|
||||
|
||||
init {
|
||||
controller.listener = this
|
||||
}
|
||||
|
@ -66,6 +69,7 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
fun clear() {
|
||||
coroutineScope.coroutineContext.cancelChildren()
|
||||
controller.listener = null
|
||||
expandedSections.clear()
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
@ -81,7 +85,24 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
dispatchClick(t)
|
||||
}
|
||||
|
||||
override fun onLoadMoreClick(item: AutocompleteEmojiDataItem.Expand) {
|
||||
expandedSections.add(Pair(item.loadMoreKey, item.loadMoreKeySecondary))
|
||||
//Timber.d("Load more emojis for ${item.loadMoreKey}/${item.loadMoreKeySecondary} ${expandedSections.contains(Pair(item.loadMoreKey, item.loadMoreKeySecondary))}")
|
||||
onQuery(lastQuery)
|
||||
}
|
||||
|
||||
override fun maxShowSizeOverride(): Int? {
|
||||
if (expandedSections.isNotEmpty()) {
|
||||
return AutocompleteEmojiController.MAX_EXPAND
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onQuery(query: CharSequence?) {
|
||||
if (query?.isNotEmpty() != true && lastQuery?.isEmpty() != true) {
|
||||
expandedSections.clear()
|
||||
}
|
||||
lastQuery = query
|
||||
coroutineScope.launch {
|
||||
// Plain emojis
|
||||
val data = if (query.isNullOrBlank()) {
|
||||
|
@ -93,30 +114,37 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
|
||||
// Custom emotes: This room's emotes
|
||||
val currentRoomEmotes = room.getAllEmojiItems(query)
|
||||
val emoteData = currentRoomEmotes.toAutocompleteItems().let {
|
||||
val allEmoteData = currentRoomEmotes.toAutocompleteItems().let {
|
||||
if (it.isNotEmpty()) {
|
||||
listOf(AutocompleteEmojiDataItem.Header(roomId, context.getString(R.string.custom_emotes_this_room))) + it
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}.limit(AutocompleteEmojiController.CUSTOM_THIS_ROOM_MAX).toMutableList()
|
||||
}
|
||||
val emoteData = allEmoteData.maybeLimit(AutocompleteEmojiController.CUSTOM_THIS_ROOM_MAX, roomId, null).toMutableList()
|
||||
val emoteUrls = HashSet<String>()
|
||||
emoteUrls.addAll(currentRoomEmotes.map { it.mxcUrl })
|
||||
if (allEmoteData.size > emoteData.size) {
|
||||
emoteData += listOf(AutocompleteEmojiDataItem.Expand(roomId, null, allEmoteData.size - emoteData.size))
|
||||
}
|
||||
// Global emotes (only while searching)
|
||||
if (!query.isNullOrBlank()) {
|
||||
// Account emotes
|
||||
val userPack = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_USER_EMOTES)?.content
|
||||
val allUserPack = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_USER_EMOTES)?.content
|
||||
?.toModel<RoomEmoteContent>().getEmojiItems(query)
|
||||
.limit(AutocompleteEmojiController.CUSTOM_ACCOUNT_MAX)
|
||||
val userPack = allUserPack.maybeLimit(AutocompleteEmojiController.CUSTOM_ACCOUNT_MAX, AutocompleteEmojiController.ACCOUNT_DATA_EMOTE_ID, null)
|
||||
if (userPack.isNotEmpty()) {
|
||||
emoteUrls.addAll(userPack.map { it.mxcUrl })
|
||||
emoteData += listOf(
|
||||
AutocompleteEmojiDataItem.Header(
|
||||
"de.spiritcroc.riotx.ACCOUNT_EMOJI_HEADER",
|
||||
AutocompleteEmojiController.ACCOUNT_DATA_EMOTE_ID,
|
||||
context.getString(R.string.custom_emotes_account_data)
|
||||
)
|
||||
)
|
||||
emoteData += userPack.toAutocompleteItems()
|
||||
if (allUserPack.size > userPack.size) {
|
||||
emoteData += listOf(AutocompleteEmojiDataItem.Expand(AutocompleteEmojiController.ACCOUNT_DATA_EMOTE_ID, null, allUserPack.size - userPack.size))
|
||||
}
|
||||
}
|
||||
// Global emotes from rooms
|
||||
val globalPacks = session.accountDataService().getUserAccountDataEvent(UserAccountDataTypes.TYPE_EMOTE_ROOMS)
|
||||
|
@ -138,9 +166,10 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
val emojiItems = packRoom.getEmojiItems(query, QueryStringValue.Equals(packId))
|
||||
val packName = emojiItems.first
|
||||
// Filter out duplicate emotes with the exact same mxc url
|
||||
val packImages = emojiItems.second.filter {
|
||||
val allPackImages = emojiItems.second.filter {
|
||||
it.mxcUrl !in emoteUrls
|
||||
}.limit(AutocompleteEmojiController.CUSTOM_OTHER_ROOM_MAX)
|
||||
}
|
||||
val packImages = allPackImages.maybeLimit(AutocompleteEmojiController.CUSTOM_OTHER_ROOM_MAX, packRoomId, packId)
|
||||
// Add header + emotes
|
||||
if (packImages.isNotEmpty()) {
|
||||
packsAdded++
|
||||
|
@ -165,6 +194,9 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
)
|
||||
)
|
||||
emoteData += packImages.toAutocompleteItems()
|
||||
if (allPackImages.size > packImages.size) {
|
||||
emoteData += listOf(AutocompleteEmojiDataItem.Expand(packRoomId, packId, allPackImages.size - packImages.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,6 +212,19 @@ class AutocompleteEmojiPresenter @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't limit if only one more would be required, such that showing a "load more" button would be a waste
|
||||
*/
|
||||
private fun <T>List<T>.maybeLimit(limit: Int, loadMoreKey: String, loadMoreKeySecondary: String?): List<T> {
|
||||
return if (size > limit + 1 && !expandedSections.contains(Pair(loadMoreKey, loadMoreKeySecondary))) {
|
||||
//Timber.d("maybeLimit $loadMoreKey/$loadMoreKeySecondary true ${expandedSections.contains(Pair(loadMoreKey, loadMoreKeySecondary))}")
|
||||
limit(limit)
|
||||
} else {
|
||||
//Timber.d("maybeLimit $loadMoreKey/$loadMoreKeySecondary false")
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<EmojiItem>.toAutocompleteItems(): List<AutocompleteEmojiDataItem> {
|
||||
return map { AutocompleteEmojiDataItem.Emoji(it) }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.app.features.autocomplete.emoji
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Typeface
|
||||
import androidx.core.view.isVisible
|
||||
import com.airbnb.epoxy.EpoxyAttribute
|
||||
import com.airbnb.epoxy.EpoxyModelClass
|
||||
import im.vector.app.R
|
||||
import im.vector.app.core.epoxy.ClickListener
|
||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||
import im.vector.app.core.epoxy.onClick
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
|
||||
@EpoxyModelClass // Re-using item_autocomplete_emoji layout for now because I'm lazy - may want to change that if it causes troubles
|
||||
abstract class AutocompleteExpandItem : VectorEpoxyModel<AutocompleteEmojiItem.Holder>(R.layout.item_autocomplete_emoji) {
|
||||
|
||||
@EpoxyAttribute
|
||||
var count: Int? = null
|
||||
|
||||
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
|
||||
var onClickListener: ClickListener? = null
|
||||
|
||||
override fun bind(holder: AutocompleteEmojiItem.Holder) {
|
||||
super.bind(holder)
|
||||
holder.emojiText.isVisible = false
|
||||
holder.emoteImage.isVisible = true
|
||||
holder.emoteImage.setImageResource(R.drawable.ic_expand_more)
|
||||
holder.emoteImage.imageTintList = ColorStateList.valueOf(ThemeUtils.getColor(holder.emoteImage.context, R.attr.vctr_content_secondary))
|
||||
holder.emojiText.typeface = Typeface.DEFAULT
|
||||
count.let {
|
||||
if (it == null) {
|
||||
holder.emojiNameText.setText(R.string.room_profile_section_more)
|
||||
} else {
|
||||
holder.emojiNameText.text = holder.emojiNameText.resources.getQuantityString(R.plurals.message_reaction_show_more, it, it)
|
||||
}
|
||||
}
|
||||
holder.emojiKeywordText.isVisible = false
|
||||
holder.view.onClick(onClickListener)
|
||||
}
|
||||
|
||||
/*
|
||||
class Holder : VectorEpoxyHolder() {
|
||||
val emojiText by bind<TextView>(R.id.itemAutocompleteEmoji)
|
||||
val emoteImage by bind<ImageView>(R.id.itemAutocompleteEmote)
|
||||
val emojiNameText by bind<TextView>(R.id.itemAutocompleteEmojiName)
|
||||
val emojiKeywordText by bind<TextView>(R.id.itemAutocompleteEmojiSubname)
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -21,4 +21,5 @@ import im.vector.app.features.reactions.data.EmojiItem
|
|||
sealed class AutocompleteEmojiDataItem {
|
||||
data class Header(val id: String, val title: String) : AutocompleteEmojiDataItem()
|
||||
data class Emoji(val emojiItem: EmojiItem) : AutocompleteEmojiDataItem()
|
||||
data class Expand(val loadMoreKey: String, val loadMoreKeySecondary: String?, val count: Int?) : AutocompleteEmojiDataItem()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue