Making progress

This commit is contained in:
Nehemiah of Zebulun 2023-12-01 16:42:18 -05:00
parent a7e17eda9d
commit ea361c1586
14 changed files with 656811 additions and 0 deletions

123
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

BIN
app/release/app-release.apk Normal file

Binary file not shown.

View File

@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "net.mezimmah.wkt9",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
package net.mezimmah.wkt9.inputhandler
import android.text.InputType
import android.view.KeyEvent
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.KeyEventStat
import net.mezimmah.wkt9.keypad.KeyLayout
import net.mezimmah.wkt9.keypad.Keypad
import java.util.Locale
open class DefaultInputHandler(
private val wkt9: IME,
private val context: WKT9IME
) {
protected val tag = "WKT9"
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_WORDS,
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
null
)
var index = modes.indexOf(capMode)
when (key) {
Key.B2 -> {
if (index == 0) index = modes.count()
index--
}
else -> index++
}
return modes[index % modes.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) {
val down = KeyEvent(KeyEvent.ACTION_DOWN, keyCode)
val up = KeyEvent(KeyEvent.ACTION_UP, keyCode)
wkt9.onTriggerKeyEvent(down)
wkt9.onTriggerKeyEvent(up)
}
protected fun triggerOriginalKeyEvent(key: Key) {
triggerKeyEvent(key.keyCode)
}
}

View File

@ -0,0 +1,50 @@
package net.mezimmah.wkt9.inputhandler
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.view.KeyEvent
import net.mezimmah.wkt9.WKT9IME
import net.mezimmah.wkt9.IME
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 java.util.Locale
class IdleInputHandler(
val ime: IME,
private var wkt9: WKT9IME,
) : DefaultInputHandler(ime, wkt9), InputHandler {
override fun onFinishComposing() {}
override fun onLongClickCandidate(text: String) {
TODO("Not yet implemented")
}
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
when (command) {
Command.DIAL -> dial()
Command.CAMERA -> triggerKeyEvent(KeyEvent.KEYCODE_CAMERA)
// Command.NUMBER -> triggerOriginalKeyEvent(key)
else -> Log.d(tag, "Command not implemented: $command")
}
}
override fun onSwitchLocale(locale: Locale) {
TODO("Not yet implemented")
}
override fun onWordSelected(word: Word) {}
private fun dial() {
val uri = "tel:"
val intent = Intent(Intent.ACTION_VIEW)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.data = Uri.parse(uri)
// wkt9.onStartIntent(intent)
}
}

View File

@ -0,0 +1,20 @@
package net.mezimmah.wkt9.inputhandler
import android.view.KeyEvent
import net.mezimmah.wkt9.entity.Word
import net.mezimmah.wkt9.keypad.Command
import net.mezimmah.wkt9.keypad.Key
import net.mezimmah.wkt9.keypad.KeyEventStat
import java.util.Locale
interface InputHandler {
fun onFinishComposing()
fun onLongClickCandidate(text: String)
fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat)
fun onSwitchLocale(locale: Locale)
fun onWordSelected(word: Word)
}

View File

@ -0,0 +1,200 @@
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,
): SpellCheckerSession.SpellCheckerSessionListener, DefaultInputHandler(ime, wkt9), InputHandler {
private val db = AppDatabase.getInstance(wkt9)
private val wordDao = db.getWordDao()
private var lastKey: Key? = null
private var repeats: Int = 0
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 val content = StringBuilder()
init {
val textServiceManager = wkt9.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
spellCheckerSession = textServiceManager.newSpellCheckerSession(
null,
locale,
this,
false
)
}
override fun finalizeWordOrSentence(stats: KeyEventStat) {
timeoutJob?.cancel()
val ends = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
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
wkt9.onCommit(ends[index], beforeCursor)
}
override fun onSwitchLocale(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) {
when (command) {
Command.CAP_MODE -> capMode(key)
Command.CHARACTER -> composeCharacter(key, stats)
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, stats: KeyEventStat) {
if (lastKey == key) repeats++
else {
wkt9.onCommit()
lastKey = key
repeats = 0
}
val layout = KeyLayout.chars[key] ?: return
val index = repeats % layout.count()
wkt9.onCompose(layout[index].toString())
setComposeTimeout()
}
private fun setComposeTimeout() {
timeoutJob?.cancel()
timeoutJob = timeoutScope.launch {
delay(400L)
wkt9.onCommit("")
lastKey = null
repeats = 0
}
}
private fun delete() {
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 (capMode) {
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> R.drawable.letter_upper
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) R.drawable.letter_cap else R.drawable.letter_lower
else -> R.drawable.letter_lower
}
// wkt9.onUpdateStatusIcon(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)
// }

View File

@ -0,0 +1,37 @@
package net.mezimmah.wkt9.inputhandler
import android.util.Log
import android.view.KeyEvent
import net.mezimmah.wkt9.WKT9IME
import net.mezimmah.wkt9.IME
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 java.util.Locale
class NumberInputHandler(
val ime: IME,
private var wkt9: WKT9IME,
) : InputHandler {
private val tag = "WKT9"
// We don't need to implement methods below
override fun onFinishComposing() {}
override fun onLongClickCandidate(text: String) {}
override fun onSwitchLocale(locale: Locale) {}
override fun onWordSelected(word: Word) {}
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
when (command) {
Command.INPUT_MODE -> inputMode(key)
else -> Log.d(tag, "Command not implemented: $command")
}
}
private fun inputMode(key: Key) {
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Letter)
else wkt9.onSwitchInputHandler(InputMode.Word)
}
}

