Пользовательская клавиатура не работает программно, и при наборе текста отсутствуют клавиши UX.Android

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Пользовательская клавиатура не работает программно, и при наборе текста отсутствуют клавиши UX.

Сообщение Anonymous »

Я создаю собственную клавиатуру во flutter android, и все работает идеально, у меня возникают проблемы с набором текста, как будто он не плавный, клавиши отсутствуют и не очень плавные, как клавиатура Gboard или Samsung, как будто ими так удобно пользоваться, но на моей клавиатуре все работает не так гладко, пожалуйста, кто-нибудь может помочь мне в этом, как заставить это работать правильно, как gboard
RewriterKeyboardService.kt

package com.arleven.rephraseplus

import android.content.ActivityNotFoundException
import android.inputmethodservice.InputMethodService
import android.os.*
import android.util.Log
import android.view.*
import android.widget.*
import android.view.inputmethod.*
import kotlinx.coroutines.*
import org.json.JSONObject
import java.net.HttpURLConnection
import java.net.URL
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.LinearLayoutManager
import android.app.AlertDialog
import android.content.Intent
import android.view.WindowManager

class DebouncedClickListener(
private val debounceTime: Long = 50, // Reduced to 50ms for faster typing
private val onClick: () -> Unit
) : View.OnClickListener {
private var lastClickTime = 0L

override fun onClick(v: View?) {
val now = System.currentTimeMillis()
if (now - lastClickTime >= debounceTime) {
lastClickTime = now
onClick()
}
}
}

private const val TAG = "KeyboardService"

class RewriterKeyboardService : InputMethodService() {

private fun handleKeyPress(key: String) {
// ✅ OK
}

private enum class ShiftState { OFF, ONCE, LOCKED }

private var shiftState = ShiftState.OFF
private var lastShiftTapTime = 0L
private val DOUBLE_TAP_TIMEOUT = 400L

private var fixedKeyboardHeight = 0

private lateinit var floatingBackspace: ImageButton

private var previousKeyboardMode: KeyboardMode = KeyboardMode.LETTERS

private enum class KeyboardMode {
LETTERS, SYMBOLS_1, SYMBOLS_2, EMOJIS
}

private enum class EmojiCategory {
RECENT, SMILEYS, ANIMALS, FOOD, ACTIVITY, OBJECTS, TRAVEL, SYMBOLS, FLAGS
}

private val emojiMap = mapOf(
EmojiCategory.SMILEYS to listOf("😀", "😃", "😄", "😁", "😆", "😅", "😂", ),
EmojiCategory.ANIMALS to listOf("🐵", "🐒", "🦍", "🦧", "🐶", "🐕", "🦮", ),
EmojiCategory.FOOD to listOf("🍎", "🍏", "🍐", "🍊", "🍋", "🍌", "🍉", "🥭", "🍎", "🍏"),
EmojiCategory.ACTIVITY to listOf("⚽", "🏀", "🏈", "⚾", "🥎", "🏐", "🏉", "🎲", "♟️", "🧩"),
EmojiCategory.OBJECTS to listOf("📱", "📲", "☎️", "📞", "📟", "📠", "🔋", "🛏️", "🛋️", "🪑", "🚽", "🪠", "🚿", "🛁", "🪥", "🧼", "🪒", "🧽", ),
EmojiCategory.TRAVEL to listOf("🚗", "🚕", "🚙", "🚌", "🚎", "🏎️", "🚓", ),
EmojiCategory.SYMBOLS to listOf("❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🗄️", "🗑️", "🔒", "🔓", "🔏", "🔐", "🔑", "🗝️"),
EmojiCategory.FLAGS to listOf("🇦🇨","🇦🇩","🇦🇪","🇦🇫","🇦🇬","🇦🇮","🇦🇱","🇦🇲","🇦🇴","🇦🇶", )

)

private val emojiCategories = listOf(
"🕘\uFE0E", // Recent
"😀\uFE0E", // Smileys
"🐶\uFE0E", // Animals
"🍔\uFE0E", // Food
"⚽\uFE0E", // Activity
"🏠\uFE0E", // Objects
"🚗\uFE0E", // Travel
"💡\uFE0E", // Symbols
"🚩\uFE0E" // Flags
)

private val recentEmojis = mutableListOf()
private var currentEmojiCategory = EmojiCategory.SMILEYS

private var pendingText: String? = null
private var selectedFeature: String? = null

private lateinit var keyboardView: View
private var inputConnection: InputConnection? = null

private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

private lateinit var purchasePreferences: PurchasePreferences

override fun onCreate() {
super.onCreate()
purchasePreferences = PurchasePreferences(this)
}

private fun checkApiLimitAndProceed(action: () -> Unit) {
purchasePreferences.resetIfNewDay()

if (purchasePreferences.isProUser()) {
// Pro user - unlimited access
action()
} else {
val usedCalls = purchasePreferences.getDailyCallCount()
if (usedCalls < 25) {
// Free user - within limit
purchasePreferences.incrementDailyCallCount()
action()
} else {
// Free user - limit reached, open app's purchase screen directly
openPurchaseScreen()
}
}
}

private fun openPurchaseScreen() {
try {
requestHideSelf(0)

val intent = Intent(this, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("SHOW_PAYWALL", true)
}

startActivity(intent)

} catch (e: Exception) {
Log.e(TAG, "Failed to open purchase screen", e)
}
}

// ================= LIFECYCLE =================

private fun bindKeyWithDebounce(button: Button, text: String) {
button.setOnClickListener(DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()
inputConnection?.commitText(text, 1)
updateFloatingBackspaceVisibility()
})
}

