From c5341e00f50c7595d1f715da32f367347c720cbd Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Thu, 23 May 2019 18:40:27 +0200 Subject: [PATCH] Fixes archived chats screen, significantly improves its performance --- .../ui/chat/add/query/QueryCreateFragment.kt | 2 +- .../ui/chat/archive/ArchiveFragment.kt | 80 ++- .../ui/chat/archive/ArchiveListAdapter.kt | 601 ++++++++++++++++++ .../ui/chat/archive/ArchiveListItem.kt | 39 ++ .../ui/chat/buffers/BufferListAdapter.kt | 155 ++--- app/src/main/res/layout/chat_archive.xml | 69 +- .../res/layout/widget_archive_placeholder.xml | 11 + app/src/main/res/layout/widget_header.xml | 30 + app/src/main/res/values/strings.xml | 2 + .../util/helper/ObservableHelper.kt | 7 +- .../viewmodel/data/BufferStatus.kt | 10 +- .../helper/ArchiveViewModelHelper.kt | 7 +- .../viewmodel/helper/ChatViewModelHelper.kt | 4 +- .../helper/QuasselViewModelHelper.kt | 36 +- 14 files changed, 852 insertions(+), 201 deletions(-) create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListAdapter.kt create mode 100644 app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListItem.kt create mode 100644 app/src/main/res/layout/widget_archive_placeholder.xml create mode 100644 app/src/main/res/layout/widget_header.xml diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/query/QueryCreateFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/query/QueryCreateFragment.kt index fc9f7d400..95c12d4bd 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/query/QueryCreateFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/add/query/QueryCreateFragment.kt @@ -209,7 +209,7 @@ class QueryCreateFragment : ServiceBoundFragment() { it.liveIrcUsers() }.mapOrElse(emptyList()).safeSwitchMap { combineLatest<IrcUserItem>( - it.map<IrcUser, Observable<IrcUserItem>?> { + it.mapNotNull<IrcUser, Observable<IrcUserItem>> { it.updates().map { user -> IrcUserItem( user.network().networkId(), diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveFragment.kt index 720a49cc1..28199f556 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveFragment.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveFragment.kt @@ -37,7 +37,6 @@ import de.kuschku.quasseldroid.persistence.db.QuasselDatabase import de.kuschku.quasseldroid.persistence.models.Filtered import de.kuschku.quasseldroid.settings.MessageSettings import de.kuschku.quasseldroid.ui.chat.ChatActivity -import de.kuschku.quasseldroid.ui.chat.buffers.BufferListAdapter import de.kuschku.quasseldroid.util.helper.toLiveData import de.kuschku.quasseldroid.util.service.ServiceBoundFragment import de.kuschku.quasseldroid.util.ui.presenter.BufferContextPresenter @@ -47,11 +46,8 @@ import de.kuschku.quasseldroid.viewmodel.helper.ArchiveViewModelHelper import javax.inject.Inject class ArchiveFragment : ServiceBoundFragment() { - @BindView(R.id.list_temporary) - lateinit var listTemporary: RecyclerView - - @BindView(R.id.list_permanently) - lateinit var listPermanently: RecyclerView + @BindView(R.id.list) + lateinit var list: RecyclerView @Inject lateinit var modelHelper: ArchiveViewModelHelper @@ -68,9 +64,7 @@ class ArchiveFragment : ServiceBoundFragment() { @Inject lateinit var messageSettings: MessageSettings - private lateinit var listTemporaryAdapter: BufferListAdapter - - private lateinit var listPermanentlyAdapter: BufferListAdapter + private lateinit var listAdapter: ArchiveListAdapter private var actionMode: ActionMode? = null @@ -112,8 +106,7 @@ class ArchiveFragment : ServiceBoundFragment() { override fun onDestroyActionMode(mode: ActionMode?) { actionMode = null - listTemporaryAdapter.unselectAll() - listPermanentlyAdapter.unselectAll() + listAdapter.unselectAll() } } @@ -125,27 +118,16 @@ class ArchiveFragment : ServiceBoundFragment() { val chatlistId = arguments?.getInt("chatlist_id", -1) ?: -1 modelHelper.archive.bufferViewConfigId.onNext(chatlistId) - listTemporaryAdapter = BufferListAdapter( + listAdapter = ArchiveListAdapter( messageSettings, modelHelper.archive.selectedBufferId, modelHelper.archive.temporarilyExpandedNetworks ) - listTemporaryAdapter.setOnClickListener(::clickListener) - listTemporaryAdapter.setOnLongClickListener(::longClickListener) - listTemporary.adapter = listTemporaryAdapter - listTemporary.layoutManager = LinearLayoutManager(listTemporary.context) - listTemporary.itemAnimator = DefaultItemAnimator() - - listPermanentlyAdapter = BufferListAdapter( - messageSettings, - modelHelper.archive.selectedBufferId, - modelHelper.archive.permanentlyExpandedNetworks - ) - listPermanentlyAdapter.setOnClickListener(::clickListener) - listPermanentlyAdapter.setOnLongClickListener(::longClickListener) - listPermanently.adapter = listPermanentlyAdapter - listPermanently.layoutManager = LinearLayoutManager(listPermanently.context) - listPermanently.itemAnimator = DefaultItemAnimator() + listAdapter.setOnClickListener(::clickListener) + listAdapter.setOnLongClickListener(::longClickListener) + list.adapter = listAdapter + list.layoutManager = LinearLayoutManager(list.context) + list.itemAnimator = DefaultItemAnimator() val filtered = combineLatest( database.filtered().listenRx(accountId).toObservable().map { @@ -154,20 +136,36 @@ class ArchiveFragment : ServiceBoundFragment() { accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable() ) - fun processArchiveBufferList(bufferListType: BufferHiddenState, showHandle: Boolean) = - modelHelper.processArchiveBufferList(bufferListType, showHandle, filtered).map { buffers -> - bufferPresenter.render(buffers) + combineLatest( + modelHelper.processArchiveBufferList(BufferHiddenState.HIDDEN_TEMPORARY, false, filtered), + modelHelper.processArchiveBufferList(BufferHiddenState.HIDDEN_PERMANENT, false, filtered) + ).map { (temporary, permanently) -> + listOf(ArchiveListItem.Header( + title = getString(R.string.label_temporarily_archived), + content = getString(R.string.label_temporarily_archived_long) + )) + temporary.map { + ArchiveListItem.Buffer(it.copy( + props = bufferPresenter.render(it.props) + )) + }.ifEmpty { + listOf(ArchiveListItem.Placeholder( + content = getString(R.string.label_temporarily_archived_empty) + )) + } + listOf(ArchiveListItem.Header( + title = getString(R.string.label_permanently_archived), + content = getString(R.string.label_permanently_archived_long) + )) + permanently.map { + ArchiveListItem.Buffer(it.copy( + props = bufferPresenter.render(it.props) + )) + }.ifEmpty { + listOf(ArchiveListItem.Placeholder( + content = getString(R.string.label_permanently_archived_empty) + )) } - - processArchiveBufferList(BufferHiddenState.HIDDEN_TEMPORARY, false) - .toLiveData().observe(this, Observer { processedList -> - listTemporaryAdapter.submitList(processedList) - }) - - processArchiveBufferList(BufferHiddenState.HIDDEN_PERMANENT, false) - .toLiveData().observe(this, Observer { processedList -> - listPermanentlyAdapter.submitList(processedList) - }) + }.toLiveData().observe(this, Observer { processedList -> + listAdapter.submitList(processedList) + }) modelHelper.selectedBuffer.toLiveData().observe(this, Observer { buffer -> actionMode?.let { diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListAdapter.kt new file mode 100644 index 000000000..cfd355e21 --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListAdapter.kt @@ -0,0 +1,601 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) 2019 Janne Mareike Koschinski + * Copyright (c) 2019 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/>. + */ + +package de.kuschku.quasseldroid.ui.chat.archive + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import butterknife.BindView +import butterknife.ButterKnife +import de.kuschku.libquassel.protocol.* +import de.kuschku.libquassel.quassel.BufferInfo +import de.kuschku.libquassel.util.flag.hasFlag +import de.kuschku.quasseldroid.R +import de.kuschku.quasseldroid.settings.MessageSettings +import de.kuschku.quasseldroid.util.helper.* +import de.kuschku.quasseldroid.util.lists.ListAdapter +import de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView +import de.kuschku.quasseldroid.viewmodel.data.BufferListItem +import de.kuschku.quasseldroid.viewmodel.data.BufferStatus +import io.reactivex.subjects.BehaviorSubject + +class ArchiveListAdapter( + private val messageSettings: MessageSettings, + private val selectedBuffer: BehaviorSubject<BufferId>, + private val expandedNetworks: BehaviorSubject<Map<NetworkId, Boolean>> +) : ListAdapter<ArchiveListItem, ArchiveListAdapter.ArchiveViewHolder>( + object : DiffUtil.ItemCallback<ArchiveListItem>() { + override fun areItemsTheSame(oldItem: ArchiveListItem, newItem: ArchiveListItem) = when { + oldItem is ArchiveListItem.Buffer && + newItem is ArchiveListItem.Buffer -> + oldItem.item.props.info.bufferId == newItem.item.props.info.bufferId + oldItem is ArchiveListItem.Header && + newItem is ArchiveListItem.Header -> + oldItem == newItem + oldItem is ArchiveListItem.Placeholder && + newItem is ArchiveListItem.Placeholder -> + oldItem == newItem + else -> + false + } + + override fun areContentsTheSame(oldItem: ArchiveListItem, newItem: ArchiveListItem) = + oldItem == newItem + } +), FastScrollRecyclerView.SectionedAdapter { + override fun getSectionName(position: Int) = when (val item = getItem(position)) { + is ArchiveListItem.Header -> + item.title + is ArchiveListItem.Placeholder -> + "" + is ArchiveListItem.Buffer -> + item.item.props.network.networkName + } + + private var clickListener: ((BufferId) -> Unit)? = null + fun setOnClickListener(listener: ((BufferId) -> Unit)?) { + this.clickListener = listener + } + + private var longClickListener: ((BufferId) -> Unit)? = null + fun setOnLongClickListener(listener: ((BufferId) -> Unit)?) { + this.longClickListener = listener + } + + private var dragListener: ((ArchiveViewHolder) -> Unit)? = null + fun setOnDragListener(listener: ((ArchiveViewHolder) -> Unit)?) { + dragListener = listener + } + + private var updateFinishedListener: ((List<ArchiveListItem>) -> Unit)? = null + fun setOnUpdateFinishedListener(listener: ((List<ArchiveListItem>) -> Unit)?) { + this.updateFinishedListener = listener + } + + override fun onUpdateFinished(list: List<ArchiveListItem>) { + this.updateFinishedListener?.invoke(list) + } + + fun expandListener(networkId: NetworkId, expand: Boolean) { + expandedNetworks.onNext(expandedNetworks.value.orEmpty() + Pair(networkId, expand)) + } + + fun unselectAll() { + selectedBuffer.onNext(BufferId.MAX_VALUE) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArchiveViewHolder { + val viewType = ViewType(viewType.toUInt()) + return when (viewType.type) { + ArchiveListItem.Type.HEADER -> ArchiveViewHolder.HeaderViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_header, parent, false + ) + ) + ArchiveListItem.Type.PLACEHOLDER -> ArchiveViewHolder.PlaceholderViewHolder( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_archive_placeholder, parent, false + ) + ) + ArchiveListItem.Type.BUFFER -> when (viewType.bufferType?.enabledValues()?.firstOrNull()) { + BufferInfo.Type.ChannelBuffer -> ArchiveViewHolder.BufferViewHolder.ChannelBuffer( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_buffer, parent, false + ), + clickListener = clickListener, + longClickListener = longClickListener, + dragListener = dragListener + ) + BufferInfo.Type.QueryBuffer -> ArchiveViewHolder.BufferViewHolder.QueryBuffer( + LayoutInflater.from(parent.context).inflate( + if (viewType.bufferStatus == BufferStatus.AWAY) R.layout.widget_buffer_away + else R.layout.widget_buffer, parent, false + ), + clickListener = clickListener, + longClickListener = longClickListener, + dragListener = dragListener + ) + BufferInfo.Type.GroupBuffer -> ArchiveViewHolder.BufferViewHolder.GroupBuffer( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_buffer, parent, false + ), + clickListener = clickListener, + longClickListener = longClickListener, + dragListener = dragListener + ) + BufferInfo.Type.StatusBuffer -> ArchiveViewHolder.BufferViewHolder.StatusBuffer( + LayoutInflater.from(parent.context).inflate( + R.layout.widget_network, parent, false + ), + clickListener = clickListener, + longClickListener = longClickListener, + expansionListener = ::expandListener + ) + else -> + throw IllegalArgumentException("No such viewType: $viewType") + } + } + } + + override fun onBindViewHolder(holder: ArchiveViewHolder, position: Int) { + when (val item = getItem(position)) { + is ArchiveListItem.Header -> + (holder as? ArchiveViewHolder.HeaderViewHolder)?.bind(item) + is ArchiveListItem.Placeholder -> + (holder as? ArchiveViewHolder.PlaceholderViewHolder)?.bind(item) + is ArchiveListItem.Buffer -> + (holder as? ArchiveViewHolder.BufferViewHolder)?.bind(item.item, messageSettings) + } + } + + data class ViewType( + val type: ArchiveListItem.Type, + val bufferStatus: BufferStatus?, + val bufferType: Buffer_Types? + ) { + constructor(item: ArchiveListItem) : this( + type = item.type, + bufferStatus = (item as? ArchiveListItem.Buffer)?.item?.props?.bufferStatus, + bufferType = (item as? ArchiveListItem.Buffer)?.item?.props?.info?.type + ) + + constructor(viewType: UInt) : this( + type = ArchiveListItem.Type.of(viewType.shr(24).and(0xFFu).toUByte()) + ?: throw IllegalArgumentException("Unknown View Type"), + bufferStatus = BufferStatus.of(viewType.shr(16).and(0xFFu).toUByte()), + bufferType = Buffer_Type.of(viewType.and(0xFFFFu).toUShort()) + ) + + fun compute(): UInt { + val typeValue = type.value + val bufferStatusValue = bufferStatus?.value ?: 0xFFu + val bufferTypeValue = bufferType?.value ?: 0xFFu + return typeValue.toUInt().shl(24) + + bufferStatusValue.toUInt().shl(16) + + bufferTypeValue + } + } + + override fun getItemViewType(position: Int) = ViewType(getItem(position)).compute().toInt() + + sealed class ArchiveViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + class HeaderViewHolder( + itemView: View + ) : ArchiveViewHolder(itemView) { + @BindView(R.id.title) + lateinit var title: TextView + + @BindView(R.id.content) + lateinit var content: TextView + + init { + ButterKnife.bind(this, itemView) + } + + fun bind(item: ArchiveListItem.Header) { + title.text = item.title + content.text = item.content + } + } + + class PlaceholderViewHolder( + itemView: View + ) : ArchiveViewHolder(itemView) { + @BindView(R.id.content) + lateinit var content: TextView + + init { + ButterKnife.bind(this, itemView) + } + + fun bind(item: ArchiveListItem.Placeholder) { + content.text = item.content + } + } + + sealed class BufferViewHolder(itemView: View) : ArchiveViewHolder(itemView) { + abstract fun bind(item: BufferListItem, messageSettings: MessageSettings) + + class StatusBuffer( + itemView: View, + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null, + private val expansionListener: ((NetworkId, Boolean) -> Unit)? = null + ) : BufferViewHolder(itemView) { + @BindView(R.id.status) + lateinit var status: ImageView + + @BindView(R.id.name) + lateinit var name: TextView + + var bufferId: BufferId? = null + var networkId: NetworkId? = null + + private var none: Int = 0 + private var activity: Int = 0 + private var message: Int = 0 + private var highlight: Int = 0 + + private var expanded: Boolean = false + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val buffer = bufferId + if (buffer != null) + clickListener?.invoke(buffer) + } + + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + + status.setOnClickListener { + val network = networkId + if (network != null) + expansionListener?.invoke(network, !expanded) + } + + itemView.context.theme.styledAttributes( + R.attr.colorTextSecondary, R.attr.colorTintActivity, R.attr.colorTintMessage, + R.attr.colorTintHighlight + ) { + none = getColor(0, 0) + activity = getColor(1, 0) + message = getColor(2, 0) + highlight = getColor(3, 0) + } + } + + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + name.text = item.props.network.networkName + bufferId = item.props.info.bufferId + networkId = item.props.info.networkId + + name.setTextColor( + when { + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none + } + ) + + this.expanded = item.state.networkExpanded + + itemView.isSelected = item.state.selected + + if (item.state.networkExpanded) { + status.setImageResource(R.drawable.ic_chevron_up) + } else { + status.setImageResource(R.drawable.ic_chevron_down) + } + } + } + + class GroupBuffer( + itemView: View, + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null, + private val dragListener: ((BufferViewHolder) -> Unit)? = null + ) : BufferViewHolder(itemView) { + @BindView(R.id.status) + lateinit var status: ImageView + + @BindView(R.id.name) + lateinit var name: TextView + + @BindView(R.id.description) + lateinit var description: TextView + + @BindView(R.id.handle) + lateinit var handle: View + + var bufferId: BufferId? = null + + private val online: Drawable? + private val offline: Drawable? + + private var none: Int = 0 + private var activity: Int = 0 + private var message: Int = 0 + private var highlight: Int = 0 + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val buffer = bufferId + if (buffer != null) + clickListener?.invoke(buffer) + } + + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + + handle.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + dragListener?.invoke(this) + } + false + } + + online = itemView.context.getVectorDrawableCompat(R.drawable.ic_status)?.mutate() + offline = itemView.context.getVectorDrawableCompat(R.drawable.ic_status_offline)?.mutate() + + itemView.context.theme.styledAttributes( + R.attr.colorAccent, R.attr.colorAway, + R.attr.colorTextPrimary, R.attr.colorTintActivity, R.attr.colorTintMessage, + R.attr.colorTintHighlight + ) { + online?.tint(getColor(0, 0)) + offline?.tint(getColor(1, 0)) + + none = getColor(2, 0) + activity = getColor(3, 0) + message = getColor(4, 0) + highlight = getColor(5, 0) + } + } + + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId + + name.text = item.props.info.bufferName + description.text = item.props.description + + name.setTextColor( + when { + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none + } + ) + + itemView.isSelected = item.state.selected + + handle.visibleIf(item.state.showHandle) + + description.visibleIf(item.props.description.isNotBlank()) + + status.setImageDrawable( + when (item.props.bufferStatus) { + BufferStatus.ONLINE -> online + else -> offline + } + ) + } + } + + class ChannelBuffer( + itemView: View, + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null, + private val dragListener: ((BufferViewHolder) -> Unit)? = null + ) : BufferViewHolder(itemView) { + @BindView(R.id.status) + lateinit var status: ImageView + + @BindView(R.id.name) + lateinit var name: TextView + + @BindView(R.id.description) + lateinit var description: TextView + + @BindView(R.id.handle) + lateinit var handle: View + + var bufferId: BufferId? = null + + private var none: Int = 0 + private var activity: Int = 0 + private var message: Int = 0 + private var highlight: Int = 0 + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val buffer = bufferId + if (buffer != null) + clickListener?.invoke(buffer) + } + + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + + handle.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + dragListener?.invoke(this) + } + false + } + + itemView.context.theme.styledAttributes( + R.attr.colorTextPrimary, R.attr.colorTintActivity, R.attr.colorTintMessage, + R.attr.colorTintHighlight + ) { + none = getColor(0, 0) + activity = getColor(1, 0) + message = getColor(2, 0) + highlight = getColor(3, 0) + } + } + + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId + + name.text = item.props.info.bufferName + description.text = item.props.description + + name.setTextColor( + when { + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none + } + ) + + itemView.isSelected = item.state.selected + + handle.visibleIf(item.state.showHandle) + + description.visibleIf(item.props.description.isNotBlank()) + + status.setImageDrawable(item.props.fallbackDrawable) + } + } + + class QueryBuffer( + itemView: View, + private val clickListener: ((BufferId) -> Unit)? = null, + private val longClickListener: ((BufferId) -> Unit)? = null, + private val dragListener: ((BufferViewHolder) -> Unit)? = null + ) : BufferViewHolder(itemView) { + @BindView(R.id.status) + lateinit var status: ImageView + + @BindView(R.id.name) + lateinit var name: TextView + + @BindView(R.id.description) + lateinit var description: TextView + + @BindView(R.id.handle) + lateinit var handle: View + + var bufferId: BufferId? = null + + private var none: Int = 0 + private var activity: Int = 0 + private var message: Int = 0 + private var highlight: Int = 0 + + init { + ButterKnife.bind(this, itemView) + itemView.setOnClickListener { + val buffer = bufferId + if (buffer != null) + clickListener?.invoke(buffer) + } + + itemView.setOnLongClickListener { + val buffer = bufferId + if (buffer != null) { + longClickListener?.invoke(buffer) + true + } else { + false + } + } + + handle.setOnTouchListener { _, event -> + if (event.action == MotionEvent.ACTION_DOWN) { + dragListener?.invoke(this) + } + false + } + + itemView.context.theme.styledAttributes( + R.attr.colorTextPrimary, R.attr.colorTintActivity, R.attr.colorTintMessage, + R.attr.colorTintHighlight + ) { + none = getColor(0, 0) + activity = getColor(1, 0) + message = getColor(2, 0) + highlight = getColor(3, 0) + } + } + + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId + + name.text = item.props.info.bufferName + description.text = item.props.description + + name.setTextColor( + when { + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none + } + ) + + itemView.isSelected = item.state.selected + + handle.visibleIf(item.state.showHandle) + + description.visibleIf(item.props.description.isNotBlank()) + + status.loadAvatars(item.props.avatarUrls, + item.props.fallbackDrawable, + crop = !messageSettings.squareAvatars) + } + } + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListItem.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListItem.kt new file mode 100644 index 000000000..946051b5f --- /dev/null +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/archive/ArchiveListItem.kt @@ -0,0 +1,39 @@ +package de.kuschku.quasseldroid.ui.chat.archive + +import de.kuschku.quasseldroid.viewmodel.data.BufferListItem + +sealed class ArchiveListItem(val type: Type) { + data class Header( + val title: String, + val content: String + ) : ArchiveListItem(Type.HEADER) + + data class Placeholder( + val content: String + ) : ArchiveListItem(Type.PLACEHOLDER) + + data class Buffer( + val item: BufferListItem + ) : ArchiveListItem(Type.BUFFER) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ArchiveListItem) return false + return true + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + + enum class Type(val value: UByte) { + HEADER(0u), + PLACEHOLDER(1u), + BUFFER(2u); + + companion object { + private val map = values().associateBy { it.value } + fun of(value: UByte) = map[value] + } + } +} diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt index 128d769dd..5fd727f29 100644 --- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt +++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt @@ -30,9 +30,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import butterknife.BindView import butterknife.ButterKnife -import de.kuschku.libquassel.protocol.BufferId -import de.kuschku.libquassel.protocol.Buffer_Activity -import de.kuschku.libquassel.protocol.NetworkId +import de.kuschku.libquassel.protocol.* import de.kuschku.libquassel.quassel.BufferInfo import de.kuschku.libquassel.util.flag.hasFlag import de.kuschku.quasseldroid.R @@ -41,8 +39,6 @@ import de.kuschku.quasseldroid.util.helper.* import de.kuschku.quasseldroid.util.lists.ListAdapter import de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView import de.kuschku.quasseldroid.viewmodel.data.BufferListItem -import de.kuschku.quasseldroid.viewmodel.data.BufferProps -import de.kuschku.quasseldroid.viewmodel.data.BufferState import de.kuschku.quasseldroid.viewmodel.data.BufferStatus import io.reactivex.subjects.BehaviorSubject @@ -94,10 +90,9 @@ class BufferListAdapter( } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BufferViewHolder { - val bufferType = viewType and 0xFFFF - val bufferStatus = BufferStatus.of(((viewType ushr 16) and 0xFFFF).toShort()) - return when (bufferType) { - BufferInfo.Type.ChannelBuffer.toInt() -> BufferViewHolder.ChannelBuffer( + val viewType = ViewType(viewType.toUInt()) + return when (viewType.bufferType?.enabledValues()?.firstOrNull()) { + BufferInfo.Type.ChannelBuffer -> BufferViewHolder.ChannelBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_buffer, parent, false ), @@ -105,17 +100,16 @@ class BufferListAdapter( longClickListener = longClickListener, dragListener = dragListener ) - BufferInfo.Type.QueryBuffer.toInt() -> BufferViewHolder.QueryBuffer( + BufferInfo.Type.QueryBuffer -> BufferViewHolder.QueryBuffer( LayoutInflater.from(parent.context).inflate( - if (bufferStatus == BufferStatus.AWAY) R.layout.widget_buffer_away - else R.layout.widget_buffer - , parent, false + if (viewType.bufferStatus == BufferStatus.AWAY) R.layout.widget_buffer_away + else R.layout.widget_buffer, parent, false ), clickListener = clickListener, longClickListener = longClickListener, dragListener = dragListener ) - BufferInfo.Type.GroupBuffer.toInt() -> BufferViewHolder.GroupBuffer( + BufferInfo.Type.GroupBuffer -> BufferViewHolder.GroupBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_buffer, parent, false ), @@ -123,7 +117,7 @@ class BufferListAdapter( longClickListener = longClickListener, dragListener = dragListener ) - BufferInfo.Type.StatusBuffer.toInt() -> BufferViewHolder.StatusBuffer( + BufferInfo.Type.StatusBuffer -> BufferViewHolder.StatusBuffer( LayoutInflater.from(parent.context).inflate( R.layout.widget_network, parent, false ), @@ -131,21 +125,40 @@ class BufferListAdapter( longClickListener = longClickListener, expansionListener = ::expandListener ) - else -> throw IllegalArgumentException( - "No such viewType: ${viewType.toString(16)}" - ) + else -> + throw IllegalArgumentException("No such viewType: $viewType") } } override fun onBindViewHolder(holder: BufferViewHolder, position: Int) = - holder.bind(getItem(position).props, getItem(position).state, messageSettings) - - override fun getItemViewType(position: Int) = getItem(position).let { - (it.props.bufferStatus.ordinal shl 16) + (it.props.info.type.toInt() and 0xFFFF) + holder.bind(getItem(position), messageSettings) + + data class ViewType( + val bufferStatus: BufferStatus?, + val bufferType: Buffer_Types? + ) { + constructor(item: BufferListItem) : this( + bufferStatus = item.props.bufferStatus, + bufferType = item.props.info.type + ) + + constructor(viewType: UInt) : this( + bufferStatus = BufferStatus.of(viewType.shr(16).and(0xFFu).toUByte()), + bufferType = Buffer_Type.of(viewType.and(0xFFFFu).toUShort()) + ) + + fun compute(): UInt { + val bufferStatusValue = bufferStatus?.value ?: 0xFFu + val bufferTypeValue = bufferType?.value ?: 0xFFu + return bufferStatusValue.toUInt().shl(16) + + bufferTypeValue + } } + override fun getItemViewType(position: Int) = ViewType(getItem(position)).compute().toInt() + abstract class BufferViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - abstract fun bind(props: BufferProps, state: BufferState, messageSettings: MessageSettings) + abstract fun bind(item: BufferListItem, messageSettings: MessageSettings) class StatusBuffer( itemView: View, @@ -204,25 +217,25 @@ class BufferListAdapter( } } - override fun bind(props: BufferProps, state: BufferState, messageSettings: MessageSettings) { - name.text = props.network.networkName - bufferId = props.info.bufferId - networkId = props.info.networkId + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + name.text = item.props.network.networkName + bufferId = item.props.info.bufferId + networkId = item.props.info.networkId name.setTextColor( when { - props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight - props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message - props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity - else -> none + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none } ) - this.expanded = state.networkExpanded + this.expanded = item.state.networkExpanded - itemView.isSelected = state.selected + itemView.isSelected = item.state.selected - if (state.networkExpanded) { + if (item.state.networkExpanded) { status.setImageResource(R.drawable.ic_chevron_up) } else { status.setImageResource(R.drawable.ic_chevron_down) @@ -301,29 +314,29 @@ class BufferListAdapter( } } - override fun bind(props: BufferProps, state: BufferState, messageSettings: MessageSettings) { - bufferId = props.info.bufferId + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId - name.text = props.info.bufferName - description.text = props.description + name.text = item.props.info.bufferName + description.text = item.props.description name.setTextColor( when { - props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight - props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message - props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity - else -> none + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none } ) - itemView.isSelected = state.selected + itemView.isSelected = item.state.selected - handle.visibleIf(state.showHandle) + handle.visibleIf(item.state.showHandle) - description.visibleIf(props.description.isNotBlank()) + description.visibleIf(item.props.description.isNotBlank()) status.setImageDrawable( - when (props.bufferStatus) { + when (item.props.bufferStatus) { BufferStatus.ONLINE -> online else -> offline } @@ -392,28 +405,28 @@ class BufferListAdapter( } } - override fun bind(props: BufferProps, state: BufferState, messageSettings: MessageSettings) { - bufferId = props.info.bufferId + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId - name.text = props.info.bufferName - description.text = props.description + name.text = item.props.info.bufferName + description.text = item.props.description name.setTextColor( when { - props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight - props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message - props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity - else -> none + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none } ) - itemView.isSelected = state.selected + itemView.isSelected = item.state.selected - handle.visibleIf(state.showHandle) + handle.visibleIf(item.state.showHandle) - description.visibleIf(props.description.isNotBlank()) + description.visibleIf(item.props.description.isNotBlank()) - status.setImageDrawable(props.fallbackDrawable) + status.setImageDrawable(item.props.fallbackDrawable) } } @@ -478,29 +491,29 @@ class BufferListAdapter( } } - override fun bind(props: BufferProps, state: BufferState, messageSettings: MessageSettings) { - bufferId = props.info.bufferId + override fun bind(item: BufferListItem, messageSettings: MessageSettings) { + bufferId = item.props.info.bufferId - name.text = props.info.bufferName - description.text = props.description + name.text = item.props.info.bufferName + description.text = item.props.description name.setTextColor( when { - props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight - props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message - props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity - else -> none + item.props.bufferActivity.hasFlag(Buffer_Activity.Highlight) -> highlight + item.props.bufferActivity.hasFlag(Buffer_Activity.NewMessage) -> message + item.props.bufferActivity.hasFlag(Buffer_Activity.OtherActivity) -> activity + else -> none } ) - itemView.isSelected = state.selected + itemView.isSelected = item.state.selected - handle.visibleIf(state.showHandle) + handle.visibleIf(item.state.showHandle) - description.visibleIf(props.description.isNotBlank()) + description.visibleIf(item.props.description.isNotBlank()) - status.loadAvatars(props.avatarUrls, - props.fallbackDrawable, + status.loadAvatars(item.props.avatarUrls, + item.props.fallbackDrawable, crop = !messageSettings.squareAvatars) } } diff --git a/app/src/main/res/layout/chat_archive.xml b/app/src/main/res/layout/chat_archive.xml index f5d3bdfd1..880e94188 100644 --- a/app/src/main/res/layout/chat_archive.xml +++ b/app/src/main/res/layout/chat_archive.xml @@ -18,71 +18,10 @@ --> -<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" +<de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:scrollbars="vertical"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader"> - - <androidx.appcompat.widget.AppCompatImageView - style="@style/Widget.CoreSettings.PrimaryItemIcon" - app:srcCompat="@drawable/ic_clock" /> - - <TextView - style="@style/Widget.CoreSettings.PrimaryItemSwitch" - android:text="@string/label_temporarily_archived" /> - </LinearLayout> - - <TextView - style="@style/Widget.CoreSettings.TextView" - android:layout_marginStart="72dp" - android:layout_marginLeft="72dp" - android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" - android:text="@string/label_temporarily_archived_long" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list_temporary" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="56dp" - android:layout_marginLeft="56dp" - tools:listitem="@layout/widget_buffer" /> - - <LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader"> - - <androidx.appcompat.widget.AppCompatImageView - style="@style/Widget.CoreSettings.PrimaryItemIcon" - app:srcCompat="@drawable/ic_eye_off" /> - - <TextView - style="@style/Widget.CoreSettings.PrimaryItemSwitch" - android:text="@string/label_permanently_archived" /> - </LinearLayout> - - <TextView - style="@style/Widget.CoreSettings.TextView" - android:layout_marginStart="72dp" - android:layout_marginLeft="72dp" - android:layout_marginEnd="16dp" - android:layout_marginRight="16dp" - android:text="@string/label_permanently_archived_long" /> - - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/list_permanently" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="56dp" - android:layout_marginLeft="56dp" - tools:listitem="@layout/widget_buffer" /> - </LinearLayout> -</androidx.core.widget.NestedScrollView> + android:id="@+id/list" + style="@style/Widget.FastScroller" + tools:listitem="@layout/widget_buffer" /> diff --git a/app/src/main/res/layout/widget_archive_placeholder.xml b/app/src/main/res/layout/widget_archive_placeholder.xml new file mode 100644 index 000000000..aa4a6e279 --- /dev/null +++ b/app/src/main/res/layout/widget_archive_placeholder.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/content" + style="@style/Widget.CoreSettings.TextView" + android:paddingStart="72dp" + android:paddingLeft="72dp" + android:paddingEnd="16dp" + android:paddingRight="16dp" + android:textStyle="italic" + tools:text="@string/label_temporarily_archived_empty" /> diff --git a/app/src/main/res/layout/widget_header.xml b/app/src/main/res/layout/widget_header.xml new file mode 100644 index 000000000..40a965aa7 --- /dev/null +++ b/app/src/main/res/layout/widget_header.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader"> + + <androidx.appcompat.widget.AppCompatImageView + style="@style/Widget.CoreSettings.PrimaryItemIcon" + app:srcCompat="@drawable/ic_clock" /> + + <TextView + android:id="@+id/title" + style="@style/Widget.CoreSettings.PrimaryItemSwitch" + tools:text="@string/label_temporarily_archived" /> + </LinearLayout> + + <TextView + android:id="@+id/content" + style="@style/Widget.CoreSettings.TextView" + android:layout_marginStart="72dp" + android:layout_marginLeft="72dp" + android:layout_marginEnd="16dp" + android:layout_marginRight="16dp" + tools:text="@string/label_temporarily_archived_long" /> + +</LinearLayout> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e64a3fffb..a06e2a8dd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,7 @@ <string name="label_password_old">Old Password</string> <string name="label_password_repeat">Repeat Password</string> <string name="label_permanently_archived">Permanently Archived</string> + <string name="label_permanently_archived_empty">You have no permanently archived chats</string> <string name="label_permanently_archived_long">Permanently archived chats stay hidden until you manually choose to un-archive them.</string> <string name="label_placeholder_message">Write a message…</string> <string name="label_placeholder_topic">Describe the channel topic…</string> @@ -134,6 +135,7 @@ <string name="label_sort">Sort</string> <string name="label_source">Source</string> <string name="label_temporarily_archived">Temporarily Archived</string> + <string name="label_temporarily_archived_empty">You have no temporarily archived chats</string> <string name="label_temporarily_archived_long">Chats which are temporarily archived will be shown again once they get a new message.</string> <string name="label_topic">Channel Topic</string> <string name="label_translators">Translators</string> diff --git a/lib/src/main/java/de/kuschku/libquassel/util/helper/ObservableHelper.kt b/lib/src/main/java/de/kuschku/libquassel/util/helper/ObservableHelper.kt index 682618b75..baec16d9a 100644 --- a/lib/src/main/java/de/kuschku/libquassel/util/helper/ObservableHelper.kt +++ b/lib/src/main/java/de/kuschku/libquassel/util/helper/ObservableHelper.kt @@ -125,8 +125,11 @@ inline fun <reified A, B, C, D, E, F> combineLatest( Tuple6(it[0], it[1], it[2], it[3], it[4], it[5]) as Tuple6<A, B, C, D, E, F> } -inline fun <reified T> combineLatest(sources: Iterable<ObservableSource<out T>?>) = - Observable.combineLatest(sources) { t -> t.toList() as List<T> } +inline fun <reified T> combineLatest( + sources: Collection<ObservableSource<out T>> +): Observable<List<T>> = + if (sources.isEmpty()) Observable.just(emptyList()) + else Observable.combineLatest(sources) { t -> t.toList() as List<T> } inline operator fun <T, U> Observable<T>.invoke(f: (T) -> U?) = blockingLatest().firstOrNull()?.let(f) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferStatus.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferStatus.kt index 93c47f586..3e4e824d0 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferStatus.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferStatus.kt @@ -19,13 +19,13 @@ package de.kuschku.quasseldroid.viewmodel.data -enum class BufferStatus(val value: Short) { - ONLINE(0), - AWAY(1), - OFFLINE(2); +enum class BufferStatus(val value: UByte) { + ONLINE(0u), + AWAY(1u), + OFFLINE(2u); companion object { private val map = values().associateBy { it.value } - fun of(value: Short) = map[value] + fun of(value: UByte) = map[value] } } diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt index 08ec7fc98..c2d34d484 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt @@ -45,7 +45,12 @@ open class ArchiveViewModelHelper @Inject constructor( showHandle: Boolean, filtered: Observable<Pair<Map<BufferId, Int>, Int>> ) = filterBufferList( - processRawBufferList(bufferViewConfig, filtered, bufferListType = bufferListType), + processRawBufferList( + bufferViewConfig, + filtered, + bufferListType = bufferListType, + showAllNetworks = false + ), when (bufferListType) { BufferHiddenState.VISIBLE -> archive.visibleExpandedNetworks BufferHiddenState.HIDDEN_TEMPORARY -> archive.temporarilyExpandedNetworks diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt index 7dfb92e14..223670166 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ChatViewModelHelper.kt @@ -137,7 +137,7 @@ open class ChatViewModelHelper @Inject constructor( network?.liveIrcChannel(bufferInfo.bufferName)?.switchMapNullable(IrcChannel.NULL) { ircChannel -> ircChannel?.liveIrcUsers()?.safeSwitchMap { users -> combineLatest<IrcUserItem>( - users.map<IrcUser, Observable<IrcUserItem>?> { + users.mapNotNull<IrcUser, Observable<IrcUserItem>> { it.updates().map { user -> val userModes = ircChannel.userModes(user) val prefixModes = network.prefixModes() @@ -158,7 +158,7 @@ open class ChatViewModelHelper @Inject constructor( network.support("CASEMAPPING") ) } - } + }.toList() ) } ?: Observable.just(emptyList()) } ?: Observable.just(emptyList()) diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt index f57a19ac5..89ad8851c 100644 --- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt +++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/QuasselViewModelHelper.kt @@ -137,8 +137,7 @@ open class QuasselViewModelHelper @Inject constructor( filtered: Map<BufferId, Int>, defaultFiltered: Int, bufferSearch: String = "" - ) = - ids.asSequence().mapNotNull { id -> + ): Sequence<Observable<BufferProps>> = ids.asSequence().mapNotNull { id -> bufferSyncer.bufferInfo(id) }.filter { bufferSearch.isBlank() || @@ -156,7 +155,7 @@ open class QuasselViewModelHelper @Inject constructor( } else { it to network } - }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) -> + }.mapNotNull<Pair<BufferInfo, Network>, Observable<BufferProps>> { (info, network) -> bufferSyncer.liveActivity(info.bufferId).safeSwitchMap { activity -> bufferSyncer.liveHighlightCount(info.bufferId).map { highlights -> Pair(activity, highlights) @@ -284,7 +283,8 @@ open class QuasselViewModelHelper @Inject constructor( bufferViewConfig: Observable<Optional<BufferViewConfig>>, filteredTypes: Observable<Pair<Map<BufferId, Int>, Int>>, bufferSearch: Observable<String> = Observable.just(""), - bufferListType: BufferHiddenState = BufferHiddenState.VISIBLE + bufferListType: BufferHiddenState = BufferHiddenState.VISIBLE, + showAllNetworks: Boolean = true ): Observable<Pair<BufferViewConfig?, List<BufferProps>>> = combineLatest(connectedSession, bufferViewConfig, filteredTypes, bufferSearch) .safeSwitchMap { (sessionOptional, configOptional, rawFiltered, bufferSearch) -> @@ -316,18 +316,28 @@ open class QuasselViewModelHelper @Inject constructor( ) fun missingStatusBuffers( - list: Collection<BufferId>): Sequence<Observable<BufferProps>?> { - val totalNetworks = networks.keys - val wantedNetworks = if (!currentConfig.networkId().isValidId()) totalNetworks - else listOf(currentConfig.networkId()) - - val availableNetworks = list.asSequence().mapNotNull { id -> + list: Collection<BufferId> + ): Sequence<Observable<BufferProps>> { + val buffers = list.asSequence().mapNotNull { id -> bufferSyncer.bufferInfo(id) - }.filter { + } + + val totalNetworks = + if (showAllNetworks) networks.keys + else buffers.filter { + !it.type.hasFlag(Buffer_Type.StatusBuffer) + }.map { + it.networkId + }.toList() + + val availableNetworks = buffers.filter { it.type.hasFlag(Buffer_Type.StatusBuffer) }.map { it.networkId - } + }.toList() + + val wantedNetworks = if (!currentConfig.networkId().isValidId()) totalNetworks + else listOf(currentConfig.networkId()) val missingNetworks = wantedNetworks - availableNetworks @@ -339,7 +349,7 @@ open class QuasselViewModelHelper @Inject constructor( networks[it] }.filter { !config.hideInactiveNetworks() || it.isConnected() - }.map<Network, Observable<BufferProps>?> { network -> + }.mapNotNull<Network, Observable<BufferProps>> { network -> network.liveNetworkInfo().safeSwitchMap { networkInfo -> network.liveConnectionState().map { connectionState -> BufferProps( -- GitLab