View File

@ -0,0 +1,168 @@
package net.mezimmah.wkt9.inputhandler
import android.annotation.SuppressLint
import android.util.Log
import android.view.KeyEvent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
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 WordInputHandler(
val ime: IME,
private var wkt9: WKT9IME,
private var locale: Locale,
) : DefaultInputHandler(ime, wkt9), InputHandler {
private val codeword = StringBuilder()
private var staleCodeword = false
private var lastSelectedWord: Word? = null
private val db = AppDatabase.getInstance(wkt9)
private val wordDao = db.getWordDao()
private val queryScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var queryJob: Job? = null
override fun capMode(key: Key): Int? {
return super.capMode(key)
}
override fun finalizeWordOrSentence(stats: KeyEventStat) {
val ends = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
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
wkt9.onCommit(ends[index], beforeCursor)
}
override fun onSwitchLocale(locale: Locale) {
this.locale = locale
}
override fun onFinishComposing() {
queryJob?.cancel()
codeword.clear()
increaseWordWeight()
staleCodeword = false
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) {
when (command) {
// Command.CAP_MODE -> capMode(key)
Command.CHARACTER -> buildCodeword(key)
Command.DELETE -> delete()
Command.ENTER -> enter(key)
Command.INPUT_MODE -> inputMode(key)
Command.MOVE_CURSOR -> moveCursor(key)
Command.NUMBER -> triggerOriginalKeyEvent(key)
Command.SPACE -> finalizeWordOrSentence(stats)
else -> Log.d(tag, "Command not implemented: $command")
}
}
override fun onWordSelected(word: Word) {
lastSelectedWord = word
}
private fun buildCodeword(key: Key) {
// Don't build fruitless codeword
if (staleCodeword) return
val code = KeyLayout.numeric[key]
codeword.append(code)
queryJob?.cancel()
handleCodewordChange(codeword)
}
private fun delete() {
lastSelectedWord = null
if (codeword.length > 1) {
codeword.deleteAt(codeword.length - 1)
handleCodewordChange(codeword)
} else if (codeword.isNotEmpty()) {
codeword.clear()
wkt9.onCompose("")
} else {
wkt9.onDeleteText(1)
}
}
private fun enter(key: Key) {
if (codeword.isNotEmpty()) wkt9.onCommit("")
else triggerOriginalKeyEvent(key)
}
private fun handleCodewordChange(codeword: StringBuilder) {
queryJob?.cancel()
queryJob = queryScope.launch(Dispatchers.Main) {
val words = wordDao.findCandidates(locale.language, codeword.toString(), 10)
// The codeword is stale when it does not yield any candidates in the DB
staleCodeword = words.isEmpty()
if (words.isNotEmpty()) wkt9.onWords(words)
}
}
private fun increaseWordWeight() {
val lastWord = lastSelectedWord ?: return
queryScope.launch {
wordDao.increaseWeight(lastWord.id)
}
}
private fun inputMode(key: Key) {
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Number)
else wkt9.onSwitchInputHandler(InputMode.Letter)
}
private fun moveCursor(key: Key) {
if (codeword.isEmpty()) triggerOriginalKeyEvent(key)
else if (key == Key.RIGHT) wkt9.onNextWord()
else if (key == Key.LEFT) wkt9.onPreviousWord()
}
@SuppressLint("DiscouragedApi")
private fun updateIcon() {
// val mode = when (capMode) {
// InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> "upper"
// InputType.TYPE_TEXT_FLAG_CAP_WORDS,
// InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) "cap" else "lower"
// else -> "lower"
// }
// val name = "word_${locale}_${mode}".replace('-', '_').lowercase()
// val icon = context.resources.getIdentifier(name, "drawable", context.packageName)
// wkt9.onUpdateStatusIcon(icon)
}
}