private fun bindKeyWithDebounce(buttonId: Int, text: String) {
val button = keyboardView.findViewById(buttonId)
button?.setOnClickListener(DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()
inputConnection?.commitText(text, 1)
updateFloatingBackspaceVisibility()
})
}

private fun hideAllKeyboards() {
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_1)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_2)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_emojis)?.visibility = View.GONE
}

private val backspaceHandler = Handler(Looper.getMainLooper())
private var isBackspacePressed = false

private val backspaceRunnable = object : Runnable {
override fun run() {
if (isBackspacePressed) {
ensureInputConnection()
inputConnection?.deleteSurroundingText(1, 0)
backspaceHandler.postDelayed(this, 50) // speed (lower = faster)
}
}
}

private fun setupBackspace(view: View?) {
view ?: return

var longPressTriggered = false

// Single tap → delete once
view.setOnClickListener {
ensureInputConnection()
inputConnection?.deleteSurroundingText(1, 0)
updateFloatingBackspaceVisibility()
}

view.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
longPressTriggered = false
isBackspacePressed = true

backspaceHandler.postDelayed({
if (isBackspacePressed) {
longPressTriggered = true
backspaceRunnable.run()
}
}, 300)

true
}

MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
isBackspacePressed = false
backspaceHandler.removeCallbacks(backspaceRunnable)

if (!longPressTriggered) {
v.performClick()
}

updateFloatingBackspaceVisibility()
true
}

else -> false
}
}
}

override fun onStartInputView(info: EditorInfo?, restarting: Boolean) {
super.onStartInputView(info, restarting)
inputConnection = currentInputConnection
}

private fun lockKeyboardHeight() {
if (fixedKeyboardHeight
ensureInputConnection()
inputConnection?.commitText(emoji, 1)

recentEmojis.remove(emoji)
recentEmojis.add(0, emoji)
if (recentEmojis.size > 30) recentEmojis.removeLast()

updateFloatingBackspaceVisibility() // ✅ REQUIRED
}

emojiRecycler.layoutManager = GridLayoutManager(this, 8)
emojiRecycler.adapter = emojiAdapter

categoryRecycler.layoutManager =
LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)

categoryRecycler.adapter = EmojiCategoryAdapter(emojiCategories) { index ->
currentEmojiCategory = EmojiCategory.values()[index]
updateEmojiGrid(emojiAdapter)
}

updateEmojiGrid(emojiAdapter)

floatingBackspace = keyboardView.findViewById(R.id.floating_backspace)

// Use SAME backspace logic (tap + long press)
setupBackspace(floatingBackspace)

// Initial state
updateFloatingBackspaceVisibility()

