mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add basic emoji search using string matching
This commit is contained in:
parent
b6206e3059
commit
e181717692
@ -678,11 +678,40 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var overrideInputConnection: InputConnection? = null
|
private var overrideInputConnection: InputConnection? = null
|
||||||
|
private var overrideEditorInfo: EditorInfo? = null
|
||||||
|
fun overrideInputConnection(to: InputConnection?, editorInfo: EditorInfo?) {
|
||||||
|
this.overrideInputConnection = to
|
||||||
|
this.overrideEditorInfo = editorInfo
|
||||||
|
|
||||||
|
latinIMELegacy.loadSettings()
|
||||||
|
|
||||||
|
inputLogic.finishInput()
|
||||||
|
inputLogic.startInput(RichInputMethodManager.getInstance().combiningRulesExtraValueOfCurrentSubtype, latinIMELegacy.mSettings.current)
|
||||||
|
|
||||||
|
val currentIC = currentInputConnection
|
||||||
|
currentIC?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE)
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
super.getCurrentInputConnection()?.setImeConsumesInput(to != null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isInputConnectionOverridden
|
||||||
|
get() = overrideInputConnection != null
|
||||||
|
|
||||||
override fun getCurrentInputConnection(): InputConnection? {
|
override fun getCurrentInputConnection(): InputConnection? {
|
||||||
return overrideInputConnection ?: super.getCurrentInputConnection()
|
return overrideInputConnection ?: super.getCurrentInputConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCurrentInputEditorInfo(): EditorInfo? {
|
||||||
|
return overrideEditorInfo ?: super.getCurrentInputEditorInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBaseInputConnection(): InputConnection? {
|
||||||
|
return super.getCurrentInputConnection()
|
||||||
|
}
|
||||||
|
|
||||||
override val lifecycle: Lifecycle
|
override val lifecycle: Lifecycle
|
||||||
get() = mLifecycleRegistry
|
get() = mLifecycleRegistry
|
||||||
override val savedStateRegistry: SavedStateRegistry
|
override val savedStateRegistry: SavedStateRegistry
|
||||||
|
@ -3,6 +3,7 @@ package org.futo.inputmethod.latin.uix
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@ -56,7 +57,7 @@ interface KeyboardManagerForAction {
|
|||||||
fun announce(s: String)
|
fun announce(s: String)
|
||||||
fun getActiveLocale(): Locale
|
fun getActiveLocale(): Locale
|
||||||
|
|
||||||
fun overrideInputConnection(inputConnection: InputConnection)
|
fun overrideInputConnection(inputConnection: InputConnection, editorInfo: EditorInfo)
|
||||||
fun unsetInputConnection()
|
fun unsetInputConnection()
|
||||||
|
|
||||||
fun requestDialog(text: String, options: List<DialogRequestItem>, onCancel: () -> Unit)
|
fun requestDialog(text: String, options: List<DialogRequestItem>, onCancel: () -> Unit)
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
package org.futo.inputmethod.latin.uix
|
package org.futo.inputmethod.latin.uix
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.InputType
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
|
||||||
|
|
||||||
class ActionEditText(context: Context, val textChanged: (String) -> Unit) :
|
class ActionEditText(context: Context) :
|
||||||
androidx.appcompat.widget.AppCompatEditText(context) {
|
androidx.appcompat.widget.AppCompatEditText(context) {
|
||||||
var inputConnection: InputConnection? = null
|
var inputConnection: InputConnection? = null
|
||||||
private set
|
private set
|
||||||
@ -26,6 +25,13 @@ class ActionEditText(context: Context, val textChanged: (String) -> Unit) :
|
|||||||
return inputConnection
|
return inputConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var textChanged: (String) -> Unit = { }
|
||||||
|
fun setTextChangeCallback(
|
||||||
|
textChanged: (String) -> Unit
|
||||||
|
) {
|
||||||
|
this.textChanged = textChanged
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTextChanged(
|
override fun onTextChanged(
|
||||||
text: CharSequence?,
|
text: CharSequence?,
|
||||||
start: Int,
|
start: Int,
|
||||||
@ -33,7 +39,12 @@ class ActionEditText(context: Context, val textChanged: (String) -> Unit) :
|
|||||||
lengthAfter: Int
|
lengthAfter: Int
|
||||||
) {
|
) {
|
||||||
super.onTextChanged(text, start, lengthBefore, lengthAfter)
|
super.onTextChanged(text, start, lengthBefore, lengthAfter)
|
||||||
textChanged(text?.toString() ?: "")
|
|
||||||
|
// For some strange reason this IS null sometimes, even though it
|
||||||
|
// shouldn't be
|
||||||
|
if(textChanged != null) {
|
||||||
|
textChanged(text?.toString() ?: "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,26 +54,41 @@ fun ActionTextEditor(text: MutableState<String>) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val manager = LocalManager.current
|
val manager = LocalManager.current
|
||||||
|
|
||||||
|
val height = with(LocalDensity.current) {
|
||||||
|
48.dp.toPx()
|
||||||
|
}
|
||||||
|
|
||||||
|
val inputType = EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE or EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||||
|
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = {
|
factory = {
|
||||||
ActionEditText(context) {
|
ActionEditText(context).apply {
|
||||||
text.value = it
|
this.inputType = inputType
|
||||||
}.apply {
|
|
||||||
onCreateInputConnection(
|
setTextChangeCallback { text.value = it }
|
||||||
EditorInfo()
|
|
||||||
)
|
setText(text.value)
|
||||||
|
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
inputType = InputType.TYPE_CLASS_TEXT
|
|
||||||
|
|
||||||
manager.overrideInputConnection(inputConnection!!)
|
setHeight(height.toInt())
|
||||||
|
|
||||||
|
val editorInfo = EditorInfo().apply {
|
||||||
|
this.inputType = inputType
|
||||||
|
}
|
||||||
|
onCreateInputConnection(editorInfo)
|
||||||
|
|
||||||
|
manager.overrideInputConnection(inputConnection!!, editorInfo)
|
||||||
|
|
||||||
|
requestFocus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.fillMaxHeight(),
|
||||||
onRelease = {
|
onRelease = {
|
||||||
manager.unsetInputConnection()
|
manager.unsetInputConnection()
|
||||||
}
|
}
|
||||||
|
@ -36,4 +36,8 @@ object EmojiTracker {
|
|||||||
.filter { it.isNotBlank() }
|
.filter { it.isNotBlank() }
|
||||||
.distinct()
|
.distinct()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun Context.resetRecentEmojis() {
|
||||||
|
setSetting(lastUsedEmoji, "")
|
||||||
|
}
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ import android.os.Vibrator
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InlineSuggestionsResponse
|
import android.view.inputmethod.InlineSuggestionsResponse
|
||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
import android.view.inputmethod.InputContentInfo
|
import android.view.inputmethod.InputContentInfo
|
||||||
@ -146,7 +147,11 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun typeText(v: String) {
|
override fun typeText(v: String) {
|
||||||
latinIME.latinIMELegacy.onTextInput(v)
|
if(latinIME.isInputConnectionOverridden) {
|
||||||
|
latinIME.getBaseInputConnection()?.commitText(v, 1)
|
||||||
|
} else {
|
||||||
|
latinIME.latinIMELegacy.onTextInput(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun typeUri(uri: Uri, mimeTypes: List<String>): Boolean {
|
override fun typeUri(uri: Uri, mimeTypes: List<String>): Boolean {
|
||||||
@ -224,16 +229,13 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
|
|||||||
return latinIME.latinIMELegacy.locale
|
return latinIME.latinIMELegacy.locale
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun overrideInputConnection(inputConnection: InputConnection) {
|
override fun overrideInputConnection(inputConnection: InputConnection, editorInfo: EditorInfo) {
|
||||||
latinIME.overrideInputConnection = inputConnection
|
latinIME.overrideInputConnection(inputConnection, editorInfo)
|
||||||
latinIME.inputLogic.startInput(RichInputMethodManager.getInstance().combiningRulesExtraValueOfCurrentSubtype,
|
uixManager.toggleExpandAction(true)
|
||||||
latinIME.latinIMELegacy.mSettings.current)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unsetInputConnection() {
|
override fun unsetInputConnection() {
|
||||||
latinIME.overrideInputConnection = null
|
latinIME.overrideInputConnection(null, null)
|
||||||
latinIME.inputLogic.startInput(RichInputMethodManager.getInstance().combiningRulesExtraValueOfCurrentSubtype,
|
|
||||||
latinIME.latinIMELegacy.mSettings.current)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestDialog(text: String, options: List<DialogRequestItem>, onCancel: () -> Unit) {
|
override fun requestDialog(text: String, options: List<DialogRequestItem>, onCancel: () -> Unit) {
|
||||||
@ -361,8 +363,8 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
keyboardManagerForAction.announce("$name closed")
|
keyboardManagerForAction.announce("$name closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleExpandAction() {
|
fun toggleExpandAction(to: Boolean? = null) {
|
||||||
mainKeyboardHidden = !mainKeyboardHidden
|
mainKeyboardHidden = !(to ?: mainKeyboardHidden)
|
||||||
if(!mainKeyboardHidden) {
|
if(!mainKeyboardHidden) {
|
||||||
latinIME.onKeyboardShown()
|
latinIME.onKeyboardShown()
|
||||||
}
|
}
|
||||||
@ -378,7 +380,7 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
1.5
|
1.5
|
||||||
}
|
}
|
||||||
Column {
|
Column {
|
||||||
if(mainKeyboardHidden) {
|
if(mainKeyboardHidden || latinIME.isInputConnectionOverridden) {
|
||||||
ActionWindowBar(
|
ActionWindowBar(
|
||||||
onBack = { returnBackToMainKeyboardViewFromAction() },
|
onBack = { returnBackToMainKeyboardViewFromAction() },
|
||||||
canExpand = currWindowAction!!.canShowKeyboard,
|
canExpand = currWindowAction!!.canShowKeyboard,
|
||||||
@ -398,7 +400,7 @@ class UixManager(private val latinIME: LatinIME) {
|
|||||||
windowImpl.WindowContents(keyboardShown = !isMainKeyboardHidden)
|
windowImpl.WindowContents(keyboardShown = !isMainKeyboardHidden)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mainKeyboardHidden) {
|
if(!mainKeyboardHidden && !latinIME.isInputConnectionOverridden) {
|
||||||
val suggestedWordsOrNull = if (shouldShowSuggestionStrip) {
|
val suggestedWordsOrNull = if (shouldShowSuggestionStrip) {
|
||||||
suggestedWords
|
suggestedWords
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,9 +11,11 @@ import androidx.annotation.UiThread
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.absoluteOffset
|
import androidx.compose.foundation.layout.absoluteOffset
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -25,6 +27,8 @@ import androidx.compose.foundation.lazy.LazyRow
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Search
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@ -43,6 +47,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
@ -73,8 +78,10 @@ import androidx.compose.ui.unit.IntSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
|
||||||
|
import androidx.recyclerview.widget.ListAdapter
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -88,9 +95,12 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
import org.futo.inputmethod.latin.R
|
import org.futo.inputmethod.latin.R
|
||||||
import org.futo.inputmethod.latin.common.Constants
|
import org.futo.inputmethod.latin.common.Constants
|
||||||
import org.futo.inputmethod.latin.uix.Action
|
import org.futo.inputmethod.latin.uix.Action
|
||||||
|
import org.futo.inputmethod.latin.uix.ActionTextEditor
|
||||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||||
import org.futo.inputmethod.latin.uix.AutoFitText
|
import org.futo.inputmethod.latin.uix.AutoFitText
|
||||||
|
import org.futo.inputmethod.latin.uix.DialogRequestItem
|
||||||
import org.futo.inputmethod.latin.uix.EmojiTracker.getRecentEmojis
|
import org.futo.inputmethod.latin.uix.EmojiTracker.getRecentEmojis
|
||||||
|
import org.futo.inputmethod.latin.uix.EmojiTracker.resetRecentEmojis
|
||||||
import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
|
import org.futo.inputmethod.latin.uix.EmojiTracker.useEmoji
|
||||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||||
import org.futo.inputmethod.latin.uix.actions.emoji.EmojiItem
|
import org.futo.inputmethod.latin.uix.actions.emoji.EmojiItem
|
||||||
@ -127,14 +137,24 @@ class EmojiItemItem(val emoji: EmojiItem) : EmojiViewItem() {
|
|||||||
const val VIEW_EMOJI = 0
|
const val VIEW_EMOJI = 0
|
||||||
const val VIEW_CATEGORY = 1
|
const val VIEW_CATEGORY = 1
|
||||||
|
|
||||||
|
private object EmojiViewItemDiffCallback : DiffUtil.ItemCallback<EmojiViewItem>() {
|
||||||
|
override fun areItemsTheSame(oldItem: EmojiViewItem, newItem: EmojiViewItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: EmojiViewItem, newItem: EmojiViewItem): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Note: Using traditional View here, because Android Compose leaves a lot of performance to be desired
|
// Note: Using traditional View here, because Android Compose leaves a lot of performance to be desired
|
||||||
class EmojiGridAdapter(
|
class EmojiGridAdapter(
|
||||||
private val data: List<EmojiViewItem>,
|
|
||||||
private val onClick: (EmojiItem) -> Unit,
|
private val onClick: (EmojiItem) -> Unit,
|
||||||
private val onSelectSkinTone: (PopupInfo) -> Unit,
|
private val onSelectSkinTone: (PopupInfo) -> Unit,
|
||||||
private val emojiCellWidth: Int,
|
private val emojiCellWidth: Int,
|
||||||
private val contentColor: Color
|
private val contentColor: Color
|
||||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
) : ListAdapter<EmojiViewItem, RecyclerView.ViewHolder>(EmojiViewItemDiffCallback) {
|
||||||
|
|
||||||
class EmojiViewHolder(
|
class EmojiViewHolder(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -189,7 +209,7 @@ class EmojiGridAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||||
val item = data[position]
|
val item = getItem(position)
|
||||||
if(item is EmojiItemItem && holder is EmojiViewHolder) {
|
if(item is EmojiItemItem && holder is EmojiViewHolder) {
|
||||||
holder.bindEmoji(item.emoji, onClick, onSelectSkinTone, contentColor.toArgb())
|
holder.bindEmoji(item.emoji, onClick, onSelectSkinTone, contentColor.toArgb())
|
||||||
}else if(item is CategoryItem && holder is CategoryViewHolder) {
|
}else if(item is CategoryItem && holder is CategoryViewHolder) {
|
||||||
@ -197,10 +217,8 @@ class EmojiGridAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount() = data.size
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
return when(data[position]) {
|
return when(getItem(position)) {
|
||||||
is CategoryItem -> VIEW_CATEGORY
|
is CategoryItem -> VIEW_CATEGORY
|
||||||
is EmojiItemItem -> VIEW_EMOJI
|
is EmojiItemItem -> VIEW_EMOJI
|
||||||
}
|
}
|
||||||
@ -259,7 +277,6 @@ fun Emojis(
|
|||||||
|
|
||||||
val emojiAdapter = remember {
|
val emojiAdapter = remember {
|
||||||
EmojiGridAdapter(
|
EmojiGridAdapter(
|
||||||
emojis,
|
|
||||||
onClick,
|
onClick,
|
||||||
onSelectSkinTone = {
|
onSelectSkinTone = {
|
||||||
activePopup = it
|
activePopup = it
|
||||||
@ -270,6 +287,10 @@ fun Emojis(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(emojis) {
|
||||||
|
emojiAdapter.submitList(emojis)
|
||||||
|
}
|
||||||
|
|
||||||
var viewWidth by remember { mutableIntStateOf(0) }
|
var viewWidth by remember { mutableIntStateOf(0) }
|
||||||
var viewHeight by remember { mutableIntStateOf(0) }
|
var viewHeight by remember { mutableIntStateOf(0) }
|
||||||
var popupSize by remember { mutableStateOf(IntSize(0, 0)) }
|
var popupSize by remember { mutableStateOf(IntSize(0, 0)) }
|
||||||
@ -281,13 +302,14 @@ fun Emojis(
|
|||||||
layoutManager = GridLayoutManager(context, 8).apply {
|
layoutManager = GridLayoutManager(context, 8).apply {
|
||||||
spanSizeLookup = object : SpanSizeLookup() {
|
spanSizeLookup = object : SpanSizeLookup() {
|
||||||
override fun getSpanSize(position: Int): Int {
|
override fun getSpanSize(position: Int): Int {
|
||||||
return when(emojis[position]) {
|
return when(emojiAdapter.currentList[position]) {
|
||||||
is EmojiItemItem -> 1
|
is EmojiItemItem -> 1
|
||||||
is CategoryItem -> spanCount
|
is CategoryItem -> spanCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adapter = emojiAdapter
|
adapter = emojiAdapter
|
||||||
|
|
||||||
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
@ -566,7 +588,9 @@ fun EmojiGrid(
|
|||||||
emojis: List<EmojiItem>,
|
emojis: List<EmojiItem>,
|
||||||
keyboardShown: Boolean,
|
keyboardShown: Boolean,
|
||||||
emojiMap: Map<String, EmojiItem>,
|
emojiMap: Map<String, EmojiItem>,
|
||||||
keyBackground: Drawable
|
keyBackground: Drawable,
|
||||||
|
isSearching: Boolean,
|
||||||
|
searchFilter: String
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val recentEmojis = remember {
|
val recentEmojis = remember {
|
||||||
@ -596,6 +620,23 @@ fun EmojiGrid(
|
|||||||
val jumpCategory: MutableState<CategoryItem?> = remember { mutableStateOf(null) }
|
val jumpCategory: MutableState<CategoryItem?> = remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
|
||||||
|
var emojiList = listOf(CategoryItem("Recent")) + recentEmojis.map { EmojiItemItem(it) } + categorizedEmojis
|
||||||
|
|
||||||
|
if(isSearching) {
|
||||||
|
emojiList = emojiList.filter {
|
||||||
|
(it is EmojiItemItem) &&
|
||||||
|
(it.emoji.description.contains(searchFilter)
|
||||||
|
|| it.emoji.aliases.joinToString().contains(searchFilter)
|
||||||
|
|| it.emoji.tags.joinToString().contains(searchFilter))
|
||||||
|
}.take(48).map {
|
||||||
|
EmojiItemItem((it as EmojiItemItem).emoji.copy(category = "Search Results"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if(emojiList.isEmpty()) {
|
||||||
|
emojiList = emojiList + listOf(CategoryItem("No results found"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
Emojis(
|
Emojis(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -612,25 +653,27 @@ fun EmojiGrid(
|
|||||||
keyBackground.state = intArrayOf()
|
keyBackground.state = intArrayOf()
|
||||||
keyBackground.draw(this.drawContext.canvas.nativeCanvas)
|
keyBackground.draw(this.drawContext.canvas.nativeCanvas)
|
||||||
},
|
},
|
||||||
emojis = listOf(CategoryItem("Recent")) + recentEmojis.map { EmojiItemItem(it) } + categorizedEmojis,
|
emojis = emojiList,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
emojiMap = emojiMap,
|
emojiMap = emojiMap,
|
||||||
currentCategory = currentCategory,
|
currentCategory = currentCategory,
|
||||||
jumpCategory = jumpCategory
|
jumpCategory = jumpCategory
|
||||||
)
|
)
|
||||||
|
|
||||||
EmojiNavigation(
|
if(!isSearching) {
|
||||||
showKeys = !keyboardShown,
|
EmojiNavigation(
|
||||||
onExit = onExit,
|
showKeys = !keyboardShown,
|
||||||
onBackspace = onBackspace,
|
onExit = onExit,
|
||||||
categories = listOf(
|
onBackspace = onBackspace,
|
||||||
CategoryItem("Recent")
|
categories = listOf(
|
||||||
) + categorizedEmojis.filterIsInstance<CategoryItem>(),
|
CategoryItem("Recent")
|
||||||
activeCategoryItem = currentCategory.value,
|
) + categorizedEmojis.filterIsInstance<CategoryItem>(),
|
||||||
goToCategory = {
|
activeCategoryItem = currentCategory.value,
|
||||||
jumpCategory.value = it
|
goToCategory = {
|
||||||
}
|
jumpCategory.value = it
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -719,6 +762,9 @@ val EmojiAction = Action(
|
|||||||
windowImpl = { manager, persistentState ->
|
windowImpl = { manager, persistentState ->
|
||||||
val state = persistentState as PersistentEmojiState
|
val state = persistentState as PersistentEmojiState
|
||||||
object : ActionWindow {
|
object : ActionWindow {
|
||||||
|
private val searchText = mutableStateOf("")
|
||||||
|
private val searching = mutableStateOf(false)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun windowName(): String {
|
override fun windowName(): String {
|
||||||
return stringResource(R.string.emoji_action_title)
|
return stringResource(R.string.emoji_action_title)
|
||||||
@ -745,7 +791,67 @@ val EmojiAction = Action(
|
|||||||
if(!isRepeated) {
|
if(!isRepeated) {
|
||||||
manager.performHapticAndAudioFeedback(Constants.CODE_DELETE, view)
|
manager.performHapticAndAudioFeedback(Constants.CODE_DELETE, view)
|
||||||
}
|
}
|
||||||
}, emojis = emojis, keyboardShown = keyboardShown, emojiMap = state.emojiMap, keyBackground = manager.getThemeProvider().keyBackground)
|
}, emojis = emojis, keyboardShown = keyboardShown, emojiMap = state.emojiMap, keyBackground = manager.getThemeProvider().keyBackground,
|
||||||
|
isSearching = searching.value, searchFilter = searchText.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun WindowTitleBar(rowScope: RowScope) {
|
||||||
|
if(searching.value) {
|
||||||
|
with(rowScope) {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceBright,
|
||||||
|
shape = RoundedCornerShape(24.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.padding(2.dp)
|
||||||
|
.weight(1.0f)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
ActionTextEditor(text = searchText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.WindowTitleBar(rowScope)
|
||||||
|
Surface(color = MaterialTheme.colorScheme.surfaceBright, shape = RoundedCornerShape(24.dp), modifier = Modifier
|
||||||
|
.minimumInteractiveComponentSize()
|
||||||
|
.padding(2.dp)
|
||||||
|
.width(128.dp)
|
||||||
|
.clickable { searching.value = true }) {
|
||||||
|
Box(modifier = Modifier.padding(8.dp), contentAlignment = Alignment.CenterStart) {
|
||||||
|
Row {
|
||||||
|
Icon(Icons.Default.Search, contentDescription = null)
|
||||||
|
Text("Search", style = Typography.bodySmall, modifier = Modifier
|
||||||
|
.alpha(0.75f)
|
||||||
|
.align(Alignment.CenterVertically))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = {
|
||||||
|
manager.requestDialog(
|
||||||
|
"Clear recent emojis?",
|
||||||
|
listOf(
|
||||||
|
DialogRequestItem("Cancel") {},
|
||||||
|
DialogRequestItem("Clear") {
|
||||||
|
runBlocking {
|
||||||
|
manager.getContext().resetRecentEmojis()
|
||||||
|
}
|
||||||
|
manager.closeActionWindow()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Icon(painterResource(id = R.drawable.close), contentDescription = "Clear recent emojis")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -772,6 +878,8 @@ fun EmojiGridPreview() {
|
|||||||
},
|
},
|
||||||
keyboardShown = false,
|
keyboardShown = false,
|
||||||
emojiMap = hashMapOf(),
|
emojiMap = hashMapOf(),
|
||||||
keyBackground = context.getDrawable(R.drawable.btn_keyboard_spacebar_lxx_dark)!!
|
keyBackground = context.getDrawable(R.drawable.btn_keyboard_spacebar_lxx_dark)!!,
|
||||||
|
isSearching = false,
|
||||||
|
searchFilter = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class EmojiView @JvmOverloads constructor(
|
|||||||
View(context, attrs) {
|
View(context, attrs) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val EMOJI_DRAW_TEXT_SIZE_SP = 32
|
private const val EMOJI_DRAW_TEXT_SIZE_DP = 42
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -61,8 +61,8 @@ class EmojiView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply {
|
private val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply {
|
||||||
textSize = TypedValue.applyDimension(
|
textSize = TypedValue.applyDimension(
|
||||||
TypedValue.COMPLEX_UNIT_SP,
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
EMOJI_DRAW_TEXT_SIZE_SP.toFloat(),
|
EMOJI_DRAW_TEXT_SIZE_DP.toFloat(),
|
||||||
context.resources.displayMetrics
|
context.resources.displayMetrics
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user