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