setupActionButtons()
setupKeyListeners()
setupSymbolSwitching()
setupResultButtons()
showKeyboardState()
return keyboardView
}

private fun updateEmojiGrid(adapter: EmojiAdapter) {
val list = when (currentEmojiCategory) {
EmojiCategory.RECENT -> recentEmojis
else -> emojiMap[currentEmojiCategory] ?: emptyList()
}
adapter.update(list)
}

private fun updateFloatingBackspaceVisibility() {
// 🚫 Floating backspace ONLY for emoji keyboard
if (keyboardMode != KeyboardMode.EMOJIS) {
floatingBackspace.visibility = View.GONE
return
}

ensureInputConnection()

val text =
inputConnection?.getSelectedText(0)
?: inputConnection?.getExtractedText(
ExtractedTextRequest(), 0
)?.text

floatingBackspace.visibility =
if (!text.isNullOrEmpty()) View.VISIBLE
else View.GONE
}

private fun bindSymbolKeys(containerId: Int) {
val container = keyboardView.findViewById(containerId)

val blockedIds = setOf(
R.id.key_abc_1,
R.id.key_abc_2,
R.id.key_symbols,
R.id.key_symbols_1,
R.id.key_symbols_2,
R.id.key_emoji_symbols_1,
R.id.key_emoji_symbols_2,
R.id.key_backspace,
R.id.key_backspace_symbols_1,
R.id.key_backspace_symbols_2,
R.id.key_space_symbols_1,
R.id.key_space_symbols_2,
R.id.key_enter_symbols_1,
R.id.key_enter_symbols_2
)

for (i in 0 until container.childCount) {
val row = container.getChildAt(i) as? ViewGroup ?: continue

for (j in 0 until row.childCount) {
val btn = row.getChildAt(j) as? Button ?: continue

// Skip navigation/special keys
if (btn.id in blockedIds) continue
if (btn.text.isNullOrBlank()) continue

// Use debounced click listener
bindKeyWithDebounce(btn, btn.text.toString())
}
}
}

// ================= ACTIONS =================

private fun showSnackbar(message: String) = runOnUiThread {
val container = keyboardView.findViewById(R.id.snackbar_container)
val textView = container.findViewById(R.id.snackbar_text)

textView.text = message
container.visibility = View.VISIBLE

textView.alpha = 0f
textView.translationY = 40f

textView.animate()
.alpha(1f)
.translationY(0f)
.setDuration(180)
.start()

Handler(Looper.getMainLooper()).postDelayed({
textView.animate()
.alpha(0f)
.translationY(40f)
.setDuration(200)
.withEndAction {
container.visibility = View.GONE
}
.start()
}, 2000)
}

private fun Int.dp(): Int =
(this * resources.displayMetrics.density).toInt()

private fun getInputTextOrShowError(actionName: String): String? {
val text =
inputConnection?.getSelectedText(0)?.toString()
?: inputConnection?.getExtractedText(
ExtractedTextRequest(), 0
)?.text?.toString()

if (text.isNullOrBlank()) {
showSnackbar("Please add text to $actionName")
return null
}
return text
}

private fun handleRephrase() {
if (!ensureInputConnection()) return

// 🔥 SAVE CURRENT MODE
previousKeyboardMode = keyboardMode

val text = getInputTextOrShowError("rephrase") ?: return
pendingText = text

// Check API limit before proceeding
checkApiLimitAndProceed {
selectedFeature = "change-tone"
showToneState()
}
}

fun onToneClick(view: View) {
val tone = view.tag as String
val text = pendingText ?: return

// Note: Tone selection is already within the API call flow
// so we don't need to check limits here again
showLoadingState()

serviceScope.launch {
val result = callApiWithTone(text, tone)
showResultState(result)
}
}

// ================= API =================

private suspend fun callApiWithTone(text: String, tone: String): String =
withContext(Dispatchers.IO) {
try {
val url =
"https://rephrase-plus.onrender.com/v1/convert" +
"?version=v2&mode=change-tone&tone=$tone"

val conn = URL(url).openConnection() as HttpURLConnection
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json")
conn.doOutput = true

val body = JSONObject().put("text", text).toString()
conn.outputStream.use { it.write(body.toByteArray()) }

val response = conn.inputStream.bufferedReader().readText()
JSONObject(response).optString("data", response)

} catch (e: Exception) {
Log.e(TAG, "API ERROR", e)
"Error: ${e.message}"
}
}

private suspend fun callApi(text: String, feature: String): String =
withContext(Dispatchers.IO) {
try {
val url =
"https://rephrase-plus.onrender.com/v1/convert" +
"?version=v2&mode=$feature"

val conn = URL(url).openConnection() as HttpURLConnection
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json")
conn.doOutput = true

val body = JSONObject().put("text", text).toString()
conn.outputStream.use { it.write(body.toByteArray()) }

val response = conn.inputStream.bufferedReader().readText()
JSONObject(response).optString("data", response)

} catch (e: Exception) {
Log.e(TAG, "API ERROR", e)
"Error: ${e.message}"
}
}

private var keyboardMode = KeyboardMode.LETTERS

private fun showLetters() {
keyboardMode = KeyboardMode.LETTERS
lockKeyboardHeight()
shiftState = ShiftState.OFF
updateShiftKeyUI()

floatingBackspace.visibility = View.GONE

keyboardView.findViewById(R.id.layout_letters).visibility = View.VISIBLE
keyboardView.findViewById(R.id.layout_symbols_1).visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_2).visibility = View.GONE
}

private fun showSymbols1() {
keyboardMode = KeyboardMode.SYMBOLS_1
floatingBackspace.visibility = View.GONE

keyboardView.findViewById(R.id.layout_letters).visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_2).visibility = View.GONE

val symbols1 = keyboardView.findViewById(R.id.layout_symbols_1)
symbols1.visibility = View.VISIBLE

bindSymbolKeys(R.id.layout_symbols_1)
}

private fun showSymbols2() {
keyboardMode = KeyboardMode.SYMBOLS_2
floatingBackspace.visibility = View.GONE

keyboardView.findViewById(R.id.layout_letters).visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_1).visibility = View.GONE

val symbols2 = keyboardView.findViewById(R.id.layout_symbols_2)
symbols2.visibility = View.VISIBLE

bindSymbolKeys(R.id.layout_symbols_2) // 🔥 ensure binding
}

private fun bindAllKeys(containerId: Int) {
val container = keyboardView.findViewById(containerId)
for (i in 0 until container.childCount) {
val row = container.getChildAt(i) as? ViewGroup ?: continue
for (j in 0 until row.childCount) {
val btn = row.getChildAt(j) as? Button ?: continue

// FIX: Use OnClickListener for better performance
btn.setOnClickListener {
ensureInputConnection()
inputConnection?.commitText(btn.text.toString(), 1)
}
}
}
}

// ================= UI STATES =================

private fun showKeyboardState() = runOnUiThread {
keyboardView.findViewById(R.id.layout_letters)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.tone_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.layout_emojis)?.visibility = View.GONE

updateFloatingBackspaceVisibility()

}

private fun showToneState() = runOnUiThread {
lockKeyboardHeight()
hideAllKeyboards()

keyboardView.findViewById(R.id.tone_container)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE

floatingBackspace.visibility = View.GONE
}

private fun showLoadingState() = runOnUiThread {
lockKeyboardHeight()
hideAllKeyboards()

keyboardView.findViewById(R.id.loading_container)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.tone_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.result_container)?.visibility = View.GONE

floatingBackspace.visibility = View.GONE
}

private fun showResultState(text: String) = runOnUiThread {
lockKeyboardHeight()
hideAllKeyboards()

keyboardView.findViewById(R.id.result_text)?.text = text
keyboardView.findViewById(R.id.result_container)?.visibility = View.VISIBLE
keyboardView.findViewById(R.id.loading_container)?.visibility = View.GONE
keyboardView.findViewById(R.id.tone_container)?.visibility = View.GONE

floatingBackspace.visibility = View.GONE
}

// ================= HELPERS =================

private fun setupActionButtons() {
keyboardView.findViewById(R.id.btn_rephrase)
?.setOnClickListener { handleRephrase() }

keyboardView.findViewById(R.id.btn_check_grammar)
?.setOnClickListener { handleAction("fix-grammar") }

keyboardView.findViewById(R.id.btn_summarize)
?.setOnClickListener { handleAction("summarise") }

keyboardView.findViewById(R.id.btn_ai_reply)
?.setOnClickListener { handleAction("ai-reply") }

keyboardView.findViewById(R.id.btn_email_gen)
?.setOnClickListener { handleAction("email-generator") }

keyboardView.findViewById(R.id.btn_prompt_gen)
?.setOnClickListener { handleAction("prompt-generator") }
}

private fun setupKeyListeners() {

// Number keys with debouncing
listOf("1","2","3","4","5","6","7","8","9","0").forEach {
val id = resources.getIdentifier("key_$it", "id", packageName)
bindKeyWithDebounce(id, it)
}

// Letter keys
listOf(
"q","w","e","r","t","y","u","i","o","p",
"a","s","d","f","g","h","j","k","l",
"z","x","c","v","b","n","m"
).forEach {
bindLetterKey(
resources.getIdentifier("key_$it", "id", packageName),
it
)
}

// Space keys
bindSpaceKey(R.id.key_space)
bindSpaceKey(R.id.key_space_symbols_1)
bindSpaceKey(R.id.key_space_symbols_2)

// Enter keys
bindEnter(R.id.key_enter)
bindEnter(R.id.key_enter_symbols_1)
bindEnter(R.id.key_enter_symbols_2)

// Shift key
keyboardView.findViewById(R.id.key_shift)?.setOnClickListener {
val now = System.currentTimeMillis()

shiftState = if (now - lastShiftTapTime < DOUBLE_TAP_TIMEOUT) {
ShiftState.LOCKED // caps lock
} else {
when (shiftState) {
ShiftState.OFF -> ShiftState.ONCE
ShiftState.ONCE -> ShiftState.OFF
ShiftState.LOCKED -> ShiftState.OFF
}
}

lastShiftTapTime = now
updateShiftKeyUI()
}

// Backspace
setupBackspace(keyboardView.findViewById(R.id.key_backspace))
setupBackspace(keyboardView.findViewById(R.id.key_backspace_symbols_1))
setupBackspace(keyboardView.findViewById(R.id.key_backspace_symbols_2))

// Emoji keys
listOf(
R.id.key_emoji_letters,
R.id.key_emoji_symbols_1,
R.id.key_emoji_symbols_2
).forEach { id ->
keyboardView.findViewById(id)?.setOnClickListener {
showEmojis()
}
}

// Emoji keyboard → ABC
keyboardView.findViewById(R.id.key_abc_emoji)?.setOnClickListener {
hideEmojis()
}

// Emoji keyboard space
keyboardView.findViewById(R.id.key_space_emoji)?.setOnClickListener {
ensureInputConnection()
inputConnection?.commitText(" ", 1)
updateFloatingBackspaceVisibility()
}

// Emoji keyboard enter
keyboardView.findViewById(R.id.key_enter_emoji)?.setOnClickListener {
ensureInputConnection()
inputConnection?.sendKeyEvent(
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)
)
updateFloatingBackspaceVisibility()
}

// Dot key
keyboardView.findViewById(R.id.key_dot)?.setOnClickListener(
DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()
inputConnection?.commitText(".", 1)
updateFloatingBackspaceVisibility()
}
)
}
private fun updateLetterKeysUI() {
val letters = listOf(
"q","w","e","r","t","y","u","i","o","p",
"a","s","d","f","g","h","j","k","l",
"z","x","c","v","b","n","m"
)

letters.forEach { letter ->
val id = resources.getIdentifier("key_$letter", "id", packageName)
val btn = keyboardView.findViewById(id) ?: return@forEach

btn.text = when (shiftState) {
ShiftState.OFF -> letter.lowercase()
ShiftState.ONCE, ShiftState.LOCKED -> letter.uppercase()
}
}
}

private fun bindLetterKey(id: Int, letter: String) {
val btn = keyboardView.findViewById(id) ?: return

btn.setOnClickListener(DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()

val output = when (shiftState) {
ShiftState.OFF -> letter.lowercase()
ShiftState.ONCE, ShiftState.LOCKED -> letter.uppercase()
}

inputConnection?.commitText(output, 1)
updateFloatingBackspaceVisibility()

// Auto-reset after single use
if (shiftState == ShiftState.ONCE) {
shiftState = ShiftState.OFF
updateShiftKeyUI()
}
})
}

private fun updateShiftKeyUI() {
val shiftKey = keyboardView.findViewById(R.id.key_shift)

// 🔒 keep color exactly the same in all states
shiftKey?.alpha = 1f

when (shiftState) {
ShiftState.OFF -> {
shiftKey?.text = "↑" // outlined arrow
}
ShiftState.ONCE -> {
shiftKey?.text = "↑" // outlined arrow
}
ShiftState.LOCKED -> {
shiftKey?.text = "⇪" // filled caps arrow
}
}

updateLetterKeysUI()
}

private fun setupSymbolSwitching() {

// !#1 → Symbols page 1
keyboardView.findViewById(R.id.key_symbols)?.setOnClickListener {
showSymbols1()
}

// 1/2 → Symbols page 2
keyboardView.findViewById(R.id.key_symbols_2)?.setOnClickListener {
showSymbols2()
}

// 2/2 → Symbols page 1
keyboardView.findViewById(R.id.key_symbols_1)?.setOnClickListener {
showSymbols1()
}

// ABC → Letters
keyboardView.findViewById(R.id.key_abc_1)?.setOnClickListener {
showLetters()
}
keyboardView.findViewById(R.id.key_abc_2)?.setOnClickListener {
showLetters()
}

}

private fun setupResultButtons() {
keyboardView.findViewById(R.id.btn_apply)?.setOnClickListener {
inputConnection?.commitText(
keyboardView.findViewById(R.id.result_text).text, 1
)
showKeyboardState()
}

keyboardView.findViewById(R.id.btn_close)
?.setOnClickListener { showKeyboardState() }
}

private fun handleAction(feature: String) {
if (!ensureInputConnection()) return

val actionName = when (feature) {
"fix-grammar" -> "check grammar"
"summarise" -> "summarize"
"ai-reply" -> "generate AI reply"
"email-generator" -> "generate email"
"prompt-generator" -> "generate prompt"
else -> "process text"
}

val text = getInputTextOrShowError(actionName) ?: return

// Check API limit before proceeding
checkApiLimitAndProceed {
showLoadingState()

serviceScope.launch {
val result = callApi(text, feature)
showResultState(result)
}
}
}
private fun ensureInputConnection(): Boolean {
if (inputConnection == null) inputConnection = currentInputConnection
return inputConnection != null
}

private fun runOnUiThread(action: () -> Unit) {
Handler(Looper.getMainLooper()).post(action)
}

override fun onEvaluateFullscreenMode(): Boolean = false

override fun onDestroy() {
serviceScope.cancel()
super.onDestroy()
}

private fun showEmojis() {
keyboardMode = KeyboardMode.EMOJIS
lockKeyboardHeight()

keyboardView.findViewById(R.id.layout_letters).visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_1).visibility = View.GONE
keyboardView.findViewById(R.id.layout_symbols_2).visibility = View.GONE
keyboardView.findViewById(R.id.layout_emojis).visibility = View.VISIBLE

floatingBackspace.bringToFront()
updateFloatingBackspaceVisibility()
}

private fun hideEmojis() {
showLetters()
keyboardView.findViewById(R.id.layout_emojis).visibility = View.GONE

// 🚫 HARD STOP
floatingBackspace.visibility = View.GONE
}

private fun bindSpaceKey(id: Int) {
keyboardView.findViewById(id)?.setOnClickListener(
DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()
inputConnection?.commitText(" ", 1)
updateFloatingBackspaceVisibility()
}
)
}
private fun bindEnter(id: Int) {
keyboardView.findViewById(id)?.setOnClickListener(
DebouncedClickListener(debounceTime = 50) {
ensureInputConnection()
inputConnection?.sendKeyEvent(
KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)
)
updateFloatingBackspaceVisibility()



Подробнее здесь: https://stackoverflow.com/questions/798 ... ile-typing
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»