Compare commits
8 Commits
6c59ee4d82
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| a6ccd6d703 | |||
| 4da4eabc19 | |||
| 23cccf2455 | |||
| 48a9a2e822 | |||
| 0ec4f4b262 | |||
| 3b0ce64b09 | |||
| 3a1f1a595c | |||
| 92a4ac541c |
Binary file not shown.
@@ -15,7 +15,7 @@ interface IME {
|
|||||||
current: Int? = 0
|
current: Int? = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
fun onWords(words: List<Word>)
|
fun onWords(words: List<Word>, capMode: Int?)
|
||||||
|
|
||||||
fun onNextWord()
|
fun onNextWord()
|
||||||
|
|
||||||
@@ -23,15 +23,21 @@ interface IME {
|
|||||||
|
|
||||||
fun onWordSelected(word: Word)
|
fun onWordSelected(word: Word)
|
||||||
|
|
||||||
|
fun onDeleteWord(word: Word)
|
||||||
|
|
||||||
fun onCommit(text: CharSequence = "", beforeCursor: Int = 0, afterCursor: Int = 0)
|
fun onCommit(text: CharSequence = "", beforeCursor: Int = 0, afterCursor: Int = 0)
|
||||||
|
|
||||||
fun onCompose(text: CharSequence)
|
fun onCompose(text: CharSequence)
|
||||||
|
|
||||||
fun onDeleteText(beforeCursor: Int = 0, afterCursor: Int = 0, finishComposing: Boolean = false)
|
fun onDeleteText(beforeCursor: Int = 0, afterCursor: Int = 0, finishComposing: Boolean = false)
|
||||||
|
|
||||||
fun onGetTextBeforeCursor(n: Int): CharSequence?
|
fun defaultView()
|
||||||
|
|
||||||
fun onSwitchInputHandler(inputMode: InputMode)
|
fun onSwitchInputHandler(inputMode: InputMode)
|
||||||
|
|
||||||
fun onUpdateStatusIcon(icon: Int?)
|
fun onUpdateStatusIcon(icon: Int?)
|
||||||
|
|
||||||
|
fun record()
|
||||||
|
|
||||||
|
fun transcribe()
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,10 @@ package net.mezimmah.wkt9
|
|||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.SharedPreferences
|
||||||
import android.inputmethodservice.InputMethodService
|
import android.inputmethodservice.InputMethodService
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.text.InputType
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
@@ -12,19 +14,23 @@ import android.view.inputmethod.EditorInfo
|
|||||||
import android.view.inputmethod.InputConnection
|
import android.view.inputmethod.InputConnection
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.view.inputmethod.InputMethodSubtype
|
import android.view.inputmethod.InputMethodSubtype
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import net.mezimmah.wkt9.entity.Word
|
import net.mezimmah.wkt9.entity.Word
|
||||||
import net.mezimmah.wkt9.inputhandler.IdleInputHandler
|
import net.mezimmah.wkt9.inputhandler.IdleInputHandler
|
||||||
import net.mezimmah.wkt9.inputhandler.InputHandler
|
import net.mezimmah.wkt9.inputhandler.InputHandler
|
||||||
import net.mezimmah.wkt9.inputhandler.LetterInputHandler
|
import net.mezimmah.wkt9.inputhandler.LetterInputHandler
|
||||||
import net.mezimmah.wkt9.inputhandler.NumberInputHandler
|
import net.mezimmah.wkt9.inputhandler.NumberInputHandler
|
||||||
import net.mezimmah.wkt9.inputhandler.WordInputHandler
|
import net.mezimmah.wkt9.inputhandler.WordInputHandler
|
||||||
import net.mezimmah.wkt9.inputmode.InputModeManager
|
|
||||||
import net.mezimmah.wkt9.inputmode.InputMode
|
import net.mezimmah.wkt9.inputmode.InputMode
|
||||||
|
import net.mezimmah.wkt9.inputmode.InputModeManager
|
||||||
import net.mezimmah.wkt9.keypad.Event
|
import net.mezimmah.wkt9.keypad.Event
|
||||||
import net.mezimmah.wkt9.keypad.Key
|
import net.mezimmah.wkt9.keypad.Key
|
||||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
import net.mezimmah.wkt9.keypad.KeyEventStat
|
||||||
import net.mezimmah.wkt9.layout.Words
|
import net.mezimmah.wkt9.layout.LoadingLayout
|
||||||
|
import net.mezimmah.wkt9.layout.MessageLayout
|
||||||
|
import net.mezimmah.wkt9.layout.WordsLayout
|
||||||
import net.mezimmah.wkt9.t9.T9
|
import net.mezimmah.wkt9.t9.T9
|
||||||
|
import net.mezimmah.wkt9.voice.Whisper
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
@@ -32,11 +38,14 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
private val tag = "WKT9"
|
private val tag = "WKT9"
|
||||||
|
|
||||||
private val inputModeManager = InputModeManager(this)
|
private val inputModeManager = InputModeManager(this)
|
||||||
|
private val whisper: Whisper = Whisper(this)
|
||||||
|
|
||||||
private lateinit var locale: Locale
|
private lateinit var locale: Locale
|
||||||
private var inputHandler: InputHandler? = null
|
private var inputHandler: InputHandler? = null
|
||||||
|
|
||||||
private var wordsView: Words? = null
|
private var wordsLayoutView: WordsLayout? = null
|
||||||
|
private var loadingLayoutView: LoadingLayout? = null
|
||||||
|
private var messageLayoutView: MessageLayout? = null
|
||||||
|
|
||||||
private val keyDownStats = KeyEventStat(0, 0)
|
private val keyDownStats = KeyEventStat(0, 0)
|
||||||
private val keyUpStats = KeyEventStat(0, 0)
|
private val keyUpStats = KeyEventStat(0, 0)
|
||||||
@@ -45,6 +54,8 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
private var selectionStart: Int = 0
|
private var selectionStart: Int = 0
|
||||||
private var selectionEnd: Int = 0
|
private var selectionEnd: Int = 0
|
||||||
|
|
||||||
|
private var capMode: Int? = null
|
||||||
|
|
||||||
override fun onCandidates(candidates: ArrayList<CharSequence>, current: Int?) {
|
override fun onCandidates(candidates: ArrayList<CharSequence>, current: Int?) {
|
||||||
// this.candidates?.load(candidates, current)
|
// this.candidates?.load(candidates, current)
|
||||||
}
|
}
|
||||||
@@ -83,9 +94,11 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
override fun onCreateInputView(): View? {
|
override fun onCreateInputView(): View? {
|
||||||
wordsView = layoutInflater.inflate(R.layout.words, null) as Words
|
wordsLayoutView = layoutInflater.inflate(R.layout.words, null) as WordsLayout
|
||||||
|
loadingLayoutView = layoutInflater.inflate(R.layout.loading, null) as LoadingLayout
|
||||||
|
messageLayoutView = layoutInflater.inflate(R.layout.message, null) as MessageLayout
|
||||||
|
|
||||||
return wordsView
|
return wordsLayoutView
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) {
|
override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) {
|
||||||
@@ -103,8 +116,8 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
deleteText(beforeCursor, afterCursor)
|
deleteText(beforeCursor, afterCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTextBeforeCursor(n: Int): CharSequence? {
|
override fun onDeleteWord(word: Word) {
|
||||||
return this.currentInputConnection?.getTextBeforeCursor(n, 0)
|
inputHandler?.onDeleteWord(word)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
@@ -264,21 +277,30 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
else showStatusIcon(icon)
|
else showStatusIcon(icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWords(words: List<Word>) {
|
override fun onWords(words: List<Word>, capMode: Int?) {
|
||||||
wordsView?.words = words
|
this.capMode = capMode
|
||||||
|
|
||||||
|
wordsLayoutView?.words = words
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWordSelected(word: Word) {
|
override fun onWordSelected(word: Word) {
|
||||||
this.onCompose(word.word)
|
val compose = when (capMode) {
|
||||||
|
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> word.word.uppercase()
|
||||||
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||||
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> word.word.replaceFirstChar { it.uppercase() }
|
||||||
|
else -> word.word
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onCompose(compose)
|
||||||
this.inputHandler?.onWordSelected(word)
|
this.inputHandler?.onWordSelected(word)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNextWord() {
|
override fun onNextWord() {
|
||||||
wordsView?.next()
|
wordsLayoutView?.next()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreviousWord() {
|
override fun onPreviousWord() {
|
||||||
wordsView?.previous()
|
wordsLayoutView?.previous()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteText(beforeCursor: Int, afterCursor: Int) {
|
private fun deleteText(beforeCursor: Int, afterCursor: Int) {
|
||||||
@@ -287,8 +309,22 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun record() {
|
||||||
|
setInputView(messageLayoutView)
|
||||||
|
whisper.record()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun transcribe() {
|
||||||
|
setInputView(loadingLayoutView)
|
||||||
|
whisper.transcribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun defaultView() {
|
||||||
|
setInputView(wordsLayoutView)
|
||||||
|
}
|
||||||
|
|
||||||
private fun finishComposing() {
|
private fun finishComposing() {
|
||||||
wordsView?.clear()
|
wordsLayoutView?.clear()
|
||||||
inputHandler?.onFinishComposing()
|
inputHandler?.onFinishComposing()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +337,15 @@ class WKT9IME: IME, InputMethodService() {
|
|||||||
private fun switchInputMode(mode: InputMode) {
|
private fun switchInputMode(mode: InputMode) {
|
||||||
inputHandler = when(mode) {
|
inputHandler = when(mode) {
|
||||||
InputMode.Word -> WordInputHandler(this, this, locale)
|
InputMode.Word -> WordInputHandler(this, this, locale)
|
||||||
InputMode.Letter -> LetterInputHandler(this, this, locale)
|
|
||||||
|
InputMode.Letter -> {
|
||||||
|
val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
val timeout = prefs.getString(getString(R.string.compose_timeout), "700")
|
||||||
|
val composeTimeout = timeout?.toLong() ?: 700L
|
||||||
|
|
||||||
|
timeout?.let { LetterInputHandler(this, this, locale, composeTimeout) }
|
||||||
|
}
|
||||||
|
|
||||||
InputMode.Number -> NumberInputHandler(this, this)
|
InputMode.Number -> NumberInputHandler(this, this)
|
||||||
else -> IdleInputHandler(this, this)
|
else -> IdleInputHandler(this, this)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ interface WordDao {
|
|||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insert(vararg words: Word)
|
suspend fun insert(vararg words: Word)
|
||||||
|
|
||||||
@Query("DELETE FROM word WHERE word = :word AND locale = :locale")
|
@Query("DELETE FROM word WHERE id = :id")
|
||||||
fun delete(word: String, locale: String)
|
fun delete(id: Int)
|
||||||
|
|
||||||
@Query("SELECT * FROM word WHERE code LIKE :code || '%' AND locale = :locale " +
|
@Query("SELECT * FROM word WHERE code LIKE :code || '%' AND locale = :locale " +
|
||||||
"ORDER BY length, weight DESC LIMIT :limit")
|
"ORDER BY length, weight DESC LIMIT :limit")
|
||||||
@@ -20,4 +20,7 @@ interface WordDao {
|
|||||||
|
|
||||||
@Query("UPDATE word SET weight = weight + 1 WHERE id=:id")
|
@Query("UPDATE word SET weight = weight + 1 WHERE id=:id")
|
||||||
suspend fun increaseWeight(id: Int)
|
suspend fun increaseWeight(id: Int)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM word WHERE word = :word AND locale = :locale COLLATE NOCASE")
|
||||||
|
suspend fun selectWord(word: String, locale: String): Word?
|
||||||
}
|
}
|
||||||
@@ -3,79 +3,77 @@ package net.mezimmah.wkt9.inputhandler
|
|||||||
import android.text.InputType
|
import android.text.InputType
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import net.mezimmah.wkt9.WKT9IME
|
import net.mezimmah.wkt9.WKT9IME
|
||||||
import net.mezimmah.wkt9.IME
|
|
||||||
import net.mezimmah.wkt9.inputmode.InputMode
|
|
||||||
import net.mezimmah.wkt9.inputmode.TextPositionInfo
|
|
||||||
import net.mezimmah.wkt9.keypad.Command
|
|
||||||
import net.mezimmah.wkt9.keypad.Key
|
import net.mezimmah.wkt9.keypad.Key
|
||||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
|
||||||
import net.mezimmah.wkt9.keypad.KeyLayout
|
|
||||||
import net.mezimmah.wkt9.keypad.Keypad
|
import net.mezimmah.wkt9.keypad.Keypad
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
open class DefaultInputHandler(
|
open class DefaultInputHandler(
|
||||||
private val wkt9: IME,
|
private val wkt9: WKT9IME
|
||||||
private val context: WKT9IME
|
|
||||||
) {
|
) {
|
||||||
protected val tag = "WKT9"
|
private val capModes = listOf(
|
||||||
|
|
||||||
protected var keypad: Keypad = Keypad()
|
|
||||||
protected var capMode: Int = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
|
||||||
|
|
||||||
protected open fun capMode(key: Key): Int? {
|
|
||||||
val modes = listOf(
|
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
|
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
var index = modes.indexOf(capMode)
|
|
||||||
|
private var currentCapMode: Int? = null
|
||||||
|
|
||||||
|
protected val punctuationMarks = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
||||||
|
protected val sentenceDelimiters = listOf('.', '?', '!')
|
||||||
|
protected val wordDelimiters = listOf('\t', '\n', ' ', ',', ':', ';')
|
||||||
|
|
||||||
|
protected val tag = "WKT9"
|
||||||
|
protected val keypad: Keypad = Keypad()
|
||||||
|
|
||||||
|
protected var wordStart: Boolean = false
|
||||||
|
protected var sentenceStart: Boolean = false
|
||||||
|
|
||||||
|
init {
|
||||||
|
setCursorPositionStatus()
|
||||||
|
|
||||||
|
wkt9.currentInputEditorInfo?.let {
|
||||||
|
val inputType = it.inputType
|
||||||
|
val typeFlags = inputType.and(InputType.TYPE_MASK_FLAGS)
|
||||||
|
|
||||||
|
if (typeFlags.and(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) == InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) {
|
||||||
|
currentCapMode = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||||
|
} else if (typeFlags.and(InputType.TYPE_TEXT_FLAG_CAP_WORDS) == InputType.TYPE_TEXT_FLAG_CAP_WORDS) {
|
||||||
|
currentCapMode = InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
||||||
|
} else if (typeFlags.and(InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) == InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) {
|
||||||
|
currentCapMode = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun effectiveCapMode(): Int? {
|
||||||
|
return when (currentCapMode) {
|
||||||
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS -> {
|
||||||
|
if (wordStart) InputType.TYPE_TEXT_FLAG_CAP_WORDS
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
|
||||||
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> {
|
||||||
|
if (sentenceStart) InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> currentCapMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun toggleCapMode(key: Key) {
|
||||||
|
var index = capModes.indexOf(currentCapMode)
|
||||||
|
|
||||||
when (key) {
|
when (key) {
|
||||||
Key.B2 -> {
|
Key.B2 -> {
|
||||||
if (index == 0) index = modes.count()
|
if (index == 0) index = capModes.count()
|
||||||
|
|
||||||
index--
|
index--
|
||||||
}
|
}
|
||||||
else -> index++
|
else -> index++
|
||||||
}
|
}
|
||||||
|
|
||||||
return modes[index % modes.count()]
|
currentCapMode = capModes[index % capModes.count()]
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun finalizeWordOrSentence(stats: KeyEventStat) {
|
|
||||||
val candidates = ArrayList<CharSequence>()
|
|
||||||
|
|
||||||
wkt9.onCandidates(
|
|
||||||
candidates = candidates,
|
|
||||||
current = stats.repeats % candidates.count()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun getTextPositionInfo(text: CharSequence): TextPositionInfo {
|
|
||||||
val trimmed = text.trimEnd()
|
|
||||||
val regex = "[.!?]$".toRegex()
|
|
||||||
val startSentence = text.isEmpty() || regex.containsMatchIn(trimmed)
|
|
||||||
val startWord = text.isEmpty() || (startSentence || trimmed.length < text.length)
|
|
||||||
|
|
||||||
return TextPositionInfo(
|
|
||||||
startSentence = startSentence,
|
|
||||||
startWord = startWord
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun getDefaultCapMode(typeFlags: Int): Int? {
|
|
||||||
val modes = listOf(
|
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
|
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
|
||||||
)
|
|
||||||
|
|
||||||
modes.forEach {
|
|
||||||
if (typeFlags.and(it) == it) return it
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun triggerKeyEvent(keyCode: Int) {
|
protected fun triggerKeyEvent(keyCode: Int) {
|
||||||
@@ -89,4 +87,27 @@ open class DefaultInputHandler(
|
|||||||
protected fun triggerOriginalKeyEvent(key: Key) {
|
protected fun triggerOriginalKeyEvent(key: Key) {
|
||||||
triggerKeyEvent(key.keyCode)
|
triggerKeyEvent(key.keyCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun setCursorPositionStatus() {
|
||||||
|
val textBeforeCursor = getTextBeforeCursor()
|
||||||
|
|
||||||
|
if (
|
||||||
|
textBeforeCursor.isNullOrEmpty() ||
|
||||||
|
textBeforeCursor.trim().isEmpty() ||
|
||||||
|
sentenceDelimiters.contains(textBeforeCursor.trim().last())
|
||||||
|
) {
|
||||||
|
sentenceStart = true
|
||||||
|
wordStart = true
|
||||||
|
} else if (wordDelimiters.contains(textBeforeCursor.last())) {
|
||||||
|
sentenceStart = false
|
||||||
|
wordStart = true
|
||||||
|
} else {
|
||||||
|
sentenceStart = false
|
||||||
|
wordStart = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTextBeforeCursor(): CharSequence? {
|
||||||
|
return wkt9.currentInputConnection?.getTextBeforeCursor(15, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import android.util.Log
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import net.mezimmah.wkt9.WKT9IME
|
import net.mezimmah.wkt9.WKT9IME
|
||||||
import net.mezimmah.wkt9.IME
|
import net.mezimmah.wkt9.IME
|
||||||
|
import net.mezimmah.wkt9.R
|
||||||
import net.mezimmah.wkt9.entity.Word
|
import net.mezimmah.wkt9.entity.Word
|
||||||
import net.mezimmah.wkt9.keypad.Command
|
import net.mezimmah.wkt9.keypad.Command
|
||||||
import net.mezimmah.wkt9.keypad.Key
|
import net.mezimmah.wkt9.keypad.Key
|
||||||
@@ -15,12 +16,14 @@ import java.util.Locale
|
|||||||
class IdleInputHandler(
|
class IdleInputHandler(
|
||||||
val ime: IME,
|
val ime: IME,
|
||||||
val wkt9: WKT9IME,
|
val wkt9: WKT9IME,
|
||||||
) : DefaultInputHandler(ime, wkt9), InputHandler {
|
) : DefaultInputHandler(wkt9), InputHandler {
|
||||||
|
init {
|
||||||
|
wkt9.showStatusIcon(R.drawable.idle_input)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFinishComposing() {}
|
override fun onFinishComposing() {}
|
||||||
|
|
||||||
override fun onLongClickCandidate(text: String) {
|
override fun onDeleteWord(word: Word) {}
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
||||||
when (command) {
|
when (command) {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import net.mezimmah.wkt9.keypad.KeyEventStat
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
interface InputHandler {
|
interface InputHandler {
|
||||||
fun onFinishComposing()
|
fun onDeleteWord(word: Word)
|
||||||
|
|
||||||
fun onLongClickCandidate(text: String)
|
fun onFinishComposing()
|
||||||
|
|
||||||
fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat)
|
fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat)
|
||||||
|
|
||||||
|
|||||||
@@ -30,23 +30,22 @@ class LetterInputHandler(
|
|||||||
val ime: IME,
|
val ime: IME,
|
||||||
private var wkt9: WKT9IME,
|
private var wkt9: WKT9IME,
|
||||||
private var locale: Locale,
|
private var locale: Locale,
|
||||||
): SpellCheckerSession.SpellCheckerSessionListener, DefaultInputHandler(ime, wkt9), InputHandler {
|
private var composeTimeout: Long,
|
||||||
|
): SpellCheckerSession.SpellCheckerSessionListener, DefaultInputHandler(wkt9), InputHandler {
|
||||||
private val db = AppDatabase.getInstance(wkt9)
|
private val db = AppDatabase.getInstance(wkt9)
|
||||||
private val wordDao = db.getWordDao()
|
private val wordDao = db.getWordDao()
|
||||||
|
|
||||||
|
private val word = StringBuilder()
|
||||||
|
|
||||||
private var lastKey: Key? = null
|
private var lastKey: Key? = null
|
||||||
private var repeats: Int = 0
|
private var repeats: Int = 0
|
||||||
|
private var lastComposeChar: Char? = null
|
||||||
|
private var lastIcon: Int? = null
|
||||||
|
|
||||||
private var spellCheckerSession: SpellCheckerSession? = null
|
private var spellCheckerSession: SpellCheckerSession? = null
|
||||||
|
|
||||||
private var sentenceStart: Boolean = false
|
|
||||||
private var wordStart: Boolean = false
|
|
||||||
|
|
||||||
private val timeoutScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
||||||
private var timeoutJob: Job? = null
|
private var timeoutJob: Job? = null
|
||||||
|
|
||||||
private val content = StringBuilder()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val textServiceManager = wkt9.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
|
val textServiceManager = wkt9.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
|
||||||
|
|
||||||
@@ -56,85 +55,157 @@ class LetterInputHandler(
|
|||||||
this,
|
this,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
updateIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalizeWordOrSentence(stats: KeyEventStat) {
|
override fun onDeleteWord(word: Word) {}
|
||||||
timeoutJob?.cancel()
|
|
||||||
|
|
||||||
val ends = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {}
|
||||||
val index = stats.repeats % ends.count()
|
|
||||||
val lastIndex = if (stats.repeats > 0) (stats.repeats - 1) % ends.count() else null
|
|
||||||
var beforeCursor = 0
|
|
||||||
|
|
||||||
if (lastIndex != null) beforeCursor += ends[lastIndex].length
|
override fun onFinishComposing() {}
|
||||||
|
|
||||||
wkt9.onCommit(ends[index], beforeCursor)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {}
|
||||||
override fun onSwitchLocale(locale: Locale) {
|
override fun onSwitchLocale(locale: Locale) {
|
||||||
this.locale = locale
|
this.locale = locale
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onFinishComposing() {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLongClickCandidate(text: String) {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
||||||
when (command) {
|
when (command) {
|
||||||
Command.CAP_MODE -> capMode(key)
|
Command.CAP_MODE -> toggleCapMode(key)
|
||||||
Command.CHARACTER -> composeCharacter(key, stats)
|
Command.CHARACTER -> composeCharacter(key)
|
||||||
Command.DELETE -> delete()
|
Command.DELETE -> delete()
|
||||||
|
Command.FINISH_DELETE -> finishDelete()
|
||||||
Command.INPUT_MODE -> inputMode(key)
|
Command.INPUT_MODE -> inputMode(key)
|
||||||
Command.NUMBER -> triggerOriginalKeyEvent(key)
|
Command.NUMBER -> triggerOriginalKeyEvent(key)
|
||||||
// Command.RECORD -> wkt9.onRecord(true)
|
Command.RECORD -> wkt9.record()
|
||||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||||
// Command.TRANSCRIBE -> wkt9.onTranscribe()
|
Command.TRANSCRIBE -> wkt9.transcribe()
|
||||||
else -> Log.d(tag, "Command not implemented: $command")
|
else -> Log.d(tag, "Command not implemented: $command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWordSelected(word: Word) {}
|
override fun onWordSelected(word: Word) {}
|
||||||
|
|
||||||
private fun composeCharacter(key: Key, stats: KeyEventStat) {
|
override fun toggleCapMode(key: Key) {
|
||||||
if (lastKey == key) repeats++
|
super.toggleCapMode(key)
|
||||||
else {
|
|
||||||
wkt9.onCommit()
|
|
||||||
|
|
||||||
lastKey = key
|
updateIcon()
|
||||||
repeats = 0
|
}
|
||||||
|
|
||||||
|
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 layout = KeyLayout.chars[key] ?: return
|
||||||
val index = repeats % layout.count()
|
val index = repeats % layout.count()
|
||||||
|
val char = when(effectiveCapMode()) {
|
||||||
|
null -> layout[index].toString()
|
||||||
|
else -> layout[index].uppercase()
|
||||||
|
}
|
||||||
|
|
||||||
wkt9.onCompose(layout[index].toString())
|
lastComposeChar = layout[index]
|
||||||
|
|
||||||
|
word.append(char)
|
||||||
|
wkt9.onCompose(char)
|
||||||
setComposeTimeout()
|
setComposeTimeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setComposeTimeout() {
|
private fun finalizeWordOrSentence(stats: KeyEventStat) {
|
||||||
|
if (word.isNotEmpty()) storeWord()
|
||||||
|
|
||||||
timeoutJob?.cancel()
|
timeoutJob?.cancel()
|
||||||
|
|
||||||
timeoutJob = timeoutScope.launch {
|
val index = stats.repeats % punctuationMarks.count()
|
||||||
delay(400L)
|
val lastIndex = if (stats.repeats > 0) (stats.repeats - 1) % punctuationMarks.count() else null
|
||||||
wkt9.onCommit("")
|
var beforeCursor = 0
|
||||||
|
|
||||||
lastKey = null
|
if (lastIndex != null) beforeCursor += punctuationMarks[lastIndex].length
|
||||||
|
|
||||||
|
wordStart = true
|
||||||
|
sentenceStart = index in 1..3
|
||||||
|
|
||||||
|
wkt9.onCommit(punctuationMarks[index], beforeCursor)
|
||||||
|
updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishDelete() {
|
||||||
|
setCursorPositionStatus()
|
||||||
|
updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetKey(key: Key? = null) {
|
||||||
|
lastKey = key
|
||||||
repeats = 0
|
repeats = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishComposingChar() {
|
||||||
|
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() {
|
private fun delete() {
|
||||||
|
if (word.isNotEmpty()) word.deleteAt(word.length - 1)
|
||||||
|
|
||||||
|
resetKey()
|
||||||
|
|
||||||
if (timeoutJob?.isActive == true) {
|
if (timeoutJob?.isActive == true) {
|
||||||
timeoutJob?.cancel()
|
timeoutJob?.cancel()
|
||||||
wkt9.onCompose("")
|
wkt9.onCompose("")
|
||||||
@@ -149,14 +220,18 @@ class LetterInputHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIcon() {
|
private fun updateIcon() {
|
||||||
val icon = when (capMode) {
|
val icon = when (effectiveCapMode()) {
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> R.drawable.letter_upper
|
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> R.drawable.letter_upper
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) R.drawable.letter_cap else R.drawable.letter_lower
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> R.drawable.letter_cap
|
||||||
else -> R.drawable.letter_lower
|
else -> R.drawable.letter_lower
|
||||||
}
|
}
|
||||||
|
|
||||||
// wkt9.onUpdateStatusIcon(icon)
|
if (icon == lastIcon) return
|
||||||
|
|
||||||
|
wkt9.onUpdateStatusIcon(icon)
|
||||||
|
|
||||||
|
lastIcon = icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.util.Log
|
|||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import net.mezimmah.wkt9.WKT9IME
|
import net.mezimmah.wkt9.WKT9IME
|
||||||
import net.mezimmah.wkt9.IME
|
import net.mezimmah.wkt9.IME
|
||||||
|
import net.mezimmah.wkt9.R
|
||||||
import net.mezimmah.wkt9.entity.Word
|
import net.mezimmah.wkt9.entity.Word
|
||||||
import net.mezimmah.wkt9.inputmode.InputMode
|
import net.mezimmah.wkt9.inputmode.InputMode
|
||||||
import net.mezimmah.wkt9.keypad.Command
|
import net.mezimmah.wkt9.keypad.Command
|
||||||
@@ -17,9 +18,13 @@ class NumberInputHandler(
|
|||||||
) : InputHandler {
|
) : InputHandler {
|
||||||
private val tag = "WKT9"
|
private val tag = "WKT9"
|
||||||
|
|
||||||
|
init {
|
||||||
|
wkt9.showStatusIcon(R.drawable.number_input)
|
||||||
|
}
|
||||||
|
|
||||||
// We don't need to implement methods below
|
// We don't need to implement methods below
|
||||||
override fun onFinishComposing() {}
|
override fun onFinishComposing() {}
|
||||||
override fun onLongClickCandidate(text: String) {}
|
override fun onDeleteWord(word: Word) {}
|
||||||
override fun onSwitchLocale(locale: Locale) {}
|
override fun onSwitchLocale(locale: Locale) {}
|
||||||
override fun onWordSelected(word: Word) {}
|
override fun onWordSelected(word: Word) {}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package net.mezimmah.wkt9.inputhandler
|
package net.mezimmah.wkt9.inputhandler
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.InputType
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -22,8 +23,8 @@ import java.util.Locale
|
|||||||
class WordInputHandler(
|
class WordInputHandler(
|
||||||
val ime: IME,
|
val ime: IME,
|
||||||
private var wkt9: WKT9IME,
|
private var wkt9: WKT9IME,
|
||||||
private var locale: Locale,
|
private var locale: Locale
|
||||||
) : DefaultInputHandler(ime, wkt9), InputHandler {
|
) : DefaultInputHandler(wkt9), InputHandler {
|
||||||
private val codeword = StringBuilder()
|
private val codeword = StringBuilder()
|
||||||
private var staleCodeword = false
|
private var staleCodeword = false
|
||||||
private var lastSelectedWord: Word? = null
|
private var lastSelectedWord: Word? = null
|
||||||
@@ -35,11 +36,17 @@ class WordInputHandler(
|
|||||||
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
private var queryJob: Job? = null
|
private var queryJob: Job? = null
|
||||||
|
|
||||||
override fun capMode(key: Key): Int? {
|
init {
|
||||||
return super.capMode(key)
|
updateIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun finalizeWordOrSentence(stats: KeyEventStat) {
|
override fun toggleCapMode(key: Key) {
|
||||||
|
super.toggleCapMode(key)
|
||||||
|
|
||||||
|
updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finalizeWordOrSentence(stats: KeyEventStat) {
|
||||||
val ends = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
val ends = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
||||||
val index = stats.repeats % ends.count()
|
val index = stats.repeats % ends.count()
|
||||||
val lastIndex = if (stats.repeats > 0) (stats.repeats - 1) % ends.count() else null
|
val lastIndex = if (stats.repeats > 0) (stats.repeats - 1) % ends.count() else null
|
||||||
@@ -48,10 +55,24 @@ class WordInputHandler(
|
|||||||
if (lastIndex != null) beforeCursor += ends[lastIndex].length
|
if (lastIndex != null) beforeCursor += ends[lastIndex].length
|
||||||
|
|
||||||
wkt9.onCommit(ends[index], beforeCursor)
|
wkt9.onCommit(ends[index], beforeCursor)
|
||||||
|
|
||||||
|
sentenceStart = index in 1..3
|
||||||
|
|
||||||
|
updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeleteWord(word: Word) {
|
||||||
|
ioScope.launch {
|
||||||
|
wordDao.delete(word.id)
|
||||||
|
|
||||||
|
handleCodewordChange(codeword)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSwitchLocale(locale: Locale) {
|
override fun onSwitchLocale(locale: Locale) {
|
||||||
this.locale = locale
|
this.locale = locale
|
||||||
|
|
||||||
|
updateIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFinishComposing() {
|
override fun onFinishComposing() {
|
||||||
@@ -63,28 +84,36 @@ class WordInputHandler(
|
|||||||
lastSelectedWord = null
|
lastSelectedWord = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClickCandidate(text: String) {
|
|
||||||
ioScope.launch {
|
|
||||||
wordDao.delete(text, locale.language)
|
|
||||||
|
|
||||||
handleCodewordChange(codeword)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
||||||
when (command) {
|
when (command) {
|
||||||
// Command.CAP_MODE -> capMode(key)
|
Command.CAP_MODE -> toggleCapMode(key)
|
||||||
Command.CHARACTER -> buildCodeword(key)
|
Command.CHARACTER -> buildCodeword(key)
|
||||||
Command.DELETE -> delete()
|
Command.DELETE -> delete(event.repeatCount)
|
||||||
Command.ENTER -> enter(key)
|
Command.ENTER -> enter(key)
|
||||||
|
Command.FINISH_DELETE -> finishDelete()
|
||||||
Command.INPUT_MODE -> inputMode(key)
|
Command.INPUT_MODE -> inputMode(key)
|
||||||
Command.MOVE_CURSOR -> moveCursor(key)
|
Command.MOVE_CURSOR -> moveCursor(key)
|
||||||
Command.NUMBER -> triggerOriginalKeyEvent(key)
|
Command.NUMBER -> triggerOriginalKeyEvent(key)
|
||||||
|
Command.RECORD -> record()
|
||||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||||
|
Command.TRANSCRIBE -> transcribe()
|
||||||
else -> Log.d(tag, "Command not implemented: $command")
|
else -> Log.d(tag, "Command not implemented: $command")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun record() {
|
||||||
|
if (codeword.isNotEmpty()) {
|
||||||
|
wkt9.onCommit()
|
||||||
|
codeword.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
wkt9.record()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transcribe() {
|
||||||
|
wkt9.transcribe()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onWordSelected(word: Word) {
|
override fun onWordSelected(word: Word) {
|
||||||
lastSelectedWord = word
|
lastSelectedWord = word
|
||||||
}
|
}
|
||||||
@@ -101,12 +130,15 @@ class WordInputHandler(
|
|||||||
handleCodewordChange(codeword)
|
handleCodewordChange(codeword)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun delete() {
|
private fun delete(repeatCount: Int) {
|
||||||
lastSelectedWord = null
|
lastSelectedWord = null
|
||||||
|
|
||||||
if (codeword.length > 1) {
|
if (codeword.length > 1) {
|
||||||
codeword.deleteAt(codeword.length - 1)
|
codeword.deleteAt(codeword.length - 1)
|
||||||
handleCodewordChange(codeword)
|
handleCodewordChange(codeword)
|
||||||
|
} else if (codeword.isNotEmpty() && repeatCount > 1) {
|
||||||
|
codeword.clear()
|
||||||
|
wkt9.onCompose("")
|
||||||
} else if (codeword.isNotEmpty()) {
|
} else if (codeword.isNotEmpty()) {
|
||||||
codeword.clear()
|
codeword.clear()
|
||||||
wkt9.onCompose("")
|
wkt9.onCompose("")
|
||||||
@@ -115,6 +147,11 @@ class WordInputHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun finishDelete() {
|
||||||
|
setCursorPositionStatus()
|
||||||
|
updateIcon()
|
||||||
|
}
|
||||||
|
|
||||||
private fun enter(key: Key) {
|
private fun enter(key: Key) {
|
||||||
if (codeword.isNotEmpty()) wkt9.onCommit("")
|
if (codeword.isNotEmpty()) wkt9.onCommit("")
|
||||||
else triggerOriginalKeyEvent(key)
|
else triggerOriginalKeyEvent(key)
|
||||||
@@ -129,7 +166,7 @@ class WordInputHandler(
|
|||||||
// The codeword is stale when it does not yield any candidates in the DB
|
// The codeword is stale when it does not yield any candidates in the DB
|
||||||
staleCodeword = words.isEmpty()
|
staleCodeword = words.isEmpty()
|
||||||
|
|
||||||
if (words.isNotEmpty()) wkt9.onWords(words)
|
if (words.isNotEmpty()) wkt9.onWords(words, effectiveCapMode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +179,8 @@ class WordInputHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun inputMode(key: Key) {
|
private fun inputMode(key: Key) {
|
||||||
|
if (codeword.isNotEmpty()) wkt9.onCommit()
|
||||||
|
|
||||||
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Number)
|
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Number)
|
||||||
else wkt9.onSwitchInputHandler(InputMode.Letter)
|
else wkt9.onSwitchInputHandler(InputMode.Letter)
|
||||||
}
|
}
|
||||||
@@ -154,15 +193,16 @@ class WordInputHandler(
|
|||||||
|
|
||||||
@SuppressLint("DiscouragedApi")
|
@SuppressLint("DiscouragedApi")
|
||||||
private fun updateIcon() {
|
private fun updateIcon() {
|
||||||
// val mode = when (capMode) {
|
val mode = when (effectiveCapMode()) {
|
||||||
// InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> "upper"
|
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> "upper"
|
||||||
// InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||||
// InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) "cap" else "lower"
|
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> "cap"
|
||||||
// else -> "lower"
|
else -> "lower"
|
||||||
// }
|
}
|
||||||
// val name = "word_${locale}_${mode}".replace('-', '_').lowercase()
|
|
||||||
// val icon = context.resources.getIdentifier(name, "drawable", context.packageName)
|
|
||||||
|
|
||||||
// wkt9.onUpdateStatusIcon(icon)
|
val name = "word_${locale}_${mode}".replace('-', '_').lowercase()
|
||||||
|
val icon = wkt9.resources.getIdentifier(name, "drawable", wkt9.packageName)
|
||||||
|
|
||||||
|
wkt9.onUpdateStatusIcon(icon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ enum class Command {
|
|||||||
DELETE,
|
DELETE,
|
||||||
DIAL,
|
DIAL,
|
||||||
ENTER,
|
ENTER,
|
||||||
|
FINISH_DELETE,
|
||||||
INPUT_MODE,
|
INPUT_MODE,
|
||||||
MOVE_CURSOR,
|
MOVE_CURSOR,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
|
|||||||
@@ -133,8 +133,7 @@ enum class Key(
|
|||||||
),
|
),
|
||||||
|
|
||||||
CommandMapping(
|
CommandMapping(
|
||||||
events = listOf(Event.keyDown),
|
inputModes = listOf(InputMode.Number, InputMode.Idle),
|
||||||
inputModes = listOf(InputMode.Number),
|
|
||||||
overrideConsume = true,
|
overrideConsume = true,
|
||||||
consume = null
|
consume = null
|
||||||
)
|
)
|
||||||
@@ -389,6 +388,12 @@ enum class Key(
|
|||||||
command = Command.DELETE
|
command = Command.DELETE
|
||||||
),
|
),
|
||||||
|
|
||||||
|
CommandMapping(
|
||||||
|
events = listOf(Event.afterShortDown, Event.afterLongDown),
|
||||||
|
inputModes = listOf(InputMode.Word, InputMode.Letter),
|
||||||
|
command = Command.FINISH_DELETE
|
||||||
|
),
|
||||||
|
|
||||||
CommandMapping(
|
CommandMapping(
|
||||||
inputModes = listOf(InputMode.Number),
|
inputModes = listOf(InputMode.Number),
|
||||||
overrideConsume = true,
|
overrideConsume = true,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package net.mezimmah.wkt9.layout
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
|
||||||
|
class LoadingLayout(context: Context, attributeSet: AttributeSet): LinearLayout(context, attributeSet) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package net.mezimmah.wkt9.layout
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
|
||||||
|
class MessageLayout(context: Context, attributeSet: AttributeSet): LinearLayout(context, attributeSet) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package net.mezimmah.wkt9.layout
|
package net.mezimmah.wkt9.layout
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent.getActivity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.util.Log
|
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.HorizontalScrollView
|
import android.widget.HorizontalScrollView
|
||||||
@@ -15,8 +13,7 @@ import net.mezimmah.wkt9.R
|
|||||||
import net.mezimmah.wkt9.WKT9IME
|
import net.mezimmah.wkt9.WKT9IME
|
||||||
import net.mezimmah.wkt9.entity.Word
|
import net.mezimmah.wkt9.entity.Word
|
||||||
|
|
||||||
class Words(context: Context, attributeSet: AttributeSet): HorizontalScrollView(context, attributeSet), View.OnClickListener, View.OnLongClickListener {
|
class WordsLayout(context: Context, attributeSet: AttributeSet): HorizontalScrollView(context, attributeSet), View.OnClickListener, View.OnLongClickListener {
|
||||||
private val tag = "WKT9"
|
|
||||||
private var wkt9: WKT9IME
|
private var wkt9: WKT9IME
|
||||||
private var wordCount: Int = 0
|
private var wordCount: Int = 0
|
||||||
private var current: Int = 0
|
private var current: Int = 0
|
||||||
@@ -94,7 +91,19 @@ class Words(context: Context, attributeSet: AttributeSet): HorizontalScrollView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View?): Boolean {
|
override fun onLongClick(v: View?): Boolean {
|
||||||
Log.d(tag, "We need to delete this word from the db")
|
val words = this.words ?: return true
|
||||||
|
val wordContainer = findViewById<LinearLayout>(R.id.words)
|
||||||
|
|
||||||
|
for (i in 0 until wordCount) {
|
||||||
|
val child: View = wordContainer.getChildAt(i)
|
||||||
|
|
||||||
|
if (v != child) continue
|
||||||
|
|
||||||
|
wordContainer.removeView(child)
|
||||||
|
wkt9.onDeleteWord(words[i])
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -2,17 +2,12 @@ package net.mezimmah.wkt9.voice
|
|||||||
|
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
|
||||||
import android.widget.HorizontalScrollView
|
|
||||||
import android.widget.LinearLayout
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.mezimmah.wkt9.R
|
|
||||||
import net.mezimmah.wkt9.WKT9IME
|
import net.mezimmah.wkt9.WKT9IME
|
||||||
import net.mezimmah.wkt9.inputhandler.InputHandler
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
@@ -23,17 +18,11 @@ import java.io.IOException
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class Whisper(
|
class Whisper(
|
||||||
private val context: WKT9IME,
|
private val wkt9: WKT9IME,
|
||||||
private val inputHandler: InputHandler?,
|
|
||||||
private val ui: View
|
|
||||||
) {
|
) {
|
||||||
private val tag = "WKT9"
|
private val tag = "WKT9"
|
||||||
|
|
||||||
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
||||||
private val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
||||||
|
|
||||||
private var ioJob: Job? = null
|
private var ioJob: Job? = null
|
||||||
|
|
||||||
private var recorder: MediaRecorder? = null
|
private var recorder: MediaRecorder? = null
|
||||||
private var recording: File? = null
|
private var recording: File? = null
|
||||||
|
|
||||||
@@ -47,19 +36,18 @@ class Whisper(
|
|||||||
stopRecording()
|
stopRecording()
|
||||||
|
|
||||||
val recording = this.recording ?: return
|
val recording = this.recording ?: return
|
||||||
|
val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
showTranscribing()
|
|
||||||
|
|
||||||
ioJob?.cancel()
|
ioJob?.cancel()
|
||||||
ioJob = ioScope.launch {
|
ioJob = ioScope.launch {
|
||||||
try {
|
try {
|
||||||
val transcription = run(recording)
|
val transcription = run(recording)
|
||||||
|
val mainScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
showCandidates()
|
wkt9.onCommit(transcription)
|
||||||
|
wkt9.defaultView()
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputHandler?.onInsertText(transcription.plus(" "))
|
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.d(tag, "A failure occurred in the communication with the speech-to-text server", e)
|
Log.d(tag, "A failure occurred in the communication with the speech-to-text server", e)
|
||||||
}
|
}
|
||||||
@@ -70,9 +58,7 @@ class Whisper(
|
|||||||
fun record() {
|
fun record() {
|
||||||
if (recorder != null) stopRecording()
|
if (recorder != null) stopRecording()
|
||||||
|
|
||||||
showMessage()
|
recording = File.createTempFile("recording.3gp", null, wkt9.cacheDir)
|
||||||
|
|
||||||
recording = File.createTempFile("recording.3gp", null, context.cacheDir)
|
|
||||||
recorder = MediaRecorder().also {
|
recorder = MediaRecorder().also {
|
||||||
it.setAudioSource(MediaRecorder.AudioSource.MIC)
|
it.setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||||
it.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
|
it.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
|
||||||
@@ -88,36 +74,6 @@ class Whisper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCandidates() {
|
|
||||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
|
||||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
|
||||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
|
||||||
//
|
|
||||||
// candidatesView.visibility = View.VISIBLE
|
|
||||||
// loadingView.visibility = View.GONE
|
|
||||||
// messageView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showMessage() {
|
|
||||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
|
||||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
|
||||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
|
||||||
//
|
|
||||||
// candidatesView.visibility = View.GONE
|
|
||||||
// loadingView.visibility = View.GONE
|
|
||||||
// messageView.visibility = View.VISIBLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showTranscribing() {
|
|
||||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
|
||||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
|
||||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
|
||||||
//
|
|
||||||
// candidatesView.visibility = View.GONE
|
|
||||||
// loadingView.visibility = View.VISIBLE
|
|
||||||
// messageView.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun stopRecording() {
|
private fun stopRecording() {
|
||||||
recorder?.run {
|
recorder?.run {
|
||||||
stop()
|
stop()
|
||||||
|
|||||||
29
app/src/main/res/layout/loading.xml
Normal file
29
app/src/main/res/layout/loading.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<net.mezimmah.wkt9.layout.LoadingLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:theme="@style/Theme.AppCompat.DayNight"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/suggestions"
|
||||||
|
style="?android:attr/progressBarStyleLarge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:textColor="@color/suggestion_text"
|
||||||
|
android:paddingVertical="5dp"
|
||||||
|
android:paddingHorizontal="8dp"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textFontWeight="400"
|
||||||
|
android:text="Transcribing..." />
|
||||||
|
|
||||||
|
</net.mezimmah.wkt9.layout.LoadingLayout>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<net.mezimmah.wkt9.layout.MessageLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
@@ -10,11 +10,11 @@
|
|||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<ProgressBar
|
<ImageView
|
||||||
android:id="@+id/suggestions"
|
android:layout_height="40dp"
|
||||||
style="?android:attr/progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="40dp" />
|
android:src="@drawable/mic"/>
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -24,6 +24,6 @@
|
|||||||
android:paddingHorizontal="8dp"
|
android:paddingHorizontal="8dp"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textFontWeight="400"
|
android:textFontWeight="400"
|
||||||
android:text="Transcribing, please wait..." />
|
android:text="Recording..." />
|
||||||
|
|
||||||
</LinearLayout>
|
</net.mezimmah.wkt9.layout.MessageLayout>
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@+id/suggestion"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="2dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/suggestion_text"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:textColor="@color/suggestion_text"
|
|
||||||
android:minWidth="40dp"
|
|
||||||
android:paddingVertical="5dp"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textFontWeight="400" />
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<net.mezimmah.wkt9.layout.Words xmlns:android="http://schemas.android.com/apk/res/android"
|
<net.mezimmah.wkt9.layout.WordsLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentBottom="true"
|
android:layout_alignParentBottom="true"
|
||||||
@@ -14,4 +14,4 @@
|
|||||||
android:layout_height="44dp"
|
android:layout_height="44dp"
|
||||||
android:orientation="horizontal" />
|
android:orientation="horizontal" />
|
||||||
|
|
||||||
</net.mezimmah.wkt9.layout.Words>
|
</net.mezimmah.wkt9.layout.WordsLayout>
|
||||||
|
|||||||
@@ -29,6 +29,6 @@
|
|||||||
<item>400</item>
|
<item>400</item>
|
||||||
<item>600</item>
|
<item>600</item>
|
||||||
<item>800</item>
|
<item>800</item>
|
||||||
<item>900</item>
|
<item>1000</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -20,6 +20,12 @@
|
|||||||
android:languageTag="es-ES"
|
android:languageTag="es-ES"
|
||||||
android:imeSubtypeMode="keyboard" />
|
android:imeSubtypeMode="keyboard" />
|
||||||
|
|
||||||
|
<subtype
|
||||||
|
android:label="Spanish AR"
|
||||||
|
android:imeSubtypeLocale="es_AR"
|
||||||
|
android:languageTag="es-AR"
|
||||||
|
android:imeSubtypeMode="keyboard" />
|
||||||
|
|
||||||
<subtype
|
<subtype
|
||||||
android:label="German DE"
|
android:label="German DE"
|
||||||
android:imeSubtypeLocale="de_DE"
|
android:imeSubtypeLocale="de_DE"
|
||||||
@@ -32,4 +38,10 @@
|
|||||||
android:languageTag="pt-PT"
|
android:languageTag="pt-PT"
|
||||||
android:imeSubtypeMode="keyboard" />
|
android:imeSubtypeMode="keyboard" />
|
||||||
|
|
||||||
|
<subtype
|
||||||
|
android:label="Portuguese BR"
|
||||||
|
android:imeSubtypeLocale="pt_BR"
|
||||||
|
android:languageTag="pt-BR"
|
||||||
|
android:imeSubtypeMode="keyboard" />
|
||||||
|
|
||||||
</input-method>
|
</input-method>
|
||||||
|
|||||||
Reference in New Issue
Block a user