mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Add React With Any Search and update UX.
This commit is contained in:
@@ -7,6 +7,7 @@ import androidx.appcompat.widget.AppCompatImageView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
import java.util.function.Consumer
|
||||
|
||||
interface KeyboardPageCategoryIconMappingModel<T : KeyboardPageCategoryIconMappingModel<T>> : MappingModel<T> {
|
||||
val key: String
|
||||
@@ -15,14 +16,14 @@ interface KeyboardPageCategoryIconMappingModel<T : KeyboardPageCategoryIconMappi
|
||||
fun getIcon(context: Context): Drawable
|
||||
}
|
||||
|
||||
class KeyboardPageCategoryIconViewHolder<T : KeyboardPageCategoryIconMappingModel<T>>(itemView: View, private val onPageSelected: (String) -> Unit) : MappingViewHolder<T>(itemView) {
|
||||
class KeyboardPageCategoryIconViewHolder<T : KeyboardPageCategoryIconMappingModel<T>>(itemView: View, private val onPageSelected: Consumer<String>) : MappingViewHolder<T>(itemView) {
|
||||
|
||||
private val iconView: AppCompatImageView = itemView.findViewById(R.id.category_icon)
|
||||
private val iconSelected: View = itemView.findViewById(R.id.category_icon_selected)
|
||||
|
||||
override fun bind(model: T) {
|
||||
itemView.setOnClickListener {
|
||||
onPageSelected(model.key)
|
||||
onPageSelected.accept(model.key)
|
||||
}
|
||||
|
||||
iconView.setImageDrawable(model.getIcon(context))
|
||||
|
||||
@@ -3,8 +3,9 @@ package org.thoughtcrime.securesms.keyboard.emoji
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPageCategoryIconViewHolder
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import java.util.function.Consumer
|
||||
|
||||
class EmojiKeyboardPageCategoriesAdapter(private val onPageSelected: (String) -> Unit) : MappingAdapter() {
|
||||
class EmojiKeyboardPageCategoriesAdapter(private val onPageSelected: Consumer<String>) : MappingAdapter() {
|
||||
init {
|
||||
registerFactory(EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel::class.java, LayoutFactory({ v -> KeyboardPageCategoryIconViewHolder<EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel>(v, onPageSelected) }, R.layout.keyboard_pager_category_icon))
|
||||
registerFactory(EmojiKeyboardPageCategoryMappingModel.EmojiCategoryMappingModel::class.java, LayoutFactory({ v -> KeyboardPageCategoryIconViewHolder<EmojiKeyboardPageCategoryMappingModel.EmojiCategoryMappingModel>(v, onPageSelected) }, R.layout.keyboard_pager_category_icon))
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.AttrRes
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.emoji.EmojiCategory
|
||||
import org.thoughtcrime.securesms.keyboard.KeyboardPageCategoryIconMappingModel
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
@@ -22,14 +23,10 @@ sealed class EmojiKeyboardPageCategoryMappingModel(
|
||||
return newItem.key == key
|
||||
}
|
||||
|
||||
class RecentsMappingModel(selected: Boolean) : EmojiKeyboardPageCategoryMappingModel(KEY, R.attr.emoji_category_recent, selected) {
|
||||
class RecentsMappingModel(selected: Boolean) : EmojiKeyboardPageCategoryMappingModel(RecentEmojiPageModel.KEY, R.attr.emoji_category_recent, selected) {
|
||||
override fun areContentsTheSame(newItem: EmojiKeyboardPageCategoryMappingModel): Boolean {
|
||||
return newItem is RecentsMappingModel && super.areContentsTheSame(newItem)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY = "Recents"
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiCategoryMappingModel(private val emojiCategory: EmojiCategory, selected: Boolean) : EmojiKeyboardPageCategoryMappingModel(emojiCategory.key, emojiCategory.icon, selected) {
|
||||
|
||||
@@ -20,7 +20,7 @@ class EmojiKeyboardPageViewModel : ViewModel() {
|
||||
|
||||
val categories: LiveData<MappingModelList> = Transformations.map(internalSelectedKey) { selected ->
|
||||
MappingModelList().apply {
|
||||
add(EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel(selected == EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel.KEY))
|
||||
add(EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel(selected == RecentEmojiPageModel.KEY))
|
||||
|
||||
EmojiCategory.values().forEach {
|
||||
add(EmojiKeyboardPageCategoryMappingModel.EmojiCategoryMappingModel(it, it.key == selected))
|
||||
@@ -41,7 +41,7 @@ class EmojiKeyboardPageViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun getPageForCategory(mappingModel: EmojiKeyboardPageCategoryMappingModel): EmojiPageMappingModel {
|
||||
val page = if (mappingModel.key == EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel.KEY) {
|
||||
val page = if (mappingModel.key == RecentEmojiPageModel.KEY) {
|
||||
RecentEmojiPageModel(ApplicationDependencies.getApplication(), EmojiKeyboardProvider.RECENT_STORAGE_KEY)
|
||||
} else {
|
||||
EmojiSource.latest.displayPages.first { it.iconAttr == mappingModel.iconId }
|
||||
@@ -57,7 +57,7 @@ class EmojiKeyboardPageViewModel : ViewModel() {
|
||||
companion object {
|
||||
fun getStartingTab(): String {
|
||||
return if (RecentEmojiPageModel.hasRecents(ApplicationDependencies.getApplication(), EmojiKeyboardProvider.RECENT_STORAGE_KEY)) {
|
||||
EmojiKeyboardPageCategoryMappingModel.RecentsMappingModel.KEY
|
||||
RecentEmojiPageModel.KEY
|
||||
} else {
|
||||
EmojiCategory.PEOPLE.key
|
||||
}
|
||||
|
||||
@@ -71,9 +71,7 @@ class KeyboardPageSearchView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
clearButton.setOnClickListener {
|
||||
input.text.clear()
|
||||
}
|
||||
clearButton.setOnClickListener { clearQuery() }
|
||||
|
||||
context.obtainStyledAttributes(attrs, R.styleable.KeyboardPageSearchView, 0, 0).use { typedArray ->
|
||||
val showAlways: Boolean = typedArray.getBoolean(R.styleable.KeyboardPageSearchView_show_always, false)
|
||||
@@ -97,6 +95,7 @@ class KeyboardPageSearchView @JvmOverloads constructor(
|
||||
val iconTint = typedArray.getColorStateList(R.styleable.KeyboardPageSearchView_search_icon_tint) ?: ContextCompat.getColorStateList(context, R.color.signal_icon_tint_primary)
|
||||
ImageViewCompat.setImageTintList(navButton, iconTint)
|
||||
ImageViewCompat.setImageTintList(clearButton, iconTint)
|
||||
input.setHintTextColor(iconTint)
|
||||
|
||||
val clickOnly: Boolean = typedArray.getBoolean(R.styleable.KeyboardPageSearchView_click_only, false)
|
||||
if (clickOnly) {
|
||||
@@ -109,10 +108,14 @@ class KeyboardPageSearchView @JvmOverloads constructor(
|
||||
|
||||
fun showRequested(): Boolean = state == State.SHOW_REQUESTED
|
||||
|
||||
fun enableBackNavigation() {
|
||||
navButton.setImageResource(R.drawable.ic_arrow_left_24)
|
||||
navButton.setOnClickListener {
|
||||
callbacks?.onNavigationClicked()
|
||||
fun enableBackNavigation(enable: Boolean = true) {
|
||||
navButton.setImageResource(if (enable) R.drawable.ic_arrow_left_24 else R.drawable.ic_search_24)
|
||||
if (enable) {
|
||||
navButton.setImageResource(R.drawable.ic_arrow_left_24)
|
||||
navButton.setOnClickListener { callbacks?.onNavigationClicked() }
|
||||
} else {
|
||||
navButton.setImageResource(R.drawable.ic_search_24)
|
||||
navButton.setOnClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +171,15 @@ class KeyboardPageSearchView @JvmOverloads constructor(
|
||||
enableBackNavigation()
|
||||
}
|
||||
|
||||
override fun clearFocus() {
|
||||
super.clearFocus()
|
||||
clearChildFocus(input)
|
||||
}
|
||||
|
||||
fun clearQuery() {
|
||||
input.text.clear()
|
||||
}
|
||||
|
||||
interface Callbacks {
|
||||
fun onFocusLost() = Unit
|
||||
fun onFocusGained() = Unit
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.thoughtcrime.securesms.components.emoji.RecentEmojiPageModel
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||
import org.thoughtcrime.securesms.database.EmojiSearchDatabase
|
||||
import org.thoughtcrime.securesms.emoji.EmojiSource
|
||||
import java.util.function.Consumer
|
||||
|
||||
private const val MINIMUM_QUERY_THRESHOLD = 1
|
||||
private const val EMOJI_SEARCH_LIMIT = 20
|
||||
@@ -18,18 +19,18 @@ class EmojiSearchRepository(private val context: Context) {
|
||||
|
||||
private val emojiSearchDatabase: EmojiSearchDatabase = DatabaseFactory.getEmojiSearchDatabase(context)
|
||||
|
||||
fun submitQuery(query: String, consumer: (EmojiPageModel) -> Unit) {
|
||||
if (query.length < MINIMUM_QUERY_THRESHOLD) {
|
||||
consumer(RecentEmojiPageModel(context, EmojiKeyboardProvider.RECENT_STORAGE_KEY))
|
||||
fun submitQuery(query: String, includeRecents: Boolean, limit: Int = EMOJI_SEARCH_LIMIT, consumer: Consumer<EmojiPageModel>) {
|
||||
if (query.length < MINIMUM_QUERY_THRESHOLD && includeRecents) {
|
||||
consumer.accept(RecentEmojiPageModel(context, EmojiKeyboardProvider.RECENT_STORAGE_KEY))
|
||||
} else {
|
||||
SignalExecutors.SERIAL.execute {
|
||||
val emoji: List<String> = emojiSearchDatabase.query(query, EMOJI_SEARCH_LIMIT)
|
||||
val emoji: List<String> = emojiSearchDatabase.query(query, limit)
|
||||
|
||||
val displayEmoji: List<Emoji> = emoji
|
||||
.mapNotNull { canonical -> EmojiSource.latest.canonicalToVariations[canonical] }
|
||||
.map { Emoji(it) }
|
||||
|
||||
consumer(EmojiSearchResultsPageModel(emoji, displayEmoji))
|
||||
consumer.accept(EmojiSearchResultsPageModel(emoji, displayEmoji))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +39,8 @@ class EmojiSearchRepository(private val context: Context) {
|
||||
private val emoji: List<String>,
|
||||
private val displayEmoji: List<Emoji>
|
||||
) : EmojiPageModel {
|
||||
override fun getKey(): String = ""
|
||||
|
||||
override fun getIconAttr(): Int = -1
|
||||
|
||||
override fun getEmoji(): List<String> = emoji
|
||||
|
||||
@@ -17,7 +17,7 @@ class EmojiSearchViewModel(private val repository: EmojiSearchRepository) : View
|
||||
}
|
||||
|
||||
fun onQueryChanged(query: String) {
|
||||
repository.submitQuery(query, internalPageModel::postValue)
|
||||
repository.submitQuery(query = query, includeRecents = true, consumer = internalPageModel::postValue)
|
||||
}
|
||||
|
||||
class Factory(private val repository: EmojiSearchRepository) : ViewModelProvider.Factory {
|
||||
|
||||
Reference in New Issue
Block a user