From 8a88866ddbd49baf62f8e176b5e01eeac4ac9da2 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Fri, 18 May 2018 19:19:39 +0200 Subject: [PATCH] Fixes double-tap autocomplete reliably --- .../ui/chat/input/AutoCompleteHelper.kt | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt index 8858f25a9..c789de64c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt @@ -25,7 +25,15 @@ import android.support.v4.app.FragmentActivity import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan +import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.quassel.syncables.IrcChannel +import de.kuschku.libquassel.quassel.syncables.IrcUser +import de.kuschku.libquassel.quassel.syncables.Network +import de.kuschku.libquassel.session.ISession 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.nullIf import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AutoCompleteSettings @@ -37,6 +45,7 @@ import de.kuschku.quasseldroid.util.ui.TextDrawable import de.kuschku.quasseldroid.viewmodel.EditorViewModel import de.kuschku.quasseldroid.viewmodel.EditorViewModel.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem +import de.kuschku.quasseldroid.viewmodel.data.BufferStatus class AutoCompleteHelper( activity: FragmentActivity, @@ -143,11 +152,102 @@ class AutoCompleteHelper( this.dataListeners -= listener } + private fun fullAutoComplete(sessionOptional: Optional<ISession>, id: Int, + lastWord: Pair<String, IntRange>): List<AutoCompleteItem> { + val session = sessionOptional.orNull() + val bufferSyncer = session?.bufferSyncer + val bufferInfo = bufferSyncer?.bufferInfo(id) + return if (bufferSyncer != null) { + val networks = session.networks + val infos = bufferSyncer.bufferInfos() + val aliases = session.aliasManager?.aliasList().orEmpty() + val network = networks[bufferInfo?.networkId] ?: Network.NULL + val ircChannel = if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { + network.ircChannel(bufferInfo.bufferName) ?: IrcChannel.NULL + } else IrcChannel.NULL + val users = ircChannel.ircUsers() + fun processResults(list: List<AutoCompleteItem>) = list.filter { + it.name.trimStart(*IGNORED_CHARS) + .startsWith( + lastWord.first.trimStart(*IGNORED_CHARS), + ignoreCase = true + ) + }.sorted() + + fun getAliases() = aliases.map { + AutoCompleteItem.AliasItem( + "/${it.name}", + it.expansion + ) + } + + fun getBuffers() = 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 + ).nullIf { it == IrcChannel.NULL } + + AutoCompleteItem.ChannelItem( + info = info, + network = network.networkInfo(), + bufferStatus = when (channel) { + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE + }, + description = channel?.topic() ?: "" + ) + } + + fun getUsers(): Set<IrcUser> = when { + bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true -> + users + bufferInfo?.type?.hasFlag(Buffer_Type.QueryBuffer) == true -> + network.ircUser(bufferInfo.bufferName).nullIf { it == IrcUser.NULL }?.let { + setOf(it) + } ?: emptySet() + else -> + emptySet() + } + + fun getNicks() = getUsers().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(), + user.network().isMyNick(user.nick()), + network.support("CASEMAPPING") + ) + } + + when (lastWord.first.firstOrNull()) { + '/' -> processResults(getAliases()) + '@' -> processResults(getNicks()) + '#' -> processResults(getBuffers()) + else -> processResults(getAliases() + getNicks() + getBuffers()) + } + } else { + emptyList() + } + } + fun autoComplete(reverse: Boolean = false) { viewModel.lastWord.switchMap { it }.value?.let { originalWord -> val previous = autoCompletionState if (!originalWord.second.isEmpty()) { - val autoCompletedWords = viewModel.autoCompleteData.value?.second?.filter { + val autoCompletedWords = viewModel.rawAutoCompleteData.value?.let { (sessionOptional, id, lastWord) -> + fullAutoComplete(sessionOptional, id, lastWord) + }?.filter { it is AutoCompleteItem.AliasItem && autoCompleteSettings.aliases || it is AutoCompleteItem.UserItem && autoCompleteSettings.nicks || it is AutoCompleteItem.ChannelItem && autoCompleteSettings.buffers -- GitLab