Making progress
This commit is contained in:
parent
96d0443892
commit
a7e17eda9d
@ -22,7 +22,7 @@
|
||||
android:theme="@style/Theme.WKT9">
|
||||
|
||||
<service
|
||||
android:name=".WKT9"
|
||||
android:name=".WKT9IME"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_INPUT_METHOD"
|
||||
android:exported="true">
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -2,39 +2,36 @@ package net.mezimmah.wkt9
|
||||
|
||||
import android.content.Intent
|
||||
import android.view.KeyEvent
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
|
||||
interface WKT9Interface {
|
||||
interface IME {
|
||||
fun onTriggerKeyEvent(event: KeyEvent)
|
||||
|
||||
fun onStartIntent(intent: Intent)
|
||||
|
||||
fun onCandidates(
|
||||
candidates: List<String>,
|
||||
candidates: ArrayList<CharSequence>,
|
||||
current: Int? = 0
|
||||
)
|
||||
|
||||
fun onCancelCompose()
|
||||
fun onWords(words: List<Word>)
|
||||
|
||||
fun onClearCandidates()
|
||||
fun onNextWord()
|
||||
|
||||
fun onCompose(text: String, rangeStart: Int, rangeEnd: Int)
|
||||
fun onPreviousWord()
|
||||
|
||||
fun onDeleteText(beforeCursor: Int = 0, afterCursor: Int = 0, finishComposing: Boolean)
|
||||
fun onWordSelected(word: Word)
|
||||
|
||||
fun onFinishComposing(cursorPosition: Int)
|
||||
fun onCommit(text: CharSequence = "", beforeCursor: Int = 0, afterCursor: Int = 0)
|
||||
|
||||
fun onGetText(): CharSequence?
|
||||
fun onCompose(text: CharSequence)
|
||||
|
||||
fun onDeleteText(beforeCursor: Int = 0, afterCursor: Int = 0, finishComposing: Boolean = false)
|
||||
|
||||
fun onGetTextBeforeCursor(n: Int): CharSequence?
|
||||
|
||||
fun onReplaceText(text: String)
|
||||
|
||||
fun onSwitchInputHandler(inputMode: InputMode)
|
||||
|
||||
fun onRecord(finishComposing: Boolean)
|
||||
|
||||
fun onTranscribe()
|
||||
|
||||
fun onUpdateStatusIcon(icon: Int?)
|
||||
}
|
@ -3,113 +3,110 @@ package net.mezimmah.wkt9
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.inputmethodservice.InputMethodService
|
||||
import android.media.MediaRecorder
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.CursorAnchorInfo
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.ExtractedTextRequest
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import net.mezimmah.wkt9.candidates.Candidates
|
||||
import net.mezimmah.wkt9.inputmode.InputManager
|
||||
import android.view.inputmethod.InputMethodSubtype
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
import net.mezimmah.wkt9.inputhandler.IdleInputHandler
|
||||
import net.mezimmah.wkt9.inputhandler.InputHandler
|
||||
import net.mezimmah.wkt9.inputhandler.LetterInputHandler
|
||||
import net.mezimmah.wkt9.inputhandler.NumberInputHandler
|
||||
import net.mezimmah.wkt9.inputhandler.WordInputHandler
|
||||
import net.mezimmah.wkt9.inputmode.InputModeManager
|
||||
import net.mezimmah.wkt9.inputmode.InputMode
|
||||
import net.mezimmah.wkt9.keypad.Event
|
||||
import net.mezimmah.wkt9.keypad.Key
|
||||
import net.mezimmah.wkt9.keypad.KeyEventStat
|
||||
import net.mezimmah.wkt9.voice.Whisper
|
||||
import net.mezimmah.wkt9.layout.Words
|
||||
import net.mezimmah.wkt9.t9.T9
|
||||
import java.util.Locale
|
||||
|
||||
class WKT9: WKT9Interface, InputMethodService() {
|
||||
|
||||
class WKT9IME: IME, InputMethodService() {
|
||||
private val tag = "WKT9"
|
||||
private val longPressTimeout = 400L
|
||||
|
||||
private lateinit var inputManager: InputManager
|
||||
private val inputModeManager = InputModeManager(this)
|
||||
|
||||
private var inputView: View? = null
|
||||
private lateinit var locale: Locale
|
||||
private var inputHandler: InputHandler? = null
|
||||
|
||||
private var composing: Boolean = false
|
||||
private var cursorPosition: Int = 0
|
||||
private var wordsView: Words? = null
|
||||
|
||||
private val keyDownStats = KeyEventStat(0, 0)
|
||||
private val keyUpStats = KeyEventStat(0, 0)
|
||||
|
||||
// Whisper
|
||||
private lateinit var whisper: Whisper
|
||||
private var recorder: MediaRecorder? = null
|
||||
private var composing: Boolean = false
|
||||
private var selectionStart: Int = 0
|
||||
private var selectionEnd: Int = 0
|
||||
|
||||
private lateinit var candidates: Candidates
|
||||
|
||||
override fun onCandidates(candidates: List<String>, current: Int?) {
|
||||
this.candidates.load(candidates, current)
|
||||
override fun onCandidates(candidates: ArrayList<CharSequence>, current: Int?) {
|
||||
// this.candidates?.load(candidates, current)
|
||||
}
|
||||
|
||||
override fun onCancelCompose() {
|
||||
cancelCompose()
|
||||
}
|
||||
|
||||
override fun onClearCandidates() {
|
||||
candidates.clear()
|
||||
}
|
||||
|
||||
override fun onCompose(text: String, rangeStart: Int, rangeEnd: Int) {
|
||||
val selectionEnd = rangeStart + text.length
|
||||
|
||||
override fun onCommit(text: CharSequence, beforeCursor: Int, afterCursor: Int) {
|
||||
currentInputConnection?.run {
|
||||
beginBatchEdit()
|
||||
setComposingRegion(rangeStart, rangeEnd)
|
||||
commitText(text, 1)
|
||||
setSelection(rangeStart, selectionEnd)
|
||||
setComposingRegion(selectionEnd - beforeCursor, selectionEnd + afterCursor)
|
||||
setComposingText(text, 1)
|
||||
finishComposingText()
|
||||
endBatchEdit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCompose(text: CharSequence) {
|
||||
currentInputConnection?.run {
|
||||
if (!composing) setComposingRegion(selectionStart, selectionEnd)
|
||||
|
||||
setComposingText(text, 1)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreate() {
|
||||
Log.d(tag, "Starting WKT9")
|
||||
|
||||
inputManager = InputManager(this)
|
||||
val inputMethodManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
val languageTag = inputMethodManager.currentInputMethodSubtype?.languageTag ?: "en-US"
|
||||
|
||||
locale = Locale.forLanguageTag(languageTag)
|
||||
|
||||
initializeDictionary(locale)
|
||||
|
||||
super.onCreate()
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateInputView(): View? {
|
||||
inputView = layoutInflater.inflate(R.layout.suggestions, null).also {
|
||||
candidates = Candidates(this, inputManager, it)
|
||||
whisper = Whisper(this, inputManager, it)
|
||||
}
|
||||
wordsView = layoutInflater.inflate(R.layout.words, null) as Words
|
||||
|
||||
return inputView
|
||||
return wordsView
|
||||
}
|
||||
|
||||
override fun onCurrentInputMethodSubtypeChanged(newSubtype: InputMethodSubtype?) {
|
||||
super.onCurrentInputMethodSubtypeChanged(newSubtype)
|
||||
|
||||
newSubtype?.let {
|
||||
locale = Locale.forLanguageTag(it.languageTag)
|
||||
|
||||
initializeDictionary(locale)
|
||||
inputHandler?.onSwitchLocale(locale)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleteText(beforeCursor: Int, afterCursor: Int, finishComposing: Boolean) {
|
||||
if (finishComposing) finishComposingText()
|
||||
|
||||
deleteText(beforeCursor, afterCursor)
|
||||
}
|
||||
|
||||
override fun onFinishComposing(cursorPosition: Int) {
|
||||
currentInputConnection?.run {
|
||||
setSelection(cursorPosition, cursorPosition)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinishInputView(finishingInput: Boolean) {
|
||||
super.onFinishInputView(finishingInput)
|
||||
|
||||
onClearCandidates()
|
||||
}
|
||||
|
||||
override fun onGetTextBeforeCursor(n: Int): CharSequence? {
|
||||
return this.currentInputConnection?.getTextBeforeCursor(n, 0)
|
||||
}
|
||||
|
||||
override fun onGetText(): CharSequence? {
|
||||
val request = ExtractedTextRequest()
|
||||
val text = currentInputConnection.getExtractedText(request, 0)
|
||||
|
||||
return text?.text
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
if (keyDownStats.keyCode != keyCode) {
|
||||
keyDownStats.keyCode = keyCode
|
||||
@ -124,16 +121,16 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
val hasLongDownMapping = key.mappings.hasLongDownMapping(InputMode.Word)
|
||||
val mappings = key.mappings.match(
|
||||
event = if (event.repeatCount > 0) Event.keyDownRepeat else Event.keyDown,
|
||||
inputMode = inputManager.mode,
|
||||
inputMode = inputModeManager.currentMode,
|
||||
packageName = currentInputEditorInfo.packageName,
|
||||
alt = event.isAltPressed,
|
||||
fn = event.isFunctionPressed,
|
||||
ctrl = event.isCtrlPressed,
|
||||
repeatCount = event.repeatCount
|
||||
)
|
||||
|
||||
mappings?.map { mapping ->
|
||||
if (mapping.command != null) {
|
||||
inputManager.handler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
||||
inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
||||
}
|
||||
|
||||
if (mapping.overrideConsume) consume = mapping.consume
|
||||
@ -161,17 +158,17 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
var consume = key.consume
|
||||
val keyDownMS = event.eventTime - event.downTime
|
||||
val mappings = key.mappings.match(
|
||||
event = if (keyDownMS >= longPressTimeout) Event.afterLongDown else Event.afterShortDown,
|
||||
inputMode = inputManager.mode,
|
||||
event = if (keyDownMS >= 400L) Event.afterLongDown else Event.afterShortDown,
|
||||
inputMode = inputModeManager.currentMode,
|
||||
packageName = currentInputEditorInfo.packageName,
|
||||
alt = event.isAltPressed,
|
||||
fn = event.isFunctionPressed,
|
||||
ctrl = event.isCtrlPressed,
|
||||
repeatCount = event.repeatCount
|
||||
)
|
||||
|
||||
mappings?.map { mapping ->
|
||||
if (mapping.command != null) {
|
||||
inputManager.handler?.onRunCommand(mapping.command, key, event, keyUpStats)
|
||||
inputHandler?.onRunCommand(mapping.command, key, event, keyUpStats)
|
||||
}
|
||||
|
||||
if (mapping.overrideConsume) consume = mapping.consume
|
||||
@ -192,15 +189,15 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
var consume = key.consume
|
||||
val mappings = key.mappings.match(
|
||||
event = Event.keyLongDown,
|
||||
inputMode = inputManager.mode,
|
||||
inputMode = inputModeManager.currentMode,
|
||||
packageName = currentInputEditorInfo.packageName,
|
||||
alt = event.isAltPressed,
|
||||
fn = event.isFunctionPressed,
|
||||
ctrl = event.isCtrlPressed
|
||||
)
|
||||
|
||||
mappings?.map { mapping ->
|
||||
if (mapping.command != null) {
|
||||
inputManager.handler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
||||
inputHandler?.onRunCommand(mapping.command, key, event, keyDownStats)
|
||||
}
|
||||
|
||||
if (mapping.overrideConsume) consume = mapping.consume
|
||||
@ -213,50 +210,19 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRecord(finishComposing: Boolean) {
|
||||
if (finishComposing) finishComposingText()
|
||||
if (!isInputViewShown) requestShowSelf(InputMethodManager.SHOW_IMPLICIT)
|
||||
|
||||
record()
|
||||
}
|
||||
|
||||
override fun onReplaceText(text: String) {
|
||||
currentInputConnection?.run {
|
||||
beginBatchEdit()
|
||||
setComposingRegion(0, text.length)
|
||||
commitText(text, 1)
|
||||
endBatchEdit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShowInputRequested(flags: Int, configChange: Boolean): Boolean {
|
||||
return (inputManager.mode != InputMode.Number && inputManager.mode != InputMode.Idle)
|
||||
return (
|
||||
inputModeManager.currentMode != InputMode.Number &&
|
||||
inputModeManager.currentMode != InputMode.Idle
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStartInput(editorInfo: EditorInfo?, restarting: Boolean) {
|
||||
if (editorInfo == null) return
|
||||
val mode = inputModeManager.selectModeByEditor(editorInfo)
|
||||
|
||||
inputManager.selectHandler(editorInfo)
|
||||
}
|
||||
currentInputConnection?.requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR)
|
||||
|
||||
override fun onUpdateSelection(
|
||||
oldSelStart: Int,
|
||||
oldSelEnd: Int,
|
||||
newSelStart: Int,
|
||||
newSelEnd: Int,
|
||||
candidatesStart: Int,
|
||||
candidatesEnd: Int
|
||||
) {
|
||||
inputManager.handler?.onUpdateCursorPosition(newSelEnd)
|
||||
|
||||
super.onUpdateSelection(
|
||||
oldSelStart,
|
||||
oldSelEnd,
|
||||
newSelStart,
|
||||
newSelEnd,
|
||||
candidatesStart,
|
||||
candidatesEnd
|
||||
)
|
||||
switchInputMode(mode)
|
||||
}
|
||||
|
||||
override fun onStartIntent(intent: Intent) {
|
||||
@ -265,12 +231,28 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSwitchInputHandler(inputMode: InputMode) {
|
||||
inputManager.switchToHandler(inputMode, cursorPosition)
|
||||
override fun onUpdateCursorAnchorInfo(cursorAnchorInfo: CursorAnchorInfo?) {
|
||||
cursorAnchorInfo?.let {
|
||||
selectionStart = it.selectionStart
|
||||
selectionEnd = it.selectionEnd
|
||||
composing = if (it.composingTextStart == -1) {
|
||||
if (composing) finishComposing()
|
||||
|
||||
false
|
||||
} else if (it.selectionEnd != (it.composingTextStart + it.composingText.length)) {
|
||||
onCommit()
|
||||
|
||||
true
|
||||
} else true
|
||||
}
|
||||
|
||||
super.onUpdateCursorAnchorInfo(cursorAnchorInfo)
|
||||
}
|
||||
|
||||
override fun onTranscribe() {
|
||||
transcribe()
|
||||
override fun onSwitchInputHandler(inputMode: InputMode) {
|
||||
val mode = inputModeManager.switchToMode(inputMode)
|
||||
|
||||
switchInputMode(mode)
|
||||
}
|
||||
|
||||
override fun onTriggerKeyEvent(event: KeyEvent) {
|
||||
@ -282,15 +264,21 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
else showStatusIcon(icon)
|
||||
}
|
||||
|
||||
private fun cancelCompose() {
|
||||
if (!composing) return
|
||||
override fun onWords(words: List<Word>) {
|
||||
wordsView?.words = words
|
||||
}
|
||||
|
||||
currentInputConnection?.let {
|
||||
it.beginBatchEdit()
|
||||
it.setComposingText("", 1)
|
||||
it.finishComposingText()
|
||||
it.endBatchEdit()
|
||||
}
|
||||
override fun onWordSelected(word: Word) {
|
||||
this.onCompose(word.word)
|
||||
this.inputHandler?.onWordSelected(word)
|
||||
}
|
||||
|
||||
override fun onNextWord() {
|
||||
wordsView?.next()
|
||||
}
|
||||
|
||||
override fun onPreviousWord() {
|
||||
wordsView?.previous()
|
||||
}
|
||||
|
||||
private fun deleteText(beforeCursor: Int, afterCursor: Int) {
|
||||
@ -299,28 +287,23 @@ class WKT9: WKT9Interface, InputMethodService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishComposingText() {
|
||||
if (!composing) return
|
||||
private fun finishComposing() {
|
||||
wordsView?.clear()
|
||||
inputHandler?.onFinishComposing()
|
||||
}
|
||||
|
||||
currentInputConnection?.let {
|
||||
it.finishComposingText()
|
||||
private fun initializeDictionary(locale: Locale) {
|
||||
val t9 = T9(this, locale)
|
||||
|
||||
inputManager.handler?.onCommitText()
|
||||
t9.initializeWords()
|
||||
}
|
||||
|
||||
private fun switchInputMode(mode: InputMode) {
|
||||
inputHandler = when(mode) {
|
||||
InputMode.Word -> WordInputHandler(this, this, locale)
|
||||
InputMode.Letter -> LetterInputHandler(this, this, locale)
|
||||
InputMode.Number -> NumberInputHandler(this, this)
|
||||
else -> IdleInputHandler(this, this)
|
||||
}
|
||||
|
||||
composing = false
|
||||
}
|
||||
|
||||
private fun record() {
|
||||
// The recorder must be busy...
|
||||
if (recorder != null) return
|
||||
|
||||
requestShowSelf(InputMethodManager.SHOW_IMPLICIT)
|
||||
|
||||
whisper.record()
|
||||
}
|
||||
|
||||
private fun transcribe() {
|
||||
whisper.transcribe()
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package net.mezimmah.wkt9.candidates
|
||||
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.inputmode.InputManager
|
||||
|
||||
class Candidates(
|
||||
private val context: WKT9,
|
||||
private val inputManager: InputManager,
|
||||
|
||||
ui: View
|
||||
) {
|
||||
private val candidatesView: LinearLayout = ui.findViewById(R.id.suggestions)
|
||||
|
||||
fun clear() {
|
||||
candidatesView.removeAllViews()
|
||||
}
|
||||
|
||||
fun load(candidates: List<String>, current: Int?) {
|
||||
clear()
|
||||
|
||||
candidates.forEachIndexed { index, candidate ->
|
||||
val layout = if (index == current) R.layout.current_suggestion else R.layout.suggestion
|
||||
val candidateView = context.layoutInflater.inflate(layout, null)
|
||||
val textView = candidateView.findViewById<TextView>(R.id.suggestion_text)
|
||||
|
||||
textView.text = candidate
|
||||
|
||||
candidateView.setOnClickListener { view ->
|
||||
getIndex(view)?.let { index ->
|
||||
load(candidates, index)
|
||||
}
|
||||
}
|
||||
|
||||
candidateView.setOnLongClickListener {
|
||||
val view = it as LinearLayout
|
||||
val suggestion = view.findViewById<TextView>(R.id.suggestion_text)
|
||||
val text = suggestion.text
|
||||
|
||||
inputManager.handler?.onLongClickCandidate(text.toString())
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
if (index == current) {
|
||||
inputManager.handler?.onCandidateSelected(index)
|
||||
}
|
||||
|
||||
candidatesView.addView(candidateView)
|
||||
}
|
||||
|
||||
if (!context.isInputViewShown) {
|
||||
context.requestShowSelf(InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIndex(candidate: View): Int? {
|
||||
for (i in 0 until candidatesView.childCount) {
|
||||
val child: View = candidatesView.getChildAt(i)
|
||||
|
||||
if (candidate == child) return i
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
@ -14,10 +14,10 @@ interface WordDao {
|
||||
@Query("DELETE FROM word WHERE word = :word AND locale = :locale")
|
||||
fun delete(word: String, locale: String)
|
||||
|
||||
@Query("SELECT * FROM word WHERE code LIKE :code || '%' " +
|
||||
@Query("SELECT * FROM word WHERE code LIKE :code || '%' AND locale = :locale " +
|
||||
"ORDER BY length, weight DESC LIMIT :limit")
|
||||
suspend fun findCandidates(code: String, limit: Int = 10): List<Word>
|
||||
suspend fun findCandidates(locale: String, code: String, limit: Int = 10): List<Word>
|
||||
|
||||
@Query("UPDATE word SET weight = weight + 1 WHERE word=:word")
|
||||
suspend fun increaseWeight(word: String)
|
||||
@Query("UPDATE word SET weight = weight + 1 WHERE id=:id")
|
||||
suspend fun increaseWeight(id: Int)
|
||||
}
|
@ -4,7 +4,13 @@ import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(indices = [Index(value = ["word"]), Index(value = ["code"]), Index(value = ["locale"]), Index(value = ["word", "locale"], unique = true)])
|
||||
@Entity(indices = [
|
||||
Index(value = ["word"]),
|
||||
Index(value = ["code"]),
|
||||
Index(value = ["locale"]),
|
||||
Index(value = ["word", "locale"], unique = true),
|
||||
Index(value = ["code", "locale"], unique = false)
|
||||
])
|
||||
data class Word(
|
||||
var word: String,
|
||||
var code: String,
|
||||
|
@ -1,42 +0,0 @@
|
||||
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)
|
||||
Command.NUMBER -> triggerOriginalKeyEvent(key)
|
||||
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,117 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
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(index: Int) {}
|
||||
|
||||
override fun onCommitText() {}
|
||||
|
||||
override fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int) {}
|
||||
|
||||
override fun onFinish() {}
|
||||
|
||||
override fun onInsertText(text: CharSequence) {}
|
||||
|
||||
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) {
|
||||
val modes = listOf(
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS,
|
||||
null
|
||||
)
|
||||
var index = modes.indexOf(capMode)
|
||||
|
||||
when (key) {
|
||||
Key.B2 -> {
|
||||
if (index == 0) index = modes.count()
|
||||
|
||||
index--
|
||||
}
|
||||
else -> index++
|
||||
}
|
||||
|
||||
capMode = modes[index % modes.count()]
|
||||
}
|
||||
|
||||
protected open fun finalizeWordOrSentence(stats: KeyEventStat) {
|
||||
val candidates = listOf(" ", ". ", "? ", "! ", ", ", ": ", "; ")
|
||||
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
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 getDefaultCapMode(typeFlags: Int): Int? {
|
||||
val modes = listOf(
|
||||
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_WORDS,
|
||||
InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
|
||||
)
|
||||
|
||||
modes.forEach {
|
||||
if (typeFlags.and(it) == it) return it
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
protected fun triggerKeyEvent(keyCode: Int) {
|
||||
val down = KeyEvent(KeyEvent.ACTION_DOWN, keyCode)
|
||||
val up = KeyEvent(KeyEvent.ACTION_UP, keyCode)
|
||||
|
||||
wkt9.onTriggerKeyEvent(down)
|
||||
wkt9.onTriggerKeyEvent(up)
|
||||
}
|
||||
|
||||
protected fun triggerOriginalKeyEvent(key: Key) {
|
||||
triggerKeyEvent(key.keyCode)
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
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(index: Int)
|
||||
|
||||
fun onComposeText(text: CharSequence, composingTextStart: Int, composingTextEnd: Int)
|
||||
|
||||
fun onCommitText()
|
||||
|
||||
fun onFinish()
|
||||
|
||||
fun onInsertText(text: CharSequence)
|
||||
|
||||
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)
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
import android.util.Log
|
||||
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, editor.initialSelEnd)
|
||||
|
||||
val mode = 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(mode, editor.initialSelEnd)
|
||||
}
|
||||
|
||||
fun switchToHandler(inputMode: InputMode, cursorPosition: Int) {
|
||||
val lastHandler = this.handler
|
||||
val cursor: Int = lastHandler?.cursorPosition ?: cursorPosition
|
||||
val newHandler = when (inputMode) {
|
||||
InputMode.Word -> wordInputHandler
|
||||
InputMode.Letter -> letterInputHandler
|
||||
InputMode.Number -> numberInputHandler
|
||||
else -> idleInputHandler
|
||||
}
|
||||
|
||||
newHandler.apply {
|
||||
onStart(typeClass, typeVariation, typeFlags)
|
||||
onUpdateCursorPosition(cursor)
|
||||
}
|
||||
|
||||
lastHandler?.onFinish()
|
||||
|
||||
this.mode = inputMode
|
||||
this.handler = newHandler
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import net.mezimmah.wkt9.R
|
||||
|
||||
enum class InputMode(val icon: Int) {
|
||||
Word(R.drawable.word_en_us_cap),
|
||||
Letter(R.drawable.alpha_en_us_cap),
|
||||
Number(R.drawable.numeric_en_us_num),
|
||||
Letter(R.drawable.letter_cap),
|
||||
Number(R.drawable.number_input),
|
||||
Idle(R.drawable.wkt9)
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
import android.text.InputType
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9IME
|
||||
|
||||
class InputModeManager(private val context: WKT9IME) {
|
||||
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
|
||||
)
|
||||
|
||||
var currentMode: InputMode = InputMode.Idle
|
||||
private set
|
||||
|
||||
private var lastMode: InputMode = InputMode.Idle
|
||||
|
||||
fun selectModeByEditor(editor: EditorInfo?): InputMode {
|
||||
if (editor == null) return switchToMode(InputMode.Idle)
|
||||
|
||||
val override = getPackageOverride(editor.packageName)
|
||||
val inputType = editor.inputType
|
||||
val typeClass = inputType.and(InputType.TYPE_MASK_CLASS)
|
||||
val typeVariation = inputType.and(InputType.TYPE_MASK_VARIATION)
|
||||
|
||||
if (override != null) return switchToMode(override)
|
||||
|
||||
// allowSuggestions = typeFlags != InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
|
||||
|
||||
val mode = if (numericClasses.contains(typeClass)) {
|
||||
InputMode.Number
|
||||
} else if (typeClass == InputType.TYPE_CLASS_TEXT) {
|
||||
if (letterVariations.contains(typeVariation) || lastMode == InputMode.Letter) {
|
||||
InputMode.Letter
|
||||
} else {
|
||||
InputMode.Word
|
||||
}
|
||||
} else {
|
||||
InputMode.Idle
|
||||
}
|
||||
|
||||
return switchToMode(mode)
|
||||
}
|
||||
|
||||
fun switchToMode(inputMode: InputMode): InputMode {
|
||||
lastMode = currentMode
|
||||
currentMode = inputMode
|
||||
|
||||
return inputMode
|
||||
}
|
||||
|
||||
private fun getPackageOverride(packageName: String): InputMode? {
|
||||
val numeric = context.resources.getStringArray(R.array.input_mode_numeric)
|
||||
|
||||
return if (numeric.contains(packageName)) InputMode.Number
|
||||
else null
|
||||
}
|
||||
}
|
@ -1,314 +0,0 @@
|
||||
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.TextServicesManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.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
|
||||
|
||||
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 val candidates = mutableListOf<String>()
|
||||
private var candidateIndex: Int? = null
|
||||
private var composeRangeStart: Int? = null
|
||||
private var composeRangeEnd: Int? = null
|
||||
|
||||
private var sentenceStart: Boolean = false
|
||||
private var wordStart: Boolean = false
|
||||
|
||||
private val queryScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private val timeoutScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||
private var timeoutJob: Job? = null
|
||||
|
||||
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 finalizeWordOrSentence(stats: KeyEventStat) {
|
||||
if (stats.repeats == 0) {
|
||||
timeoutJob?.cancel()
|
||||
|
||||
clearCandidates(true)
|
||||
storeLastWord()
|
||||
}
|
||||
|
||||
candidates.addAll(listOf(" ", ". ", "? ", "! ", ", ", ": ", "; "))
|
||||
wkt9.onCandidates(candidates, stats.repeats % candidates.count())
|
||||
}
|
||||
|
||||
override fun onCandidateSelected(index: Int) {
|
||||
val candidate = candidates[index]
|
||||
val rangeStart = composeRangeStart ?: cursorPosition
|
||||
val rangeEnd = composeRangeEnd ?: cursorPosition
|
||||
|
||||
content.replace(rangeStart, rangeEnd, candidate)
|
||||
|
||||
candidateIndex = index
|
||||
composeRangeStart = rangeStart
|
||||
composeRangeEnd = rangeStart + candidate.length
|
||||
|
||||
wkt9.onCompose(candidate, rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
timeoutJob?.cancel()
|
||||
|
||||
clearCandidates(true)
|
||||
|
||||
cursorPosition = 0
|
||||
sentenceStart = false
|
||||
wordStart = false
|
||||
|
||||
content.clear()
|
||||
}
|
||||
|
||||
override fun onGetSuggestions(results: Array<out SuggestionsInfo>?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onInsertText(text: CharSequence) {
|
||||
clearCandidates(true)
|
||||
|
||||
content.replace(cursorPosition, cursorPosition, text.toString())
|
||||
wkt9.onCompose(text.toString(), cursorPosition, cursorPosition)
|
||||
wkt9.onFinishComposing(cursorPosition + text.length)
|
||||
}
|
||||
|
||||
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)
|
||||
Command.RECORD -> wkt9.onRecord(true)
|
||||
Command.SPACE -> finalizeWordOrSentence(stats)
|
||||
Command.TRANSCRIBE -> wkt9.onTranscribe()
|
||||
else -> Log.d(tag, "Command not implemented: $command")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUpdateCursorPosition(cursorPosition: Int) {
|
||||
super.onUpdateCursorPosition(cursorPosition)
|
||||
|
||||
try {
|
||||
val info = getCursorPositionInfo(content.substring(0, cursorPosition))
|
||||
|
||||
sentenceStart = info.startSentence
|
||||
wordStart = info.startWord
|
||||
} catch (e: Exception) {
|
||||
Log.d(tag, "Cursor position out of range.\nContent: $content\nCursor position: $cursorPosition", e)
|
||||
}
|
||||
|
||||
updateIcon()
|
||||
}
|
||||
|
||||
override fun onStart(typeClass: Int, typeVariations: Int, typeFlags: Int) {
|
||||
capMode = getDefaultCapMode(typeFlags)
|
||||
|
||||
// Get current editor content on start
|
||||
wkt9.onGetText()?.let {
|
||||
content.replace(0, content.length, it.toString())
|
||||
} ?: content.clear()
|
||||
}
|
||||
|
||||
private fun composeCharacter(key: Key, stats: KeyEventStat) {
|
||||
val layout = KeyLayout.en_US[key] ?: return
|
||||
val capitalize = Capitalize(capMode)
|
||||
|
||||
if (stats.repeats == 0) clearCandidates(true)
|
||||
|
||||
layout.forEach {
|
||||
candidates.add(capitalize.character(it, sentenceStart, wordStart))
|
||||
}
|
||||
|
||||
wkt9.onCandidates(
|
||||
candidates = candidates,
|
||||
current = stats.repeats % candidates.count()
|
||||
)
|
||||
|
||||
timeoutJob?.cancel()
|
||||
timeoutJob = timeoutScope.launch {
|
||||
delay(400)
|
||||
clearCandidates(true)
|
||||
// getSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearCandidates(finishComposing: Boolean) {
|
||||
if (finishComposing && candidateIndex != null) {
|
||||
wkt9.onFinishComposing(cursorPosition)
|
||||
}
|
||||
|
||||
candidateIndex = null
|
||||
composeRangeStart = null
|
||||
composeRangeEnd = null
|
||||
|
||||
candidates.clear()
|
||||
wkt9.onClearCandidates()
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
if (candidateIndex != null) undoCandidate()
|
||||
else if (content.isNotEmpty()) {
|
||||
content.deleteAt(content.length - 1)
|
||||
wkt9.onDeleteText(1, 0, true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastWord(): String {
|
||||
val beforeCursor = content.substring(0, cursorPosition)
|
||||
val words = beforeCursor.split("\\s+".toRegex())
|
||||
|
||||
return words.last()
|
||||
}
|
||||
|
||||
private fun inputMode(key: Key) {
|
||||
if (key == Key.B4) wkt9.onSwitchInputHandler(InputMode.Word)
|
||||
else wkt9.onSwitchInputHandler(InputMode.Number)
|
||||
}
|
||||
|
||||
private fun moveCursor() {
|
||||
timeoutJob?.cancel()
|
||||
clearCandidates(true)
|
||||
}
|
||||
|
||||
private fun storeLastWord() {
|
||||
val lastWord = getLastWord()
|
||||
// We're not storing single char words...
|
||||
if (lastWord.length < 2) return
|
||||
|
||||
try {
|
||||
val codeword = keypad.getCodeForWord(lastWord)
|
||||
val word = Word(
|
||||
word = lastWord,
|
||||
code = codeword,
|
||||
length = lastWord.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 undoCandidate() {
|
||||
val rangeStart = composeRangeStart ?: cursorPosition
|
||||
val rangeEnd = composeRangeEnd ?: cursorPosition
|
||||
|
||||
// Remove text codeword produced from editor
|
||||
wkt9.onCompose("", rangeStart, rangeEnd)
|
||||
|
||||
// Remove text codeword produced from content
|
||||
content.deleteRange(rangeStart, rangeEnd)
|
||||
|
||||
clearCandidates(false)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
//
|
||||
// override fun onGetSentenceSuggestions(results: Array<out SentenceSuggestionsInfo>?) {
|
||||
// results?.map {
|
||||
// val suggestions = it.getSuggestionsInfoAt(0)
|
||||
//
|
||||
// for (index in 0 until suggestions.suggestionsCount) {
|
||||
// val suggestion = suggestions.getSuggestionAt(index)
|
||||
//
|
||||
// candidates.add(suggestion)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (candidates.isEmpty()) return
|
||||
//
|
||||
// candidateSource = CandidateSource.Dictionary
|
||||
//
|
||||
// wkt9.onCandidates(
|
||||
// candidates = candidates,
|
||||
// current = null
|
||||
// )
|
||||
// }
|
||||
|
||||
// private fun getSuggestions() {
|
||||
// val lastWord = getLastWord()
|
||||
//
|
||||
// if (lastWord.length < 3) return
|
||||
//
|
||||
// val words = arrayOf(
|
||||
// TextInfo(
|
||||
// lastWord.plus("#"), // Add hash to string to get magic performance
|
||||
// 0,
|
||||
// lastWord.length + 1, // We added the hash, remember
|
||||
// 0,
|
||||
// 0
|
||||
// )
|
||||
// )
|
||||
//
|
||||
// spellCheckerSession?.getSentenceSuggestions(words, 15)
|
||||
// }
|
@ -1,42 +0,0 @@
|
||||
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,6 +1,6 @@
|
||||
package net.mezimmah.wkt9.inputmode
|
||||
|
||||
data class CursorPositionInfo(
|
||||
data class TextPositionInfo(
|
||||
val startWord: Boolean,
|
||||
val startSentence: Boolean
|
||||
)
|
@ -1,294 +0,0 @@
|
||||
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 candidates = mutableListOf<String>()
|
||||
private var candidateIndex: Int? = null
|
||||
private var composeRangeStart: Int? = null
|
||||
private var composeRangeEnd: Int? = null
|
||||
|
||||
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 finalizeWordOrSentence(stats: KeyEventStat) {
|
||||
if (codeword.isNotEmpty()) commit()
|
||||
|
||||
candidates.addAll(listOf(" ", ". ", "? ", "! ", ", ", ": ", "; "))
|
||||
wkt9.onCandidates(candidates, stats.repeats % candidates.count())
|
||||
}
|
||||
|
||||
override fun onCandidateSelected(index: Int) {
|
||||
val candidate = candidates[index]
|
||||
val rangeStart = composeRangeStart ?: cursorPosition
|
||||
val rangeEnd = composeRangeEnd ?: cursorPosition
|
||||
|
||||
content.replace(rangeStart, rangeEnd, candidate)
|
||||
|
||||
candidateIndex = index
|
||||
composeRangeStart = rangeStart
|
||||
composeRangeEnd = rangeStart + candidate.length
|
||||
|
||||
wkt9.onCompose(candidate, rangeStart, rangeEnd)
|
||||
}
|
||||
|
||||
override fun onInsertText(text: CharSequence) {
|
||||
commit()
|
||||
|
||||
content.replace(cursorPosition, cursorPosition, text.toString())
|
||||
wkt9.onCompose(text.toString(), cursorPosition, cursorPosition)
|
||||
wkt9.onFinishComposing(cursorPosition + text.length)
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
queryJob?.cancel()
|
||||
|
||||
commit()
|
||||
|
||||
cursorPosition = 0
|
||||
sentenceStart = false
|
||||
wordStart = false
|
||||
|
||||
content.clear()
|
||||
}
|
||||
|
||||
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)
|
||||
Command.RECORD -> record()
|
||||
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) {
|
||||
capMode = getDefaultCapMode(typeFlags)
|
||||
|
||||
// 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)
|
||||
|
||||
try {
|
||||
val info = getCursorPositionInfo(content.substring(0, cursorPosition))
|
||||
|
||||
sentenceStart = info.startSentence
|
||||
wordStart = info.startWord
|
||||
} catch (e: Exception) {
|
||||
Log.d(tag, "Cursor position out of range", e)
|
||||
}
|
||||
|
||||
updateIcon()
|
||||
}
|
||||
|
||||
private fun buildCodeword(key: Key) {
|
||||
// Don't build fruitless codeword
|
||||
if (staleCodeword) return
|
||||
|
||||
if (codeword.isEmpty() && candidateIndex != null) clearCandidates(true)
|
||||
|
||||
val code = KeyLayout.numeric[key]
|
||||
|
||||
codeword.append(code)
|
||||
|
||||
handleCodewordChange(codeword)
|
||||
}
|
||||
|
||||
private fun clearCandidates(finishComposing: Boolean) {
|
||||
candidateIndex = null
|
||||
composeRangeStart = null
|
||||
composeRangeEnd = null
|
||||
|
||||
candidates.clear()
|
||||
|
||||
wkt9.onClearCandidates()
|
||||
|
||||
if (finishComposing) wkt9.onFinishComposing(cursorPosition)
|
||||
}
|
||||
|
||||
private fun clearCodeword(increaseWordWeight: Boolean) {
|
||||
staleCodeword = false
|
||||
|
||||
codeword.clear()
|
||||
|
||||
if (!increaseWordWeight) return
|
||||
|
||||
candidateIndex?.let {
|
||||
val candidate = candidates[it]
|
||||
|
||||
if (candidate.isNotEmpty()) increaseWordWeight(candidate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun commit() {
|
||||
if (codeword.isNotEmpty()) clearCodeword(true)
|
||||
if (candidates.isNotEmpty()) clearCandidates(true)
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
staleCodeword = false
|
||||
|
||||
if (codeword.length > 1) reduceCodeword()
|
||||
else if (codeword.isNotEmpty()) undoCodeword()
|
||||
else if (candidateIndex != null) undoCandidate()
|
||||
else if (content.isNotEmpty()) {
|
||||
content.deleteAt(content.length - 1)
|
||||
wkt9.onDeleteText(1, 0, false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCodewordChange(codeword: StringBuilder) {
|
||||
queryJob?.cancel()
|
||||
|
||||
queryJob = queryScope.launch {
|
||||
queryT9Candidates(codeword, 25)
|
||||
|
||||
if (candidates.isEmpty()) staleCodeword = true
|
||||
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() {
|
||||
commit()
|
||||
}
|
||||
|
||||
private fun record() {
|
||||
commit()
|
||||
wkt9.onRecord(true)
|
||||
}
|
||||
|
||||
private fun reduceCodeword() {
|
||||
codeword.deleteAt(codeword.length - 1)
|
||||
|
||||
handleCodewordChange(codeword)
|
||||
}
|
||||
|
||||
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 fun undoCodeword() {
|
||||
undoCandidate()
|
||||
clearCodeword(false)
|
||||
}
|
||||
|
||||
private fun undoCandidate() {
|
||||
val rangeStart = composeRangeStart ?: cursorPosition
|
||||
val rangeEnd = composeRangeEnd ?: cursorPosition
|
||||
|
||||
// Remove text codeword produced from editor
|
||||
wkt9.onCompose("", rangeStart, rangeEnd)
|
||||
|
||||
// Remove text codeword produced from content
|
||||
content.deleteRange(rangeStart, rangeEnd)
|
||||
|
||||
clearCandidates(false)
|
||||
}
|
||||
|
||||
private suspend fun queryT9Candidates(codeWord: StringBuilder, limit: Int = 10) {
|
||||
val results = wordDao.findCandidates(codeWord.toString(), limit)
|
||||
val capitalize = Capitalize(capMode)
|
||||
|
||||
candidates.clear()
|
||||
|
||||
results.forEach { result ->
|
||||
candidates.add(capitalize.word(result.word, sentenceStart))
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ enum class Command {
|
||||
CHARACTER,
|
||||
DELETE,
|
||||
DIAL,
|
||||
ENTER,
|
||||
INPUT_MODE,
|
||||
MOVE_CURSOR,
|
||||
NUMBER,
|
||||
|
@ -6,7 +6,7 @@ data class CommandMapping(
|
||||
val events: List<Event>? = null,
|
||||
val inputModes: List<InputMode>? = null,
|
||||
val packageNames: List<String>? = null,
|
||||
val alt: Boolean = false,
|
||||
val fn: Boolean = false,
|
||||
val ctrl: Boolean = false,
|
||||
val repeatCount: Int? = null,
|
||||
val overrideConsume: Boolean = false,
|
||||
|
@ -8,6 +8,14 @@ enum class Key(
|
||||
val consume: Boolean?,
|
||||
val mappings: Mappings
|
||||
) {
|
||||
FN(KeyEvent.KEYCODE_FUNCTION, consume = false, Mappings(
|
||||
listOf()
|
||||
)),
|
||||
|
||||
CTRL_RIGHT(KeyEvent.KEYCODE_CTRL_RIGHT, consume = false, Mappings(
|
||||
listOf()
|
||||
)),
|
||||
|
||||
B1(KeyEvent.KEYCODE_BUTTON_1, consume = null, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
@ -76,16 +84,22 @@ enum class Key(
|
||||
N0(KeyEvent.KEYCODE_0, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -100,16 +114,22 @@ enum class Key(
|
||||
N1(KeyEvent.KEYCODE_1, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -124,16 +144,22 @@ enum class Key(
|
||||
N2(KeyEvent.KEYCODE_2, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -148,16 +174,22 @@ enum class Key(
|
||||
N3(KeyEvent.KEYCODE_3, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -172,16 +204,22 @@ enum class Key(
|
||||
N4(KeyEvent.KEYCODE_4, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -196,16 +234,22 @@ enum class Key(
|
||||
N5(KeyEvent.KEYCODE_5, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -220,16 +264,22 @@ enum class Key(
|
||||
N6(KeyEvent.KEYCODE_6, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -244,16 +294,22 @@ enum class Key(
|
||||
N7(KeyEvent.KEYCODE_7, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -268,16 +324,22 @@ enum class Key(
|
||||
N8(KeyEvent.KEYCODE_8, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -292,16 +354,22 @@ enum class Key(
|
||||
N9(KeyEvent.KEYCODE_9, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.afterShortDown),
|
||||
events = listOf(Event.keyDown),
|
||||
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
|
||||
events = listOf(Event.keyLongDown),
|
||||
inputModes = listOf(InputMode.Letter),
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
fn = true,
|
||||
command = Command.NUMBER
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
@ -317,38 +385,34 @@ enum class Key(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown, Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Word, InputMode.Letter, InputMode.Number),
|
||||
inputModes = listOf(InputMode.Word, InputMode.Letter),
|
||||
command = Command.DELETE
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
inputModes = listOf(InputMode.Number),
|
||||
overrideConsume = true,
|
||||
consume = null
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
listOf()
|
||||
)),
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
listOf()
|
||||
)),
|
||||
|
||||
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
|
||||
events = listOf(Event.keyDown, Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
command = Command.MOVE_CURSOR,
|
||||
overrideConsume = true,
|
||||
consume = true
|
||||
)
|
||||
)
|
||||
)),
|
||||
@ -356,22 +420,30 @@ enum class Key(
|
||||
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
|
||||
events = listOf(Event.keyDown, Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Word),
|
||||
command = Command.MOVE_CURSOR,
|
||||
overrideConsume = true,
|
||||
consume = true
|
||||
)
|
||||
)
|
||||
)),
|
||||
|
||||
ENTER(KeyEvent.KEYCODE_ENTER, consume = null, Mappings(
|
||||
ENTER(KeyEvent.KEYCODE_ENTER, consume = true, Mappings(
|
||||
listOf(
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown, Event.keyDownRepeat),
|
||||
inputModes = listOf(InputMode.Letter, InputMode.Word),
|
||||
command = Command.ENTER,
|
||||
overrideConsume = true,
|
||||
consume = true
|
||||
),
|
||||
|
||||
CommandMapping(
|
||||
events = listOf(Event.keyDown),
|
||||
inputModes = listOf(InputMode.Idle),
|
||||
packageNames = listOf("com.android.camera2"),
|
||||
command = Command.CAMERA,
|
||||
overrideConsume = true,
|
||||
consume = true
|
||||
command = Command.CAMERA
|
||||
)
|
||||
)
|
||||
));
|
||||
|
@ -15,15 +15,15 @@ object KeyLayout {
|
||||
Key.N9 to 9,
|
||||
)
|
||||
|
||||
val en_US = mapOf(
|
||||
Key.N1 to listOf('.','?','!',',','-','+','=','\'','"','@','$','/','%',':','(',')'),
|
||||
Key.N2 to listOf('a','b','c','ä','æ','å','à','á','â','ã','ç'),
|
||||
Key.N3 to listOf('d','e','f','è','é','ê','ë','đ'),
|
||||
Key.N4 to listOf('g','h','i','ì','í','î','ï'),
|
||||
Key.N5 to listOf('j','k','l','£'),
|
||||
Key.N6 to listOf('m','n','o','ö','ø','ò','ó','ô','õ','õ'),
|
||||
Key.N7 to listOf('p','q','r','s','ß','$'),
|
||||
Key.N8 to listOf('t','u','v','ù','ú','û','ü'),
|
||||
Key.N9 to listOf('w','x','y','z','ý','þ')
|
||||
val chars = mapOf(
|
||||
Key.N1 to listOf('.','?','!',',','-','+','=','\'','"','@','$','&','/','%',':','(',')','0','1'),
|
||||
Key.N2 to listOf('a','b','c','ä','æ','å','à','á','â','ã','ç','2','₂','²'),
|
||||
Key.N3 to listOf('d','e','f','è','é','ê','ë','đ','3','³'),
|
||||
Key.N4 to listOf('g','h','i','ì','í','î','ï','4'),
|
||||
Key.N5 to listOf('j','k','l','£','5'),
|
||||
Key.N6 to listOf('m','n','o','ö','ø','ò','ó','ô','õ','õ','ñ','6'),
|
||||
Key.N7 to listOf('p','q','r','s','ß','$','7'),
|
||||
Key.N8 to listOf('t','u','v','ù','ú','û','ü','8'),
|
||||
Key.N9 to listOf('w','x','y','z','ý','þ','9')
|
||||
)
|
||||
}
|
@ -3,21 +3,17 @@ package net.mezimmah.wkt9.keypad
|
||||
import net.mezimmah.wkt9.exception.MissingLetterCode
|
||||
import java.lang.StringBuilder
|
||||
|
||||
class Keypad(
|
||||
private val letterLayout: Map<Key, List<Char>>,
|
||||
|
||||
numericLayout: Map<Key, Int>
|
||||
) {
|
||||
class Keypad {
|
||||
private val letterCodeMap: MutableMap<Char, Int> = mutableMapOf()
|
||||
|
||||
init {
|
||||
numericLayout.forEach { (key, code) ->
|
||||
KeyLayout.numeric.forEach { (key, code) ->
|
||||
indexKeyLetters(key, code)
|
||||
}
|
||||
}
|
||||
|
||||
private fun indexKeyLetters(key: Key, code: Int) {
|
||||
letterLayout[key]?.map { letter ->
|
||||
KeyLayout.chars[key]?.map { letter ->
|
||||
letterCodeMap[letter] = code
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ class Mappings(private val mappings: List<CommandMapping>) {
|
||||
event: Event,
|
||||
inputMode: InputMode,
|
||||
packageName: String,
|
||||
alt: Boolean = false,
|
||||
fn: Boolean = false,
|
||||
ctrl: Boolean = false,
|
||||
repeatCount: Int = 0,
|
||||
): MutableList<CommandMapping>? {
|
||||
@ -18,7 +18,7 @@ class Mappings(private val mappings: List<CommandMapping>) {
|
||||
((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.fn == fn) &&
|
||||
(it.ctrl == ctrl) &&
|
||||
((it.repeatCount == null) || (it.repeatCount == repeatCount))
|
||||
) commands.add(it)
|
||||
|
114
app/src/main/java/net/mezimmah/wkt9/layout/Words.kt
Normal file
114
app/src/main/java/net/mezimmah/wkt9/layout/Words.kt
Normal file
@ -0,0 +1,114 @@
|
||||
package net.mezimmah.wkt9.layout
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent.getActivity
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.View
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import net.mezimmah.wkt9.R
|
||||
import net.mezimmah.wkt9.WKT9IME
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
|
||||
class Words(context: Context, attributeSet: AttributeSet): HorizontalScrollView(context, attributeSet), View.OnClickListener, View.OnLongClickListener {
|
||||
private val tag = "WKT9"
|
||||
private var wkt9: WKT9IME
|
||||
private var wordCount: Int = 0
|
||||
private var current: Int = 0
|
||||
|
||||
var words: List<Word>? = null
|
||||
@SuppressLint("InflateParams")
|
||||
set (words) {
|
||||
val wordContainer = clear() ?: return
|
||||
|
||||
wordCount = 0
|
||||
|
||||
words?.forEach { word ->
|
||||
val view = wkt9.layoutInflater.inflate(R.layout.word, null) as TextView
|
||||
|
||||
view.text = word.word
|
||||
|
||||
view.setOnClickListener(this)
|
||||
view.setOnLongClickListener(this)
|
||||
wordContainer.addView(view)
|
||||
|
||||
wordCount++
|
||||
}
|
||||
|
||||
current = 0
|
||||
|
||||
field = words
|
||||
|
||||
select(current)
|
||||
}
|
||||
|
||||
init {
|
||||
wkt9 = if (context is ContextThemeWrapper) {
|
||||
context.baseContext as WKT9IME
|
||||
} else {
|
||||
context as WKT9IME
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(): LinearLayout? {
|
||||
val wordContainer = findViewById<LinearLayout>(R.id.words)
|
||||
|
||||
wordContainer?.removeAllViews()
|
||||
|
||||
return wordContainer
|
||||
}
|
||||
|
||||
fun next() {
|
||||
val next = if (current + 1 >= wordCount) 0 else current + 1
|
||||
|
||||
select(next)
|
||||
}
|
||||
|
||||
fun previous() {
|
||||
val previous = if (current == 0) wordCount - 1 else current - 1
|
||||
|
||||
select(previous)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val words = this.words ?: return
|
||||
val wordContainer = findViewById<LinearLayout>(R.id.words)
|
||||
|
||||
for (i in 0 until wordCount) {
|
||||
val child: View = wordContainer.getChildAt(i)
|
||||
|
||||
if (v != child) {
|
||||
child.background = null
|
||||
} else {
|
||||
child.background = ContextCompat.getDrawable(wkt9, R.drawable.button_radius)
|
||||
current = i
|
||||
|
||||
wkt9.onWordSelected(words[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLongClick(v: View?): Boolean {
|
||||
Log.d(tag, "We need to delete this word from the db")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun select(index: Int) {
|
||||
val wordContainer = findViewById<LinearLayout>(R.id.words)
|
||||
|
||||
for (i in 0 until wordCount) {
|
||||
val child: View = wordContainer.getChildAt(i)
|
||||
|
||||
if (i != index) continue
|
||||
|
||||
onClick(child)
|
||||
smoothScrollTo(child.left, 0)
|
||||
}
|
||||
}
|
||||
}
|
@ -34,13 +34,13 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
|
||||
preferenceScreen.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)
|
||||
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay_key))?.isChecked = Settings.canDrawOverlays(context)
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay))?.isChecked = Settings.canDrawOverlays(context)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay_key))?.isChecked = Settings.canDrawOverlays(context)
|
||||
findPreference<SwitchPreference>(getString(R.string.overlay))?.isChecked = Settings.canDrawOverlays(context)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@ -53,7 +53,7 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
this.key = key
|
||||
|
||||
when (key) {
|
||||
getString(R.string.speech_to_text_key) -> {
|
||||
getString(R.string.speech_to_text) -> {
|
||||
if (findPreference<SwitchPreference>(key)?.isChecked == true) {
|
||||
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
arrayOf(
|
||||
@ -66,7 +66,7 @@ class PreferencesFragment: PreferenceFragmentCompat(),
|
||||
}
|
||||
}
|
||||
|
||||
getString(R.string.overlay_key) -> {
|
||||
getString(R.string.overlay) -> {
|
||||
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
|
||||
|
||||
startActivity(intent)
|
||||
|
@ -7,96 +7,100 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.mezimmah.wkt9.WKT9
|
||||
import net.mezimmah.wkt9.WKT9IME
|
||||
import net.mezimmah.wkt9.dao.SettingDao
|
||||
import net.mezimmah.wkt9.dao.WordDao
|
||||
import net.mezimmah.wkt9.db.AppDatabase
|
||||
import net.mezimmah.wkt9.entity.Setting
|
||||
import net.mezimmah.wkt9.entity.Word
|
||||
import net.mezimmah.wkt9.exception.MissingLetterCode
|
||||
import net.mezimmah.wkt9.keypad.Keypad
|
||||
import java.util.Locale
|
||||
|
||||
class T9 (
|
||||
private val context: WKT9,
|
||||
private val keypad: Keypad,
|
||||
private val settingDao: SettingDao,
|
||||
private val wordDao: WordDao
|
||||
private val context: WKT9IME,
|
||||
private val locale: Locale,
|
||||
private val batchSize: Int = 1000
|
||||
) {
|
||||
// Debugging
|
||||
private val tag = "WKT9"
|
||||
|
||||
private val db: AppDatabase = AppDatabase.getInstance(context)
|
||||
private val wordDao: WordDao = db.getWordDao()
|
||||
private val settingDao: SettingDao = db.getSettingDao()
|
||||
|
||||
private val keypad: Keypad = Keypad()
|
||||
|
||||
// Coroutines
|
||||
private val job = SupervisorJob()
|
||||
private val scope = CoroutineScope(Dispatchers.IO + job)
|
||||
|
||||
fun initializeWords(locale: CharSequence) {
|
||||
fun initializeWords() {
|
||||
scope.launch {
|
||||
val setting = settingDao.getByKey("initialized")
|
||||
val key = locale.language.plus(":word:initialized")
|
||||
val setting = settingDao.getByKey(key)
|
||||
|
||||
if (setting == null) {
|
||||
Log.d(tag,"Initializing word database...")
|
||||
readWords(locale.toString())
|
||||
Setting.set("initialized", "t", settingDao)
|
||||
Log.d(tag,"Initializing '$locale' word database.")
|
||||
readWords()
|
||||
Setting.set(key, "check", settingDao)
|
||||
} else Log.d(tag, "Word database already initialized")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWord(word: String, weight: Int = 1, locale: String) = Word(
|
||||
private fun getWord(word: String, locale: String) = Word(
|
||||
word = word,
|
||||
code = keypad.getCodeForWord(word),
|
||||
length = word.length,
|
||||
weight = weight,
|
||||
weight = 1,
|
||||
locale = locale
|
||||
)
|
||||
|
||||
private fun readWords(locale: String) {
|
||||
val fileName = "$locale/words.txt"
|
||||
val batchSize = 1000
|
||||
val wordBatch = ArrayList<Word>(batchSize)
|
||||
private fun readBatch(batch: Sequence<String>) {
|
||||
batch.chunked(batchSize).forEach {
|
||||
readLines(it)
|
||||
}
|
||||
}
|
||||
|
||||
context.assets.open(fileName).bufferedReader().useLines { lines ->
|
||||
for (chunk in lines.chunked(batchSize)) {
|
||||
var arrayListIndex = 0
|
||||
private fun readLine(line: String): Word? {
|
||||
return try {
|
||||
getWord(
|
||||
word = line.trim(),
|
||||
locale = locale.language
|
||||
)
|
||||
} catch (e: MissingLetterCode) {
|
||||
Log.d(tag, "Character missing:", e)
|
||||
|
||||
wordBatch.clear()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
chunk.forEach { line ->
|
||||
val parts = line.split("\\s".toRegex())
|
||||
private fun readLines(lines: List<String>) {
|
||||
val words = mutableListOf<Word>()
|
||||
|
||||
try {
|
||||
wordBatch.add(
|
||||
arrayListIndex,
|
||||
lines.forEach { line ->
|
||||
readLine(line)?.also {
|
||||
words.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
when (parts.size) {
|
||||
// Emoji support
|
||||
3 -> Word(
|
||||
word = parts[0],
|
||||
code = parts[2],
|
||||
length = 1,
|
||||
weight = parts[1].toInt(),
|
||||
locale = locale
|
||||
)
|
||||
// Regular dictionary words
|
||||
else -> getWord(
|
||||
word = parts[0],
|
||||
weight = if (parts.size > 1) parts[1].toInt() else 0,
|
||||
locale = locale
|
||||
)
|
||||
}
|
||||
)
|
||||
insertWordBatch(words)
|
||||
}
|
||||
|
||||
arrayListIndex++
|
||||
} catch (ex: MissingLetterCode) {
|
||||
Log.w(tag, "Problem adding ${parts[0]}: " + ex.message)
|
||||
}
|
||||
}
|
||||
private fun readWords() {
|
||||
val fileName = locale.language.plus("/words.txt")
|
||||
|
||||
runBlocking {
|
||||
try {
|
||||
wordDao.insert(*wordBatch.toTypedArray())
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
Log.d(tag, "Oh, Brother!")
|
||||
}
|
||||
}
|
||||
context.assets.open(fileName).bufferedReader().useLines {
|
||||
readBatch(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertWordBatch(words: List<Word>) {
|
||||
runBlocking {
|
||||
try {
|
||||
wordDao.insert(*words.toTypedArray())
|
||||
} catch (e: SQLiteConstraintException) {
|
||||
Log.d(tag, "Oh, Brother!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,8 +11,8 @@ 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.inputmode.InputManager
|
||||
import net.mezimmah.wkt9.WKT9IME
|
||||
import net.mezimmah.wkt9.inputhandler.InputHandler
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.OkHttpClient
|
||||
@ -23,8 +23,8 @@ import java.io.IOException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class Whisper(
|
||||
private val context: WKT9,
|
||||
private val inputManager: InputManager,
|
||||
private val context: WKT9IME,
|
||||
private val inputHandler: InputHandler?,
|
||||
private val ui: View
|
||||
) {
|
||||
private val tag = "WKT9"
|
||||
@ -59,7 +59,7 @@ class Whisper(
|
||||
showCandidates()
|
||||
}
|
||||
|
||||
inputManager.handler?.onInsertText(transcription.plus(" "))
|
||||
// inputHandler?.onInsertText(transcription.plus(" "))
|
||||
} catch (e: IOException) {
|
||||
Log.d(tag, "A failure occurred in the communication with the speech-to-text server", e)
|
||||
}
|
||||
@ -89,33 +89,33 @@ class Whisper(
|
||||
}
|
||||
|
||||
private fun showCandidates() {
|
||||
val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
|
||||
candidatesView.visibility = View.VISIBLE
|
||||
loadingView.visibility = View.GONE
|
||||
messageView.visibility = View.GONE
|
||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
//
|
||||
// candidatesView.visibility = View.VISIBLE
|
||||
// loadingView.visibility = View.GONE
|
||||
// messageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun showMessage() {
|
||||
val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
|
||||
candidatesView.visibility = View.GONE
|
||||
loadingView.visibility = View.GONE
|
||||
messageView.visibility = View.VISIBLE
|
||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
//
|
||||
// candidatesView.visibility = View.GONE
|
||||
// loadingView.visibility = View.GONE
|
||||
// messageView.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun showTranscribing() {
|
||||
val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
|
||||
candidatesView.visibility = View.GONE
|
||||
loadingView.visibility = View.VISIBLE
|
||||
messageView.visibility = View.GONE
|
||||
// val candidatesView = ui.findViewById<HorizontalScrollView>(R.id.suggestion_container)
|
||||
// val loadingView = ui.findViewById<LinearLayout>(R.id.loading_container)
|
||||
// val messageView = ui.findViewById<LinearLayout>(R.id.message_container)
|
||||
//
|
||||
// candidatesView.visibility = View.GONE
|
||||
// loadingView.visibility = View.VISIBLE
|
||||
// messageView.visibility = View.GONE
|
||||
}
|
||||
|
||||
private fun stopRecording() {
|
||||
|
@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.884,30.784V1.216H14.153V4.501H4.724v9.301h8.363v3.2H4.724V30.784ZM16.951,9.024q1.579,-0.469 3.413,-0.768 1.835,-0.341 3.371,-0.341 1.664,0 3.029,0.469 1.365,0.427 2.304,1.493 0.981,1.067 1.493,2.901 0.555,1.792 0.555,4.48V30.784H27.447V17.557q0,-3.328 -0.853,-4.864 -0.853,-1.536 -3.2,-1.536 -1.237,0 -2.773,0.427V30.784H16.951Z"
|
||||
android:strokeWidth="0.815"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/number_input.xml
Normal file
9
app/src/main/res/drawable/number_input.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.303,7.366Q1.121,6.892 1.982,6.203 2.887,5.514 3.705,4.739 4.523,3.92 5.212,3.102 5.944,2.241 6.418,1.423L9.26,1.423L9.26,31.266L5.557,31.266L5.557,7.279Q4.825,8.055 3.834,8.744 2.887,9.433 1.81,10.036ZM17.701,3.274q2.541,-2.541 6.201,-2.541 3.488,0 5.34,1.852 1.852,1.809 1.852,5.685 0,1.809 -0.603,3.445 -0.603,1.636 -1.507,3.187 -0.904,1.507 -1.981,3.015 -1.077,1.507 -2.067,3.101 -0.99,1.55 -1.723,3.273 -0.732,1.68 -0.904,3.617l9.388,0L31.697,31.266L18.347,31.266l0,-0.818q0,-2.885 0.646,-5.125 0.689,-2.239 1.636,-4.048 0.99,-1.809 2.153,-3.359 1.163,-1.55 2.11,-3.015 0.99,-1.507 1.636,-3.058 0.689,-1.593 0.689,-3.488 0,-2.282 -0.904,-3.273 -0.904,-1.034 -2.713,-1.034 -1.249,0 -2.282,0.517Q20.328,5.04 19.51,5.815Z"/>
|
||||
</vector>
|
@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.948,7.445Q1.759,6.976 2.612,6.293 3.508,5.611 4.319,4.843 5.129,4.032 5.812,3.221 6.537,2.368 7.007,1.557H9.823V31.125H6.153V7.36Q5.428,8.128 4.447,8.811 3.508,9.493 2.441,10.091ZM17.185,3.392q2.517,-2.517 6.144,-2.517 3.456,0 5.291,1.835 1.835,1.792 1.835,5.632 0,1.792 -0.597,3.413 -0.597,1.621 -1.493,3.157 -0.896,1.493 -1.963,2.987 -1.067,1.493 -2.048,3.072 -0.981,1.536 -1.707,3.243 -0.725,1.664 -0.896,3.584h9.301v3.328H17.825v-0.811q0,-2.859 0.64,-5.077 0.683,-2.219 1.621,-4.011 0.981,-1.792 2.133,-3.328 1.152,-1.536 2.091,-2.987 0.981,-1.493 1.621,-3.029 0.683,-1.579 0.683,-3.456 0,-2.261 -0.896,-3.243 -0.896,-1.024 -2.688,-1.024 -1.237,0 -2.261,0.512 -0.981,0.469 -1.792,1.237z"
|
||||
android:strokeWidth="0.815"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_de_de_cap.xml
Normal file
9
app/src/main/res/drawable/word_de_de_cap.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m15.929,15.893q0,3.454 -0.748,5.876 -0.712,2.386 -2.101,3.882 -1.353,1.496 -3.312,2.172 -1.923,0.677 -4.309,0.677 -2.422,0 -5.128,-0.641L0.331,3.928Q1.685,3.643 2.967,3.465 4.249,3.287 5.424,3.287q2.422,0 4.344,0.677 1.959,0.677 3.312,2.172 1.389,1.496 2.101,3.917 0.748,2.386 0.748,5.84zM12.51,15.893q0,-2.635 -0.392,-4.487Q11.727,9.555 10.908,8.379 10.089,7.204 8.735,6.67 7.418,6.136 5.566,6.136q-0.499,0 -0.997,0.036 -0.499,0.036 -1.033,0.107L3.536,25.508q0.534,0.071 1.033,0.107 0.499,0.036 0.961,0.036 1.887,0 3.205,-0.534 1.318,-0.534 2.137,-1.674 0.855,-1.175 1.246,-3.027 0.392,-1.852 0.392,-4.523zM22.766,19.81q0.036,1.353 0.249,2.493 0.214,1.104 0.677,1.959 0.499,0.819 1.282,1.282 0.819,0.463 2.065,0.463 1.033,0 1.887,-0.285 0.89,-0.32 1.282,-0.534l0.57,2.457q-0.463,0.32 -1.567,0.677 -1.068,0.392 -2.564,0.392 -1.959,0 -3.312,-0.712 -1.318,-0.712 -2.172,-1.994 -0.819,-1.282 -1.175,-3.062 -0.356,-1.781 -0.356,-3.917 0,-5.164 1.709,-7.549 1.709,-2.386 4.523,-2.386 3.169,0 4.487,2.386 1.318,2.386 1.318,6.695 0,0.392 0,0.819 0,0.392 -0.036,0.819zM25.864,11.691q-1.531,0 -2.243,1.531 -0.677,1.531 -0.819,4.095l5.662,0q0,-2.6 -0.534,-4.095 -0.534,-1.531 -2.065,-1.531z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_de_de_lower.xml
Normal file
9
app/src/main/res/drawable/word_de_de_lower.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10.238,13.472Q9.101,12.845 7.808,12.845q-0.98,0 -1.725,0.392 -0.745,0.392 -1.294,1.294 -0.51,0.902 -0.784,2.391 -0.274,1.489 -0.274,3.685 0,4.233 1.098,6.076 1.137,1.803 3.41,1.803 0.431,0 0.941,-0.039 0.51,-0.078 1.058,-0.235zM10.238,1.125 L13.609,0.537L13.609,30.405q-1.098,0.47 -2.509,0.745 -1.411,0.314 -2.861,0.314 -4.233,0 -6.154,-2.705 -1.881,-2.705 -1.881,-8.153 0,-2.469 0.392,-4.429 0.431,-1.999 1.294,-3.371 0.902,-1.411 2.273,-2.156 1.372,-0.784 3.253,-0.784 0.902,0 1.529,0.196 0.666,0.157 1.294,0.47zM21.997,21.664q0.039,1.489 0.274,2.744 0.235,1.215 0.745,2.156 0.549,0.902 1.411,1.411 0.902,0.51 2.273,0.51 1.137,0 2.077,-0.314 0.98,-0.353 1.411,-0.588l0.627,2.705q-0.51,0.353 -1.725,0.745 -1.176,0.431 -2.822,0.431 -2.156,0 -3.645,-0.784 -1.45,-0.784 -2.391,-2.195 -0.902,-1.411 -1.294,-3.371 -0.392,-1.96 -0.392,-4.312 0,-5.684 1.881,-8.31 1.881,-2.626 4.978,-2.626 3.489,0 4.939,2.626 1.45,2.626 1.45,7.369 0,0.431 0,0.902 0,0.431 -0.039,0.902zM25.407,12.727q-1.685,0 -2.469,1.685 -0.745,1.685 -0.902,4.508l6.232,0q0,-2.861 -0.588,-4.508 -0.588,-1.685 -2.273,-1.685z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_de_de_upper.xml
Normal file
9
app/src/main/res/drawable/word_de_de_upper.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m15.516,16q0,3.353 -0.726,5.703 -0.691,2.316 -2.039,3.767 -1.313,1.452 -3.214,2.108 -1.866,0.657 -4.182,0.657 -2.35,0 -4.977,-0.622L0.377,4.387Q1.691,4.11 2.935,3.937 4.179,3.764 5.32,3.764q2.35,0 4.217,0.657 1.901,0.657 3.214,2.108 1.348,1.452 2.039,3.802 0.726,2.316 0.726,5.668zM12.198,16q0,-2.558 -0.38,-4.355Q11.438,9.848 10.643,8.707 9.848,7.566 8.534,7.048 7.255,6.529 5.458,6.529q-0.484,0 -0.968,0.035 -0.484,0.035 -1.002,0.104L3.488,25.332q0.518,0.069 1.002,0.104 0.484,0.035 0.933,0.035 1.832,0 3.111,-0.518 1.279,-0.518 2.074,-1.625 0.83,-1.141 1.21,-2.938 0.38,-1.797 0.38,-4.39zM20.32,27.994L20.32,4.041l10.749,0l0,2.661l-7.639,0l0,7.431l6.913,0l0,2.592l-6.913,0l0,8.606l8.192,0l0,2.661z"/>
|
||||
</vector>
|
@ -5,6 +5,5 @@
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M1.903,28.936V3.064H13.513V5.939H5.263v8.027h7.467v2.8H5.263v9.296H14.111v2.875zM17.703,9.896q1.381,-0.411 2.987,-0.672 1.605,-0.299 2.949,-0.299 1.456,0 2.651,0.411 1.195,0.373 2.016,1.307 0.859,0.933 1.307,2.539 0.485,1.568 0.485,3.92V28.936H26.887V17.363q0,-2.912 -0.747,-4.256 -0.747,-1.344 -2.8,-1.344 -1.083,0 -2.427,0.373v16.8h-3.211z"
|
||||
android:strokeWidth="0.815"/>
|
||||
android:pathData="M0.42,29.993L0.42,2.007L12.979,2.007L12.979,5.117L4.055,5.117l0,8.682l8.077,0l0,3.029L4.055,16.828l0,10.055l9.571,0L13.626,29.993ZM18.593,9.397q1.494,-0.444 3.231,-0.727 1.736,-0.323 3.19,-0.323 1.575,0 2.867,0.444 1.292,0.404 2.181,1.413 0.929,1.01 1.413,2.746 0.525,1.696 0.525,4.24l0,12.802l-3.473,0l0,-12.519q0,-3.15 -0.808,-4.604 -0.808,-1.454 -3.029,-1.454 -1.171,0 -2.625,0.404L22.066,29.993l-3.473,0z"/>
|
||||
</vector>
|
||||
|
@ -5,6 +5,5 @@
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m4.908,16.952q0.037,1.419 0.261,2.613 0.224,1.157 0.709,2.053 0.523,0.859 1.344,1.344 0.859,0.485 2.165,0.485 1.083,0 1.979,-0.299 0.933,-0.336 1.344,-0.56l0.597,2.576q-0.485,0.336 -1.643,0.709 -1.12,0.411 -2.688,0.411 -2.053,0 -3.472,-0.747 -1.381,-0.747 -2.277,-2.091 -0.859,-1.344 -1.232,-3.211 -0.373,-1.867 -0.373,-4.107 0,-5.413 1.792,-7.915 1.792,-2.501 4.741,-2.501 3.323,0 4.704,2.501 1.381,2.501 1.381,7.019 0,0.411 0,0.859 0,0.411 -0.037,0.859zM8.156,8.44q-1.605,0 -2.352,1.605 -0.709,1.605 -0.859,4.293h5.936q0,-2.725 -0.56,-4.293Q9.761,8.44 8.156,8.44ZM17.983,6.76q1.381,-0.411 2.987,-0.672 1.605,-0.299 2.949,-0.299 1.456,0 2.651,0.411 1.195,0.373 2.016,1.307 0.859,0.933 1.307,2.539 0.485,1.568 0.485,3.92V25.8H27.167V14.227q0,-2.912 -0.747,-4.256 -0.747,-1.344 -2.8,-1.344 -1.083,0 -2.427,0.373V25.8H17.983Z"
|
||||
android:strokeWidth="0.815"/>
|
||||
android:pathData="m3.75,17.006q0.039,1.499 0.276,2.762 0.237,1.223 0.75,2.17 0.552,0.907 1.42,1.42 0.907,0.513 2.288,0.513 1.144,0 2.091,-0.316 0.986,-0.355 1.42,-0.592l0.631,2.722q-0.513,0.355 -1.736,0.75 -1.184,0.434 -2.841,0.434 -2.17,0 -3.669,-0.789Q2.922,25.291 1.975,23.871 1.068,22.45 0.673,20.478q-0.395,-1.973 -0.395,-4.34 0,-5.72 1.894,-8.364 1.894,-2.643 5.01,-2.643 3.511,0 4.971,2.643 1.46,2.643 1.46,7.417 0,0.434 0,0.907 0,0.434 -0.039,0.907zM7.183,8.011q-1.696,0 -2.485,1.696Q3.947,11.404 3.79,14.244l6.273,0q0,-2.88 -0.592,-4.537Q8.879,8.011 7.183,8.011ZM18.624,6.236q1.46,-0.434 3.156,-0.71 1.696,-0.316 3.117,-0.316 1.539,0 2.801,0.434 1.262,0.395 2.13,1.381 0.907,0.986 1.381,2.683 0.513,1.657 0.513,4.142l0,12.506l-3.393,0l0,-12.23q0,-3.077 -0.789,-4.497 -0.789,-1.42 -2.959,-1.42 -1.144,0 -2.564,0.395L22.016,26.356L18.624,26.356Z"/>
|
||||
</vector>
|
||||
|
@ -5,6 +5,5 @@
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.279,28.936L0.279,3.064L11.889,3.064L11.889,5.939L3.639,5.939L3.639,13.965L11.105,13.965v2.8L3.639,16.765v9.296h8.848v2.875zM28.996,28.936Q28.1,26.733 26.943,24.195 25.823,21.619 24.553,19.043 23.321,16.429 22.015,13.891 20.708,11.315 19.439,9.112L19.439,28.936L16.303,28.936L16.303,3.064h2.8q1.419,2.315 2.725,4.741 1.344,2.427 2.539,4.816 1.195,2.352 2.24,4.667 1.083,2.277 1.979,4.331L28.585,3.064h3.136L31.721,28.936Z"
|
||||
android:strokeWidth="0.815"/>
|
||||
android:pathData="M0.25,28.56L0.25,3.44L11.523,3.44L11.523,6.231L3.512,6.231l0,7.793l7.25,0l0,2.719L3.512,16.743l0,9.026l8.591,0l0,2.791zM29.104,28.56Q28.234,26.422 27.11,23.957 26.023,21.455 24.79,18.954 23.594,16.417 22.325,13.952 21.057,11.451 19.824,9.312L19.824,28.56L16.779,28.56L16.779,3.44l2.719,0q1.377,2.247 2.646,4.604 1.305,2.356 2.465,4.676 1.16,2.284 2.175,4.531 1.051,2.211 1.921,4.205L28.705,3.44l3.045,0L31.75,28.56Z"/>
|
||||
</vector>
|
||||
|
9
app/src/main/res/drawable/word_es_es_cap.xml
Normal file
9
app/src/main/res/drawable/word_es_es_cap.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.626,31.218L0.626,0.2L14.545,0.2L14.545,3.647L4.654,3.647l0,9.623l8.952,0l0,3.357L4.654,16.627l0,11.145l10.608,0l0,3.446zM24.034,28.488q1.701,0 2.551,-0.94 0.895,-0.985 0.895,-2.551 0,-0.985 -0.358,-1.656 -0.358,-0.716 -0.94,-1.253 -0.582,-0.537 -1.343,-0.94 -0.761,-0.448 -1.522,-0.94Q22.557,19.76 21.841,19.178 21.125,18.551 20.543,17.79 20.006,16.985 19.648,16 19.334,14.971 19.334,13.628q0,-2.909 1.79,-4.7 1.835,-1.79 4.923,-1.79 1.298,0 2.462,0.358 1.164,0.313 1.925,0.716l-0.85,3.088q-0.806,-0.448 -1.611,-0.671 -0.806,-0.224 -1.746,-0.224 -1.432,0 -2.283,0.85 -0.85,0.806 -0.85,2.372 0,0.895 0.313,1.567 0.313,0.627 0.806,1.164 0.537,0.492 1.164,0.94 0.671,0.448 1.388,0.85 0.85,0.492 1.656,1.074 0.85,0.582 1.477,1.388 0.671,0.761 1.074,1.835 0.403,1.029 0.403,2.506 0,3.088 -1.835,4.968 -1.79,1.88 -5.281,1.88 -1.79,0 -3.223,-0.492Q19.603,30.815 18.887,30.412l0.806,-3.178q0.671,0.358 1.79,0.806 1.164,0.448 2.551,0.448z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_es_es_lower.xml
Normal file
9
app/src/main/res/drawable/word_es_es_lower.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m4.229,17.141q0.045,1.701 0.313,3.133 0.269,1.388 0.85,2.462 0.627,1.029 1.611,1.611 1.029,0.582 2.596,0.582 1.298,0 2.372,-0.358 1.119,-0.403 1.611,-0.671l0.716,3.088q-0.582,0.403 -1.969,0.85 -1.343,0.492 -3.223,0.492 -2.462,0 -4.163,-0.895Q3.289,26.541 2.214,24.929 1.185,23.318 0.737,21.08q-0.448,-2.238 -0.448,-4.923 0,-6.49 2.148,-9.489 2.148,-2.999 5.684,-2.999 3.983,0 5.64,2.999 1.656,2.999 1.656,8.415 0,0.492 0,1.029 0,0.492 -0.045,1.029zM8.123,6.936q-1.925,0 -2.82,1.925Q4.452,10.786 4.273,14.008l7.117,0q0,-3.267 -0.671,-5.147Q10.047,6.936 8.123,6.936ZM24.37,25.019q1.701,0 2.551,-0.94 0.895,-0.985 0.895,-2.551 0,-0.985 -0.358,-1.656 -0.358,-0.716 -0.94,-1.253 -0.582,-0.537 -1.343,-0.94 -0.761,-0.448 -1.522,-0.94Q22.893,16.291 22.177,15.709 21.461,15.082 20.879,14.322 20.342,13.516 19.983,12.531 19.67,11.502 19.67,10.159q0,-2.909 1.79,-4.7 1.835,-1.79 4.923,-1.79 1.298,0 2.462,0.358 1.164,0.313 1.925,0.716l-0.85,3.088q-0.806,-0.448 -1.611,-0.671 -0.806,-0.224 -1.746,-0.224 -1.432,0 -2.283,0.85 -0.85,0.806 -0.85,2.372 0,0.895 0.313,1.567 0.313,0.627 0.806,1.164 0.537,0.492 1.164,0.94 0.671,0.448 1.388,0.85 0.85,0.492 1.656,1.074 0.85,0.582 1.477,1.388 0.671,0.761 1.074,1.835 0.403,1.029 0.403,2.506 0,3.088 -1.835,4.968 -1.79,1.88 -5.281,1.88 -1.79,0 -3.223,-0.492Q19.939,27.346 19.223,26.943l0.806,-3.178q0.671,0.358 1.79,0.806 1.164,0.448 2.551,0.448z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_es_es_upper.xml
Normal file
9
app/src/main/res/drawable/word_es_es_upper.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M0.309,30.539L0.309,1.503L13.339,1.503L13.339,4.729L4.08,4.729l0,9.008l8.38,0l0,3.142L4.08,16.88l0,10.433l9.93,0L14.01,30.539ZM23.018,27.941q2.262,0 3.478,-1.257 1.257,-1.257 1.257,-3.394 0,-1.131 -0.377,-2.011 -0.377,-0.88 -1.006,-1.592 -0.587,-0.712 -1.383,-1.299 -0.796,-0.587 -1.634,-1.173 -0.964,-0.67 -2.011,-1.425 -1.047,-0.754 -1.927,-1.76Q18.577,13.025 18.032,11.684 17.487,10.344 17.487,8.542q0,-1.76 0.587,-3.184 0.628,-1.425 1.676,-2.43 1.089,-1.006 2.556,-1.55 1.508,-0.545 3.226,-0.545 1.676,0 3.1,0.419 1.425,0.419 2.388,1.006L29.847,5.232Q28.967,4.646 27.92,4.352 26.914,4.017 25.783,4.017q-2.053,0 -3.31,1.131 -1.215,1.089 -1.215,3.226 0,1.173 0.377,2.053 0.419,0.88 1.047,1.592 0.67,0.712 1.508,1.299 0.88,0.587 1.844,1.215 0.964,0.628 1.969,1.383 1.006,0.754 1.802,1.76 0.838,0.964 1.341,2.304 0.545,1.299 0.545,3.1 0,1.634 -0.503,3.1 -0.503,1.466 -1.592,2.598 -1.047,1.089 -2.681,1.76 -1.592,0.628 -3.771,0.628 -2.011,0 -3.519,-0.461Q18.116,30.245 16.943,29.491L18.116,26.475q1.089,0.67 2.221,1.089 1.173,0.377 2.681,0.377z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_nl_nl_cap.xml
Normal file
9
app/src/main/res/drawable/word_nl_nl_cap.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15.158,31.364Q14.195,28.997 12.951,26.269 11.748,23.501 10.384,20.734 9.06,17.926 7.656,15.198 6.252,12.43 4.888,10.063L4.888,31.364L1.519,31.364L1.519,3.564L4.527,3.564Q6.052,6.052 7.456,8.659 8.9,11.266 10.183,13.834 11.467,16.361 12.59,18.848 13.754,21.295 14.716,23.501L14.716,3.564L18.086,3.564L18.086,31.364ZM30.04,31.765Q27.072,31.685 25.788,30.321 24.504,28.917 24.504,25.908L24.504,0.837L27.954,0.235L27.954,25.989q0,1.404 0.562,2.046 0.562,0.602 1.966,0.842z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_nl_nl_lower.xml
Normal file
9
app/src/main/res/drawable/word_nl_nl_lower.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M3.39,10.908Q4.873,10.467 6.598,10.186 8.322,9.865 9.765,9.865q1.564,0 2.847,0.441 1.283,0.401 2.165,1.403 0.922,1.002 1.403,2.727 0.521,1.684 0.521,4.21L16.702,31.357L13.253,31.357l0,-12.43q0,-3.127 -0.802,-4.571 -0.802,-1.443 -3.007,-1.443 -1.163,0 -2.606,0.401L6.838,31.357L3.39,31.357ZM28.169,31.758q-2.967,-0.08 -4.25,-1.443 -1.283,-1.403 -1.283,-4.411L22.636,0.844L26.084,0.242L26.084,25.984q0,1.403 0.561,2.045 0.561,0.601 1.965,0.842z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_nl_nl_upper.xml
Normal file
9
app/src/main/res/drawable/word_nl_nl_upper.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12.297,28.221Q11.45,26.14 10.357,23.741 9.299,21.308 8.1,18.874 6.936,16.406 5.702,14.007 4.467,11.574 3.268,9.493L3.268,28.221L0.305,28.221L0.305,3.779L2.951,3.779Q4.291,5.966 5.525,8.259 6.795,10.551 7.923,12.808 9.052,15.03 10.04,17.217 11.062,19.368 11.909,21.308L11.909,3.779L14.871,3.779L14.871,28.221ZM31.695,25.505L31.695,28.221L20.797,28.221L20.797,3.779l3.174,0L23.971,25.505Z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_pt_pt_cap.xml
Normal file
9
app/src/main/res/drawable/word_pt_pt_cap.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m0.378,1.041q3.093,-0.751 6.054,-0.751 2.298,0 4.154,0.575 1.9,0.53 3.226,1.724 1.37,1.149 2.077,3.005 0.751,1.856 0.751,4.463 0,2.652 -0.751,4.552 -0.751,1.856 -2.121,3.049 -1.37,1.149 -3.314,1.679 -1.9,0.53 -4.243,0.53L4.355,19.867l0,11.358l-3.977,0zM4.355,16.376l1.591,0q1.503,0 2.696,-0.309 1.237,-0.354 2.077,-1.105 0.84,-0.751 1.282,-1.944 0.442,-1.193 0.442,-2.961 0,-1.768 -0.442,-2.961Q11.559,5.858 10.763,5.151 10.012,4.399 8.907,4.09 7.802,3.781 6.476,3.781q-1.149,0 -2.121,0.133zM25.082,8.023l5.833,0L30.915,11.293l-5.833,0l0,12.772q0,2.386 0.84,3.359 0.884,0.928 2.342,0.928 0.751,0 1.414,-0.221 0.707,-0.221 1.193,-0.53l0.751,3.049q-1.812,1.061 -3.845,1.061 -3.27,0 -4.905,-1.812 -1.591,-1.856 -1.591,-6.099L21.281,1.792L25.082,1.129Z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_pt_pt_lower.xml
Normal file
9
app/src/main/res/drawable/word_pt_pt_lower.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m6.431,22.172q1.059,0.584 2.264,0.584 0.913,0 1.607,-0.365 0.694,-0.365 1.169,-1.205 0.511,-0.84 0.767,-2.228 0.256,-1.388 0.256,-3.433 0,-3.945 -1.023,-5.625 -0.986,-1.717 -3.178,-1.717 -0.402,0 -0.877,0.073 -0.475,0.037 -0.986,0.183zM6.431,31.815L3.29,31.815L3.29,6.394Q4.312,5.956 5.591,5.7 6.906,5.408 8.257,5.408q2.009,0 3.433,0.694 1.461,0.694 2.337,2.009 0.913,1.278 1.315,3.178 0.438,1.863 0.438,4.273 0,4.858 -1.571,7.414 -1.534,2.557 -5.04,2.557 -0.84,0 -1.497,-0.183 -0.657,-0.146 -1.242,-0.438zM23.305,5.883l4.821,0L28.126,8.586l-4.821,0l0,10.555q0,1.972 0.694,2.776 0.73,0.767 1.936,0.767 0.621,0 1.169,-0.183 0.584,-0.183 0.986,-0.438l0.621,2.52q-1.497,0.877 -3.178,0.877 -2.703,0 -4.054,-1.497 -1.315,-1.534 -1.315,-5.04L20.164,0.733L23.305,0.185Z"/>
|
||||
</vector>
|
9
app/src/main/res/drawable/word_pt_pt_upper.xml
Normal file
9
app/src/main/res/drawable/word_pt_pt_upper.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportWidth="32"
|
||||
android:viewportHeight="32">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="m0.267,3.112q2.709,-0.658 5.302,-0.658 2.013,0 3.638,0.503 1.664,0.464 2.825,1.509 1.2,1.006 1.819,2.632 0.658,1.626 0.658,3.909 0,2.322 -0.658,3.986 -0.658,1.626 -1.858,2.67 -1.2,1.006 -2.903,1.471 -1.664,0.464 -3.715,0.464L3.751,19.599l0,9.947l-3.483,0zM3.751,16.542l1.393,0q1.316,0 2.361,-0.271 1.084,-0.31 1.819,-0.968 0.735,-0.658 1.122,-1.703 0.387,-1.045 0.387,-2.593 0,-1.548 -0.387,-2.593Q10.059,7.331 9.362,6.711 8.705,6.053 7.737,5.783 6.769,5.512 5.608,5.512q-1.006,0 -1.858,0.116zM31.733,2.725L31.733,5.705L25.772,5.705L25.772,29.546L22.289,29.546L22.289,5.705L16.329,5.705L16.329,2.725Z"/>
|
||||
</vector>
|
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/current_suggestion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/suggestion_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_radius"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:minWidth="40dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="500" />
|
||||
</LinearLayout>
|
@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_gravity="bottom"
|
||||
android:theme="@style/Theme.AppCompat.DayNight"
|
||||
android:gravity="bottom"
|
||||
android:background="@color/black"
|
||||
android:orientation="vertical">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/suggestion_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/suggestions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/loading_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="2dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="400"
|
||||
android:text="Transcribing, please wait..." />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/message_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="2dp"
|
||||
android:src="@drawable/mic" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="400"
|
||||
android:text="Transcribing, please wait..." />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
@ -1,16 +0,0 @@
|
||||
<?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>
|
10
app/src/main/res/layout/word.xml
Normal file
10
app/src/main/res/layout/word.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:textColor="@color/suggestion_text"
|
||||
android:paddingVertical="5dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textSize="20sp"
|
||||
android:textFontWeight="400" />
|
17
app/src/main/res/layout/words.xml
Normal file
17
app/src/main/res/layout/words.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<net.mezimmah.wkt9.layout.Words xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_gravity="bottom"
|
||||
android:theme="@style/Theme.WKT9"
|
||||
android:gravity="bottom"
|
||||
android:background="@color/black">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/words"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="44dp"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
</net.mezimmah.wkt9.layout.Words>
|
@ -3,21 +3,10 @@
|
||||
|
||||
<string name="app_preferences_name">WKT9 Preferences</string>
|
||||
|
||||
<!-- Preference categories -->
|
||||
<string name="speech_to_text_cat">Speech to Text</string>
|
||||
<string name="overlay_cat">Speech to Text</string>
|
||||
|
||||
<string name="speech_to_text_key">speech_to_text</string>
|
||||
<string name="speech_to_text_title">Enable Speech to Text</string>
|
||||
<string name="speech_to_text_summary">For this feature to work WKT9 needs permission to show notifications and record audio. You will be asked to grant these permissions if you haven\'t already granted it.</string>
|
||||
|
||||
<string name="whisper_url_key">whisper_url</string>
|
||||
<string name="whisper_url_title">Whisper Server URL</string>
|
||||
<string name="whisper_url_summary">Provide an URL to the Whisper server.</string>
|
||||
|
||||
<string name="overlay_key">Draw over other activities</string>
|
||||
<string name="overlay_title">Draw over activities</string>
|
||||
<string name="overlay_summary">Grant WKT9 permission to draw over other applications</string>
|
||||
<string name="speech_to_text">speech_to_text</string>
|
||||
<string name="whisper_url">whisper_url</string>
|
||||
<string name="overlay">overlay</string>
|
||||
<string name="compose_timeout">compose_timeout</string>
|
||||
|
||||
<string-array name="input_mode_numeric">
|
||||
<item>org.linphone</item>
|
||||
@ -26,4 +15,20 @@
|
||||
<string-array name="camera_apps">
|
||||
<item>com.android.camera2</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="timeout_keys">
|
||||
<item>Very short</item>
|
||||
<item>Short</item>
|
||||
<item>Medium</item>
|
||||
<item>Long</item>
|
||||
<item>Very long</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="timeout_values">
|
||||
<item>300</item>
|
||||
<item>400</item>
|
||||
<item>600</item>
|
||||
<item>800</item>
|
||||
<item>900</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -8,4 +8,28 @@
|
||||
android:languageTag="en-US"
|
||||
android:imeSubtypeMode="keyboard" />
|
||||
|
||||
<subtype
|
||||
android:label="Dutch NL"
|
||||
android:imeSubtypeLocale="nl_NL"
|
||||
android:languageTag="nl-NL"
|
||||
android:imeSubtypeMode="keyboard" />
|
||||
|
||||
<subtype
|
||||
android:label="Spanish ES"
|
||||
android:imeSubtypeLocale="es_ES"
|
||||
android:languageTag="es-ES"
|
||||
android:imeSubtypeMode="keyboard" />
|
||||
|
||||
<subtype
|
||||
android:label="German DE"
|
||||
android:imeSubtypeLocale="de_DE"
|
||||
android:languageTag="de-DE"
|
||||
android:imeSubtypeMode="keyboard" />
|
||||
|
||||
<subtype
|
||||
android:label="Portuguese PT"
|
||||
android:imeSubtypeLocale="pt_PT"
|
||||
android:languageTag="pt-PT"
|
||||
android:imeSubtypeMode="keyboard" />
|
||||
|
||||
</input-method>
|
||||
|
@ -3,26 +3,37 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/speech_to_text_cat" />
|
||||
app:title="Speech to text" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/speech_to_text_key"
|
||||
app:title="@string/speech_to_text_title"
|
||||
app:summary="@string/speech_to_text_summary" />
|
||||
app:key="@string/speech_to_text"
|
||||
app:title="Enable speech to text"
|
||||
app:summary="Grant WKT9 access to the microphone." />
|
||||
|
||||
<EditTextPreference
|
||||
app:key="@string/whisper_url_key"
|
||||
app:title="@string/whisper_url_title"
|
||||
app:summary="@string/whisper_url_summary"
|
||||
app:dependency="@string/speech_to_text_key" />
|
||||
app:key="@string/whisper_url"
|
||||
app:title="Whisper server URL"
|
||||
app:summary="URL of server that transcribes the recording"
|
||||
app:dependency="speech_to_text" />
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="@string/overlay_cat" />
|
||||
app:title="Start other activities" />
|
||||
|
||||
<SwitchPreference
|
||||
app:key="@string/overlay_key"
|
||||
app:title="@string/overlay_title"
|
||||
app:summary="@string/overlay_summary" />
|
||||
app:key="@string/overlay"
|
||||
app:title="Start other activities"
|
||||
app:summary="Permit WKT9 to start other activities, like, for example, the dialer." />
|
||||
|
||||
<PreferenceCategory
|
||||
app:title="General settings" />
|
||||
|
||||
<DropDownPreference
|
||||
app:key="@string/compose_timeout"
|
||||
app:title="Compose timeout"
|
||||
app:summary="Time before a character gets committed to the editor."
|
||||
app:entries="@array/timeout_keys"
|
||||
app:entryValues="@array/timeout_values"
|
||||
app:defaultValue="400" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
||||
|
Loading…
x
Reference in New Issue
Block a user