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?) {} override fun onGetSentenceSuggestions(results: Array?) {} 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?) { // 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) // }