wekeyT9/app/src/main/java/net/mezimmah/wkt9/inputhandler/LetterInputHandler.kt

272 lines
7.7 KiB
Kotlin

package net.mezimmah.wkt9.inputhandler
import android.content.Context
import android.text.InputType
import android.util.Log
import android.view.KeyEvent
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SpellCheckerSession
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextServicesManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mezimmah.wkt9.R
import net.mezimmah.wkt9.WKT9IME
import net.mezimmah.wkt9.IME
import net.mezimmah.wkt9.db.AppDatabase
import net.mezimmah.wkt9.entity.Word
import net.mezimmah.wkt9.inputmode.InputMode
import net.mezimmah.wkt9.keypad.Command
import net.mezimmah.wkt9.keypad.Key
import net.mezimmah.wkt9.keypad.KeyEventStat
import net.mezimmah.wkt9.keypad.KeyLayout
import java.util.Locale
class LetterInputHandler(
val ime: IME,
private var wkt9: WKT9IME,
private var locale: Locale,
private var composeTimeout: Long
): SpellCheckerSession.SpellCheckerSessionListener, DefaultInputHandler(wkt9), InputHandler {
private val db = AppDatabase.getInstance(wkt9)
private val wordDao = db.getWordDao()
private val word = StringBuilder()
private var lastKey: Key? = null
private var repeats: Int = 0
private var lastComposeChar: Char? = null
private var lastIcon: Int? = null
private var spellCheckerSession: SpellCheckerSession? = null
private var timeoutJob: Job? = null
init {
val textServiceManager = wkt9.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
spellCheckerSession = textServiceManager.newSpellCheckerSession(
null,
locale,
this,
false
)
updateIcon()
}
private fun finalizeWordOrSentence(stats: KeyEventStat) {
if (word.isNotEmpty()) storeWord()
timeoutJob?.cancel()
val index = stats.repeats % punctuationMarks.count()
val lastIndex = if (stats.repeats > 0) (stats.repeats - 1) % punctuationMarks.count() else null
var beforeCursor = 0
if (lastIndex != null) beforeCursor += punctuationMarks[lastIndex].length
wordStart = true
sentenceStart = index in 1..3
wkt9.onCommit(punctuationMarks[index], beforeCursor)
updateIcon()
}
private fun storeWord() {
val str = word.toString()
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
word.clear()
scope.launch {
val word = Word(
word = str,
code = keypad.getCodeForWord(str),
weight = 0,
length = str.length,
locale = locale.language
)
val result = wordDao.selectWord(str, locale.language)
if (result == null) wordDao.insert(word)
}
}
override fun onSwitchLocale(locale: Locale) {
this.locale = locale
}
override fun onDeleteWord(word: Word) {}
override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {}
override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {}
override fun onFinishComposing() {}
override fun toggleCapMode(key: Key) {
super.toggleCapMode(key)
updateIcon()
}
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
when (command) {
Command.CAP_MODE -> toggleCapMode(key)
Command.CHARACTER -> composeCharacter(key)
Command.DELETE -> delete()
Command.INPUT_MODE -> inputMode(key)
Command.NUMBER -> triggerOriginalKeyEvent(key)
// Command.RECORD -> wkt9.onRecord(true)
Command.SPACE -> finalizeWordOrSentence(stats)
// Command.TRANSCRIBE -> wkt9.onTranscribe()
else -> Log.d(tag, "Command not implemented: $command")
}
}
override fun onWordSelected(word: Word) {}
private fun composeCharacter(key: Key) {
val composing = timeoutJob?.isActive ?: false
timeoutJob?.cancel()
if (lastKey == key) {
repeats++
word.deleteAt(word.length - 1)
} else {
resetKey(key)
if (composing) finishComposingChar()
}
val layout = KeyLayout.chars[key] ?: return
val index = repeats % layout.count()
val char = when(effectiveCapMode()) {
null -> layout[index].toString()
else -> layout[index].uppercase()
}
lastComposeChar = layout[index]
word.append(char)
wkt9.onCompose(char)
setComposeTimeout()
}
private fun resetKey(key: Key? = null) {
lastKey = key
repeats = 0
}
private fun finishComposingChar() {
val wordDelimiters = listOf(' ', ',', ':', ';')
val sentenceDelimiters = listOf('.', '?', '!')
wkt9.onCommit()
if (sentenceDelimiters.contains(lastComposeChar)) {
sentenceStart = true
wordStart = true
} else if (wordDelimiters.contains(lastComposeChar)) {
sentenceStart = false
wordStart = true
} else {
wordStart = false
sentenceStart = false
}
if (wordStart) {
word.deleteAt(word.length - 1)
if (word.isNotEmpty()) storeWord()
}
updateIcon()
}
private fun setComposeTimeout() {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
timeoutJob = scope.launch {
delay(composeTimeout)
resetKey()
finishComposingChar()
}
}
private fun delete() {
if (word.isNotEmpty()) word.deleteAt(word.length - 1)
resetKey()
if (timeoutJob?.isActive == true) {
timeoutJob?.cancel()
wkt9.onCompose("")
} else {
wkt9.onDeleteText(1)
}
}
private fun inputMode(key: Key) {
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Word)
else wkt9.onSwitchInputHandler(InputMode.Number)
}
private fun updateIcon() {
val icon = when (effectiveCapMode()) {
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> R.drawable.letter_upper
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> R.drawable.letter_cap
else -> R.drawable.letter_lower
}
if (icon == lastIcon) return
wkt9.onUpdateStatusIcon(icon)
lastIcon = icon
}
}
//
// override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {
// results?.map {
// val suggestions = it.getSuggestionsInfoAt(0)
//
// for (index in 0 until suggestions.suggestionsCount) {
// val suggestion = suggestions.getSuggestionAt(index)
//
// candidates.add(suggestion)
// }
// }
//
// if (candidates.isEmpty()) return
//
// candidateSource = CandidateSource.Dictionary
//
// wkt9.onCandidates(
// candidates = candidates,
// current = null
// )
// }
// private fun getSuggestions() {
// val lastWord = getLastWord()
//
// if (lastWord.length < 3) return
//
// val words = arrayOf(
// TextInfo(
// lastWord.plus("#"), // Add hash to string to get magic performance
// 0,
// lastWord.length + 1, // We added the hash, remember
// 0,
// 0
// )
// )
//
// spellCheckerSession?.getSentenceSuggestions(words, 15)
// }