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

Significantly improved autocomplete performance

parent 333e0479
Branches
Tags
No related merge requests found
...@@ -115,6 +115,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc ...@@ -115,6 +115,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
editor = Editor( editor = Editor(
this, this,
viewModel.rawAutoCompleteData,
viewModel.autoCompleteData, viewModel.autoCompleteData,
viewModel.lastWord, viewModel.lastWord,
findViewById(R.id.chatline), findViewById(R.id.chatline),
......
...@@ -17,7 +17,12 @@ import android.view.* ...@@ -17,7 +17,12 @@ import android.view.*
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import butterknife.BindView import butterknife.BindView
import butterknife.ButterKnife import butterknife.ButterKnife
import de.kuschku.libquassel.protocol.Buffer_Type
import de.kuschku.libquassel.quassel.syncables.IrcChannel
import de.kuschku.libquassel.session.ISession
import de.kuschku.libquassel.util.IrcUserUtils import de.kuschku.libquassel.util.IrcUserUtils
import de.kuschku.libquassel.util.Optional
import de.kuschku.libquassel.util.flag.hasFlag
import de.kuschku.libquassel.util.helpers.value import de.kuschku.libquassel.util.helpers.value
import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.R
import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.AppearanceSettings
...@@ -30,14 +35,15 @@ import de.kuschku.quasseldroid.util.ui.ColorChooserDialog ...@@ -30,14 +35,15 @@ import de.kuschku.quasseldroid.util.ui.ColorChooserDialog
import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange
import de.kuschku.quasseldroid.util.ui.TextDrawable import de.kuschku.quasseldroid.util.ui.TextDrawable
import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem
import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
import java.util.concurrent.TimeUnit
class Editor( class Editor(
// Contexts // Contexts
activity: AppCompatActivity, activity: AppCompatActivity,
// LiveData // LiveData
private val autoCompleteDataRaw: Observable<Triple<Optional<ISession>, Int, Pair<String, IntRange>>>,
private val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>>, private val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>>,
lastWordContainer: BehaviorSubject<Observable<Pair<String, IntRange>>>, lastWordContainer: BehaviorSubject<Observable<Pair<String, IntRange>>>,
// Views // Views
...@@ -165,8 +171,7 @@ class Editor( ...@@ -165,8 +171,7 @@ class Editor(
formatHandler::autoComplete formatHandler::autoComplete
) )
autoCompleteData.debounce(300, TimeUnit.MILLISECONDS) autoCompleteData.toLiveData().observe(activity, Observer {
.toLiveData().observe(activity, Observer {
val query = it?.first ?: "" val query = it?.first ?: ""
val shouldShowResults = (autoCompleteSettings.auto && query.length >= 3) || val shouldShowResults = (autoCompleteSettings.auto && query.length >= 3) ||
(autoCompleteSettings.prefix && query.startsWith('@')) || (autoCompleteSettings.prefix && query.startsWith('@')) ||
...@@ -452,12 +457,79 @@ class Editor( ...@@ -452,12 +457,79 @@ class Editor(
chatline.setSelection(selectionStart, selectionEnd) chatline.setSelection(selectionStart, selectionEnd)
} }
private fun autoCompleteDataFull(): List<AutoCompleteItem> {
return autoCompleteDataRaw.value?.let { (sessionOptional, id, lastWord) ->
val session = sessionOptional.orNull()
val bufferInfo = session?.bufferSyncer?.bufferInfo(id)
session?.networks?.let { networks ->
session.bufferSyncer?.bufferInfos()?.let { infos ->
if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
val network = networks[bufferInfo.networkId]
network?.ircChannel(
bufferInfo.bufferName
)?.let { ircChannel ->
val users = ircChannel.ircUsers()
val buffers = infos
.filter {
it.type.toInt() == Buffer_Type.ChannelBuffer.toInt()
}.mapNotNull { info ->
networks[info.networkId]?.let { info to it }
}.map { (info, network) ->
val channel = network.ircChannel(info.bufferName) ?: IrcChannel.NULL
AutoCompleteItem.ChannelItem(
info = info,
network = network.networkInfo(),
bufferStatus = when (channel) {
IrcChannel.NULL -> BufferStatus.OFFLINE
else -> BufferStatus.ONLINE
},
description = channel.topic()
)
}
val nicks = users.map { user ->
val userModes = ircChannel.userModes(user)
val prefixModes = network.prefixModes()
val lowestMode = userModes.mapNotNull(prefixModes::indexOf).min()
?: prefixModes.size
AutoCompleteItem.UserItem(
user.nick(),
network.modesToPrefixes(userModes),
lowestMode,
user.realName(),
user.isAway(),
network.support("CASEMAPPING"),
Regex("[us]id(\\d+)").matchEntire(user.user())?.groupValues?.lastOrNull()?.let {
"https://www.irccloud.com/avatar-redirect/$it"
}
)
}
val ignoredStartingCharacters = charArrayOf(
'-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\', '@'
)
(nicks + buffers).filter {
it.name.trimStart(*ignoredStartingCharacters)
.startsWith(
lastWord.first.trimStart(*ignoredStartingCharacters),
ignoreCase = true
)
}.sorted()
}
} else null
}
}
} ?: emptyList()
}
private fun autoComplete(reverse: Boolean = false) { private fun autoComplete(reverse: Boolean = false) {
val originalWord = lastWord.value val originalWord = lastWord.value
val previous = autocompletionState val previous = autocompletionState
if (!originalWord.second.isEmpty()) { if (!originalWord.second.isEmpty()) {
val autoCompletedWords = autoCompleteData.value?.second.orEmpty() val autoCompletedWords = autoCompleteDataFull()
if (previous != null && lastWord.value.first == previous.originalWord && lastWord.value.second.start == previous.range.start) { if (previous != null && lastWord.value.first == previous.originalWord && lastWord.value.second.start == previous.range.start) {
val previousIndex = autoCompletedWords.indexOf(previous.completion) val previousIndex = autoCompletedWords.indexOf(previous.completion)
val autoCompletedWord = if (previousIndex != -1) { val autoCompletedWord = if (previousIndex != -1) {
......
...@@ -35,7 +35,7 @@ ...@@ -35,7 +35,7 @@
android:key="@string/preference_textsize_key" android:key="@string/preference_textsize_key"
android:max="24" android:max="24"
android:title="@string/preference_textsize_title" android:title="@string/preference_textsize_title"
robobunny:min="12" /> robobunny:min="6" />
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
......
...@@ -32,6 +32,7 @@ dependencies { ...@@ -32,6 +32,7 @@ dependencies {
} }
// Utility // Utility
implementation("io.reactivex.rxjava2", "rxandroid", "2.0.2")
implementation("io.reactivex.rxjava2", "rxjava", "2.1.9") implementation("io.reactivex.rxjava2", "rxjava", "2.1.9")
implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb") implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb")
implementation("org.jetbrains", "annotations", "16.0.1") implementation("org.jetbrains", "annotations", "16.0.1")
......
...@@ -4,14 +4,21 @@ import android.arch.lifecycle.LiveData ...@@ -4,14 +4,21 @@ import android.arch.lifecycle.LiveData
import android.arch.lifecycle.LiveDataReactiveStreams import android.arch.lifecycle.LiveDataReactiveStreams
import io.reactivex.* import io.reactivex.*
import io.reactivex.functions.BiFunction import io.reactivex.functions.BiFunction
import io.reactivex.schedulers.Schedulers
inline fun <T> Observable<T>.toLiveData( inline fun <T> Observable<T>.toLiveData(
strategy: BackpressureStrategy = BackpressureStrategy.LATEST strategy: BackpressureStrategy = BackpressureStrategy.LATEST
): LiveData<T> = LiveDataReactiveStreams.fromPublisher(toFlowable(strategy)) ): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
subscribeOn(Schedulers.computation()).toFlowable(strategy)
)
inline fun <T> Maybe<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(toFlowable()) inline fun <T> Maybe<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
subscribeOn(Schedulers.computation()).toFlowable()
)
inline fun <T> Flowable<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(this) inline fun <T> Flowable<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
subscribeOn(Schedulers.computation())
)
inline fun <reified A, B> combineLatest( inline fun <reified A, B> combineLatest(
a: ObservableSource<A>, a: ObservableSource<A>,
......
...@@ -214,20 +214,41 @@ class QuasselViewModel : ViewModel() { ...@@ -214,20 +214,41 @@ class QuasselViewModel : ViewModel() {
val lastWord = BehaviorSubject.create<Observable<Pair<String, IntRange>>>() val lastWord = BehaviorSubject.create<Observable<Pair<String, IntRange>>>()
val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>> = val rawAutoCompleteData: Observable<Triple<Optional<ISession>, Int, Pair<String, IntRange>>> =
combineLatest(session, buffer, lastWord).switchMap { (sessionOptional, id, lastWordWrapper) -> combineLatest(session, buffer, lastWord).switchMap { (sessionOptional, id, lastWordWrapper) ->
lastWordWrapper lastWordWrapper
.distinctUntilChanged() .distinctUntilChanged()
.switchMap { lastWord -> .map { lastWord ->
Triple(sessionOptional, id, lastWord)
}
}
var time = 0L
var previous: Any? = null
val autoCompleteData = rawAutoCompleteData
.distinctUntilChanged()
.debounce(300, TimeUnit.MILLISECONDS)
.map {
val now = System.currentTimeMillis()
val difference = now - time
if (difference < 300) {
println("Updated too early!: $difference")
}
time = now
if (it == previous) {
println("what the fuck")
}
previous = it
it
}
.switchMap { (sessionOptional, id, lastWord) ->
val session = sessionOptional.orNull() val session = sessionOptional.orNull()
val bufferSyncer = session?.bufferSyncer val bufferSyncer = session?.bufferSyncer
val bufferInfo = bufferSyncer?.bufferInfo(id) val bufferInfo = bufferSyncer?.bufferInfo(id)
if (bufferSyncer != null) { if (bufferSyncer != null) {
session.liveNetworks().switchMap { networks -> session.liveNetworks().switchMap { networks ->
bufferSyncer.liveBufferInfos().switchMap { infos -> bufferSyncer.liveBufferInfos().switchMap { infos ->
if (bufferInfo?.type?.hasFlag( if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
Buffer_Type.ChannelBuffer
) == true) {
val network = networks[bufferInfo.networkId] val network = networks[bufferInfo.networkId]
val ircChannel = network?.ircChannel( val ircChannel = network?.ircChannel(
bufferInfo.bufferName bufferInfo.bufferName
...@@ -239,7 +260,7 @@ class QuasselViewModel : ViewModel() { ...@@ -239,7 +260,7 @@ class QuasselViewModel : ViewModel() {
it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() it.type.toInt() == Buffer_Type.ChannelBuffer.toInt()
}.mapNotNull { info -> }.mapNotNull { info ->
networks[info.networkId]?.let { info to it } networks[info.networkId]?.let { info to it }
}.map<Pair<BufferInfo, Network>, Observable<AutoCompleteItem.ChannelItem>?> { (info, network) -> }.map { (info, network) ->
network.liveIrcChannel( network.liveIrcChannel(
info.bufferName info.bufferName
).switchMap { channel -> ).switchMap { channel ->
...@@ -308,7 +329,6 @@ class QuasselViewModel : ViewModel() { ...@@ -308,7 +329,6 @@ class QuasselViewModel : ViewModel() {
Observable.just(Pair(lastWord.first, emptyList())) Observable.just(Pair(lastWord.first, emptyList()))
} }
} }
}
val bufferViewConfigs = bufferViewManager.mapSwitchMap { manager -> val bufferViewConfigs = bufferViewManager.mapSwitchMap { manager ->
manager.liveBufferViewConfigs().map { ids -> manager.liveBufferViewConfigs().map { ids ->
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment