Refactored
This commit is contained in:
parent
3b5b77c540
commit
9d5c6364f0
@ -39,11 +39,12 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.core:core-ktx:1.12.0")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
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-common:2.5.2")
|
||||||
implementation("androidx.room:room-ktx:2.5.2")
|
implementation("androidx.room:room-ktx:2.5.2")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.10")
|
implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.10")
|
||||||
|
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
@ -23,8 +27,11 @@
|
|||||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.view.InputMethod" />
|
<action android:name="android.view.InputMethod" />
|
||||||
|
<category android:name="android.intent.category.HOME" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<meta-data android:name="android.view.im" android:resource="@xml/method" />
|
<meta-data android:name="android.view.im" android:resource="@xml/method" />
|
||||||
</service>
|
</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
|
package net.mezimmah.wkt9.dao
|
||||||
|
|
||||||
import androidx.room.Dao
|
import androidx.room.Dao
|
||||||
import androidx.room.Delete
|
|
||||||
import androidx.room.Insert
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import net.mezimmah.wkt9.entity.Word
|
import net.mezimmah.wkt9.entity.Word
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface WordDao {
|
interface WordDao {
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
suspend fun insert(vararg words: Word)
|
suspend fun insert(vararg words: Word)
|
||||||
|
|
||||||
@Delete
|
@Query("DELETE FROM word WHERE word = :word AND locale = :locale")
|
||||||
fun delete(word: Word)
|
fun delete(word: String, locale: String)
|
||||||
|
|
||||||
@Query("SELECT * FROM word")
|
|
||||||
fun getAll(): List<Word>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM word WHERE code LIKE :code || '%' " +
|
@Query("SELECT * FROM word WHERE code LIKE :code || '%' " +
|
||||||
"ORDER BY length, weight DESC LIMIT :limit")
|
"ORDER BY length, weight DESC LIMIT :limit")
|
||||||
|
@ -4,7 +4,7 @@ import androidx.room.Entity
|
|||||||
import androidx.room.Index
|
import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
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(
|
data class Word(
|
||||||
var word: String,
|
var word: String,
|
||||||
var code: 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
|
package net.mezimmah.wkt9.inputmode
|
||||||
|
|
||||||
import net.mezimmah.wkt9.keypad.Key
|
import net.mezimmah.wkt9.R
|
||||||
import net.mezimmah.wkt9.keypad.KeyEventResult
|
|
||||||
|
|
||||||
interface InputMode {
|
enum class InputMode(val icon: Int) {
|
||||||
val mode: String
|
Word(R.drawable.word_en_us_cap),
|
||||||
val status: Status
|
Letter(R.drawable.alpha_en_us_cap),
|
||||||
|
Number(R.drawable.numeric_en_us_num),
|
||||||
fun onKeyDown(key: Key, composing: Boolean): KeyEventResult
|
Idle(R.drawable.wkt9)
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
@ -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
|
package net.mezimmah.wkt9.keypad
|
||||||
|
|
||||||
enum class Command {
|
enum class Command {
|
||||||
|
CAMERA,
|
||||||
|
CAP_MODE,
|
||||||
CHARACTER,
|
CHARACTER,
|
||||||
NUMBER,
|
|
||||||
SPACE,
|
|
||||||
NEWLINE,
|
|
||||||
DELETE,
|
DELETE,
|
||||||
SELECT,
|
DIAL,
|
||||||
SHIFT_MODE,
|
INPUT_MODE,
|
||||||
SWITCH_MODE,
|
MOVE_CURSOR,
|
||||||
NAVIGATE,
|
NUMBER,
|
||||||
RIGHT,
|
|
||||||
LEFT,
|
|
||||||
VOL_UP,
|
|
||||||
VOL_DOWN,
|
|
||||||
BRIGHTNESS_DOWN,
|
|
||||||
BRIGHTNESS_UP,
|
|
||||||
RECORD,
|
RECORD,
|
||||||
TRANSCRIBE,
|
SPACE,
|
||||||
BACK,
|
TRANSCRIBE
|
||||||
HOME,
|
|
||||||
FN
|
|
||||||
}
|
}
|
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
|
package net.mezimmah.wkt9.keypad
|
||||||
|
|
||||||
enum class Key() {
|
import android.view.KeyEvent
|
||||||
N0,
|
import net.mezimmah.wkt9.inputmode.InputMode
|
||||||
N1,
|
|
||||||
N2,
|
enum class Key(
|
||||||
N3,
|
val keyCode: Int,
|
||||||
N4,
|
val consume: Boolean?,
|
||||||
N5,
|
val mappings: Mappings
|
||||||
N6,
|
) {
|
||||||
N7,
|
B1(KeyEvent.KEYCODE_BUTTON_1, consume = null, Mappings(
|
||||||
N8,
|
listOf(
|
||||||
N9,
|
CommandMapping(
|
||||||
FN,
|
events = listOf(Event.keyDown),
|
||||||
STAR,
|
inputModes = listOf(InputMode.Letter, InputMode.Number, InputMode.Word),
|
||||||
POUND,
|
ctrl = true,
|
||||||
UP,
|
command = Command.INPUT_MODE
|
||||||
DOWN,
|
)
|
||||||
LEFT,
|
)
|
||||||
RIGHT,
|
)),
|
||||||
SELECT,
|
|
||||||
DELETE,
|
B2(KeyEvent.KEYCODE_BUTTON_2, consume = null, Mappings(
|
||||||
BACK,
|
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.N6 to listOf('m','n','o','ö','ø','ò','ó','ô','õ','õ'),
|
||||||
Key.N7 to listOf('p','q','r','s','ß','$'),
|
Key.N7 to listOf('p','q','r','s','ß','$'),
|
||||||
Key.N8 to listOf('t','u','v','ù','ú','û','ü'),
|
Key.N8 to listOf('t','u','v','ù','ú','û','ü'),
|
||||||
Key.N9 to listOf('w','x','y','z','ý','þ'),
|
Key.N9 to listOf('w','x','y','z','ý','þ')
|
||||||
Key.STAR to listOf('*'),
|
|
||||||
Key.POUND to listOf('#'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val nonAlphaNumeric = setOf('*','#','\'','"','.','?','!',',','-','@','$','/','%',':','(',')')
|
|
||||||
}
|
}
|
@ -1,21 +1,16 @@
|
|||||||
package net.mezimmah.wkt9.keypad
|
package net.mezimmah.wkt9.keypad
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import net.mezimmah.wkt9.exception.MissingLetterCode
|
import net.mezimmah.wkt9.exception.MissingLetterCode
|
||||||
import java.lang.StringBuilder
|
import java.lang.StringBuilder
|
||||||
|
|
||||||
class Keypad(
|
class Keypad(
|
||||||
private val keyCodeMapping: KeyCodeMapping,
|
|
||||||
private val letterLayout: Map<Key, List<Char>>,
|
private val letterLayout: Map<Key, List<Char>>,
|
||||||
|
|
||||||
numericLayout: Map<Key, Int>
|
numericLayout: Map<Key, Int>
|
||||||
) {
|
) {
|
||||||
private val tag = "WKT9"
|
|
||||||
private val letterCodeMap: MutableMap<Char, Int> = mutableMapOf()
|
private val letterCodeMap: MutableMap<Char, Int> = mutableMapOf()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d(tag, "Keypad")
|
|
||||||
|
|
||||||
numericLayout.forEach { (key, code) ->
|
numericLayout.forEach { (key, code) ->
|
||||||
indexKeyLetters(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 {
|
fun getCodeForWord(word: String): String {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
val normalized = word.lowercase()
|
val normalized = word.lowercase()
|
||||||
@ -44,7 +35,7 @@ class Keypad(
|
|||||||
return builder.toString()
|
return builder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun codeForLetter(letter: Char): Int? {
|
private fun codeForLetter(letter: Char): Int? {
|
||||||
return letterCodeMap[letter]
|
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.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.Settings
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
|
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
@ -12,13 +14,12 @@ import net.mezimmah.wkt9.R
|
|||||||
|
|
||||||
class PreferencesFragment: PreferenceFragmentCompat(),
|
class PreferencesFragment: PreferenceFragmentCompat(),
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
private var key: CharSequence? = null
|
||||||
private val tag = "WKT9"
|
private val tag = "WKT9"
|
||||||
private val requestPermissionLauncher = registerForActivityResult(RequestMultiplePermissions()) { isGranted: Map<String, Boolean> ->
|
private val requestPermissionLauncher = registerForActivityResult(RequestMultiplePermissions()) { isGranted: Map<String, Boolean> ->
|
||||||
// If any permission got denied we programmatically disable the option
|
// If any permission got denied we programmatically disable the option
|
||||||
if (isGranted.containsValue(false)) {
|
if (isGranted.containsValue(false)) {
|
||||||
val key = getString(R.string.preference_setting_speech_to_text_key)
|
findPreference<SwitchPreference>(this.key!!)?.isChecked = false
|
||||||
|
|
||||||
findPreference<SwitchPreference>(key)?.isChecked = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,6 +33,14 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
|||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
|
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() {
|
override fun onPause() {
|
||||||
@ -41,8 +50,10 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(p0: SharedPreferences?, key: String?) {
|
override fun onSharedPreferenceChanged(p0: SharedPreferences?, key: String?) {
|
||||||
|
this.key = key
|
||||||
|
|
||||||
when (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) {
|
if (findPreference<SwitchPreference>(key)?.isChecked == true) {
|
||||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
arrayOf(
|
arrayOf(
|
||||||
@ -54,6 +65,12 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
|||||||
requestPermissionLauncher.launch(permissions)
|
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
|
package net.mezimmah.wkt9.t9
|
||||||
|
|
||||||
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -90,7 +91,11 @@ class T9 (
|
|||||||
}
|
}
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
|
try {
|
||||||
wordDao.insert(*wordBatch.toTypedArray())
|
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:id="@+id/suggestion_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/yellow_radius"
|
android:background="@drawable/button_radius"
|
||||||
android:textColor="@color/suggestion_text"
|
android:textColor="@color/suggestion_text"
|
||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
android:paddingVertical="5dp"
|
android:paddingVertical="5dp"
|
||||||
|
@ -9,12 +9,11 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/suggestion_text"
|
android:id="@+id/suggestion_text"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:background="@drawable/blue_radius"
|
|
||||||
android:textColor="@color/suggestion_text"
|
android:textColor="@color/suggestion_text"
|
||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
android:paddingVertical="5dp"
|
android:paddingVertical="5dp"
|
||||||
android:paddingHorizontal="8dp"
|
android:paddingHorizontal="8dp"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textFontWeight="500" />
|
android:textFontWeight="400" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<RelativeLayout
|
<HorizontalScrollView
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -10,52 +10,10 @@
|
|||||||
android:background="@color/black"
|
android:background="@color/black"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<HorizontalScrollView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
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
|
<LinearLayout
|
||||||
android:id="@+id/suggestions"
|
android:id="@+id/suggestions"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="44dp"
|
||||||
android:orientation="horizontal" />
|
android:orientation="horizontal" />
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</HorizontalScrollView>
|
</HorizontalScrollView>
|
||||||
</RelativeLayout>
|
|
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="purple_700">#FF3700B3</color>
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
<color name="teal_700">#FF018786</color>
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#CC000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
<color name="button">#637783</color>
|
<color name="button">#CC336699</color>
|
||||||
<color name="button_text">#FFFCF0</color>
|
<color name="button_text">#FFFCF0</color>
|
||||||
<color name="suggestion">#C1E8FF</color>
|
<color name="suggestion">#FFFFFFFF</color>
|
||||||
<color name="current_suggestion">#FFE3C1</color>
|
<color name="current_suggestion">#FFFFFFFF</color>
|
||||||
<color name="suggestion_text">#424242</color>
|
<color name="suggestion_text">#FFFFFFFF</color>
|
||||||
</resources>
|
</resources>
|
@ -3,17 +3,27 @@
|
|||||||
|
|
||||||
<string name="app_preferences_name">WKT9 Preferences</string>
|
<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="speech_to_text_key">speech_to_text</string>
|
||||||
<string name="preference_setting_speech_to_text_title">Enable Speech to Text</string>
|
<string name="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_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="whisper_url_key">whisper_url</string>
|
||||||
<string name="preference_setting_whisper_url_title">Whisper Server URL</string>
|
<string name="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_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">
|
<string-array name="input_mode_numeric">
|
||||||
<item>org.linphone</item>
|
<item>org.linphone</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="camera_apps">
|
||||||
|
<item>com.android.camera2</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
@ -3,17 +3,26 @@
|
|||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
app:title="@string/preference_category_speech_to_text_name" />
|
app:title="@string/speech_to_text_cat" />
|
||||||
|
|
||||||
<SwitchPreference
|
<SwitchPreference
|
||||||
app:key="@string/preference_setting_speech_to_text_key"
|
app:key="@string/speech_to_text_key"
|
||||||
app:title="@string/preference_setting_speech_to_text_title"
|
app:title="@string/speech_to_text_title"
|
||||||
app:summary="@string/preference_setting_speech_to_text_summary" />
|
app:summary="@string/speech_to_text_summary" />
|
||||||
|
|
||||||
<EditTextPreference
|
<EditTextPreference
|
||||||
app:key="@string/preference_setting_whisper_url_key"
|
app:key="@string/whisper_url_key"
|
||||||
app:title="@string/preference_setting_whisper_url_title"
|
app:title="@string/whisper_url_title"
|
||||||
app:summary="@string/preference_setting_whisper_url_summary"
|
app:summary="@string/whisper_url_summary"
|
||||||
app:dependency="@string/preference_setting_speech_to_text_key" />
|
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>
|
</PreferenceScreen>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user