Skip to content
Snippets Groups Projects
Commit 8772ae4b authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement keyboard support for autocomplete

parent 91f6049e
No related branches found
No related tags found
No related merge requests found
......@@ -110,10 +110,31 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
}
}
private val lastWord = BehaviorSubject.createDefault("")
private val lastWord = BehaviorSubject.createDefault(Pair("", IntRange.EMPTY))
private val textWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
lastWord.onNext(s?.lastWord(chatline.selectionStart, onlyBeforeCursor = true).toString())
val previous = autocompletionState
val next = if (previous != null && s != null) {
val suffix = if (previous.range.start == 0) ": " else " "
val end = Math.min(
s.length, previous.range.start + previous.completion.name.length + suffix.length
)
val sequence = s.subSequence(previous.range.start, end)
if (sequence == previous.completion.name + suffix) {
previous.originalWord to (previous.range.start until end)
} else {
autocompletionState = null
s.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
s.substring(indices) to indices
}
}
} else {
s?.lastWordIndices(chatline.selectionStart, onlyBeforeCursor = true)?.let { indices ->
s.substring(indices) to indices
}
}
if (next != null) lastWord.onNext(next)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
......@@ -177,12 +198,20 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
}.fold(0, Int::or)
chatline.setOnKeyListener { _, keyCode, event ->
if (event.hasNoModifiers() && (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER)) {
when (keyCode) {
KeyEvent.KEYCODE_ENTER,
KeyEvent.KEYCODE_NUMPAD_ENTER -> if (event.hasNoModifiers()) {
send()
true
} else {
false
}
KeyEvent.KEYCODE_TAB -> {
autoComplete(event.isShiftPressed)
true
}
else -> false
}
}
viewModel.getBuffer().observe(
......@@ -243,6 +272,60 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
editorPanel.panelState = SlidingUpPanelLayout.PanelState.COLLAPSED
}
data class AutoCompletionState(
val originalWord: String,
val range: IntRange,
val lastCompletion: AutoCompleteAdapter.AutoCompleteItem? = null,
val completion: AutoCompleteAdapter.AutoCompleteItem
)
private var autocompletionState: AutoCompletionState? = null
private fun autoComplete(reverse: Boolean = false) {
val originalWord = lastWord.value
val previous = autocompletionState
if (!originalWord.second.isEmpty()) {
val autoCompletedWords = viewModel.autoCompleteData.value.orEmpty()
if (previous != null && lastWord.value == previous.originalWord to previous.range) {
val previousIndex = autoCompletedWords.indexOf(previous.completion)
val autoCompletedWord = if (previousIndex != -1) {
val change = if (reverse) -1 else +1
val newIndex = (previousIndex + change + autoCompletedWords.size) % autoCompletedWords.size
autoCompletedWords[newIndex]
} else {
autoCompletedWords.firstOrNull()
}
if (autoCompletedWord != null) {
val newState = AutoCompletionState(
previous.originalWord,
originalWord.second,
previous.completion,
autoCompletedWord
)
inputEditor.autoComplete(newState)
autocompletionState = newState
} else {
autocompletionState = null
}
} else {
val autoCompletedWord = autoCompletedWords.firstOrNull()
if (autoCompletedWord != null) {
val newState = AutoCompletionState(
originalWord.first,
originalWord.second,
null,
autoCompletedWord
)
inputEditor.autoComplete(newState)
autocompletionState = newState
} else {
autocompletionState = null
}
}
}
}
private fun send() {
val text = chatline.text
if (text.isNotBlank()) {
......
......@@ -223,6 +223,26 @@ class InputEditor(private val editText: EditText) {
}
}
fun autoComplete(item: ChatActivity.AutoCompletionState) {
val suffix = if (item.range.start == 0) ": " else " "
val replacement = "${item.completion.name}$suffix"
val previousReplacement = item.lastCompletion?.let { "${item.lastCompletion.name}$suffix" }
if (previousReplacement != null &&
editText.text.length >= item.range.start + previousReplacement.length &&
editText.text.substring(
item.range.start, item.range.start + previousReplacement.length
) == previousReplacement) {
editText.text.replace(
item.range.start, item.range.start + previousReplacement.length, replacement
)
editText.setSelection(item.range.start + replacement.length)
} else {
editText.text.replace(item.range.start, item.range.endInclusive + 1, replacement)
editText.setSelection(item.range.start + replacement.length)
}
}
fun share(text: CharSequence?) {
editText.setText(text)
editText.setSelection(editText.text.length)
......
......@@ -185,18 +185,18 @@ class QuasselViewModel : ViewModel() {
}
}
val lastWord = MutableLiveData<Observable<String>>()
val lastWord = MutableLiveData<Observable<Pair<String, IntRange>>>()
val autoCompleteData: LiveData<List<AutoCompleteAdapter.AutoCompleteItem>?> = session.zip(
buffer, lastWord
).switchMapRx { (session, id, lastWordWrapper) ->
lastWordWrapper
.distinctUntilChanged()
.debounce(300, TimeUnit.MILLISECONDS)
.debounce(16, TimeUnit.MILLISECONDS)
.switchMap { lastWord ->
val bufferSyncer = session?.bufferSyncer
val bufferInfo = bufferSyncer?.bufferInfo(id)
if (bufferSyncer != null && lastWord.length >= 3) {
if (bufferSyncer != null && lastWord.second.length >= 3) {
bufferSyncer.liveBufferInfos().switchMap { infos ->
if (bufferInfo?.type?.hasFlag(
Buffer_Type.ChannelBuffer
......@@ -270,7 +270,7 @@ class QuasselViewModel : ViewModel() {
.filter {
it.name.trimStart(*ignoredStartingCharacters)
.startsWith(
lastWord.trimStart(*ignoredStartingCharacters),
lastWord.first.trimStart(*ignoredStartingCharacters),
ignoreCase = true
)
}.sorted()
......
package de.kuschku.quasseldroid_ng.util.helper
val IntProgression.length: Int
get() = this.last + 1 - this.first
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment