diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt index a195661f05bd0def5241e3cadea91645b0649871..110ce5a63854fc3afdd3f6ea246c5a7540bfb90a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt @@ -24,7 +24,10 @@ data class AutoCompleteSettings( val button: Boolean = false, val doubleTap: Boolean = true, val auto: Boolean = false, - val prefix: Boolean = true + val prefix: Boolean = true, + val nicks: Boolean = true, + val buffers: Boolean = true, + val aliases: Boolean = true ) { companion object { val DEFAULT = AutoCompleteSettings() diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt index 015d621ba63d7ec3b26a128efc808fbed9b2565d..fc6e0773edddfa14271fab0aec3aa55f7e0542ff 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt @@ -184,6 +184,18 @@ object Settings { prefix = getBoolean( context.getString(R.string.preference_autocomplete_prefix_key), AutoCompleteSettings.DEFAULT.prefix + ), + nicks = getBoolean( + context.getString(R.string.preference_autocomplete_nicks_key), + AutoCompleteSettings.DEFAULT.nicks + ), + buffers = getBoolean( + context.getString(R.string.preference_autocomplete_buffers_key), + AutoCompleteSettings.DEFAULT.buffers + ), + aliases = getBoolean( + context.getString(R.string.preference_autocomplete_aliases_key), + AutoCompleteSettings.DEFAULT.aliases ) ) } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt index 3f33ead50e1e985063e5252dc53e07ed614d43f1..a8e61b0a025558e0b618bc9f09a0c8d6ff6f98e9 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt @@ -41,7 +41,6 @@ import de.kuschku.libquassel.util.helpers.value import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.ui.chat.ChatActivity -import de.kuschku.quasseldroid.ui.chat.input.AutoCompleteHelper.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.util.avatars.AvatarHelper import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.irc.format.ContentFormatter @@ -50,6 +49,7 @@ import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.BetterLinkMovementMethod import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper import de.kuschku.quasseldroid.util.ui.TextDrawable +import de.kuschku.quasseldroid.viewmodel.EditorViewModel.Companion.IGNORED_CHARS import io.reactivex.Observable import javax.inject.Inject diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt index 5da440f8e7fbd7d5a20e35adf944c8a92f2f3ab3..7bb373c4885aa4b95ae12dfd9d76c30d19dda990 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt @@ -76,6 +76,11 @@ class AutoCompleteAdapter @Inject constructor( holder } + VIEWTYPE_ALIAS -> AutoCompleteViewHolder.AliasViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.widget_alias, parent, false), + clickListener = clickListener + ) else -> throw IllegalArgumentException( "Invoked with wrong item type" ) @@ -87,6 +92,7 @@ class AutoCompleteAdapter @Inject constructor( override fun getItemViewType(position: Int) = getItem(position).let { when { it is AutoCompleteItem.ChannelItem -> VIEWTYPE_CHANNEL + it is AutoCompleteItem.AliasItem -> VIEWTYPE_ALIAS it is AutoCompleteItem.UserItem && it.away -> VIEWTYPE_NICK_AWAY else -> VIEWTYPE_NICK_ACTIVE } @@ -96,6 +102,7 @@ class AutoCompleteAdapter @Inject constructor( fun bind(data: AutoCompleteItem) = when { data is AutoCompleteItem.UserItem && this is NickViewHolder -> this.bindImpl(data) data is AutoCompleteItem.ChannelItem && this is ChannelViewHolder -> this.bindImpl(data) + data is AutoCompleteItem.AliasItem && this is AliasViewHolder -> this.bindImpl(data) else -> throw IllegalArgumentException( "Invoked with wrong item type" ) @@ -188,11 +195,41 @@ class AutoCompleteAdapter @Inject constructor( ) } } + + class AliasViewHolder( + itemView: View, + private val clickListener: ((String) -> Unit)? = null + ) : AutoCompleteViewHolder(itemView) { + @BindView(R.id.alias) + lateinit var alias: TextView + + @BindView(R.id.expansion) + lateinit var expansion: TextView + + var value: String? = null + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val value = value + if (value != null) + clickListener?.invoke(value) + } + } + + fun bindImpl(data: AutoCompleteItem.AliasItem) { + value = data.name + + alias.text = data.alias + expansion.text = data.expansion + } + } } companion object { const val VIEWTYPE_CHANNEL = 0 const val VIEWTYPE_NICK_ACTIVE = 1 const val VIEWTYPE_NICK_AWAY = 2 + const val VIEWTYPE_ALIAS = 3 } } 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 4d39776cbeebf7e28ace92c47427dc6075d60f06..8858f25a900a19cc256d0b9d2d4c4bcca5de1b41 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,23 +25,18 @@ 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.util.IrcUserUtils -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 import de.kuschku.quasseldroid.settings.MessageSettings -import de.kuschku.quasseldroid.util.avatars.AvatarHelper import de.kuschku.quasseldroid.util.helper.styledAttributes import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer 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, @@ -68,16 +63,20 @@ class AutoCompleteHelper( getColor(0, 0) } - private val avatarSize = activity.resources.getDimensionPixelSize(R.dimen.avatar_size) - init { viewModel.autoCompleteData.toLiveData().observe(activity, Observer { val query = it?.first ?: "" - val shouldShowResults = (autoCompleteSettings.auto && query.length >= 3) || - (autoCompleteSettings.prefix && query.startsWith('@')) || - (autoCompleteSettings.prefix && query.startsWith('#')) + val shouldShowResults = + (autoCompleteSettings.auto && query.length >= 3) || + (autoCompleteSettings.prefix && autoCompleteSettings.nicks && query.startsWith('@')) || + (autoCompleteSettings.prefix && autoCompleteSettings.buffers && query.startsWith('#')) || + (autoCompleteSettings.prefix && autoCompleteSettings.aliases && query.startsWith('/')) val list = if (shouldShowResults) it?.second.orEmpty() else emptyList() - val data = list.map { + val data = list.filter { + it is AutoCompleteItem.AliasItem && autoCompleteSettings.aliases || + it is AutoCompleteItem.UserItem && autoCompleteSettings.nicks || + it is AutoCompleteItem.ChannelItem && autoCompleteSettings.buffers + }.map { if (it is AutoCompleteItem.UserItem) { val nickName = it.nick val senderColorIndex = IrcUserUtils.senderColor(nickName) @@ -144,74 +143,16 @@ class AutoCompleteHelper( this.dataListeners -= listener } - private fun autoCompleteDataFull(): List<AutoCompleteItem> { - return viewModel.rawAutoCompleteData.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 - .asSequence() - .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() ?: "" - ) - } - val nicks = users.asSequence().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"), - AvatarHelper.avatar(messageSettings, user, avatarSize) - ) - } - - (nicks + buffers).filter { - it.name.trimStart(*IGNORED_CHARS) - .startsWith( - lastWord.first.trimStart(*IGNORED_CHARS), - ignoreCase = true - ) - }.sorted().toList() - } - } else null - } - } - } ?: emptyList() - } - fun autoComplete(reverse: Boolean = false) { viewModel.lastWord.switchMap { it }.value?.let { originalWord -> val previous = autoCompletionState if (!originalWord.second.isEmpty()) { - val autoCompletedWords = autoCompleteDataFull() + val autoCompletedWords = viewModel.autoCompleteData.value?.second?.filter { + it is AutoCompleteItem.AliasItem && autoCompleteSettings.aliases || + it is AutoCompleteItem.UserItem && autoCompleteSettings.nicks || + it is AutoCompleteItem.ChannelItem && autoCompleteSettings.buffers + }.orEmpty() + if (previous != null && originalWord.first == previous.originalWord && originalWord.second.start == previous.range.start) { val previousIndex = autoCompletedWords.indexOf(previous.completion) val autoCompletedWord = if (previousIndex != -1) { @@ -252,8 +193,4 @@ class AutoCompleteHelper( } } } - - companion object { - val IGNORED_CHARS = charArrayOf('-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\', '@') - } } diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt index 56d702b5e4b052d1efe513ec868b460e20a20ed9..6f97dc42c46cb72119853c6e3f78c7a43da1a572 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt @@ -47,7 +47,6 @@ import de.kuschku.quasseldroid.R import de.kuschku.quasseldroid.settings.AppearanceSettings import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.ui.chat.info.user.UserInfoActivity -import de.kuschku.quasseldroid.ui.chat.input.AutoCompleteHelper.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.util.avatars.AvatarHelper import de.kuschku.quasseldroid.util.helper.loadWithFallbacks import de.kuschku.quasseldroid.util.helper.styledAttributes @@ -55,6 +54,7 @@ import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.TextDrawable +import de.kuschku.quasseldroid.viewmodel.EditorViewModel.Companion.IGNORED_CHARS import de.kuschku.quasseldroid.viewmodel.data.Avatar import javax.inject.Inject diff --git a/app/src/main/res/layout/widget_alias.xml b/app/src/main/res/layout/widget_alias.xml new file mode 100644 index 0000000000000000000000000000000000000000..a2a17d1a6cd4ebfb633454e110125d7c6204d2a8 --- /dev/null +++ b/app/src/main/res/layout/widget_alias.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Quasseldroid - Quassel client for Android + + Copyright (c) 2018 Janne Koschinski + Copyright (c) 2018 The Quassel Project + + This program is free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License version 3 as published + by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/backgroundMenuItem" + android:minHeight="48dp" + android:paddingBottom="8dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingTop="8dp"> + + <LinearLayout + android:layout_width="0dip" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="1" + android:orientation="vertical"> + + <TextView + android:id="@+id/alias" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:fontFamily="sans-serif-medium" + android:singleLine="true" + android:textColor="?attr/colorTextPrimary" + android:textSize="13sp" + tools:text="#quasseldroid" /> + + <TextView + android:id="@+id/expansion" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|start" + android:singleLine="true" + android:textColor="?attr/colorTextSecondary" + android:textSize="12sp" + tools:text="Quasseldroid is an Android client for #quassel ♥ justJanne's much improved version: https://dl.kuschku.de/releases/quasseldroid/ ♥ http://github.com/sandsmark/Quasseldroid ♥ Quasseldroid on play https://market.android.com/details?id=com.iskrembilen.quasseldroid ♥ Sign up for beta: https://plus.google.com/communities/104094956084217666662" /> + </LinearLayout> +</LinearLayout> diff --git a/app/src/main/res/values-de/strings_preferences.xml b/app/src/main/res/values-de/strings_preferences.xml index 3f0abe89ace9de3921928b7482f44a2dc0f26252..4e7d0f140b2ad598879c5c5f3ddf5e7dcf305a6b 100644 --- a/app/src/main/res/values-de/strings_preferences.xml +++ b/app/src/main/res/values-de/strings_preferences.xml @@ -128,6 +128,12 @@ <string name="preference_autocomplete_prefix_title">Nach Präfix anzeigen</string> <string name="preference_autocomplete_prefix_summary">Vervollständigt Namen und Chats automatisch nach einem @ oder #</string> + <string name="preference_autocomplete_nicks_title">Vervollständige Spitznamen</string> + + <string name="preference_autocomplete_buffers_title">Vervollständige Chats</string> + + <string name="preference_autocomplete_aliases_title">Vervollständige Aliase</string> + <string name="preference_backlog_title">Verlauf</string> diff --git a/app/src/main/res/values/strings_preferences.xml b/app/src/main/res/values/strings_preferences.xml index 23cf0cbf92d1cd592171a9da8e27f0d7152bafcf..c2de68aca73f912dc3f770776e879f82b5726a5c 100644 --- a/app/src/main/res/values/strings_preferences.xml +++ b/app/src/main/res/values/strings_preferences.xml @@ -247,6 +247,15 @@ <string name="preference_autocomplete_prefix_title">Show after prefix</string> <string name="preference_autocomplete_prefix_summary">Suggest nicks and channels after entering @ or #</string> + <string name="preference_autocomplete_nicks_key" translatable="false">autocomplete_nicks</string> + <string name="preference_autocomplete_nicks_title">Autocomplete Nicknames</string> + + <string name="preference_autocomplete_buffers_key" translatable="false">autocomplete_buffers</string> + <string name="preference_autocomplete_buffers_title">Autocomplete Chats</string> + + <string name="preference_autocomplete_aliases_key" translatable="false">autocomplete_aliases</string> + <string name="preference_autocomplete_aliases_title">Autocomplete Commands</string> + <string name="preference_backlog_title">Backlog</string> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 5f5fb5685b1cf6129b4d92eca22400fb786859cd..f350beadd5423521d1555d68f5c63006008d067d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -245,6 +245,21 @@ android:key="@string/preference_autocomplete_prefix_key" android:summary="@string/preference_autocomplete_prefix_summary" android:title="@string/preference_autocomplete_prefix_title" /> + + <SwitchPreference + android:defaultValue="true" + android:key="@string/preference_autocomplete_nicks_key" + android:title="@string/preference_autocomplete_nicks_title" /> + + <SwitchPreference + android:defaultValue="true" + android:key="@string/preference_autocomplete_buffers_key" + android:title="@string/preference_autocomplete_buffers_title" /> + + <SwitchPreference + android:defaultValue="true" + android:key="@string/preference_autocomplete_aliases_key" + android:title="@string/preference_autocomplete_aliases_title" /> </PreferenceCategory> <PreferenceCategory android:layout="@layout/widget_preference_divider" /> diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/AliasManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/AliasManager.kt index f0c8e77fb08e3e4407216aba267b4936d7e0b800..3e26c5bcaebd8c4db46fb771fae29b99e2a8af63 100644 --- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/AliasManager.kt +++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/AliasManager.kt @@ -28,6 +28,8 @@ import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager import de.kuschku.libquassel.quassel.syncables.interfaces.IAliasManager.Alias import de.kuschku.libquassel.quassel.syncables.interfaces.ISyncableObject import de.kuschku.libquassel.session.SignalProxy +import io.reactivex.Observable +import io.reactivex.subjects.BehaviorSubject import java.util.* class AliasManager constructor( @@ -94,6 +96,8 @@ class AliasManager constructor( _aliases = list } + fun updates(): Observable<AliasManager> = live_updates.map { this } + fun copy() = AliasManager(proxy).also { it.fromVariantMap(toVariantMap()) } @@ -188,7 +192,12 @@ class AliasManager constructor( } } + private val live_updates = BehaviorSubject.createDefault(Unit) private var _aliases = listOf<IAliasManager.Alias>() + set(value) { + field = value + live_updates.onNext(Unit) + } fun isEqual(other: AliasManager): Boolean = this.aliasList() == other.aliasList() diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt index b48d462b704d21f7b2f6b49000d0ce93f372d66e..00f17a98578b1171383b8e4ab4fecd0e3baeff2c 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt @@ -21,12 +21,15 @@ package de.kuschku.quasseldroid.viewmodel import android.arch.lifecycle.ViewModel import de.kuschku.libquassel.protocol.Buffer_Type +import de.kuschku.libquassel.quassel.syncables.AliasManager 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.Optional import de.kuschku.libquassel.util.flag.hasFlag import de.kuschku.libquassel.util.helpers.mapNullable +import de.kuschku.libquassel.util.helpers.nullIf import de.kuschku.quasseldroid.util.helper.combineLatest import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem import de.kuschku.quasseldroid.viewmodel.data.BufferStatus @@ -51,7 +54,7 @@ class EditorViewModel : ViewModel() { } } - val autoCompleteData = rawAutoCompleteData + val autoCompleteData: Observable<Pair<String, List<AutoCompleteItem>>> = rawAutoCompleteData .distinctUntilChanged() .debounce(300, TimeUnit.MILLISECONDS) .switchMap { (sessionOptional, id, lastWord) -> @@ -61,78 +64,97 @@ class EditorViewModel : ViewModel() { if (bufferSyncer != null) { session.liveNetworks().switchMap { networks -> bufferSyncer.liveBufferInfos().switchMap { infos -> - if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) { - val network = networks[bufferInfo.networkId] - val ircChannel = network?.ircChannel( - bufferInfo.bufferName - ) - if (ircChannel != null) { - ircChannel.liveIrcUsers().switchMap { users -> - val buffers: List<Observable<AutoCompleteItem.ChannelItem>?> = infos.values - .filter { - it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() - }.mapNotNull { info -> - networks[info.networkId]?.let { info to it } - }.map { (info, network) -> - network.liveIrcChannel( - info.bufferName - ).switchMap { channel -> - channel.updates().mapNullable(IrcChannel.NULL) { - AutoCompleteItem.ChannelItem( - info = info, - network = network.networkInfo(), - bufferStatus = when (it) { - null -> BufferStatus.OFFLINE - else -> BufferStatus.ONLINE - }, - description = it?.topic() ?: "" + (session.aliasManager?.updates()?.map(AliasManager::aliasList) + ?: Observable.just(emptyList())).switchMap { aliases -> + 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 + ircChannel.liveIrcUsers().switchMap { users -> + fun processResults(results: List<Observable<out AutoCompleteItem>>) = + combineLatest<AutoCompleteItem>(results) + .map { list -> + val filtered = list.filter { + it.name.trimStart(*IGNORED_CHARS) + .startsWith( + lastWord.first.trimStart(*IGNORED_CHARS), + ignoreCase = true ) - } } + Pair( + lastWord.first, + filtered.sorted() + ) } - val nicks = users.map<IrcUser, Observable<AutoCompleteItem.UserItem>?> { - it.updates().map { user -> - val userModes = ircChannel.userModes(user) - val prefixModes = network.prefixModes() - val lowestMode = userModes.mapNotNull(prefixModes::indexOf).min() - ?: prefixModes.size + fun getAliases() = aliases.map { + Observable.just(AutoCompleteItem.AliasItem( + it.name, + it.expansion + )) + } - AutoCompleteItem.UserItem( - user.nick(), - network.modesToPrefixes(userModes), - lowestMode, - user.realName(), - user.isAway(), - user.network().isMyNick(user.nick()), - network.support("CASEMAPPING") - ) + fun getBuffers() = infos.values + .filter { + it.type.toInt() == Buffer_Type.ChannelBuffer.toInt() + }.mapNotNull { info -> + networks[info.networkId]?.let { info to it } + }.map { (info, network) -> + network.liveIrcChannel( + info.bufferName + ).switchMap { channel -> + channel.updates().mapNullable(IrcChannel.NULL) { + AutoCompleteItem.ChannelItem( + info = info, + network = network.networkInfo(), + bufferStatus = when (it) { + null -> BufferStatus.OFFLINE + else -> BufferStatus.ONLINE + }, + description = it?.topic() ?: "" + ) + } } } - combineLatest<AutoCompleteItem>(nicks + buffers) - .map { list -> - val ignoredStartingCharacters = charArrayOf( - '-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\', '@' - ) + 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() + } - Pair( - lastWord.first, - list.filter { - it.name.trimStart(*ignoredStartingCharacters) - .startsWith( - lastWord.first.trimStart(*ignoredStartingCharacters), - ignoreCase = true - ) - }.sorted() - ) - } + fun getNicks() = getUsers().map<IrcUser, Observable<AutoCompleteItem.UserItem>> { + it.updates().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 { - Observable.just(Pair(lastWord.first, emptyList())) } - } else { - Observable.just(Pair(lastWord.first, emptyList())) } } } @@ -140,4 +162,10 @@ class EditorViewModel : ViewModel() { Observable.just(Pair(lastWord.first, emptyList())) } } + + companion object { + val IGNORED_CHARS = charArrayOf( + '-', '_', '[', ']', '{', '}', '|', '`', '^', '.', '\\', '@', '#', '/' + ) + } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt index 88f2f5424b01ace72eab92debf11ad1b2cb3b242..2e7d7fc7b741ac082532de286edac0c9b01686f2 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt @@ -23,15 +23,11 @@ import android.graphics.drawable.Drawable import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork -sealed class AutoCompleteItem(open val name: String) : Comparable<AutoCompleteItem> { - override fun compareTo(other: AutoCompleteItem): Int { - return when { - this is UserItem && - other is ChannelItem -> -1 - this is ChannelItem && - other is UserItem -> 1 - else -> this.name.compareTo(other.name) - } +sealed class AutoCompleteItem(open val name: String, private val type: Int) : + Comparable<AutoCompleteItem> { + override fun compareTo(other: AutoCompleteItem) = when { + this.type != other.type -> this.type.compareTo(other.type) + else -> this.name.compareTo(other.name) } data class UserItem( @@ -45,12 +41,17 @@ sealed class AutoCompleteItem(open val name: String) : Comparable<AutoCompleteIt val avatarUrls: List<Avatar> = emptyList(), val fallbackDrawable: Drawable? = null, val displayNick: CharSequence? = null - ) : AutoCompleteItem(nick) + ) : AutoCompleteItem(nick, 0) + + data class AliasItem( + val alias: String, + val expansion: String + ) : AutoCompleteItem(alias, 1) data class ChannelItem( val info: BufferInfo, val network: INetwork.NetworkInfo, val bufferStatus: BufferStatus, val description: CharSequence - ) : AutoCompleteItem(info.bufferName ?: "") + ) : AutoCompleteItem(info.bufferName ?: "", 2) }