Refactored
This commit is contained in:
parent
3b5b77c540
commit
9d5c6364f0
@ -39,11 +39,12 @@ android {
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.12.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("com.google.android.material:material:1.10.0")
|
||||
implementation("androidx.room:room-common:2.5.2")
|
||||
implementation("androidx.room:room-ktx:2.5.2")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.10")
|
||||
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
|
@ -2,6 +2,10 @@
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.telephony"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
@ -23,8 +27,11 @@
|
||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||
android:exported="true">
|
||||
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.view.im" android:resource="@xml/method" />
|
||||
</service>
|
||||
|
File diff suppressed because it is too large
Load Diff
43
app/src/main/java/net/mezimmah/wkt9/WKT9Interface.kt
Normal file
43
app/src/main/java/net/mezimmah/wkt9/WKT9Interface.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package net.mezimmah.wkt9
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
|
||||
interface WKT9Interface {
|
||||
fun onTriggerKeyEvent(event: KeyEvent)
|
||||
|
||||
fun onStartIntent(intent: Intent)
|
||||
|
||||
fun onCandidates(
|
||||
candidates: List<String>,
|
||||
finishComposing: Boolean = false,
|
||||
current: Int? = 0,
|
||||
timeout: Long? = null,
|
||||
start: Int? = null,
|
||||
end: Int? = null,
|
||||
notifyOnChange: Boolean = false
|
||||
)
|
||||
|
||||
fun onCancelCompose()
|
||||
|
||||
fun onClearCandidates()
|
||||
|
||||
fun onDeleteText(beforeCursor: Int = 0, afterCursor: Int = 0, finishComposing: Boolean)
|
||||
|
||||
fun onFinishComposing()
|
||||
|
||||
fun onGetText(): CharSequence?
|
||||
|
||||
fun onGetTextBeforeCursor(n: Int): CharSequence?
|
||||
|
||||
fun onReplaceText(text: String)
|
||||
|
||||
fun onSwitchInputHandler(inputMode: InputMode)
|
||||
|
||||
fun onRecord(finishComposing: Boolean)
|
||||
|
||||
fun onTranscribe()
|
||||
|
||||
fun onUpdateStatusIcon(icon: Int?)
|
||||
}
|
@ -1,21 +1,18 @@
|
||||
package net.mezimmah.wkt9.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
|
||||
@Dao
|
||||
interface WordDao {
|
||||
@Insert
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insert(vararg words: Word)
|
||||
|
||||
@Delete
|
||||
fun delete(word: Word)
|
||||
|
||||
@Query("SELECT * FROM word")
|
||||
fun getAll(): List<Word>
|
||||
@Query("DELETE FROM word WHERE word = :word AND locale = :locale")
|
||||
fun delete(word: String, locale: String)
|
||||
|
||||
@Query("SELECT * FROM word WHERE code LIKE :code || '%' " +
|
||||
"ORDER BY length, weight DESC LIMIT :limit")
|
||||
|
@ -4,7 +4,7 @@ import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(indices = [Index(value = ["word"]), Index(value = ["code"]), Index(value = ["locale"])])
|
||||
@Entity(indices = [Index(value = ["word"]), Index(value = ["code"]), Index(value = ["locale"]), Index(value = ["word", "locale"], unique = true)])
|
||||
data class Word(
|
||||
var word: String,
|
||||
var code: String,
|
||||
|
@ -1,82 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
|
||||
class AlphaInputMode: BaseInputMode() {
|
||||
init {
|
||||
mode = "alpha"
|
||||
status = Status.CAP
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
super.onKeyDown(key, composing)
|
||||
|
||||
return when(keyCommandResolver.getCommand(key)) {
|
||||
Command.BACK -> KeyEventResult(consumed = false)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
Command.LEFT -> navigateLeft()
|
||||
Command.RIGHT -> navigateRight()
|
||||
Command.SELECT -> focus()
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, true)) {
|
||||
Command.RECORD -> record(composing)
|
||||
Command.NUMBER -> commitNumber(key, composing)
|
||||
Command.SWITCH_MODE -> switchMode(WKT9InputMode.NUMERIC, composing)
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, repeat = repeat)) {
|
||||
Command.HOME -> goHome(repeat, composing)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true)) {
|
||||
Command.BACK -> goBack(composing)
|
||||
Command.CHARACTER -> composeCharacter(key, composing)
|
||||
Command.FN -> functionMode()
|
||||
Command.SHIFT_MODE -> shiftMode()
|
||||
Command.SPACE -> finalizeWordOrSentence(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true, longPress = true)) {
|
||||
Command.TRANSCRIBE -> transcribe(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun composeCharacter(key: Key, composing: Boolean): KeyEventResult {
|
||||
if (composing && !newKey) return navigateRight()
|
||||
|
||||
return super.composeCharacter(key, composing)
|
||||
}
|
||||
|
||||
private fun shiftMode(): KeyEventResult {
|
||||
status = when(status) {
|
||||
Status.CAP -> Status.UPPER
|
||||
Status.UPPER -> Status.LOWER
|
||||
else -> Status.CAP
|
||||
}
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
updateInputStatus = true
|
||||
)
|
||||
}
|
||||
}
|
@ -1,198 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyCommandResolver
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
import net.mezimmah.wkt9.keypad.KeyLayout
|
||||
|
||||
open class BaseInputMode: InputMode {
|
||||
protected var packageName: String? = null
|
||||
|
||||
protected val tag = "WKT9"
|
||||
|
||||
protected var newKey = true
|
||||
protected var keyIndex = 0
|
||||
protected var lastKey: Key? = null
|
||||
|
||||
protected open val keyCommandResolver: KeyCommandResolver = KeyCommandResolver.getBasic()
|
||||
|
||||
override lateinit var mode: String
|
||||
protected set
|
||||
|
||||
override lateinit var status: Status
|
||||
protected set
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
keyStats(key)
|
||||
|
||||
return KeyEventResult()
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult()
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult()
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult()
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult()
|
||||
}
|
||||
|
||||
override fun packageName(packageName: String) {
|
||||
this.packageName = packageName
|
||||
}
|
||||
|
||||
override fun restart() {
|
||||
Log.d(tag, "Restart should be handled by individual input modes")
|
||||
}
|
||||
|
||||
protected fun commit(text: String, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
commit = text
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun commitNumber(key: Key, composing: Boolean): KeyEventResult {
|
||||
val number = KeyLayout.numeric[key] ?: return KeyEventResult(true)
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
commit = number.toString()
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun composeCharacter(key: Key, composing: Boolean): KeyEventResult {
|
||||
val layout = KeyLayout.en_US[key] ?: return KeyEventResult(true)
|
||||
val candidates = layout.map { it.toString() }
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
startComposing = true,
|
||||
candidates = candidates,
|
||||
timeout = 1200
|
||||
)
|
||||
}
|
||||
|
||||
protected fun composeNumber(key: Key, composing: Boolean): KeyEventResult {
|
||||
val code = KeyLayout.numeric[key] ?: return KeyEventResult(true)
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
commit = code.toString()
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun deleteCharacter(composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
finishComposing = composing,
|
||||
deleteBeforeCursor = 1
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun finalizeWordOrSentence(composing: Boolean): KeyEventResult {
|
||||
if (composing && !newKey) return navigateRight()
|
||||
|
||||
return KeyEventResult(
|
||||
finishComposing = composing,
|
||||
startComposing = true,
|
||||
candidates = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; "),
|
||||
timeout = 700
|
||||
)
|
||||
}
|
||||
|
||||
protected fun focus(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
focus = true
|
||||
)
|
||||
}
|
||||
|
||||
protected fun functionMode(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
toggleFunctionMode = true
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun goBack(composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = false,
|
||||
finishComposing = composing
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun goHome(repeat: Int, composing: Boolean): KeyEventResult {
|
||||
if (repeat > 1) return KeyEventResult(true)
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
goHome = true
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun navigateLeft(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
left = true
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun navigateRight(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
right = true
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun record(composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
record = true
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun switchMode(mode: WKT9InputMode, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
switchInputMode = mode
|
||||
)
|
||||
}
|
||||
|
||||
protected fun transcribe(composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
transcribe = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun keyStats(key: Key) {
|
||||
when (key != lastKey) {
|
||||
true -> {
|
||||
newKey = true
|
||||
keyIndex = 0
|
||||
}
|
||||
false -> {
|
||||
newKey = false
|
||||
keyIndex++
|
||||
}
|
||||
}
|
||||
|
||||
lastKey = key
|
||||
}
|
||||
}
|
50
app/src/main/java/net/mezimmah/wkt9/inputmode/Capitalize.kt
Normal file
50
app/src/main/java/net/mezimmah/wkt9/inputmode/Capitalize.kt
Normal file
@ -0,0 +1,50 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
|
||||
class Capitalize(
|
||||
private val capMode: Int?,
|
||||
private val sentenceDelimiters: List<Char> = listOf('.', ',', '?')
|
||||
) {
|
||||
fun text(original: String): String {
|
||||
var sentenceStart = false
|
||||
var wordStart = false
|
||||
var capitalized = original
|
||||
|
||||
capitalized.forEachIndexed { index, char ->
|
||||
if (index == 0) sentenceStart = true
|
||||
|
||||
if (char.isLetter()) {
|
||||
capitalized = if (
|
||||
capMode == InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS ||
|
||||
(sentenceStart && (capMode == InputType.TYPE_TEXT_FLAG_CAP_WORDS || capMode == InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)) ||
|
||||
(wordStart && capMode == InputType.TYPE_TEXT_FLAG_CAP_WORDS)
|
||||
) {
|
||||
capitalized.replaceRange(index, index +1, char.uppercase())
|
||||
} else {
|
||||
capitalized.replaceRange(index, index +1, char.lowercase())
|
||||
}
|
||||
|
||||
sentenceStart = false
|
||||
wordStart = false
|
||||
} else {
|
||||
if (sentenceDelimiters.contains(char)) sentenceStart = true
|
||||
if (char.isWhitespace()) wordStart = true
|
||||
}
|
||||
}
|
||||
|
||||
return capitalized
|
||||
}
|
||||
|
||||
fun word(word: String, sentenceStart: Boolean): String {
|
||||
return when (capMode) {
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> {
|
||||
if (sentenceStart) word.replaceFirstChar { it.uppercase() }
|
||||
else word
|
||||
}
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS -> word.replaceFirstChar { it.uppercase() }
|
||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> word.uppercase()
|
||||
else -> word
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
data class CursorPositionInfo(
|
||||
val startWord: Boolean,
|
||||
val startSentence: Boolean
|
||||
)
|
@ -1,106 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyCommandResolver
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
|
||||
class FNInputMode: BaseInputMode() {
|
||||
override val keyCommandResolver: KeyCommandResolver = KeyCommandResolver(
|
||||
parent = super.keyCommandResolver,
|
||||
|
||||
onShort = HashMap(mapOf(
|
||||
Key.N0 to Command.NEWLINE,
|
||||
Key.UP to Command.VOL_UP,
|
||||
Key.DOWN to Command.VOL_DOWN,
|
||||
Key.LEFT to Command.BRIGHTNESS_DOWN,
|
||||
Key.RIGHT to Command.BRIGHTNESS_UP
|
||||
)),
|
||||
|
||||
onRepeat = HashMap(mapOf(
|
||||
Key.N0 to Command.NEWLINE,
|
||||
Key.UP to Command.VOL_UP,
|
||||
Key.DOWN to Command.VOL_DOWN,
|
||||
Key.LEFT to Command.BRIGHTNESS_DOWN,
|
||||
Key.RIGHT to Command.BRIGHTNESS_UP,
|
||||
|
||||
Key.BACK to Command.HOME
|
||||
))
|
||||
)
|
||||
|
||||
init {
|
||||
mode = "fn"
|
||||
status = Status.NA
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key)) {
|
||||
Command.BACK -> KeyEventResult(false)
|
||||
Command.BRIGHTNESS_DOWN -> brightnessDown()
|
||||
Command.BRIGHTNESS_UP -> brightnessUp()
|
||||
Command.NEWLINE -> commit("\n", composing)
|
||||
Command.VOL_UP -> volumeUp()
|
||||
Command.VOL_DOWN -> volumeDown()
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(true)
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, repeat = repeat)) {
|
||||
Command.HOME -> goHome(repeat, composing)
|
||||
Command.BRIGHTNESS_DOWN -> brightnessDown()
|
||||
Command.BRIGHTNESS_UP -> brightnessUp()
|
||||
Command.NEWLINE -> commit("\n", composing)
|
||||
Command.VOL_UP -> volumeUp()
|
||||
Command.VOL_DOWN -> volumeDown()
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true)) {
|
||||
Command.BACK -> goBack(composing)
|
||||
Command.FN -> functionMode()
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(true)
|
||||
}
|
||||
|
||||
private fun brightnessDown(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
decreaseBrightness = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun brightnessUp(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
increaseBrightness = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun volumeUp(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
increaseVolume = true
|
||||
)
|
||||
}
|
||||
|
||||
private fun volumeDown(): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
decreaseVolume = true
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
||||
|
||||
class IdleInputHandler(wkt9: WKT9Interface, context: WKT9) : InputHandler(wkt9, context) {
|
||||
init {
|
||||
mode = InputMode.Idle
|
||||
capMode = null
|
||||
}
|
||||
|
||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
||||
when (command) {
|
||||
Command.DIAL -> dial()
|
||||
Command.CAMERA -> triggerKeyEvent(KeyEvent.KEYCODE_CAMERA, false)
|
||||
Command.NUMBER -> triggerOriginalKeyEvent(key, false)
|
||||
else -> Log.d(tag, "Command not implemented: $command")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {
|
||||
wkt9.onUpdateStatusIcon(R.drawable.idle_en_us_na)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
|
||||
class IdleInputMode : BaseInputMode() {
|
||||
init {
|
||||
mode = "idle"
|
||||
status = Status.NA
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key)) {
|
||||
Command.FN -> KeyEventResult(true)
|
||||
Command.SELECT -> conditionalSelect()
|
||||
else -> KeyEventResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, true)) {
|
||||
else -> KeyEventResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, repeat = repeat)) {
|
||||
Command.HOME -> goHome(repeat, composing)
|
||||
else -> KeyEventResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true)) {
|
||||
Command.BACK -> goBack(composing)
|
||||
Command.FN -> functionMode()
|
||||
else -> KeyEventResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true, longPress = true)) {
|
||||
else -> KeyEventResult(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun conditionalSelect(): KeyEventResult {
|
||||
return when (packageName) {
|
||||
"com.android.camera2" -> {
|
||||
KeyEventResult(
|
||||
consumed = true,
|
||||
keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CAMERA)
|
||||
)
|
||||
}
|
||||
|
||||
else -> KeyEventResult(consumed = false)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
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
|
||||
|
||||
open class InputHandler(
|
||||
override val wkt9: WKT9Interface,
|
||||
override val context: WKT9
|
||||
): InputHandlerInterface {
|
||||
protected val tag = "WKT9"
|
||||
|
||||
protected var keypad: Keypad = Keypad(KeyLayout.en_US, KeyLayout.numeric)
|
||||
|
||||
override lateinit var mode: InputMode
|
||||
protected set
|
||||
|
||||
override var capMode: Int? = null
|
||||
protected set
|
||||
|
||||
override var cursorPosition: Int = 0
|
||||
protected set
|
||||
|
||||
override fun onCandidateSelected(candidate: String) {
|
||||
Log.d(tag, "A candidate has been selected: $candidate")
|
||||
}
|
||||
|
||||
override fun onCommitText() {}
|
||||
|
||||
override fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int) {}
|
||||
|
||||
override fun onFinish() {}
|
||||
|
||||
override fun onLongClickCandidate(text: String) {}
|
||||
|
||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {}
|
||||
|
||||
override fun onUpdateCursorPosition(cursorPosition: Int) {
|
||||
this.cursorPosition = cursorPosition
|
||||
}
|
||||
|
||||
protected open fun capMode(key: Key) {
|
||||
// capMode = when (key) {
|
||||
// Key.B2 -> CapMode.previous(capMode)
|
||||
// else -> CapMode.next(capMode)
|
||||
// }
|
||||
}
|
||||
|
||||
protected open fun finalizeWordOrSentence(stats: KeyEventStat) {
|
||||
val candidates = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
||||
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
finishComposing = stats.repeats == 0,
|
||||
current = stats.repeats % candidates.count()
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun getCursorPositionInfo(text: CharSequence): CursorPositionInfo {
|
||||
val trimmed = text.trimEnd()
|
||||
val regex = "[.!?]$".toRegex()
|
||||
val startSentence = text.isEmpty() || regex.containsMatchIn(trimmed)
|
||||
val startWord = text.isEmpty() || (startSentence || trimmed.length < text.length)
|
||||
|
||||
return CursorPositionInfo(
|
||||
startSentence = startSentence,
|
||||
startWord = startWord
|
||||
)
|
||||
}
|
||||
|
||||
protected fun triggerKeyEvent(keyCode: Int, finishComposing: Boolean) {
|
||||
val down = KeyEvent(KeyEvent.ACTION_DOWN, keyCode)
|
||||
val up = KeyEvent(KeyEvent.ACTION_UP, keyCode)
|
||||
|
||||
if (finishComposing) wkt9.onFinishComposing()
|
||||
|
||||
wkt9.onTriggerKeyEvent(down)
|
||||
wkt9.onTriggerKeyEvent(up)
|
||||
}
|
||||
|
||||
protected fun triggerOriginalKeyEvent(key: Key, finishComposing: Boolean) {
|
||||
triggerKeyEvent(key.keyCode, finishComposing)
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
||||
|
||||
interface InputHandlerInterface {
|
||||
val wkt9: WKT9Interface
|
||||
val context: WKT9
|
||||
val mode: InputMode
|
||||
val capMode: Int?
|
||||
val cursorPosition: Int?
|
||||
|
||||
fun onCandidateSelected(candidate: String)
|
||||
|
||||
fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int)
|
||||
|
||||
fun onCommitText()
|
||||
|
||||
fun onFinish()
|
||||
|
||||
fun onLongClickCandidate(text: String)
|
||||
|
||||
fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat)
|
||||
|
||||
fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int)
|
||||
|
||||
fun onUpdateCursorPosition(cursorPosition: Int)
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
|
||||
class InputManager(val context: WKT9) {
|
||||
private val idleInputHandler: IdleInputHandler = IdleInputHandler(context, context)
|
||||
private val letterInputHandler: LetterInputHandler = LetterInputHandler(context, context)
|
||||
private val numberInputHandler: NumberInputHandler = NumberInputHandler(context, context)
|
||||
private val wordInputHandler: WordInputHandler = WordInputHandler(context, context)
|
||||
|
||||
private val numericClasses = listOf(
|
||||
InputType.TYPE_CLASS_DATETIME,
|
||||
InputType.TYPE_CLASS_NUMBER,
|
||||
InputType.TYPE_CLASS_PHONE
|
||||
)
|
||||
|
||||
private val letterVariations = listOf(
|
||||
InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS,
|
||||
InputType.TYPE_TEXT_VARIATION_URI,
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS,
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_FILTER,
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS,
|
||||
InputType.TYPE_TEXT_VARIATION_PERSON_NAME
|
||||
)
|
||||
|
||||
private var typeClass: Int = 0
|
||||
private var typeVariation: Int = 0
|
||||
private var typeFlags: Int = 0
|
||||
private var allowSuggestions: Boolean = false
|
||||
|
||||
var handler: InputHandler? = null
|
||||
private set
|
||||
|
||||
var mode: InputMode = InputMode.Idle
|
||||
private set
|
||||
|
||||
fun selectHandler(editor: EditorInfo) {
|
||||
val inputType = editor.inputType
|
||||
val override = selectOverride(editor.packageName)
|
||||
|
||||
typeClass = inputType.and(InputType.TYPE_MASK_CLASS)
|
||||
typeVariation = inputType.and(InputType.TYPE_MASK_VARIATION)
|
||||
typeFlags = inputType.and(InputType.TYPE_MASK_FLAGS)
|
||||
allowSuggestions = typeFlags != InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
|
||||
if (override != null) return switchToHandler(override)
|
||||
|
||||
val handler = if (numericClasses.contains(typeClass)) {
|
||||
InputMode.Number
|
||||
} else if (typeClass == InputType.TYPE_CLASS_TEXT) {
|
||||
if (letterVariations.contains(typeVariation) || mode == InputMode.Letter) {
|
||||
InputMode.Letter
|
||||
} else {
|
||||
InputMode.Word
|
||||
}
|
||||
} else {
|
||||
InputMode.Idle
|
||||
}
|
||||
|
||||
switchToHandler(handler)
|
||||
}
|
||||
|
||||
fun switchToHandler(inputMode: InputMode) {
|
||||
this.handler?.onFinish()
|
||||
this.mode = inputMode
|
||||
this.handler = when (inputMode) {
|
||||
InputMode.Word -> wordInputHandler
|
||||
InputMode.Letter -> letterInputHandler
|
||||
InputMode.Number -> numberInputHandler
|
||||
else -> idleInputHandler
|
||||
}.apply {
|
||||
onStart(typeClass, typeVariation, typeFlags)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectOverride(packageName: String): InputMode? {
|
||||
val numeric = context.resources.getStringArray(R.array.input_mode_numeric)
|
||||
|
||||
return if (numeric.contains(packageName)) {
|
||||
InputMode.Number
|
||||
} else null
|
||||
}
|
||||
}
|
@ -1,23 +1,10 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
import net.mezimmah.wkt9.R
|
||||
|
||||
interface InputMode {
|
||||
val mode: String
|
||||
val status: Status
|
||||
|
||||
fun onKeyDown(key: Key, composing: Boolean): KeyEventResult
|
||||
|
||||
fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult
|
||||
|
||||
fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult
|
||||
|
||||
fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult
|
||||
|
||||
fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult
|
||||
|
||||
fun packageName(packageName: String)
|
||||
|
||||
fun restart()
|
||||
enum class InputMode(val icon: Int) {
|
||||
Word(R.drawable.word_en_us_cap),
|
||||
Letter(R.drawable.alpha_en_us_cap),
|
||||
Number(R.drawable.numeric_en_us_num),
|
||||
Idle(R.drawable.wkt9)
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
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.TextInfo
|
||||
import android.view.textservice.TextServicesManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
import net.mezimmah.wkt9.dao.WordDao
|
||||
import net.mezimmah.wkt9.db.AppDatabase
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
import net.mezimmah.wkt9.exception.MissingLetterCode
|
||||
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
|
||||
import kotlin.text.StringBuilder
|
||||
|
||||
class LetterInputHandler(wkt9: WKT9Interface, context: WKT9): SpellCheckerSession.SpellCheckerSessionListener, InputHandler(wkt9, context) {
|
||||
private var db: AppDatabase
|
||||
private var wordDao: WordDao
|
||||
private var locale: Locale? = null
|
||||
private var spellCheckerSession: SpellCheckerSession? = null
|
||||
private var sentenceStart: Boolean = false
|
||||
private var wordStart: Boolean = false
|
||||
private var selectionStart: Int = 0
|
||||
|
||||
private val queryScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
|
||||
private val composing = StringBuilder()
|
||||
private val content = StringBuilder()
|
||||
|
||||
init {
|
||||
val textServiceManager = context.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE) as TextServicesManager
|
||||
mode = InputMode.Letter
|
||||
capMode = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
|
||||
db = AppDatabase.getInstance(context)
|
||||
wordDao = db.getWordDao()
|
||||
|
||||
locale = Locale.forLanguageTag("en-US")
|
||||
spellCheckerSession = textServiceManager.newSpellCheckerSession(
|
||||
null,
|
||||
locale,
|
||||
this,
|
||||
false
|
||||
)
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun capMode(key: Key) {
|
||||
super.capMode(key)
|
||||
|
||||
updateIcon()
|
||||
}
|
||||
|
||||
override fun onCandidateSelected(candidate: String) {
|
||||
wkt9.onFinishComposing()
|
||||
}
|
||||
|
||||
override fun onCommitText() {
|
||||
composing.clear()
|
||||
|
||||
val info = getCursorPositionInfo(content)
|
||||
|
||||
if (info.startSentence || info.startWord) return
|
||||
|
||||
val last = content.split("\\s".toRegex()).last()
|
||||
|
||||
if (last.length > 2) getSuggestions(last)
|
||||
}
|
||||
|
||||
override fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int) {
|
||||
val info = getCursorPositionInfo(text)
|
||||
val lastWord = content.split("\\s+".toRegex()).last()
|
||||
|
||||
if (lastWord.isNotEmpty() && (info.startSentence || info.startWord)) {
|
||||
storeWord(lastWord)
|
||||
}
|
||||
|
||||
content.replace(composingTextStart, composingTextEnd, text.toString())
|
||||
composing.replace(0, composing.length, text.toString())
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
super.onFinish()
|
||||
|
||||
wkt9.onFinishComposing()
|
||||
}
|
||||
|
||||
override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {
|
||||
val candidates = mutableListOf<String>()
|
||||
|
||||
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
|
||||
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
current = null,
|
||||
start = selectionStart,
|
||||
end = cursorPosition,
|
||||
notifyOnChange = true
|
||||
)
|
||||
}
|
||||
|
||||
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.MOVE_CURSOR -> moveCursor()
|
||||
Command.NUMBER -> triggerOriginalKeyEvent(key, true)
|
||||
Command.RECORD -> wkt9.onRecord(true)
|
||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||
Command.TRANSCRIBE -> wkt9.onTranscribe()
|
||||
else -> Log.d(tag, "Command not implemented: $command")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {
|
||||
// Get current editor content on start
|
||||
wkt9.onGetText()?.let {
|
||||
content.replace(0, content.length, it.toString())
|
||||
} ?: content.clear()
|
||||
}
|
||||
|
||||
override fun onUpdateCursorPosition(cursorPosition: Int) {
|
||||
super.onUpdateCursorPosition(cursorPosition)
|
||||
|
||||
var info: CursorPositionInfo
|
||||
|
||||
if (cursorPosition > content.length) {
|
||||
Log.d(tag, "This should not happen and is just a fail over.")
|
||||
|
||||
content.replace(0, content.length, wkt9.onGetText().toString())
|
||||
}
|
||||
|
||||
if (cursorPosition == 0) {
|
||||
info = CursorPositionInfo(
|
||||
startSentence = true,
|
||||
startWord = true
|
||||
)
|
||||
} else if (composing.isNotEmpty()) {
|
||||
info = getCursorPositionInfo(composing)
|
||||
|
||||
if (!info.startSentence && !info.startWord) {
|
||||
info = getCursorPositionInfo(content.substring(0, cursorPosition - composing.length))
|
||||
}
|
||||
} else {
|
||||
info = getCursorPositionInfo(content.substring(0, cursorPosition))
|
||||
}
|
||||
|
||||
sentenceStart = info.startSentence
|
||||
wordStart = info.startWord
|
||||
|
||||
updateIcon()
|
||||
}
|
||||
|
||||
private fun getSuggestions(text: String){
|
||||
val words = arrayOf(
|
||||
TextInfo(
|
||||
text.plus("#"), // Add hash to string to get magic performance
|
||||
0,
|
||||
text.length + 1, // We added the hash, remember
|
||||
0,
|
||||
0
|
||||
)
|
||||
)
|
||||
|
||||
selectionStart = cursorPosition - text.length
|
||||
|
||||
spellCheckerSession?.getSentenceSuggestions(words, 15)
|
||||
}
|
||||
|
||||
private fun moveCursor() {
|
||||
if (composing.isNotEmpty()) wkt9.onFinishComposing()
|
||||
}
|
||||
|
||||
private fun storeWord(text: String) {
|
||||
// We're not storing single char words...
|
||||
if (text.length < 2) return
|
||||
|
||||
val words = text.trim().split("\\s+".toRegex())
|
||||
|
||||
if (words.count() > 1) {
|
||||
words.forEach { storeWord(it) }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val codeword = keypad.getCodeForWord(text)
|
||||
val word = Word(
|
||||
word = text,
|
||||
code = codeword,
|
||||
length = text.length,
|
||||
weight = 1,
|
||||
locale = "en_US"
|
||||
)
|
||||
|
||||
queryScope.launch {
|
||||
wordDao.insert(word)
|
||||
}
|
||||
} catch (e: MissingLetterCode) {
|
||||
Log.d(tag, "Ignoring word because it contains characters unknown.")
|
||||
}
|
||||
}
|
||||
|
||||
private fun composeCharacter(key: Key, stats: KeyEventStat) {
|
||||
val layout = KeyLayout.en_US[key] ?: return
|
||||
val capitalize = Capitalize(capMode)
|
||||
val candidates = mutableListOf<String>()
|
||||
|
||||
if (stats.repeats == 0 && composing.isNotEmpty()) wkt9.onFinishComposing()
|
||||
|
||||
layout.forEach {
|
||||
candidates.add(capitalize.word(it.toString(),sentenceStart))
|
||||
}
|
||||
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
current = stats.repeats % candidates.count(),
|
||||
timeout = 400L
|
||||
)
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
if (composing.isNotEmpty()) {
|
||||
wkt9.onCancelCompose()
|
||||
content.delete(cursorPosition - composing.length, cursorPosition)
|
||||
composing.clear()
|
||||
} else if (content.isNotEmpty()) {
|
||||
content.deleteAt(content.length - 1)
|
||||
wkt9.onDeleteText(1, 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
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.alpha_en_us_upper
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) R.drawable.alpha_en_us_cap else R.drawable.alpha_en_us_lower
|
||||
else -> R.drawable.alpha_en_us_lower
|
||||
}
|
||||
|
||||
wkt9.onUpdateStatusIcon(icon)
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
||||
|
||||
class NumberInputHandler(wkt9: WKT9Interface, context: WKT9) : InputHandler(wkt9, context) {
|
||||
init {
|
||||
mode = InputMode.Number
|
||||
capMode = null
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onRunCommand(command: Command, key: Key, event: KeyEvent, stats: KeyEventStat) {
|
||||
when (command) {
|
||||
Command.CAP_MODE -> capMode(key)
|
||||
Command.DELETE -> delete()
|
||||
Command.INPUT_MODE -> inputMode(key)
|
||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||
else -> Log.d(tag, "Command not implemented: $command")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {
|
||||
wkt9.onUpdateStatusIcon(R.drawable.numeric_en_us_num)
|
||||
}
|
||||
|
||||
private fun inputMode(key: Key) {
|
||||
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Letter)
|
||||
else wkt9.onSwitchInputHandler(InputMode.Word)
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
wkt9.onDeleteText(1, 0, true)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyCommandResolver
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
|
||||
class NumericInputMode: BaseInputMode() {
|
||||
override val keyCommandResolver: KeyCommandResolver = KeyCommandResolver(
|
||||
parent = super.keyCommandResolver,
|
||||
|
||||
onLong = HashMap(mapOf(
|
||||
Key.N0 to Command.SPACE,
|
||||
Key.N1 to Command.CHARACTER,
|
||||
Key.N2 to Command.CHARACTER,
|
||||
Key.N3 to Command.CHARACTER,
|
||||
Key.N4 to Command.CHARACTER,
|
||||
Key.N5 to Command.CHARACTER,
|
||||
Key.N6 to Command.CHARACTER,
|
||||
Key.N7 to Command.CHARACTER,
|
||||
Key.N8 to Command.CHARACTER,
|
||||
Key.N9 to Command.CHARACTER
|
||||
)),
|
||||
|
||||
afterShort = HashMap(mapOf(
|
||||
Key.N0 to Command.NUMBER,
|
||||
Key.N1 to Command.NUMBER,
|
||||
Key.N2 to Command.NUMBER,
|
||||
Key.N3 to Command.NUMBER,
|
||||
Key.N4 to Command.NUMBER,
|
||||
Key.N5 to Command.NUMBER,
|
||||
Key.N6 to Command.NUMBER,
|
||||
Key.N7 to Command.NUMBER,
|
||||
Key.N8 to Command.NUMBER,
|
||||
Key.N9 to Command.NUMBER
|
||||
)),
|
||||
|
||||
onRepeat = HashMap(mapOf(
|
||||
Key.BACK to Command.HOME
|
||||
))
|
||||
)
|
||||
|
||||
init {
|
||||
mode = "numeric"
|
||||
status = Status.NUM
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
super.onKeyDown(key, composing)
|
||||
|
||||
return when(keyCommandResolver.getCommand(key)) {
|
||||
Command.BACK -> KeyEventResult(consumed = false)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
Command.LEFT -> navigateLeft()
|
||||
Command.RIGHT -> navigateRight()
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, true)) {
|
||||
Command.CHARACTER -> composeCharacter(key, composing)
|
||||
Command.SPACE -> insertSpace(composing)
|
||||
Command.SWITCH_MODE -> switchMode(WKT9InputMode.WORD, composing)
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, repeat = repeat)) {
|
||||
Command.HOME -> goHome(repeat, composing)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true)) {
|
||||
Command.BACK -> goBack(composing)
|
||||
Command.FN -> functionMode()
|
||||
Command.NUMBER -> composeNumber(key, composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true, longPress = true)) {
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertSpace(composing: Boolean): KeyEventResult {
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
finishComposing = composing,
|
||||
commit = " "
|
||||
)
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
enum class Status {
|
||||
CAP,
|
||||
UPPER,
|
||||
LOWER,
|
||||
NUM,
|
||||
NA
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
enum class WKT9InputMode {
|
||||
WORD,
|
||||
ALPHA,
|
||||
NUMERIC,
|
||||
IDLE,
|
||||
FN
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
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.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9Interface
|
||||
import net.mezimmah.wkt9.dao.SettingDao
|
||||
import net.mezimmah.wkt9.dao.WordDao
|
||||
import net.mezimmah.wkt9.db.AppDatabase
|
||||
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.t9.T9
|
||||
|
||||
class WordInputHandler(wkt9: WKT9Interface, context: WKT9) : InputHandler(wkt9, context) {
|
||||
private val content = StringBuilder()
|
||||
private val codeword = StringBuilder()
|
||||
private val composing = StringBuilder()
|
||||
|
||||
private var db: AppDatabase
|
||||
private var wordDao: WordDao
|
||||
private var settingDao: SettingDao
|
||||
|
||||
private var t9: T9
|
||||
|
||||
private val queryScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
private var queryJob: Job? = null
|
||||
|
||||
private var staleCodeword = false
|
||||
private var sentenceStart: Boolean = false
|
||||
private var wordStart: Boolean = false
|
||||
|
||||
init {
|
||||
mode = InputMode.Word
|
||||
capMode = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
|
||||
|
||||
db = AppDatabase.getInstance(context)
|
||||
wordDao = db.getWordDao()
|
||||
settingDao = db.getSettingDao()
|
||||
|
||||
t9 = T9(context, keypad, settingDao, wordDao)
|
||||
|
||||
// Todo: Hardcoded language
|
||||
t9.initializeWords("en_US")
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun capMode(key: Key) {
|
||||
super.capMode(key)
|
||||
|
||||
updateIcon()
|
||||
|
||||
if (codeword.isNotEmpty()) handleCodewordChange(codeword)
|
||||
}
|
||||
|
||||
override fun onCommitText() {
|
||||
if (codeword.isNotEmpty()) increaseWordWeight(composing.toString())
|
||||
|
||||
clearCodeword()
|
||||
}
|
||||
|
||||
override fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int) {
|
||||
content.replace(composingTextStart, composingTextEnd, text.toString())
|
||||
composing.replace(0, composing.length, text.toString())
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
if (composing.isNotEmpty()) wkt9.onFinishComposing()
|
||||
}
|
||||
|
||||
override fun onLongClickCandidate(text: String) {
|
||||
ioScope.launch {
|
||||
wordDao.delete(text, "en_US")
|
||||
|
||||
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.INPUT_MODE -> inputMode(key)
|
||||
Command.MOVE_CURSOR -> moveCursor()
|
||||
Command.NUMBER -> triggerOriginalKeyEvent(key, true)
|
||||
Command.RECORD -> wkt9.onRecord(true)
|
||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||
Command.TRANSCRIBE -> wkt9.onTranscribe()
|
||||
else -> Log.d(tag, "Command not implemented: $command")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {
|
||||
// Get current editor content on start
|
||||
wkt9.onGetText()?.let {
|
||||
content.replace(0, content.length, it.toString())
|
||||
} ?: content.clear()
|
||||
}
|
||||
|
||||
override fun onUpdateCursorPosition(cursorPosition: Int) {
|
||||
super.onUpdateCursorPosition(cursorPosition)
|
||||
|
||||
if (cursorPosition > content.length) {
|
||||
Log.d(tag, "This should not happen and is just a fail over.")
|
||||
|
||||
content.replace(0, content.length, wkt9.onGetText().toString())
|
||||
}
|
||||
|
||||
var info: CursorPositionInfo
|
||||
|
||||
if (cursorPosition == 0) {
|
||||
info = CursorPositionInfo(
|
||||
startSentence = true,
|
||||
startWord = true
|
||||
)
|
||||
} else if (composing.isNotEmpty()) {
|
||||
info = getCursorPositionInfo(composing)
|
||||
|
||||
if (!info.startSentence && !info.startWord) {
|
||||
info = getCursorPositionInfo(content.substring(0, cursorPosition - composing.length))
|
||||
}
|
||||
} else {
|
||||
info = getCursorPositionInfo(content.substring(0, cursorPosition))
|
||||
}
|
||||
|
||||
sentenceStart = info.startSentence
|
||||
wordStart = info.startWord
|
||||
|
||||
updateIcon()
|
||||
}
|
||||
|
||||
private fun buildCodeword(key: Key) {
|
||||
// Don't build fruitless codeword
|
||||
if (staleCodeword) return
|
||||
|
||||
val code = KeyLayout.numeric[key]
|
||||
|
||||
/**
|
||||
* This happens when some other method than buildCodeword composed text. With, for example,
|
||||
* finalizeWordOrSentence.
|
||||
*/
|
||||
if (codeword.isEmpty()) wkt9.onFinishComposing()
|
||||
|
||||
codeword.append(code)
|
||||
|
||||
handleCodewordChange(codeword)
|
||||
}
|
||||
|
||||
private fun clearCodeword() {
|
||||
codeword.clear()
|
||||
composing.clear()
|
||||
|
||||
staleCodeword = false
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
staleCodeword = false
|
||||
|
||||
if (codeword.length > 1) {
|
||||
codeword.deleteAt(codeword.length - 1)
|
||||
|
||||
handleCodewordChange(codeword)
|
||||
} else if (codeword.isNotEmpty()) {
|
||||
clearCodeword()
|
||||
wkt9.onCancelCompose()
|
||||
content.deleteAt(content.length - 1)
|
||||
} else if (composing.isNotEmpty()) {
|
||||
wkt9.onCancelCompose()
|
||||
content.delete(cursorPosition - composing.length, cursorPosition)
|
||||
composing.clear()
|
||||
} else if (content.isNotEmpty()) {
|
||||
content.deleteAt(content.length - 1)
|
||||
wkt9.onDeleteText(1, 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCodewordChange(codeword: StringBuilder) {
|
||||
queryJob?.cancel()
|
||||
|
||||
queryJob = queryScope.launch {
|
||||
val candidates = queryT9Candidates(codeword, 25)
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
staleCodeword = true
|
||||
|
||||
queryJob?.cancel()
|
||||
wkt9.onClearCandidates()
|
||||
} else {
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
current = 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun increaseWordWeight(word: String) {
|
||||
queryScope.launch {
|
||||
wordDao.increaseWeight(word)
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputMode(key: Key) {
|
||||
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Number)
|
||||
else wkt9.onSwitchInputHandler(InputMode.Letter)
|
||||
}
|
||||
|
||||
private fun moveCursor() {
|
||||
if (composing.isEmpty()) return
|
||||
|
||||
wkt9.onFinishComposing()
|
||||
wkt9.onClearCandidates()
|
||||
}
|
||||
|
||||
private fun updateIcon() {
|
||||
val icon = when (capMode) {
|
||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS -> R.drawable.word_en_us_upper
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES -> if (wordStart) R.drawable.word_en_us_cap else R.drawable.word_en_us_lower
|
||||
else -> R.drawable.word_en_us_lower
|
||||
}
|
||||
|
||||
wkt9.onUpdateStatusIcon(icon)
|
||||
}
|
||||
|
||||
private suspend fun queryT9Candidates(codeWord: StringBuilder, limit: Int = 10): List<String> {
|
||||
val results = wordDao.findCandidates(codeWord.toString(), limit)
|
||||
val capitalize = Capitalize(capMode)
|
||||
val candidates = mutableListOf<String>()
|
||||
|
||||
results.forEach { result ->
|
||||
candidates.add(capitalize.word(result.word, sentenceStart))
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.keypad.Command
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
||||
import net.mezimmah.wkt9.keypad.KeyLayout
|
||||
import java.lang.StringBuilder
|
||||
|
||||
class WordInputMode: BaseInputMode() {
|
||||
private val codeWord = StringBuilder()
|
||||
|
||||
init {
|
||||
mode = "word"
|
||||
status = Status.CAP
|
||||
|
||||
Log.d(tag, "Started $mode input mode.")
|
||||
}
|
||||
|
||||
override fun onKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
super.onKeyDown(key, composing)
|
||||
|
||||
return when(keyCommandResolver.getCommand(key)) {
|
||||
Command.BACK -> KeyEventResult(consumed = false)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
Command.LEFT -> navigateLeft()
|
||||
Command.RIGHT -> navigateRight()
|
||||
Command.SELECT -> focus()
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyLongDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, true)) {
|
||||
Command.RECORD -> record(composing)
|
||||
Command.SWITCH_MODE -> switchMode(WKT9InputMode.ALPHA, composing)
|
||||
Command.NUMBER -> commitNumber(key, composing)
|
||||
else -> KeyEventResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDownRepeatedly(key: Key, repeat: Int, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, repeat = repeat)) {
|
||||
Command.HOME -> goHome(repeat, composing)
|
||||
Command.DELETE -> deleteCharacter(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyDown(key: Key, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true)) {
|
||||
Command.BACK -> goBack(composing)
|
||||
Command.CHARACTER -> buildCodeWord(key, composing)
|
||||
Command.FN -> functionMode()
|
||||
Command.SHIFT_MODE -> shiftMode(composing)
|
||||
Command.SPACE -> finalizeWordOrSentence(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterKeyLongDown(key: Key, keyDownMS: Long, composing: Boolean): KeyEventResult {
|
||||
return when(keyCommandResolver.getCommand(key, after = true, longPress = true)) {
|
||||
Command.TRANSCRIBE -> transcribe(composing)
|
||||
else -> KeyEventResult()
|
||||
}
|
||||
}
|
||||
|
||||
override fun restart() {
|
||||
reset()
|
||||
}
|
||||
|
||||
override fun commitNumber(key: Key, composing: Boolean): KeyEventResult {
|
||||
codeWord.clear()
|
||||
|
||||
return super.commitNumber(key, composing)
|
||||
}
|
||||
|
||||
override fun deleteCharacter(composing: Boolean): KeyEventResult {
|
||||
return if (codeWord.length > 1) {
|
||||
codeWord.deleteAt(codeWord.length - 1)
|
||||
|
||||
KeyEventResult(
|
||||
codeWord = codeWord
|
||||
)
|
||||
} else {
|
||||
codeWord.clear()
|
||||
|
||||
super.deleteCharacter(composing)
|
||||
}
|
||||
}
|
||||
|
||||
override fun finalizeWordOrSentence(composing: Boolean): KeyEventResult {
|
||||
codeWord.clear()
|
||||
|
||||
return super.finalizeWordOrSentence(composing)
|
||||
}
|
||||
|
||||
override fun goBack(composing: Boolean): KeyEventResult {
|
||||
reset()
|
||||
|
||||
return super.goBack(composing)
|
||||
}
|
||||
|
||||
override fun goHome(repeat: Int, composing: Boolean): KeyEventResult {
|
||||
reset()
|
||||
|
||||
return super.goHome(repeat, composing)
|
||||
}
|
||||
|
||||
override fun record(composing: Boolean): KeyEventResult {
|
||||
codeWord.clear()
|
||||
|
||||
return super.record(composing)
|
||||
}
|
||||
|
||||
override fun switchMode(mode: WKT9InputMode, composing: Boolean): KeyEventResult {
|
||||
reset()
|
||||
|
||||
return super.switchMode(mode, composing)
|
||||
}
|
||||
|
||||
private fun buildCodeWord(key: Key, composing: Boolean): KeyEventResult {
|
||||
val startComposing = codeWord.isEmpty()
|
||||
val code = KeyLayout.numeric[key]
|
||||
|
||||
codeWord.append(code)
|
||||
|
||||
return KeyEventResult(
|
||||
codeWord = codeWord,
|
||||
finishComposing = startComposing && composing,
|
||||
startComposing = startComposing
|
||||
)
|
||||
}
|
||||
|
||||
private fun reset() {
|
||||
codeWord.clear()
|
||||
|
||||
newKey = true
|
||||
keyIndex = 0
|
||||
lastKey = null
|
||||
}
|
||||
|
||||
private fun shiftMode(composing: Boolean): KeyEventResult {
|
||||
if (!composing) {
|
||||
status = when(status) {
|
||||
Status.CAP -> Status.UPPER
|
||||
Status.UPPER -> Status.LOWER
|
||||
else -> Status.CAP
|
||||
}
|
||||
}
|
||||
|
||||
return KeyEventResult(
|
||||
consumed = true,
|
||||
updateInputStatus = !composing,
|
||||
updateWordStatus = composing
|
||||
)
|
||||
}
|
||||
}
|
@ -1,24 +1,15 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
enum class Command {
|
||||
CAMERA,
|
||||
CAP_MODE,
|
||||
CHARACTER,
|
||||
NUMBER,
|
||||
SPACE,
|
||||
NEWLINE,
|
||||
DELETE,
|
||||
SELECT,
|
||||
SHIFT_MODE,
|
||||
SWITCH_MODE,
|
||||
NAVIGATE,
|
||||
RIGHT,
|
||||
LEFT,
|
||||
VOL_UP,
|
||||
VOL_DOWN,
|
||||
BRIGHTNESS_DOWN,
|
||||
BRIGHTNESS_UP,
|
||||
DIAL,
|
||||
INPUT_MODE,
|
||||
MOVE_CURSOR,
|
||||
NUMBER,
|
||||
RECORD,
|
||||
TRANSCRIBE,
|
||||
BACK,
|
||||
HOME,
|
||||
FN
|
||||
SPACE,
|
||||
TRANSCRIBE
|
||||
}
|
15
app/src/main/java/net/mezimmah/wkt9/keypad/CommandMapping.kt
Normal file
15
app/src/main/java/net/mezimmah/wkt9/keypad/CommandMapping.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
|
||||
data class CommandMapping(
|
||||
val events: List<Event>? = null,
|
||||
val inputModes: List<InputMode>? = null,
|
||||
val packageNames: List<String>? = null,
|
||||
val alt: Boolean = false,
|
||||
val ctrl: Boolean = false,
|
||||
val repeatCount: Int? = null,
|
||||
val overrideConsume: Boolean = false,
|
||||
val consume: Boolean? = null,
|
||||
val command: Command? = null,
|
||||
)
|
9
app/src/main/java/net/mezimmah/wkt9/keypad/Event.java
Normal file
9
app/src/main/java/net/mezimmah/wkt9/keypad/Event.java
Normal file
@ -0,0 +1,9 @@
|
||||
package net.mezimmah.wkt9.keypad;
|
||||
|
||||
public enum Event {
|
||||
keyDown,
|
||||
keyLongDown,
|
||||
keyDownRepeat,
|
||||
afterShortDown,
|
||||
afterLongDown
|
||||
}
|
@ -1,24 +1,384 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
enum class Key() {
|
||||
N0,
|
||||
N1,
|
||||
N2,
|
||||
N3,
|
||||
N4,
|
||||
N5,
|
||||
N6,
|
||||
N7,
|
||||
N8,
|
||||
N9,
|
||||
FN,
|
||||
STAR,
|
||||
POUND,
|
||||
UP,
|
||||
DOWN,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
SELECT,
|
||||
DELETE,
|
||||
BACK,
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
|
||||
enum class Key(
|
||||
val keyCode: Int,
|
||||
val consume: Boolean?,
|
||||
val mappings: Mappings
|
||||
) {
|
||||
B1(KeyEvent.KEYCODE_BUTTON_1, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Number, InputMode.Word),
|
||||
ctrl = true,
|
||||
command = Command.INPUT_MODE
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
B2(KeyEvent.KEYCODE_BUTTON_2, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Number, InputMode.Word),
|
||||
ctrl = true,
|
||||
command = Command.CAP_MODE
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
B3(KeyEvent.KEYCODE_BUTTON_3, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Number, InputMode.Word),
|
||||
ctrl = true,
|
||||
command = Command.CAP_MODE
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
B4(KeyEvent.KEYCODE_BUTTON_4, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Number, InputMode.Word),
|
||||
ctrl = true,
|
||||
command = Command.INPUT_MODE
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
CALL(KeyEvent.KEYCODE_CALL, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
command = Command.DIAL
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyLongDown),
|
||||
listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.RECORD
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterLongDown),
|
||||
listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.TRANSCRIBE
|
||||
),
|
||||
)
|
||||
)),
|
||||
|
||||
N0(KeyEvent.KEYCODE_0, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.SPACE
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N1(KeyEvent.KEYCODE_1, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N2(KeyEvent.KEYCODE_2, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N3(KeyEvent.KEYCODE_3, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N4(KeyEvent.KEYCODE_4, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N5(KeyEvent.KEYCODE_5, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N6(KeyEvent.KEYCODE_6, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N7(KeyEvent.KEYCODE_7, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N8(KeyEvent.KEYCODE_8, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
N9(KeyEvent.KEYCODE_9, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.CHARACTER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.NUMBER,
|
||||
repeatCount = 2
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
DELETE(KeyEvent.KEYCODE_DEL, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown, Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Word, InputMode.Letter, InputMode.Number),
|
||||
command = Command.DELETE
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
UP(KeyEvent.KEYCODE_DPAD_UP, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown, Event.afterLongDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.MOVE_CURSOR
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
DOWN(KeyEvent.KEYCODE_DPAD_DOWN, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown, Event.afterLongDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.MOVE_CURSOR
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
LEFT(KeyEvent.KEYCODE_DPAD_LEFT, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown, Event.afterLongDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.MOVE_CURSOR
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
RIGHT(KeyEvent.KEYCODE_DPAD_RIGHT, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown, Event.afterLongDown),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.MOVE_CURSOR
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
ENTER(KeyEvent.KEYCODE_ENTER, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Idle),
|
||||
packageNames = listOf("com.android.camera2"),
|
||||
command = Command.CAMERA,
|
||||
overrideConsume = true,
|
||||
consume = true
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
companion object {
|
||||
private val map = Key.values().associateBy(Key::keyCode)
|
||||
|
||||
fun fromKeyCode(keyCode: Int) = map[keyCode]
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
import java.util.Properties
|
||||
|
||||
class KeyCodeMapping(
|
||||
private val keyMap: Map<Int, Key>,
|
||||
) {
|
||||
|
||||
fun key(keyCode: Int): Key? {
|
||||
return keyMap[keyCode]
|
||||
}
|
||||
|
||||
companion object {
|
||||
val basic = mapOf(
|
||||
4 to Key.BACK,
|
||||
7 to Key.N0,
|
||||
8 to Key.N1,
|
||||
9 to Key.N2,
|
||||
10 to Key.N3,
|
||||
11 to Key.N4,
|
||||
12 to Key.N5,
|
||||
13 to Key.N6,
|
||||
14 to Key.N7,
|
||||
15 to Key.N8,
|
||||
16 to Key.N9,
|
||||
17 to Key.STAR,
|
||||
18 to Key.POUND,
|
||||
82 to Key.FN,
|
||||
19 to Key.UP,
|
||||
20 to Key.DOWN,
|
||||
21 to Key.LEFT,
|
||||
22 to Key.RIGHT,
|
||||
23 to Key.SELECT
|
||||
)
|
||||
|
||||
fun fromProperties(props: Properties): KeyCodeMapping {
|
||||
val keyMap = HashMap<Int, Key>()
|
||||
|
||||
this.basic.forEach {
|
||||
val keyCode = props.getProperty("key.${it.value.name}")?.toInt() ?: it.key
|
||||
keyMap[keyCode] = it.value
|
||||
}
|
||||
|
||||
return KeyCodeMapping(keyMap)
|
||||
}
|
||||
|
||||
fun default(): KeyCodeMapping {
|
||||
return KeyCodeMapping(basic)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
class KeyCommandResolver (
|
||||
private val onShort: HashMap<Key, Command> = HashMap(mapOf()),
|
||||
private val onLong: HashMap<Key, Command> = HashMap(mapOf()),
|
||||
private val afterShort: HashMap<Key, Command> = HashMap(mapOf()),
|
||||
private val afterLong: HashMap<Key, Command> = HashMap(mapOf()),
|
||||
private val onRepeat: HashMap<Key, Command> = HashMap(mapOf()),
|
||||
private val parent: KeyCommandResolver? = null
|
||||
) {
|
||||
fun getCommand(key: Key, longPress: Boolean = false, after: Boolean = false, repeat: Int = 0): Command? {
|
||||
val command = when {
|
||||
repeat > 0 -> onRepeat[key]
|
||||
(longPress && after) -> afterLong[key]
|
||||
(longPress) -> onLong[key]
|
||||
(after) -> afterShort[key]
|
||||
else -> onShort[key]
|
||||
}
|
||||
|
||||
return when (command) {
|
||||
null -> parent?.getCommand(key, longPress, after)
|
||||
else -> command
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getBasic(): KeyCommandResolver {
|
||||
return KeyCommandResolver(
|
||||
onShort = HashMap(mapOf(
|
||||
Key.BACK to Command.BACK,
|
||||
|
||||
Key.LEFT to Command.LEFT,
|
||||
Key.RIGHT to Command.RIGHT,
|
||||
Key.UP to Command.NAVIGATE,
|
||||
Key.DOWN to Command.NAVIGATE,
|
||||
|
||||
Key.STAR to Command.DELETE,
|
||||
Key.SELECT to Command.SELECT,
|
||||
|
||||
Key.FN to Command.FN
|
||||
)),
|
||||
|
||||
onLong = HashMap(mapOf(
|
||||
Key.N0 to Command.NUMBER,
|
||||
Key.N1 to Command.NUMBER,
|
||||
Key.N2 to Command.NUMBER,
|
||||
Key.N3 to Command.NUMBER,
|
||||
Key.N4 to Command.NUMBER,
|
||||
Key.N5 to Command.NUMBER,
|
||||
Key.N6 to Command.NUMBER,
|
||||
Key.N7 to Command.NUMBER,
|
||||
Key.N8 to Command.NUMBER,
|
||||
Key.N9 to Command.NUMBER,
|
||||
|
||||
Key.POUND to Command.SWITCH_MODE,
|
||||
Key.SELECT to Command.RECORD
|
||||
)),
|
||||
|
||||
afterShort = HashMap(mapOf(
|
||||
Key.N0 to Command.SPACE,
|
||||
|
||||
Key.N1 to Command.CHARACTER,
|
||||
Key.N2 to Command.CHARACTER,
|
||||
Key.N3 to Command.CHARACTER,
|
||||
Key.N4 to Command.CHARACTER,
|
||||
Key.N5 to Command.CHARACTER,
|
||||
Key.N6 to Command.CHARACTER,
|
||||
Key.N7 to Command.CHARACTER,
|
||||
Key.N8 to Command.CHARACTER,
|
||||
Key.N9 to Command.CHARACTER,
|
||||
|
||||
Key.BACK to Command.BACK,
|
||||
Key.POUND to Command.SHIFT_MODE,
|
||||
|
||||
Key.FN to Command.FN
|
||||
)),
|
||||
|
||||
afterLong = HashMap(mapOf(
|
||||
Key.SELECT to Command.TRANSCRIBE,
|
||||
)),
|
||||
|
||||
onRepeat = HashMap(mapOf(
|
||||
Key.BACK to Command.HOME,
|
||||
Key.STAR to Command.DELETE,
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.inputmode.WKT9InputMode
|
||||
import java.lang.StringBuilder
|
||||
|
||||
data class KeyEventResult(
|
||||
val consumed: Boolean = true,
|
||||
val finishComposing: Boolean = false,
|
||||
val startComposing: Boolean = false,
|
||||
val increaseWeight: Boolean = false,
|
||||
val codeWord: StringBuilder? = null,
|
||||
val candidates: List<String>? = null,
|
||||
val commit: String? = null,
|
||||
val timeout: Int? = null,
|
||||
val deleteBeforeCursor: Int = 0,
|
||||
val deleteAfterCursor: Int = 0,
|
||||
val goHome: Boolean = false,
|
||||
val left: Boolean = false,
|
||||
val right: Boolean = false,
|
||||
val record: Boolean = false,
|
||||
val transcribe: Boolean = false,
|
||||
val updateInputStatus: Boolean = false,
|
||||
val updateWordStatus: Boolean = false,
|
||||
val focus: Boolean = false,
|
||||
val switchInputMode: WKT9InputMode? = null,
|
||||
val toggleFunctionMode: Boolean = false,
|
||||
val increaseVolume: Boolean = false,
|
||||
val decreaseVolume: Boolean = false,
|
||||
val increaseBrightness: Boolean = false,
|
||||
val decreaseBrightness: Boolean = false,
|
||||
val keyEvent: KeyEvent? = null
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
data class KeyEventStat(
|
||||
var keyCode: Int,
|
||||
var repeats: Int = 0
|
||||
)
|
@ -24,10 +24,6 @@ object KeyLayout {
|
||||
Key.N6 to listOf('m','n','o','ö','ø','ò','ó','ô','õ','õ'),
|
||||
Key.N7 to listOf('p','q','r','s','ß','$'),
|
||||
Key.N8 to listOf('t','u','v','ù','ú','û','ü'),
|
||||
Key.N9 to listOf('w','x','y','z','ý','þ'),
|
||||
Key.STAR to listOf('*'),
|
||||
Key.POUND to listOf('#'),
|
||||
Key.N9 to listOf('w','x','y','z','ý','þ')
|
||||
)
|
||||
|
||||
val nonAlphaNumeric = setOf('*','#','\'','"','.','?','!',',','-','@','$','/','%',':','(',')')
|
||||
}
|
@ -1,21 +1,16 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
import android.util.Log
|
||||
import net.mezimmah.wkt9.exception.MissingLetterCode
|
||||
import java.lang.StringBuilder
|
||||
|
||||
class Keypad(
|
||||
private val keyCodeMapping: KeyCodeMapping,
|
||||
private val letterLayout: Map<Key, List<Char>>,
|
||||
|
||||
numericLayout: Map<Key, Int>
|
||||
) {
|
||||
private val tag = "WKT9"
|
||||
private val letterCodeMap: MutableMap<Char, Int> = mutableMapOf()
|
||||
|
||||
init {
|
||||
Log.d(tag, "Keypad")
|
||||
|
||||
numericLayout.forEach { (key, code) ->
|
||||
indexKeyLetters(key, code)
|
||||
}
|
||||
@ -27,10 +22,6 @@ class Keypad(
|
||||
}
|
||||
}
|
||||
|
||||
fun getKey(code: Int): Key? {
|
||||
return keyCodeMapping.key(code)
|
||||
}
|
||||
|
||||
fun getCodeForWord(word: String): String {
|
||||
val builder = StringBuilder()
|
||||
val normalized = word.lowercase()
|
||||
@ -44,7 +35,7 @@ class Keypad(
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
fun codeForLetter(letter: Char): Int? {
|
||||
private fun codeForLetter(letter: Char): Int? {
|
||||
return letterCodeMap[letter]
|
||||
}
|
||||
}
|
40
app/src/main/java/net/mezimmah/wkt9/keypad/Mappings.kt
Normal file
40
app/src/main/java/net/mezimmah/wkt9/keypad/Mappings.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package net.mezimmah.wkt9.keypad
|
||||
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
|
||||
class Mappings(private val mappings: List<CommandMapping>) {
|
||||
fun match(
|
||||
event: Event,
|
||||
inputMode: InputMode,
|
||||
packageName: String,
|
||||
alt: Boolean = false,
|
||||
ctrl: Boolean = false,
|
||||
repeatCount: Int = 0,
|
||||
): MutableList<CommandMapping>? {
|
||||
val commands = mutableListOf<CommandMapping>()
|
||||
|
||||
mappings.forEach {
|
||||
if (
|
||||
((it.events == null) || it.events.contains(event)) &&
|
||||
((it.inputModes == null) || it.inputModes.contains(inputMode)) &&
|
||||
((it.packageNames == null) || it.packageNames.contains(packageName)) &&
|
||||
(it.alt == alt) &&
|
||||
(it.ctrl == ctrl) &&
|
||||
((it.repeatCount == null) || (it.repeatCount == repeatCount))
|
||||
) commands.add(it)
|
||||
}
|
||||
|
||||
return if (commands.isEmpty()) null else commands
|
||||
}
|
||||
|
||||
fun hasLongDownMapping(inputMode: InputMode): Boolean {
|
||||
mappings.forEach {
|
||||
if (
|
||||
((it.events == null) || it.events.contains(Event.keyLongDown)) &&
|
||||
((it.inputModes == null) || it.inputModes.contains(inputMode))
|
||||
) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@ -3,7 +3,9 @@ package net.mezimmah.wkt9.preferences
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
@ -12,13 +14,12 @@ import net.mezimmah.wkt9.R
|
||||
|
||||
class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private var key: CharSequence? = null
|
||||
private val tag = "WKT9"
|
||||
private val requestPermissionLauncher = registerForActivityResult(RequestMultiplePermissions()) { isGranted: Map<String, Boolean> ->
|
||||
// If any permission got denied we programmatically disable the option
|
||||
if (isGranted.containsValue(false)) {
|
||||
val key = getString(R.string.preference_setting_speech_to_text_key)
|
||||
|
||||
findPreference<SwitchPreference>(key)?.isChecked = false
|
||||
findPreference<SwitchPreference>(this.key!!)?.isChecked = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,6 +33,14 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
super.onResume()
|
||||
|
||||
preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay_key))?.isChecked = Settings.canDrawOverlays(context)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay_key))?.isChecked = Settings.canDrawOverlays(context)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@ -41,8 +50,10 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(p0: SharedPreferences?, key: String?) {
|
||||
this.key = key
|
||||
|
||||
when (key) {
|
||||
getString(R.string.preference_setting_speech_to_text_key) -> {
|
||||
getString(R.string.speech_to_text_key) -> {
|
||||
if (findPreference<SwitchPreference>(key)?.isChecked == true) {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
arrayOf(
|
||||
@ -54,6 +65,12 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
requestPermissionLauncher.launch(permissions)
|
||||
}
|
||||
}
|
||||
|
||||
getString(R.string.overlay_key) -> {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
|
||||
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.mezimmah.wkt9.t9
|
||||
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -90,7 +91,11 @@ class T9 (
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
wordDao.insert(*wordBatch.toTypedArray())
|
||||
try {
|
||||
wordDao.insert(*wordBatch.toTypedArray())
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
Log.d(tag, "Oh, Brother!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
package net.mezimmah.wkt9.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
|
||||
class Suggestions(context: Context, attrs: AttributeSet): RelativeLayout(context, attrs) {
|
||||
private val tag = "WKT9"
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
android:id="@+id/suggestion_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/yellow_radius"
|
||||
android:background="@drawable/button_radius"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:minWidth="40dp"
|
||||
android:paddingVertical="5dp"
|
||||
|
@ -9,12 +9,11 @@
|
||||
<TextView
|
||||
android:id="@+id/suggestion_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/blue_radius"
|
||||
android:layout_height="match_parent"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:minWidth="40dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="500" />
|
||||
android:textFontWeight="400" />
|
||||
</LinearLayout>
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<RelativeLayout
|
||||
<HorizontalScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@ -10,52 +10,10 @@
|
||||
android:background="@color/black"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<HorizontalScrollView
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<LinearLayout
|
||||
android:background="@color/black"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="3dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_radius"
|
||||
android:textColor="@color/button_text"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="600"
|
||||
android:text="En" />
|
||||
|
||||
<View
|
||||
android:layout_marginHorizontal="4dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/suggestion_text"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
</RelativeLayout>
|
||||
</HorizontalScrollView>
|
16
app/src/main/res/layout/symbols.xml
Normal file
16
app/src/main/res/layout/symbols.xml
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvContacts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -5,11 +5,11 @@
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="black">#CC000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="button">#637783</color>
|
||||
<color name="button">#CC336699</color>
|
||||
<color name="button_text">#FFFCF0</color>
|
||||
<color name="suggestion">#C1E8FF</color>
|
||||
<color name="current_suggestion">#FFE3C1</color>
|
||||
<color name="suggestion_text">#424242</color>
|
||||
<color name="suggestion">#FFFFFFFF</color>
|
||||
<color name="current_suggestion">#FFFFFFFF</color>
|
||||
<color name="suggestion_text">#FFFFFFFF</color>
|
||||
</resources>
|
@ -3,17 +3,27 @@
|
||||
|
||||
<string name="app_preferences_name">WKT9 Preferences</string>
|
||||
|
||||
<string name="preference_category_speech_to_text_name">Speech to Text</string>
|
||||
<!-- Preference categories -->
|
||||
<string name="speech_to_text_cat">Speech to Text</string>
|
||||
<string name="overlay_cat">Speech to Text</string>
|
||||
|
||||
<string name="preference_setting_speech_to_text_key">speech_to_text</string>
|
||||
<string name="preference_setting_speech_to_text_title">Enable Speech to Text</string>
|
||||
<string name="preference_setting_speech_to_text_summary">For this feature to work net.mezimmah.wkt9.WKT9 needs permission to show notifications and record audio. You will be asked to grant these permissions if you haven\'t already permitted it.</string>
|
||||
<string name="speech_to_text_key">speech_to_text</string>
|
||||
<string name="speech_to_text_title">Enable Speech to Text</string>
|
||||
<string name="speech_to_text_summary">For this feature to work WKT9 needs permission to show notifications and record audio. You will be asked to grant these permissions if you haven\'t already granted it.</string>
|
||||
|
||||
<string name="preference_setting_whisper_url_key">whisper_url</string>
|
||||
<string name="preference_setting_whisper_url_title">Whisper Server URL</string>
|
||||
<string name="preference_setting_whisper_url_summary">Provide an URL to the Whisper server.</string>
|
||||
<string name="whisper_url_key">whisper_url</string>
|
||||
<string name="whisper_url_title">Whisper Server URL</string>
|
||||
<string name="whisper_url_summary">Provide an URL to the Whisper server.</string>
|
||||
|
||||
<string name="overlay_key">Draw over other activities</string>
|
||||
<string name="overlay_title">Draw over activities</string>
|
||||
<string name="overlay_summary">Grant WKT9 permission to draw over other applications</string>
|
||||
|
||||
<string-array name="input_mode_numeric">
|
||||
<item>org.linphone</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="camera_apps">
|
||||
<item>com.android.camera2</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -3,17 +3,26 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/preference_category_speech_to_text_name" />
|
||||
app:title="@string/speech_to_text_cat" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/preference_setting_speech_to_text_key"
|
||||
app:title="@string/preference_setting_speech_to_text_title"
|
||||
app:summary="@string/preference_setting_speech_to_text_summary" />
|
||||
app:key="@string/speech_to_text_key"
|
||||
app:title="@string/speech_to_text_title"
|
||||
app:summary="@string/speech_to_text_summary" />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="@string/preference_setting_whisper_url_key"
|
||||
app:title="@string/preference_setting_whisper_url_title"
|
||||
app:summary="@string/preference_setting_whisper_url_summary"
|
||||
app:dependency="@string/preference_setting_speech_to_text_key" />
|
||||
app:key="@string/whisper_url_key"
|
||||
app:title="@string/whisper_url_title"
|
||||
app:summary="@string/whisper_url_summary"
|
||||
app:dependency="@string/speech_to_text_key" />
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/overlay_cat" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/overlay_key"
|
||||
app:title="@string/overlay_title"
|
||||
app:summary="@string/overlay_summary" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
||||
|
Loading…
x
Reference in New Issue
Block a user