From bfcb2b974ecde20aab48259e7b62b2d12432df4a Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Mon, 20 May 2019 15:42:50 +0200
Subject: [PATCH] Implement chat archive screen

---
 .../quasseldroid/dagger/ActivityBaseModule.kt |  11 +-
 .../ui/chat/archive/ArchiveFragment.kt        | 165 ++++++++-
 .../ui/chat/buffers/BufferListAdapter.kt      |  70 +++-
 .../chat/buffers/BufferViewConfigFragment.kt  | 323 +++---------------
 .../kuschku/quasseldroid/util/ColorContext.kt |  22 +-
 .../ui/presenter/BufferContextPresenter.kt    | 224 ++++++++++++
 .../util/ui/presenter/BufferPresenter.kt      | 101 ++++++
 app/src/main/res/layout/chat_archive.xml      |   9 +-
 app/src/main/res/layout/widget_buffer.xml     |  15 +-
 .../main/res/layout/widget_buffer_away.xml    |  11 +
 .../main/res/layout/widget_buffer_reorder.xml |  89 -----
 .../res/layout/widget_buffer_reorder_away.xml | 101 ------
 app/src/main/res/menu/context_buffer.xml      |   8 +-
 app/src/main/res/menu/context_bufferlist.xml  |   4 -
 app/src/main/res/values/strings.xml           |  11 +-
 .../viewmodel/ArchiveViewModel.kt             |  88 +++++
 .../viewmodel/data/BufferState.kt             |   3 +-
 .../helper/ArchiveViewModelHelper.kt          | 186 ++++++++++
 .../viewmodel/helper/ChatViewModelHelper.kt   | 177 +---------
 .../helper/QuasselViewModelHelper.kt          | 210 +++++++++++-
 20 files changed, 1149 insertions(+), 679 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferContextPresenter.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferPresenter.kt
 delete mode 100644 app/src/main/res/layout/widget_buffer_reorder.xml
 delete mode 100644 app/src/main/res/layout/widget_buffer_reorder_away.xml
 create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ArchiveViewModel.kt
 create mode 100644 viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt

diff --git a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt
index aded7fa43..cf50a65a6 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/dagger/ActivityBaseModule.kt
@@ -26,10 +26,7 @@ import androidx.lifecycle.ViewModelProviders
 import dagger.Module
 import dagger.Provides
 import de.kuschku.quasseldroid.ui.setup.accounts.selection.AccountViewModel
-import de.kuschku.quasseldroid.viewmodel.ChatViewModel
-import de.kuschku.quasseldroid.viewmodel.EditorViewModel
-import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
-import de.kuschku.quasseldroid.viewmodel.QueryCreateViewModel
+import de.kuschku.quasseldroid.viewmodel.*
 
 @Module
 object ActivityBaseModule {
@@ -72,4 +69,10 @@ object ActivityBaseModule {
   @JvmStatic
   fun provideQueryCreateViewModel(viewModelProvider: ViewModelProvider) =
     viewModelProvider[QueryCreateViewModel::class.java]
+
+  @ActivityScope
+  @Provides
+  @JvmStatic
+  fun provideArchiveViewModel(viewModelProvider: ViewModelProvider) =
+    viewModelProvider[ArchiveViewModel::class.java]
 }
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 8baf03ba0..ce962ebdc 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
@@ -20,15 +20,32 @@
 package de.kuschku.quasseldroid.ui.chat.archive
 
 import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.view.*
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import butterknife.BindView
 import butterknife.ButterKnife
+import com.afollestad.materialdialogs.MaterialDialog
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.util.helper.combineLatest
+import de.kuschku.libquassel.util.helper.value
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.persistence.db.AccountDatabase
+import de.kuschku.quasseldroid.persistence.db.QuasselDatabase
+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.ui.coresettings.network.NetworkEditActivity
+import de.kuschku.quasseldroid.ui.info.channellist.ChannelListActivity
+import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
-import de.kuschku.quasseldroid.viewmodel.helper.QuasselViewModelHelper
+import de.kuschku.quasseldroid.util.ui.presenter.BufferContextPresenter
+import de.kuschku.quasseldroid.util.ui.presenter.BufferPresenter
+import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState
+import de.kuschku.quasseldroid.viewmodel.helper.ArchiveViewModelHelper
 import javax.inject.Inject
 
 class ArchiveFragment : ServiceBoundFragment() {
@@ -39,19 +56,149 @@ class ArchiveFragment : ServiceBoundFragment() {
   lateinit var listPermanently: RecyclerView
 
   @Inject
-  lateinit var modelHelper: QuasselViewModelHelper
+  lateinit var modelHelper: ArchiveViewModelHelper
+
+  @Inject
+  lateinit var database: QuasselDatabase
+
+  @Inject
+  lateinit var accountDatabase: AccountDatabase
+
+  @Inject
+  lateinit var bufferPresenter: BufferPresenter
+
+  @Inject
+  lateinit var messageSettings: MessageSettings
+
+  private lateinit var listTemporaryAdapter: BufferListAdapter
+
+  private lateinit var listPermanentlyAdapter: BufferListAdapter
+
+  private var actionMode: ActionMode? = null
+
+  private val actionModeCallback = object : ActionMode.Callback {
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+      val selected = modelHelper.archive.selectedBufferId.value ?: BufferId(-1)
+      val session = modelHelper.connectedSession.value?.orNull()
+      val bufferSyncer = session?.bufferSyncer
+      val info = bufferSyncer?.bufferInfo(selected)
+      val network = session?.networks?.get(info?.networkId)
+      val bufferViewConfig = modelHelper.bufferViewConfig.value?.orNull()
+
+      return if (info != null) {
+        BufferContextPresenter.handleAction(
+          requireContext(),
+          mode,
+          item,
+          info,
+          session,
+          bufferSyncer,
+          bufferViewConfig,
+          network
+        )
+      } else {
+        false
+      }
+    }
+
+    override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+      actionMode = mode
+      mode?.menuInflater?.inflate(R.menu.context_buffer, menu)
+      return true
+    }
+
+    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
+      mode?.tag = "ARCHIVE"
+      return true
+    }
+
+    override fun onDestroyActionMode(mode: ActionMode?) {
+      actionMode = null
+      listTemporaryAdapter.unselectAll()
+      listPermanentlyAdapter.unselectAll()
+    }
+  }
 
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
     val view = inflater.inflate(R.layout.chat_archive, container, false)
     ButterKnife.bind(this, view)
 
-    val chatlistId = arguments?.getInt("chatlist_id", -1)
+    val chatlistId = arguments?.getInt("chatlist_id", -1) ?: -1
+    modelHelper.archive.bufferViewConfigId.onNext(chatlistId)
 
-    val chatlist = modelHelper.bufferViewConfigMap.map {
-      it[chatlistId]
-    }
+    listTemporaryAdapter = BufferListAdapter(
+      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()
+
+    fun processArchiveBufferList(bufferListType: BufferHiddenState, showHandle: Boolean) =
+      combineLatest(
+        modelHelper.processArchiveBufferList(bufferListType, showHandle),
+        database.filtered().listenRx(accountId).toObservable(),
+        accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable()
+      ).map { (buffers, filteredList, defaultFiltered) ->
+        bufferPresenter.render(buffers, filteredList, defaultFiltered.toUInt())
+      }
+
+    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)
+      })
+
+    modelHelper.selectedBuffer.toLiveData().observe(this, Observer { buffer ->
+      actionMode?.let {
+        BufferContextPresenter.present(it, buffer)
+      }
+    })
 
     return view
   }
+
+  private fun toggleSelection(buffer: BufferId): Boolean {
+    val next = if (modelHelper.archive.selectedBufferId.value == buffer) BufferId.MAX_VALUE else buffer
+    modelHelper.archive.selectedBufferId.onNext(next)
+    return next != BufferId.MAX_VALUE
+  }
+
+  private fun clickListener(bufferId: BufferId) {
+    if (actionMode != null) {
+      longClickListener(bufferId)
+    } else {
+      context?.let {
+        ChatActivity.launch(it, bufferId = bufferId)
+      }
+    }
+  }
+
+  private fun longClickListener(it: BufferId) {
+    if (actionMode == null) {
+      (activity as? AppCompatActivity)?.startActionMode(actionModeCallback)
+    }
+    if (!toggleSelection(it)) {
+      actionMode?.finish()
+    }
+  }
 }
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 35f7c0a67..128d769dd 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
@@ -21,6 +21,7 @@ package de.kuschku.quasseldroid.ui.chat.buffers
 
 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
@@ -61,16 +62,21 @@ class BufferListAdapter(
   override fun getSectionName(position: Int) = getItem(position).props.network.networkName
 
   private var clickListener: ((BufferId) -> Unit)? = null
-  private var longClickListener: ((BufferId) -> Unit)? = null
-  private var updateFinishedListener: ((List<BufferListItem>) -> 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: ((BufferViewHolder) -> Unit)? = null
+  fun setOnDragListener(listener: ((BufferViewHolder) -> Unit)?) {
+    dragListener = listener
+  }
+
+  private var updateFinishedListener: ((List<BufferListItem>) -> Unit)? = null
   fun setOnUpdateFinishedListener(listener: ((List<BufferListItem>) -> Unit)?) {
     this.updateFinishedListener = listener
   }
@@ -83,12 +89,6 @@ class BufferListAdapter(
     expandedNetworks.onNext(expandedNetworks.value.orEmpty() + Pair(networkId, expand))
   }
 
-  fun toggleSelection(buffer: BufferId): Boolean {
-    val next = if (selectedBuffer.value == buffer) BufferId.MAX_VALUE else buffer
-    selectedBuffer.onNext(next)
-    return next != BufferId.MAX_VALUE
-  }
-
   fun unselectAll() {
     selectedBuffer.onNext(BufferId.MAX_VALUE)
   }
@@ -102,7 +102,8 @@ class BufferListAdapter(
           R.layout.widget_buffer, parent, false
         ),
         clickListener = clickListener,
-        longClickListener = longClickListener
+        longClickListener = longClickListener,
+        dragListener = dragListener
       )
       BufferInfo.Type.QueryBuffer.toInt()   -> BufferViewHolder.QueryBuffer(
         LayoutInflater.from(parent.context).inflate(
@@ -111,14 +112,16 @@ class BufferListAdapter(
           , parent, false
         ),
         clickListener = clickListener,
-        longClickListener = longClickListener
+        longClickListener = longClickListener,
+        dragListener = dragListener
       )
       BufferInfo.Type.GroupBuffer.toInt()   -> BufferViewHolder.GroupBuffer(
         LayoutInflater.from(parent.context).inflate(
           R.layout.widget_buffer, parent, false
         ),
         clickListener = clickListener,
-        longClickListener = longClickListener
+        longClickListener = longClickListener,
+        dragListener = dragListener
       )
       BufferInfo.Type.StatusBuffer.toInt()  -> BufferViewHolder.StatusBuffer(
         LayoutInflater.from(parent.context).inflate(
@@ -230,7 +233,8 @@ class BufferListAdapter(
     class GroupBuffer(
       itemView: View,
       private val clickListener: ((BufferId) -> Unit)? = null,
-      private val longClickListener: ((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
@@ -241,6 +245,9 @@ class BufferListAdapter(
       @BindView(R.id.description)
       lateinit var description: TextView
 
+      @BindView(R.id.handle)
+      lateinit var handle: View
+
       var bufferId: BufferId? = null
 
       private val online: Drawable?
@@ -269,6 +276,13 @@ class BufferListAdapter(
           }
         }
 
+        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()
 
@@ -304,6 +318,8 @@ class BufferListAdapter(
 
         itemView.isSelected = state.selected
 
+        handle.visibleIf(state.showHandle)
+
         description.visibleIf(props.description.isNotBlank())
 
         status.setImageDrawable(
@@ -318,7 +334,8 @@ class BufferListAdapter(
     class ChannelBuffer(
       itemView: View,
       private val clickListener: ((BufferId) -> Unit)? = null,
-      private val longClickListener: ((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
@@ -329,6 +346,9 @@ class BufferListAdapter(
       @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
@@ -354,6 +374,13 @@ class BufferListAdapter(
           }
         }
 
+        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
@@ -382,6 +409,8 @@ class BufferListAdapter(
 
         itemView.isSelected = state.selected
 
+        handle.visibleIf(state.showHandle)
+
         description.visibleIf(props.description.isNotBlank())
 
         status.setImageDrawable(props.fallbackDrawable)
@@ -391,7 +420,8 @@ class BufferListAdapter(
     class QueryBuffer(
       itemView: View,
       private val clickListener: ((BufferId) -> Unit)? = null,
-      private val longClickListener: ((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
@@ -402,6 +432,9 @@ class BufferListAdapter(
       @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
@@ -427,6 +460,13 @@ class BufferListAdapter(
           }
         }
 
+        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
@@ -455,6 +495,8 @@ class BufferListAdapter(
 
         itemView.isSelected = state.selected
 
+        handle.visibleIf(state.showHandle)
+
         description.visibleIf(props.description.isNotBlank())
 
         status.loadAvatars(props.avatarUrls,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
index d316c46e5..f57e2ad30 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferViewConfigFragment.kt
@@ -40,15 +40,8 @@ import com.afollestad.materialdialogs.MaterialDialog
 import com.leinardi.android.speeddial.SpeedDialActionItem
 import com.leinardi.android.speeddial.SpeedDialView
 import de.kuschku.libquassel.protocol.BufferId
-import de.kuschku.libquassel.protocol.Buffer_Activity
-import de.kuschku.libquassel.protocol.Buffer_Type
-import de.kuschku.libquassel.protocol.Message_Type
-import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.ExtendedFeature
 import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
-import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
-import de.kuschku.libquassel.util.flag.hasFlag
-import de.kuschku.libquassel.util.flag.minus
 import de.kuschku.libquassel.util.helper.*
 import de.kuschku.quasseldroid.BuildConfig
 import de.kuschku.quasseldroid.R
@@ -64,18 +57,15 @@ import de.kuschku.quasseldroid.ui.chat.archive.ArchiveActivity
 import de.kuschku.quasseldroid.ui.coresettings.network.NetworkEditActivity
 import de.kuschku.quasseldroid.ui.info.channellist.ChannelListActivity
 import de.kuschku.quasseldroid.util.ColorContext
-import de.kuschku.quasseldroid.util.avatars.AvatarHelper
 import de.kuschku.quasseldroid.util.helper.setTooltip
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
+import de.kuschku.quasseldroid.util.ui.presenter.BufferContextPresenter
+import de.kuschku.quasseldroid.util.ui.presenter.BufferPresenter
 import de.kuschku.quasseldroid.util.ui.view.WarningBarView
-import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState
-import de.kuschku.quasseldroid.viewmodel.data.BufferListItem
-import de.kuschku.quasseldroid.viewmodel.data.BufferState
-import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
 import de.kuschku.quasseldroid.viewmodel.helper.ChatViewModelHelper
 import javax.inject.Inject
 
@@ -122,115 +112,34 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
   @Inject
   lateinit var modelHelper: ChatViewModelHelper
 
+  @Inject
+  lateinit var colorContext: ColorContext
+
+  @Inject
+  lateinit var bufferPresenter: BufferPresenter
+
   private var actionMode: ActionMode? = null
 
   private val actionModeCallback = object : ActionMode.Callback {
-    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
-      val selected = modelHelper.selectedBuffer.value
-      val info = selected?.info
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+      val selected = modelHelper.chat.selectedBufferId.value ?: BufferId(-1)
       val session = modelHelper.connectedSession.value?.orNull()
       val bufferSyncer = session?.bufferSyncer
-      val network = session?.networks?.get(selected?.info?.networkId)
-      val bufferViewConfig = modelHelper.bufferViewConfig.value
-
-      return if (info != null && session != null) {
-        when (item?.itemId) {
-          R.id.action_channellist -> {
-            network?.let {
-              ChannelListActivity.launch(requireContext(), network = it.networkId())
-            }
-            actionMode?.finish()
-            true
-          }
-          R.id.action_configure   -> {
-            network?.let {
-              NetworkEditActivity.launch(requireContext(), network = it.networkId())
-            }
-            actionMode?.finish()
-            true
-          }
-          R.id.action_connect     -> {
-            network?.requestConnect()
-            actionMode?.finish()
-            true
-          }
-          R.id.action_disconnect  -> {
-            network?.requestDisconnect()
-            actionMode?.finish()
-            true
-          }
-          R.id.action_join        -> {
-            session.rpcHandler.sendInput(info, "/join ${info.bufferName}")
-            actionMode?.finish()
-            true
-          }
-          R.id.action_part        -> {
-            session.rpcHandler.sendInput(info, "/part ${info.bufferName}")
-            actionMode?.finish()
-            true
-          }
-          R.id.action_delete      -> {
-            MaterialDialog.Builder(activity!!)
-              .content(R.string.buffer_delete_confirmation)
-              .positiveText(R.string.label_yes)
-              .negativeText(R.string.label_no)
-              .negativeColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .onPositive { _, _ ->
-                selected.info?.let {
-                  session.bufferSyncer.requestRemoveBuffer(info.bufferId)
-                }
-              }
-              .onAny { _, _ ->
-                actionMode?.finish()
-              }
-              .build()
-              .show()
-            true
-          }
-          R.id.action_rename      -> {
-            MaterialDialog.Builder(activity!!)
-              .input(
-                getString(R.string.label_buffer_name),
-                info.bufferName,
-                false
-              ) { _, input ->
-                selected.info?.let {
-                  session.bufferSyncer.requestRenameBuffer(info.bufferId, input.toString())
-                }
-              }
-              .positiveText(R.string.label_save)
-              .negativeText(R.string.label_cancel)
-              .negativeColorAttr(R.attr.colorTextPrimary)
-              .backgroundColorAttr(R.attr.colorBackgroundCard)
-              .contentColorAttr(R.attr.colorTextPrimary)
-              .onAny { _, _ ->
-                actionMode?.finish()
-              }
-              .build()
-              .show()
-            true
-          }
-          R.id.action_unhide      -> {
-            bufferSyncer?.let {
-              bufferViewConfig?.orNull()?.insertBufferSorted(info, bufferSyncer)
-            }
-            actionMode?.finish()
-            true
-          }
-          R.id.action_hide_temp   -> {
-            bufferViewConfig?.orNull()?.requestRemoveBuffer(info.bufferId)
-            actionMode?.finish()
-            true
-          }
-          R.id.action_hide_perm   -> {
-            bufferViewConfig?.orNull()?.requestRemoveBufferPermanently(info.bufferId)
-            actionMode?.finish()
-            true
-          }
-          else                    -> false
-        }
+      val info = bufferSyncer?.bufferInfo(selected)
+      val network = session?.networks?.get(info?.networkId)
+      val bufferViewConfig = modelHelper.bufferViewConfig.value?.orNull()
+
+      return if (info != null) {
+        BufferContextPresenter.handleAction(
+          requireContext(),
+          mode,
+          item,
+          info,
+          session,
+          bufferSyncer,
+          bufferViewConfig,
+          network
+        )
       } else {
         false
       }
@@ -300,18 +209,6 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
       modelHelper.chat.expandedNetworks
     )
 
-    val avatarSize = resources.getDimensionPixelSize(R.dimen.avatar_size_buffer)
-
-    val colorContext = ColorContext(requireContext(), messageSettings)
-
-    val colorAccent = requireContext().theme.styledAttributes(R.attr.colorAccent) {
-      getColor(0, 0)
-    }
-
-    val colorAway = requireContext().theme.styledAttributes(R.attr.colorAway) {
-      getColor(0, 0)
-    }
-
     var chatListState: Parcelable? = savedInstanceState?.getParcelable(KEY_STATE_LIST)
     var hasRestoredChatListState = false
     listAdapter.setOnUpdateFinishedListener {
@@ -333,73 +230,11 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     })
 
     combineLatest(
-      modelHelper.bufferList,
-      modelHelper.chat.expandedNetworks,
-      modelHelper.selectedBuffer,
+      modelHelper.processedBufferList,
       database.filtered().listenRx(accountId).toObservable(),
       accountDatabase.accounts().listenDefaultFiltered(accountId, 0).toObservable()
-    ).map { (info, expandedNetworks, selected, filteredList, defaultFiltered) ->
-      val (config, list) = info ?: Pair(null, emptyList())
-      val minimumActivity = config?.minimumActivity() ?: Buffer_Activity.NONE
-      val activities = filteredList.associate { it.bufferId to it.filtered.toUInt() }
-      list.asSequence().sortedBy { props ->
-        !props.info.type.hasFlag(Buffer_Type.StatusBuffer)
-      }.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { props ->
-        props.network.networkName
-      }).map { props ->
-        val activity = props.activity - (activities[props.info.bufferId]
-                                         ?: defaultFiltered?.toUInt()
-                                         ?: 0u)
-        BufferListItem(
-          props.copy(
-            activity = activity,
-            description = ircFormatDeserializer.formatString(
-              props.description.toString(),
-              colorize = messageSettings.colorizeMirc
-            ),
-            bufferActivity = Buffer_Activity.of(
-              when {
-                props.highlights > 0                  -> Buffer_Activity.Highlight
-                activity.hasFlag(Message_Type.Plain) ||
-                activity.hasFlag(Message_Type.Notice) ||
-                activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage
-                activity.isNotEmpty()                 -> Buffer_Activity.OtherActivity
-                else                                  -> Buffer_Activity.NoActivity
-              }
-            ),
-            fallbackDrawable = if (props.info.type.hasFlag(Buffer_Type.QueryBuffer)) {
-              props.ircUser?.let {
-                val nickName = it.nick()
-                val useSelfColor = when (messageSettings.colorizeNicknames) {
-                  MessageSettings.ColorizeNicknamesMode.ALL          -> false
-                  MessageSettings.ColorizeNicknamesMode.ALL_BUT_MINE ->
-                    props.ircUser?.network()?.isMyNick(nickName) == true
-                  MessageSettings.ColorizeNicknamesMode.NONE         -> true
-                }
-
-                colorContext.buildTextDrawable(it.nick(), useSelfColor)
-              } ?: colorContext.buildTextDrawable("", colorAway)
-            } else {
-              val color = if (props.bufferStatus == BufferStatus.ONLINE) colorAccent
-              else colorAway
-
-              colorContext.buildTextDrawable("#", color)
-            },
-            avatarUrls = props.ircUser?.let {
-              AvatarHelper.avatar(messageSettings, it, avatarSize)
-            } ?: emptyList()
-          ),
-          BufferState(
-            networkExpanded = expandedNetworks[props.network.networkId]
-                              ?: (props.networkConnectionState == INetwork.ConnectionState.Initialized),
-            selected = selected.info?.bufferId == props.info.bufferId
-          )
-        )
-      }.filter { (props, state) ->
-        (props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded) &&
-        (minimumActivity.toInt() <= props.bufferActivity.toInt() ||
-         props.info.type.hasFlag(Buffer_Type.StatusBuffer))
-      }.toList()
+    ).map { (buffers, filteredList, defaultFiltered) ->
+      bufferPresenter.render(buffers, filteredList, defaultFiltered.toUInt())
     }.toLiveData().observe(this, Observer { processedList ->
       if (hasRestoredChatListState) {
         chatListState = chatList.layoutManager?.onSaveInstanceState()
@@ -407,81 +242,14 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
       }
       listAdapter.submitList(processedList)
     })
+
     listAdapter.setOnClickListener(this@BufferViewConfigFragment::clickListener)
     listAdapter.setOnLongClickListener(this@BufferViewConfigFragment::longClickListener)
     chatList.adapter = listAdapter
 
     modelHelper.selectedBuffer.toLiveData().observe(this, Observer { buffer ->
-      if (buffer != null) {
-        val menu = actionMode?.menu
-        if (menu != null) {
-          val allActions = setOf(
-            R.id.action_channellist,
-            R.id.action_configure,
-            R.id.action_connect,
-            R.id.action_disconnect,
-            R.id.action_join,
-            R.id.action_part,
-            R.id.action_delete,
-            R.id.action_rename,
-            R.id.action_unhide,
-            R.id.action_hide_temp,
-            R.id.action_hide_perm
-          )
-
-          val visibilityActions = when (buffer.hiddenState) {
-            BufferHiddenState.VISIBLE          -> setOf(
-              R.id.action_hide_temp,
-              R.id.action_hide_perm
-            )
-            BufferHiddenState.HIDDEN_TEMPORARY -> setOf(
-              R.id.action_unhide,
-              R.id.action_hide_perm
-            )
-            BufferHiddenState.HIDDEN_PERMANENT -> setOf(
-              R.id.action_unhide,
-              R.id.action_hide_temp
-            )
-          }
-
-          val availableActions = when (buffer.info?.type?.enabledValues()?.firstOrNull()) {
-            Buffer_Type.StatusBuffer  -> {
-              when (buffer.connectionState) {
-                INetwork.ConnectionState.Disconnected -> setOf(
-                  R.id.action_configure, R.id.action_connect
-                )
-                INetwork.ConnectionState.Initialized  -> setOf(
-                  R.id.action_channellist, R.id.action_configure, R.id.action_disconnect
-                )
-                else                                  -> setOf(
-                  R.id.action_configure, R.id.action_connect, R.id.action_disconnect
-                )
-              }
-            }
-            Buffer_Type.ChannelBuffer -> {
-              if (buffer.joined) {
-                setOf(R.id.action_part)
-              } else {
-                setOf(R.id.action_join, R.id.action_delete)
-              } + visibilityActions
-            }
-            Buffer_Type.QueryBuffer   -> {
-              setOf(R.id.action_delete, R.id.action_rename) + visibilityActions
-            }
-            else                      -> visibilityActions
-          }
-
-          val unavailableActions = allActions - availableActions
-
-          for (action in availableActions) {
-            menu.findItem(action)?.isVisible = true
-          }
-          for (action in unavailableActions) {
-            menu.findItem(action)?.isVisible = false
-          }
-        }
-      } else {
-        actionMode?.finish()
+      actionMode?.let {
+        BufferContextPresenter.present(it, buffer)
       }
     })
 
@@ -491,21 +259,22 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     chatListToolbar.setOnMenuItemClickListener { item ->
       when (item.itemId) {
         R.id.action_archived_chats -> {
-          ArchiveActivity.launch(requireContext(),
-                                 chatlistId = modelHelper.chat.bufferViewConfigId.or(-1))
+          context?.let {
+            modelHelper.chat.bufferViewConfigId.value?.let { chatlistId ->
+              ArchiveActivity.launch(
+                it,
+                chatlistId = chatlistId
+              )
+            }
+          }
           true
         }
-        R.id.action_search      -> {
+        R.id.action_search         -> {
           item.isChecked = !item.isChecked
           modelHelper.chat.bufferSearchTemporarilyVisible.onNext(item.isChecked)
           true
         }
-        R.id.action_show_hidden -> {
-          item.isChecked = !item.isChecked
-          modelHelper.chat.showHidden.onNext(item.isChecked)
-          true
-        }
-        else                    -> false
+        else                       -> false
       }
     }
     chatList.layoutManager = LinearLayoutManager(context)
@@ -600,7 +369,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     )
 
     fab.setOnActionSelectedListener {
-      val networkId = modelHelper.bufferData?.value?.network?.networkId()
+      val networkId = modelHelper.bufferData.value?.network?.networkId()
       when (it.id) {
         R.id.fab_query  -> {
           context?.let {
@@ -637,6 +406,12 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     outState.putParcelable(KEY_STATE_LIST, chatList.layoutManager?.onSaveInstanceState())
   }
 
+  private fun toggleSelection(buffer: BufferId): Boolean {
+    val next = if (modelHelper.chat.selectedBufferId.value == buffer) BufferId.MAX_VALUE else buffer
+    modelHelper.chat.selectedBufferId.onNext(next)
+    return next != BufferId.MAX_VALUE
+  }
+
   private fun clickListener(bufferId: BufferId) {
     if (actionMode != null) {
       longClickListener(bufferId)
@@ -652,7 +427,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     if (actionMode == null) {
       chatListToolbar.startActionMode(actionModeCallback)
     }
-    if (!listAdapter.toggleSelection(it)) {
+    if (!toggleSelection(it)) {
       actionMode?.finish()
     }
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt
index 048c49abc..eeedd6c3e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ColorContext.kt
@@ -53,19 +53,27 @@ class ColorContext @Inject constructor(
     getColor(0, 0)
   }
 
-  private val radius = context.resources.getDimensionPixelSize(R.dimen.avatar_radius)
+  val colorAccent = context.theme.styledAttributes(R.attr.colorAccent) {
+    getColor(0, 0)
+  }
+
+  val colorAway = context.theme.styledAttributes(R.attr.colorAway) {
+    getColor(0, 0)
+  }
 
+  val avatarRadius = context.resources.getDimensionPixelSize(R.dimen.avatar_radius)
+  val avatarSize = context.resources.getDimensionPixelSize(R.dimen.avatar_size_buffer)
 
-  fun prepareTextDrawable(@ColorInt textColor: Int = this.textColor) =
+  fun prepareTextDrawable(@ColorInt textColor: Int = this.textColor): TextDrawable.IShapeBuilder =
     TextDrawable.builder()
       .beginConfig()
       .textColor(setAlpha(textColor, 0x8A))
       .useFont(Typeface.DEFAULT_BOLD)
       .endConfig()
 
-  fun buildTextDrawable(initial: String, @ColorInt backgroundColor: Int) =
+  fun buildTextDrawable(initial: String, @ColorInt backgroundColor: Int): TextDrawable =
     prepareTextDrawable(textColor).let {
-      if (messageSettings.squareAvatars) it.buildRoundRect(initial, backgroundColor, radius)
+      if (messageSettings.squareAvatars) it.buildRoundRect(initial, backgroundColor, avatarRadius)
       else it.buildRound(initial, backgroundColor)
     }
 
@@ -79,6 +87,8 @@ class ColorContext @Inject constructor(
     return buildTextDrawable(initial, senderColor)
   }
 
-  @ColorInt
-  private fun setAlpha(@ColorInt color: Int, alpha: Int) = (color and 0xFFFFFF) or (alpha shl 24)
+  companion object {
+    @ColorInt
+    private fun setAlpha(@ColorInt color: Int, alpha: Int) = (color and 0xFFFFFF) or (alpha shl 24)
+  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferContextPresenter.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferContextPresenter.kt
new file mode 100644
index 000000000..af99d3ab0
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferContextPresenter.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.util.ui.presenter
+
+import android.content.Context
+import android.view.ActionMode
+import android.view.MenuItem
+import com.afollestad.materialdialogs.MaterialDialog
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.quassel.syncables.BufferSyncer
+import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
+import de.kuschku.libquassel.quassel.syncables.Network
+import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
+import de.kuschku.libquassel.session.ISession
+import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.ui.coresettings.network.NetworkEditActivity
+import de.kuschku.quasseldroid.ui.info.channellist.ChannelListActivity
+import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState
+import de.kuschku.quasseldroid.viewmodel.data.SelectedBufferItem
+
+object BufferContextPresenter {
+  fun present(actionMode: ActionMode, buffer: SelectedBufferItem?) {
+    if (buffer != null) {
+      val menu = actionMode.menu
+      if (menu != null) {
+        val allActions = setOf(
+          R.id.action_channellist,
+          R.id.action_configure,
+          R.id.action_connect,
+          R.id.action_disconnect,
+          R.id.action_join,
+          R.id.action_part,
+          R.id.action_delete,
+          R.id.action_rename,
+          R.id.action_unhide,
+          R.id.action_archive
+        )
+
+        val visibilityActions = when (buffer.hiddenState) {
+          BufferHiddenState.VISIBLE          -> setOf(
+            R.id.action_archive
+          )
+          BufferHiddenState.HIDDEN_TEMPORARY -> setOf(
+            R.id.action_unhide
+          )
+          BufferHiddenState.HIDDEN_PERMANENT -> setOf(
+            R.id.action_unhide
+          )
+        }
+
+        val availableActions = when (buffer.info?.type?.enabledValues()?.firstOrNull()) {
+          Buffer_Type.StatusBuffer  -> {
+            when (buffer.connectionState) {
+              INetwork.ConnectionState.Disconnected -> setOf(
+                R.id.action_configure, R.id.action_connect
+              )
+              INetwork.ConnectionState.Initialized  -> setOf(
+                R.id.action_channellist, R.id.action_configure, R.id.action_disconnect
+              )
+              else                                  -> setOf(
+                R.id.action_configure, R.id.action_connect, R.id.action_disconnect
+              )
+            }
+          }
+          Buffer_Type.ChannelBuffer -> {
+            if (buffer.joined) {
+              setOf(R.id.action_part)
+            } else {
+              setOf(R.id.action_join, R.id.action_delete)
+            } + visibilityActions
+          }
+          Buffer_Type.QueryBuffer   -> {
+            setOf(R.id.action_delete, R.id.action_rename) + visibilityActions
+          }
+          else                      -> visibilityActions
+        }
+
+        val unavailableActions = allActions - availableActions
+
+        for (action in availableActions) {
+          menu.findItem(action)?.isVisible = true
+        }
+        for (action in unavailableActions) {
+          menu.findItem(action)?.isVisible = false
+        }
+      }
+    } else {
+      actionMode.finish()
+    }
+  }
+
+  fun handleAction(
+    context: Context,
+    actionMode: ActionMode,
+    item: MenuItem,
+    info: BufferInfo,
+    session: ISession,
+    bufferSyncer: BufferSyncer,
+    bufferViewConfig: BufferViewConfig?,
+    network: Network?
+  ) = when (item.itemId) {
+    R.id.action_channellist -> {
+      network?.let {
+        ChannelListActivity.launch(context, network = it.networkId())
+      }
+      actionMode.finish()
+      true
+    }
+    R.id.action_configure   -> {
+      network?.let {
+        NetworkEditActivity.launch(context, network = it.networkId())
+      }
+      actionMode.finish()
+      true
+    }
+    R.id.action_connect     -> {
+      network?.requestConnect()
+      actionMode.finish()
+      true
+    }
+    R.id.action_disconnect  -> {
+      network?.requestDisconnect()
+      actionMode.finish()
+      true
+    }
+    R.id.action_join        -> {
+      session.rpcHandler.sendInput(info, "/join ${info.bufferName}")
+      actionMode.finish()
+      true
+    }
+    R.id.action_part        -> {
+      session.rpcHandler.sendInput(info, "/part ${info.bufferName}")
+      actionMode.finish()
+      true
+    }
+    R.id.action_delete      -> {
+      MaterialDialog.Builder(context)
+        .content(R.string.buffer_delete_confirmation)
+        .positiveText(R.string.label_yes)
+        .negativeText(R.string.label_no)
+        .negativeColorAttr(R.attr.colorTextPrimary)
+        .backgroundColorAttr(R.attr.colorBackgroundCard)
+        .contentColorAttr(R.attr.colorTextPrimary)
+        .onPositive { _, _ ->
+          session.bufferSyncer.requestRemoveBuffer(info.bufferId)
+        }
+        .onAny { _, _ ->
+          actionMode.finish()
+        }
+        .build()
+        .show()
+      true
+    }
+    R.id.action_rename      -> {
+      MaterialDialog.Builder(context)
+        .input(
+          context.getString(R.string.label_buffer_name),
+          info.bufferName,
+          false
+        ) { _, input ->
+          session.bufferSyncer.requestRenameBuffer(info.bufferId, input.toString())
+        }
+        .positiveText(R.string.label_save)
+        .negativeText(R.string.label_cancel)
+        .negativeColorAttr(R.attr.colorTextPrimary)
+        .backgroundColorAttr(R.attr.colorBackgroundCard)
+        .contentColorAttr(R.attr.colorTextPrimary)
+        .onAny { _, _ ->
+          actionMode.finish()
+        }
+        .build()
+        .show()
+      true
+    }
+    R.id.action_unhide      -> {
+      bufferViewConfig?.insertBufferSorted(info, bufferSyncer)
+      actionMode.finish()
+      true
+    }
+    R.id.action_archive     -> {
+      MaterialDialog.Builder(context)
+        .title(R.string.label_archive_chat)
+        .content(R.string.buffer_archive_confirmation)
+        .checkBoxPromptRes(R.string.buffer_archive_temporarily, true, null)
+        .positiveText(R.string.label_archive)
+        .negativeText(R.string.label_cancel)
+        .negativeColorAttr(R.attr.colorTextPrimary)
+        .backgroundColorAttr(R.attr.colorBackgroundCard)
+        .contentColorAttr(R.attr.colorTextPrimary)
+        .onAny { _, _ ->
+          actionMode.finish()
+        }
+        .onPositive { dialog, _ ->
+          if (dialog.isPromptCheckBoxChecked) {
+            bufferViewConfig?.requestRemoveBuffer(info.bufferId)
+          } else {
+            bufferViewConfig?.requestRemoveBufferPermanently(info.bufferId)
+          }
+        }
+        .build()
+        .show()
+      true
+    }
+    else                    -> false
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferPresenter.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferPresenter.kt
new file mode 100644
index 000000000..7777d3499
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/presenter/BufferPresenter.kt
@@ -0,0 +1,101 @@
+/*
+ * 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.util.ui.presenter
+
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.Buffer_Activity
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.libquassel.util.flag.hasFlag
+import de.kuschku.libquassel.util.flag.minus
+import de.kuschku.quasseldroid.persistence.models.Filtered
+import de.kuschku.quasseldroid.settings.AppearanceSettings
+import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.util.ColorContext
+import de.kuschku.quasseldroid.util.avatars.AvatarHelper
+import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
+import de.kuschku.quasseldroid.viewmodel.data.BufferListItem
+import de.kuschku.quasseldroid.viewmodel.data.BufferProps
+import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
+import javax.inject.Inject
+
+class BufferPresenter @Inject constructor(
+  val appearanceSettings: AppearanceSettings,
+  val messageSettings: MessageSettings,
+  val ircFormatDeserializer: IrcFormatDeserializer,
+  val colorContext: ColorContext
+) {
+  fun render(props: BufferProps,
+             activities: Map<BufferId, UInt>,
+             defaultFiltered: UInt
+  ): BufferProps {
+    val activity = props.activity - (activities[props.info.bufferId]
+                                     ?: defaultFiltered
+                                     ?: 0u)
+
+    return props.copy(
+      activity = activity,
+      description = ircFormatDeserializer.formatString(
+        props.description.toString(),
+        colorize = messageSettings.colorizeMirc
+      ),
+      bufferActivity = Buffer_Activity.of(
+        when {
+          props.highlights > 0                  -> Buffer_Activity.Highlight
+          activity.hasFlag(Message_Type.Plain) ||
+          activity.hasFlag(Message_Type.Notice) ||
+          activity.hasFlag(Message_Type.Action) -> Buffer_Activity.NewMessage
+          activity.isNotEmpty()                 -> Buffer_Activity.OtherActivity
+          else                                  -> Buffer_Activity.NoActivity
+        }
+      ),
+      fallbackDrawable = if (props.info.type.hasFlag(Buffer_Type.QueryBuffer)) {
+        props.ircUser?.let {
+          val nickName = it.nick()
+          val useSelfColor = when (messageSettings.colorizeNicknames) {
+            MessageSettings.ColorizeNicknamesMode.ALL          -> false
+            MessageSettings.ColorizeNicknamesMode.ALL_BUT_MINE ->
+              props.ircUser?.network()?.isMyNick(nickName) == true
+            MessageSettings.ColorizeNicknamesMode.NONE         -> true
+          }
+
+          colorContext.buildTextDrawable(it.nick(), useSelfColor)
+        } ?: colorContext.buildTextDrawable("", colorContext.colorAway)
+      } else {
+        val color = if (props.bufferStatus == BufferStatus.ONLINE) colorContext.colorAccent
+        else colorContext.colorAway
+
+        colorContext.buildTextDrawable("#", color)
+      },
+      avatarUrls = props.ircUser?.let {
+        AvatarHelper.avatar(messageSettings, it, colorContext.avatarSize)
+      } ?: emptyList()
+    )
+  }
+
+  fun render(buffers: List<BufferListItem>, filteredList: List<Filtered>, defaultFiltered: UInt) =
+    buffers.map {
+      it.copy(props = render(
+        it.props,
+        filteredList.associate { it.bufferId to it.filtered.toUInt() },
+        defaultFiltered
+      ))
+    }
+}
diff --git a/app/src/main/res/layout/chat_archive.xml b/app/src/main/res/layout/chat_archive.xml
index 3c6666c29..c66e128af 100644
--- a/app/src/main/res/layout/chat_archive.xml
+++ b/app/src/main/res/layout/chat_archive.xml
@@ -23,6 +23,7 @@
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
+  android:scrollbars="vertical"
   android:orientation="vertical">
 
   <LinearLayout
@@ -53,7 +54,9 @@
       android:id="@+id/list_temporary"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
-      tools:listitem="@layout/widget_buffer_reorder" />
+      android:layout_marginStart="56dp"
+      android:layout_marginLeft="56dp"
+      tools:listitem="@layout/widget_buffer" />
 
     <LinearLayout style="@style/Widget.CoreSettings.PrimaryItemGroupHeader">
 
@@ -78,6 +81,8 @@
       android:id="@+id/list_permanently"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
-      tools:listitem="@layout/widget_buffer_reorder" />
+      android:layout_marginStart="56dp"
+      android:layout_marginLeft="56dp"
+      tools:listitem="@layout/widget_buffer" />
   </LinearLayout>
 </androidx.core.widget.NestedScrollView>
diff --git a/app/src/main/res/layout/widget_buffer.xml b/app/src/main/res/layout/widget_buffer.xml
index 777b22f80..e5d92d591 100644
--- a/app/src/main/res/layout/widget_buffer.xml
+++ b/app/src/main/res/layout/widget_buffer.xml
@@ -18,6 +18,7 @@
   -->
 
 <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"
@@ -41,9 +42,10 @@
     tools:src="@tools:sample/avatars" />
 
   <LinearLayout
-    android:layout_width="match_parent"
+    android:layout_width="0dip"
     android:layout_height="wrap_content"
     android:layout_gravity="center_vertical"
+    android:layout_weight="1"
     android:orientation="vertical">
 
     <TextView
@@ -71,4 +73,15 @@
       tools:text="@sample/messages.json/data/sender"
       tools:visibility="visible" />
   </LinearLayout>
+
+  <androidx.appcompat.widget.AppCompatImageView
+    android:id="@+id/handle"
+    android:layout_width="24dp"
+    android:layout_height="24dp"
+    android:layout_gravity="center_vertical"
+    android:layout_marginStart="16dp"
+    android:layout_marginLeft="16dp"
+    android:contentDescription="@string/label_reorder"
+    app:srcCompat="@drawable/ic_reorder"
+    app:tint="?colorTextSecondary" />
 </LinearLayout>
diff --git a/app/src/main/res/layout/widget_buffer_away.xml b/app/src/main/res/layout/widget_buffer_away.xml
index 838477b53..3e4a9c043 100644
--- a/app/src/main/res/layout/widget_buffer_away.xml
+++ b/app/src/main/res/layout/widget_buffer_away.xml
@@ -85,4 +85,15 @@
     android:contentDescription="@string/label_user_away"
     app:srcCompat="@drawable/ic_clock"
     app:tint="?colorTextSecondary" />
+
+  <androidx.appcompat.widget.AppCompatImageView
+    android:id="@+id/handle"
+    android:layout_width="24dp"
+    android:layout_height="24dp"
+    android:layout_gravity="center_vertical"
+    android:layout_marginStart="16dp"
+    android:layout_marginLeft="16dp"
+    android:contentDescription="@string/label_reorder"
+    app:srcCompat="@drawable/ic_reorder"
+    app:tint="?colorTextSecondary" />
 </LinearLayout>
diff --git a/app/src/main/res/layout/widget_buffer_reorder.xml b/app/src/main/res/layout/widget_buffer_reorder.xml
deleted file mode 100644
index d44f6d9ae..000000000
--- a/app/src/main/res/layout/widget_buffer_reorder.xml
+++ /dev/null
@@ -1,89 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  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/>.
-  -->
-
-<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:layout_marginStart="56dp"
-  android:layout_marginLeft="56dp"
-  android:background="?attr/backgroundMenuItem"
-  android:minHeight="?listPreferredItemHeightSmall"
-  android:orientation="horizontal"
-  android:paddingLeft="16dp"
-  android:paddingTop="4dp"
-  android:paddingRight="16dp"
-  android:paddingBottom="4dp"
-  android:textAppearance="?android:attr/textAppearanceListItemSmall">
-
-  <androidx.appcompat.widget.AppCompatImageView
-    android:id="@+id/status"
-    android:layout_width="@dimen/avatar_size_buffer"
-    android:layout_height="@dimen/avatar_size_buffer"
-    android:layout_gravity="center_vertical"
-    android:layout_marginEnd="16dp"
-    android:layout_marginRight="16dp"
-    android:contentDescription="@string/label_avatar"
-    tools:src="@tools:sample/avatars" />
-
-  <LinearLayout
-    android:layout_width="0dip"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_vertical"
-    android:layout_weight="1"
-    android:orientation="vertical">
-
-    <TextView
-      android:id="@+id/name"
-      style="@style/Widget.RtlConformTextView"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center_vertical|start"
-      android:ellipsize="marquee"
-      android:fontFamily="sans-serif-medium"
-      android:singleLine="true"
-      android:textColor="?attr/colorTextPrimary"
-      android:textSize="13sp"
-      tools:text="@sample/messages.json/data/sender" />
-
-    <TextView
-      android:id="@+id/description"
-      style="@style/Widget.RtlConformTextView"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:ellipsize="marquee"
-      android:singleLine="true"
-      android:textColor="?attr/colorTextSecondary"
-      android:textSize="12sp"
-      tools:text="@sample/messages.json/data/sender"
-      tools:visibility="visible" />
-  </LinearLayout>
-
-  <androidx.appcompat.widget.AppCompatImageView
-    android:id="@+id/reorder"
-    android:layout_width="24dp"
-    android:layout_height="24dp"
-    android:layout_gravity="center_vertical"
-    android:layout_marginStart="16dp"
-    android:layout_marginLeft="16dp"
-    android:contentDescription="@string/label_reorder"
-    app:srcCompat="@drawable/ic_reorder"
-    app:tint="?colorTextSecondary" />
-</LinearLayout>
diff --git a/app/src/main/res/layout/widget_buffer_reorder_away.xml b/app/src/main/res/layout/widget_buffer_reorder_away.xml
deleted file mode 100644
index f4b562f77..000000000
--- a/app/src/main/res/layout/widget_buffer_reorder_away.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  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/>.
-  -->
-
-<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:layout_marginStart="56dp"
-  android:layout_marginLeft="56dp"
-  android:background="?attr/backgroundMenuItem"
-  android:minHeight="?listPreferredItemHeightSmall"
-  android:orientation="horizontal"
-  android:paddingLeft="16dp"
-  android:paddingTop="4dp"
-  android:paddingRight="16dp"
-  android:paddingBottom="4dp"
-  android:textAppearance="?android:attr/textAppearanceListItemSmall">
-
-  <androidx.appcompat.widget.AppCompatImageView
-    android:id="@+id/status"
-    android:layout_width="@dimen/avatar_size_buffer"
-    android:layout_height="@dimen/avatar_size_buffer"
-    android:layout_gravity="center_vertical"
-    android:layout_marginEnd="16dp"
-    android:layout_marginRight="16dp"
-    android:contentDescription="@string/label_avatar"
-    tools:src="@tools:sample/avatars" />
-
-  <LinearLayout
-    android:layout_width="0dip"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center_vertical"
-    android:layout_weight="1"
-    android:orientation="vertical">
-
-    <TextView
-      android:id="@+id/name"
-      style="@style/Widget.RtlConformTextView"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center_vertical|start"
-      android:ellipsize="marquee"
-      android:fontFamily="sans-serif-medium"
-      android:singleLine="true"
-      android:textColor="?attr/colorTextSecondary"
-      android:textSize="13sp"
-      android:textStyle="italic"
-      tools:text="@sample/messages.json/data/sender" />
-
-    <TextView
-      android:id="@+id/description"
-      style="@style/Widget.RtlConformTextView"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:ellipsize="marquee"
-      android:singleLine="true"
-      android:textColor="?attr/colorTextSecondary"
-      android:textSize="12sp"
-      android:textStyle="italic"
-      tools:text="@sample/messages.json/data/sender"
-      tools:visibility="visible" />
-  </LinearLayout>
-
-  <androidx.appcompat.widget.AppCompatImageView
-    android:layout_width="24dp"
-    android:layout_height="24dp"
-    android:layout_gravity="center_vertical"
-    android:layout_marginStart="16dp"
-    android:layout_marginLeft="16dp"
-    android:contentDescription="@string/label_user_away"
-    app:srcCompat="@drawable/ic_clock"
-    app:tint="?colorTextSecondary" />
-
-  <androidx.appcompat.widget.AppCompatImageView
-    android:id="@+id/reorder"
-    android:layout_width="24dp"
-    android:layout_height="24dp"
-    android:layout_gravity="center_vertical"
-    android:layout_marginStart="16dp"
-    android:layout_marginLeft="16dp"
-    android:contentDescription="@string/label_reorder"
-    app:srcCompat="@drawable/ic_reorder"
-    app:tint="?colorTextSecondary" />
-</LinearLayout>
diff --git a/app/src/main/res/menu/context_buffer.xml b/app/src/main/res/menu/context_buffer.xml
index 87ea5e57d..7ad41c0a6 100644
--- a/app/src/main/res/menu/context_buffer.xml
+++ b/app/src/main/res/menu/context_buffer.xml
@@ -48,11 +48,7 @@
     android:title="@string/label_unhide"
     app:showAsAction="never" />
   <item
-    android:id="@+id/action_hide_temp"
-    android:title="@string/label_hide_temp"
-    app:showAsAction="never" />
-  <item
-    android:id="@+id/action_hide_perm"
-    android:title="@string/label_hide_perm"
+    android:id="@+id/action_archive"
+    android:title="@string/label_archive"
     app:showAsAction="never" />
 </menu>
diff --git a/app/src/main/res/menu/context_bufferlist.xml b/app/src/main/res/menu/context_bufferlist.xml
index c1c66b6c0..5aa271be7 100644
--- a/app/src/main/res/menu/context_bufferlist.xml
+++ b/app/src/main/res/menu/context_bufferlist.xml
@@ -25,8 +25,4 @@
     android:id="@+id/action_search"
     android:checkable="true"
     android:title="@string/label_search_buffer" />
-  <item
-    android:id="@+id/action_show_hidden"
-    android:checkable="true"
-    android:title="@string/label_show_hidden" />
 </menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c327f3f31..3205d5d09 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -28,13 +28,15 @@
   <string name="label_about">About</string>
   <string name="label_accept">Accept</string>
   <string name="label_acknowledgements">Acknowledgements</string>
+  <string name="label_archive">Archive</string>
+  <string name="label_archive_chat">Archive Chat</string>
   <string name="label_archived_chats">Archived Chats</string>
   <string name="label_ascending">Ascending</string>
   <string name="label_authors">Authors</string>
   <string name="label_autocomplete">Autocomplete</string>
   <string name="label_avatar">Avatar</string>
   <string name="label_back">Back</string>
-  <string name="label_buffer_name">Buffer Name</string>
+  <string name="label_buffer_name">Chat Name</string>
   <string name="label_cancel">Cancel</string>
   <string name="label_certificates">Certificates</string>
   <string name="label_channel_name">Channel Name</string>
@@ -59,8 +61,8 @@
   <string name="label_filter_messages">Filter Messages</string>
   <string name="label_finish">Finish</string>
   <string name="label_generate_crash_report">Generate Crash Report</string>
-  <string name="label_hide_perm">Hide Permanently</string>
-  <string name="label_hide_temp">Hide Temporarily</string>
+  <string name="label_hide_perm">Archive Permanently</string>
+  <string name="label_hide_temp">Archive Temporarily</string>
   <string name="label_ignore">Ignore</string>
   <string name="label_ignore_long">Add/remove user to/from ignore list</string>
   <string name="label_info">Details</string>
@@ -131,7 +133,6 @@
   <string name="label_share_crashreport">Share Crash Report</string>
   <string name="label_shortcut">Shortcut</string>
   <string name="label_shortcut_long">Create Shortcut on Homescreen</string>
-  <string name="label_show_hidden">Show Hidden</string>
   <string name="label_sort">Sort</string>
   <string name="label_source">Source</string>
   <string name="label_temporarily_archived">Temporarily Archived</string>
@@ -190,6 +191,8 @@
   <string name="info_missing_features" tools:ignore="StringFormatCount">Your core is missing features that are required for Quasseldroid to work correctly. You should &lt;a href="https://quassel-irc.org&gt;upgrade&lt;/a&gt; your Quassel core to %1$s or newer.</string>
 
   <string name="buffer_delete_confirmation">Do you want to delete this buffer permanently?</string>
+  <string name="buffer_archive_confirmation">Do you want to archive this buffer?</string>
+  <string name="buffer_archive_temporarily">Automatically unhide this chat once it has new messages</string>
 
   <string name="delete_confirmation">Are you sure you want to delete this permanently? This can not be undone.</string>
   <string name="cancel_confirmation">You have unsaved changes. Do you wish to discard them?</string>
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ArchiveViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ArchiveViewModel.kt
new file mode 100644
index 000000000..b26f3a5ee
--- /dev/null
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/ArchiveViewModel.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.viewmodel
+
+import android.os.Bundle
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.MsgId
+import de.kuschku.libquassel.protocol.NetworkId
+import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage
+import io.reactivex.subjects.BehaviorSubject
+import io.reactivex.subjects.PublishSubject
+
+open class ArchiveViewModel : QuasselViewModel() {
+  val bufferViewConfigId = BehaviorSubject.createDefault(-1)
+  val visibleExpandedNetworks = BehaviorSubject.createDefault(emptyMap<NetworkId, Boolean>())
+  val temporarilyExpandedNetworks = BehaviorSubject.createDefault(emptyMap<NetworkId, Boolean>())
+  val permanentlyExpandedNetworks = BehaviorSubject.createDefault(emptyMap<NetworkId, Boolean>())
+  val selectedBufferId = BehaviorSubject.createDefault(BufferId.MAX_VALUE)
+
+  fun onSaveInstanceState(outState: Bundle) {
+    outState.putInt(
+      KEY_BUFFER_VIEW_CONFIG_ID,
+      bufferViewConfigId.value)
+    outState.putSerializable(
+      KEY_VISIBLE_EXPANDED_NETWORKS,
+      HashMap(visibleExpandedNetworks.value))
+    outState.putSerializable(
+      KEY_TEMPORARILY_EXPANDED_NETWORKS,
+      HashMap(temporarilyExpandedNetworks.value))
+    outState.putSerializable(
+      KEY_PERMANENTLY_EXPANDED_NETWORKS,
+      HashMap(permanentlyExpandedNetworks.value))
+    outState.putInt(
+      KEY_SELECTED_BUFFER_ID,
+      selectedBufferId.value.id)
+  }
+
+  fun onRestoreInstanceState(savedInstanceState: Bundle) {
+    if (savedInstanceState.containsKey(KEY_BUFFER_VIEW_CONFIG_ID))
+      bufferViewConfigId.onNext(savedInstanceState.getInt(KEY_BUFFER_VIEW_CONFIG_ID))
+    if (savedInstanceState.containsKey(KEY_VISIBLE_EXPANDED_NETWORKS)) {
+      visibleExpandedNetworks.onNext(
+        savedInstanceState.getSerializable(KEY_VISIBLE_EXPANDED_NETWORKS) as? HashMap<NetworkId, Boolean>
+        ?: emptyMap()
+      )
+    }
+    if (savedInstanceState.containsKey(KEY_TEMPORARILY_EXPANDED_NETWORKS)) {
+      temporarilyExpandedNetworks.onNext(
+        savedInstanceState.getSerializable(KEY_TEMPORARILY_EXPANDED_NETWORKS) as? HashMap<NetworkId, Boolean>
+        ?: emptyMap()
+      )
+    }
+    if (savedInstanceState.containsKey(KEY_PERMANENTLY_EXPANDED_NETWORKS)) {
+      permanentlyExpandedNetworks.onNext(
+        savedInstanceState.getSerializable(KEY_PERMANENTLY_EXPANDED_NETWORKS) as? HashMap<NetworkId, Boolean>
+        ?: emptyMap()
+      )
+    }
+
+    if (savedInstanceState.containsKey(KEY_SELECTED_BUFFER_ID))
+      selectedBufferId.onNext(BufferId(savedInstanceState.getInt(KEY_SELECTED_BUFFER_ID)))
+  }
+
+  companion object {
+    const val KEY_BUFFER_VIEW_CONFIG_ID = "model_archive_bufferViewConfigId"
+    const val KEY_VISIBLE_EXPANDED_NETWORKS = "model_archive_visibleExpandedNetworks"
+    const val KEY_TEMPORARILY_EXPANDED_NETWORKS = "model_archive_temporarilyExpandedNetworks"
+    const val KEY_PERMANENTLY_EXPANDED_NETWORKS = "model_archive_permanentlyExpandedNetworks"
+    const val KEY_SELECTED_BUFFER_ID = "model_archive_selectedBufferId"
+  }
+}
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferState.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferState.kt
index 7ed5b7123..f7b89d58d 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferState.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/BufferState.kt
@@ -21,5 +21,6 @@ package de.kuschku.quasseldroid.viewmodel.data
 
 data class BufferState(
   val networkExpanded: Boolean,
-  val selected: Boolean
+  val selected: Boolean,
+  val showHandle: Boolean
 )
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
new file mode 100644
index 000000000..2f555d87f
--- /dev/null
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/helper/ArchiveViewModelHelper.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.viewmodel.helper
+
+import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.Buffer_Type
+import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
+import de.kuschku.libquassel.quassel.syncables.Network
+import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
+import de.kuschku.libquassel.util.Optional
+import de.kuschku.libquassel.util.flag.hasFlag
+import de.kuschku.libquassel.util.helper.combineLatest
+import de.kuschku.libquassel.util.helper.flatMapSwitchMap
+import de.kuschku.libquassel.util.helper.mapSwitchMap
+import de.kuschku.libquassel.util.helper.safeSwitchMap
+import de.kuschku.libquassel.util.irc.IrcCaseMappers
+import de.kuschku.quasseldroid.viewmodel.ArchiveViewModel
+import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
+import de.kuschku.quasseldroid.viewmodel.data.BufferHiddenState
+import de.kuschku.quasseldroid.viewmodel.data.BufferProps
+import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
+import io.reactivex.Observable
+import javax.inject.Inject
+
+open class ArchiveViewModelHelper @Inject constructor(
+  val archive: ArchiveViewModel,
+  quassel: QuasselViewModel
+) : QuasselViewModelHelper(quassel) {
+  val bufferViewConfig = bufferViewManager.flatMapSwitchMap { manager ->
+    archive.bufferViewConfigId.map { id ->
+      Optional.ofNullable(manager.bufferViewConfig(id))
+    }.mapSwitchMap(BufferViewConfig::liveUpdates)
+  }
+
+  fun processBufferList(bufferListType: BufferHiddenState) =
+    combineLatest(connectedSession, bufferViewConfig)
+      .safeSwitchMap { (sessionOptional, configOptional) ->
+        val session = sessionOptional.orNull()
+        val bufferSyncer = session?.bufferSyncer
+        val config = configOptional.orNull()
+        if (bufferSyncer != null && config != null) {
+          session.liveNetworks().safeSwitchMap { networks ->
+            config.liveUpdates()
+              .safeSwitchMap { currentConfig ->
+                combineLatest<Collection<BufferId>>(
+                  listOf(
+                    config.liveBuffers(),
+                    config.liveTemporarilyRemovedBuffers(),
+                    config.liveRemovedBuffers()
+                  )
+                ).safeSwitchMap { (ids, temp, perm) ->
+                  fun missingStatusBuffers(
+                    list: Collection<BufferId>
+                  ): Sequence<Observable<BufferProps>?> {
+                    val buffers = list.asSequence().mapNotNull { id ->
+                      bufferSyncer.bufferInfo(id)
+                    }
+
+                    val totalNetworks = 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
+
+                    return missingNetworks.asSequence().filter {
+                      !currentConfig.networkId().isValidId() || currentConfig.networkId() == it
+                    }.filter {
+                      currentConfig.allowedBufferTypes().hasFlag(Buffer_Type.StatusBuffer)
+                    }.mapNotNull {
+                      networks[it]
+                    }.filter {
+                      !config.hideInactiveNetworks() || it.isConnected()
+                    }.map<Network, Observable<BufferProps>?> { network ->
+                      network.liveNetworkInfo().safeSwitchMap { networkInfo ->
+                        network.liveConnectionState().map { connectionState ->
+                          BufferProps(
+                            info = BufferInfo(
+                              bufferId = BufferId(-networkInfo.networkId.id),
+                              networkId = networkInfo.networkId,
+                              groupId = 0,
+                              bufferName = networkInfo.networkName,
+                              type = Buffer_Type.of(Buffer_Type.StatusBuffer)
+                            ),
+                            network = networkInfo,
+                            networkConnectionState = connectionState,
+                            bufferStatus = BufferStatus.OFFLINE,
+                            description = "",
+                            activity = Message_Type.of(),
+                            highlights = 0,
+                            hiddenState = BufferHiddenState.VISIBLE
+                          )
+                        }
+                      }
+                    }
+                  }
+
+                  fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) =
+                    processRawBufferList(ids, state, bufferSyncer, networks, currentConfig) +
+                    missingStatusBuffers(ids)
+
+                  bufferSyncer.liveBufferInfos().safeSwitchMap {
+                    val buffers = when (bufferListType) {
+                      BufferHiddenState.VISIBLE          ->
+                        transformIds(ids, BufferHiddenState.VISIBLE)
+                      BufferHiddenState.HIDDEN_TEMPORARY ->
+                        transformIds(temp - ids, BufferHiddenState.HIDDEN_TEMPORARY)
+                      BufferHiddenState.HIDDEN_PERMANENT ->
+                        transformIds(perm - temp - ids, BufferHiddenState.HIDDEN_PERMANENT)
+                    }
+
+                    combineLatest<BufferProps>(buffers.toList()).map { list ->
+                      Pair<BufferViewConfig?, List<BufferProps>>(
+                        config,
+                        list.asSequence().filter {
+                          !config.hideInactiveNetworks() ||
+                          it.networkConnectionState == INetwork.ConnectionState.Initialized
+                        }.filter {
+                          (!config.hideInactiveBuffers()) ||
+                          it.bufferStatus != BufferStatus.OFFLINE ||
+                          it.info.type.hasFlag(Buffer_Type.StatusBuffer)
+                        }.let {
+                          if (config.sortAlphabetically())
+                            it.sortedBy { IrcCaseMappers.unicode.toLowerCaseNullable(it.info.bufferName) }
+                              .sortedBy { it.matchMode.priority }
+                              .sortedByDescending { it.hiddenState == BufferHiddenState.VISIBLE }
+                          else it
+                        }.distinctBy {
+                          it.info.bufferId
+                        }.toList()
+                      )
+                    }
+                  }
+                }
+              }
+          }
+        } else {
+          Observable.just(Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList()))
+        }
+      }
+
+  fun processArchiveBufferList(
+    bufferListType: BufferHiddenState,
+    showHandle: Boolean
+  ) = processInternalBufferList(
+    processBufferList(bufferListType),
+    when (bufferListType) {
+      BufferHiddenState.VISIBLE          -> archive.visibleExpandedNetworks
+      BufferHiddenState.HIDDEN_TEMPORARY -> archive.temporarilyExpandedNetworks
+      BufferHiddenState.HIDDEN_PERMANENT -> archive.permanentlyExpandedNetworks
+    },
+    archive.selectedBufferId,
+    showHandle
+  )
+
+  val selectedBuffer = processSelectedBuffer(archive.selectedBufferId, bufferViewConfig)
+}
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 68b177a1e..f0016d17f 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
@@ -30,7 +30,6 @@ import de.kuschku.libquassel.quassel.syncables.IrcUser
 import de.kuschku.libquassel.quassel.syncables.Network
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.util.Optional
-import de.kuschku.libquassel.util.flag.and
 import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.helper.*
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
@@ -175,63 +174,7 @@ open class ChatViewModelHelper @Inject constructor(
   val nickDataThrottled =
     nickData.distinctUntilChanged().throttleLast(100, TimeUnit.MILLISECONDS)
 
-  val selectedBuffer = combineLatest(connectedSession, chat.selectedBufferId, bufferViewConfig)
-    .safeSwitchMap { (sessionOptional, buffer, bufferViewConfigOptional) ->
-      val session = sessionOptional.orNull()
-      val bufferSyncer = session?.bufferSyncer
-      val bufferViewConfig = bufferViewConfigOptional.orNull()
-      if (bufferSyncer != null && bufferViewConfig != null) {
-        session.liveNetworks().safeSwitchMap { networks ->
-          val hiddenState = when {
-            bufferViewConfig.removedBuffers().contains(buffer)            ->
-              BufferHiddenState.HIDDEN_PERMANENT
-            bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) ->
-              BufferHiddenState.HIDDEN_TEMPORARY
-            else                                                          ->
-              BufferHiddenState.VISIBLE
-          }
-
-          val info = if (!buffer.isValidId()) networks[NetworkId(-buffer.id)]?.let {
-            BufferInfo(
-              bufferId = buffer,
-              networkId = it.networkId(),
-              groupId = 0,
-              bufferName = it.networkName(),
-              type = Buffer_Type.of(Buffer_Type.StatusBuffer)
-            )
-          } else bufferSyncer.bufferInfo(buffer)
-          if (info != null) {
-            val network = networks[info.networkId]
-            when (info.type.enabledValues().firstOrNull()) {
-              Buffer_Type.StatusBuffer  -> {
-                network?.liveConnectionState()?.map {
-                  SelectedBufferItem(
-                    info,
-                    connectionState = it,
-                    hiddenState = hiddenState
-                  )
-                } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
-              }
-              Buffer_Type.ChannelBuffer -> {
-                network?.liveIrcChannel(info.bufferName)?.mapNullable(IrcChannel.NULL) {
-                  SelectedBufferItem(
-                    info,
-                    joined = it != null,
-                    hiddenState = hiddenState
-                  )
-                } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
-              }
-              else                      ->
-                Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
-            }
-          } else {
-            Observable.just(SelectedBufferItem())
-          }
-        }
-      } else {
-        Observable.just(SelectedBufferItem())
-      }
-    }
+  val selectedBuffer = processSelectedBuffer(chat.selectedBufferId, bufferViewConfig)
 
   val bufferList: Observable<Pair<BufferViewConfig?, List<BufferProps>>> =
     combineLatest(connectedSession, bufferViewConfig, chat.showHidden, chat.bufferSearch)
@@ -252,109 +195,14 @@ open class ChatViewModelHelper @Inject constructor(
                   )
                 ).safeSwitchMap { (ids, temp, perm) ->
                   fun transformIds(ids: Collection<BufferId>, state: BufferHiddenState) =
-                    ids.asSequence().mapNotNull { id ->
-                      bufferSyncer.bufferInfo(id)
-                    }.filter {
-                      bufferSearch.isBlank() ||
-                      it.type.hasFlag(Buffer_Type.StatusBuffer) ||
-                      it.bufferName?.contains(bufferSearch, ignoreCase = true) == true
-                    }.filter {
-                      !currentConfig.networkId().isValidId() || currentConfig.networkId() == it.networkId
-                    }.filter {
-                      (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
-                      (it.type.hasFlag(Buffer_Type.StatusBuffer) && !currentConfig.networkId().isValidId())
-                    }.mapNotNull {
-                      val network = networks[it.networkId]
-                      if (network == null) {
-                        null
-                      } else {
-                        it to network
-                      }
-                    }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) ->
-                      bufferSyncer.liveActivity(info.bufferId).safeSwitchMap { activity ->
-                        bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
-                          activity to highlights
-                        }
-                      }.safeSwitchMap { (activity, highlights) ->
-                        val name = info.bufferName?.trim() ?: ""
-                        val search = bufferSearch.trim()
-                        val matchMode = when {
-                          name.equals(search, ignoreCase = true)     -> MatchMode.EXACT
-                          name.startsWith(search, ignoreCase = true) -> MatchMode.START
-                          else                                       -> MatchMode.CONTAINS
-                        }
-                        when (info.type.toInt()) {
-                          BufferInfo.Type.QueryBuffer.toInt()   -> {
-                            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
-                              network.liveConnectionState().safeSwitchMap { connectionState ->
-                                network.liveIrcUser(info.bufferName).safeSwitchMap {
-                                  it.updates().mapNullable(IrcUser.NULL) { user ->
-                                    BufferProps(
-                                      info = info,
-                                      network = networkInfo,
-                                      networkConnectionState = connectionState,
-                                      bufferStatus = when {
-                                        user == null  -> BufferStatus.OFFLINE
-                                        user.isAway() -> BufferStatus.AWAY
-                                        else          -> BufferStatus.ONLINE
-                                      },
-                                      description = user?.realName() ?: "",
-                                      activity = activity,
-                                      highlights = highlights,
-                                      hiddenState = state,
-                                      ircUser = user,
-                                      matchMode = matchMode
-                                    )
-                                  }
-                                }
-                              }
-                            }
-                          }
-                          BufferInfo.Type.ChannelBuffer.toInt() -> {
-                            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
-                              network.liveConnectionState().safeSwitchMap { connectionState ->
-                                network.liveIrcChannel(info.bufferName).safeSwitchMap { channel ->
-                                  channel.updates().mapNullable(IrcChannel.NULL) {
-                                    BufferProps(
-                                      info = info,
-                                      network = networkInfo,
-                                      networkConnectionState = connectionState,
-                                      bufferStatus = when (it) {
-                                        null -> BufferStatus.OFFLINE
-                                        else -> BufferStatus.ONLINE
-                                      },
-                                      description = it?.topic() ?: "",
-                                      activity = activity,
-                                      highlights = highlights,
-                                      hiddenState = state,
-                                      matchMode = matchMode
-                                    )
-                                  }
-                                }
-                              }
-                            }
-                          }
-                          BufferInfo.Type.StatusBuffer.toInt()  -> {
-                            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
-                              network.liveConnectionState().map { connectionState ->
-                                BufferProps(
-                                  info = info,
-                                  network = networkInfo,
-                                  networkConnectionState = connectionState,
-                                  bufferStatus = BufferStatus.OFFLINE,
-                                  description = "",
-                                  activity = activity,
-                                  highlights = highlights,
-                                  hiddenState = state,
-                                  matchMode = matchMode
-                                )
-                              }
-                            }
-                          }
-                          else                                  -> Observable.empty()
-                        }
-                      }
-                    }
+                    processRawBufferList(
+                      ids,
+                      state,
+                      bufferSyncer,
+                      networks,
+                      currentConfig,
+                      bufferSearch
+                    )
 
                   fun missingStatusBuffers(
                     list: Collection<BufferId>): Sequence<Observable<BufferProps>?> {
@@ -444,4 +292,11 @@ open class ChatViewModelHelper @Inject constructor(
           Observable.just(Pair<BufferViewConfig?, List<BufferProps>>(null, emptyList()))
         }
       }
+
+  val processedBufferList = processInternalBufferList(
+    bufferList,
+    chat.expandedNetworks,
+    chat.selectedBufferId,
+    false
+  )
 }
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 005e006e7..6ad271a7f 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
@@ -21,17 +21,21 @@ package de.kuschku.quasseldroid.viewmodel.helper
 
 import de.kuschku.libquassel.connection.ConnectionState
 import de.kuschku.libquassel.connection.Features
-import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.BufferInfo
-import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
-import de.kuschku.libquassel.quassel.syncables.CoreInfo
+import de.kuschku.libquassel.quassel.syncables.*
+import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
 import de.kuschku.libquassel.session.ISession
 import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.ssl.X509Helper
 import de.kuschku.libquassel.util.Optional
+import de.kuschku.libquassel.util.flag.and
+import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.helper.*
+import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid.Backend
 import de.kuschku.quasseldroid.viewmodel.QuasselViewModel
+import de.kuschku.quasseldroid.viewmodel.data.*
 import io.reactivex.Observable
 import javax.inject.Inject
 import javax.net.ssl.SSLSession
@@ -122,4 +126,204 @@ open class QuasselViewModelHelper @Inject constructor(
       }
     }.orElse(Observable.empty())
   }
+
+  fun processRawBufferList(ids: Collection<BufferId>, state: BufferHiddenState, bufferSyncer: BufferSyncer, networks: Map<NetworkId, Network>, currentConfig: BufferViewConfig, bufferSearch: String = "") =
+    ids.asSequence().mapNotNull { id ->
+      bufferSyncer.bufferInfo(id)
+    }.filter {
+      bufferSearch.isBlank() ||
+      it.type.hasFlag(Buffer_Type.StatusBuffer) ||
+      it.bufferName?.contains(bufferSearch, ignoreCase = true) == true
+    }.filter {
+      !currentConfig.networkId().isValidId() || currentConfig.networkId() == it.networkId
+    }.filter {
+      (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
+      (it.type.hasFlag(Buffer_Type.StatusBuffer) && !currentConfig.networkId().isValidId())
+    }.mapNotNull {
+      val network = networks[it.networkId]
+      if (network == null) {
+        null
+      } else {
+        it to network
+      }
+    }.map<Pair<BufferInfo, Network>, Observable<BufferProps>?> { (info, network) ->
+      bufferSyncer.liveActivity(info.bufferId).safeSwitchMap { activity ->
+        bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
+          activity to highlights
+        }
+      }.safeSwitchMap { (activity, highlights) ->
+        val name = info.bufferName?.trim() ?: ""
+        val search = bufferSearch.trim()
+        val matchMode = when {
+          name.equals(search, ignoreCase = true)     -> MatchMode.EXACT
+          name.startsWith(search, ignoreCase = true) -> MatchMode.START
+          else                                       -> MatchMode.CONTAINS
+        }
+        when (info.type.toInt()) {
+          BufferInfo.Type.QueryBuffer.toInt()   -> {
+            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
+              network.liveConnectionState().safeSwitchMap { connectionState ->
+                network.liveIrcUser(info.bufferName).safeSwitchMap {
+                  it.updates().mapNullable(IrcUser.NULL) { user ->
+                    BufferProps(
+                      info = info,
+                      network = networkInfo,
+                      networkConnectionState = connectionState,
+                      bufferStatus = when {
+                        user == null  -> BufferStatus.OFFLINE
+                        user.isAway() -> BufferStatus.AWAY
+                        else          -> BufferStatus.ONLINE
+                      },
+                      description = user?.realName() ?: "",
+                      activity = activity,
+                      highlights = highlights,
+                      hiddenState = state,
+                      ircUser = user,
+                      matchMode = matchMode
+                    )
+                  }
+                }
+              }
+            }
+          }
+          BufferInfo.Type.ChannelBuffer.toInt() -> {
+            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
+              network.liveConnectionState().safeSwitchMap { connectionState ->
+                network.liveIrcChannel(info.bufferName).safeSwitchMap { channel ->
+                  channel.updates().mapNullable(IrcChannel.NULL) {
+                    BufferProps(
+                      info = info,
+                      network = networkInfo,
+                      networkConnectionState = connectionState,
+                      bufferStatus = when (it) {
+                        null -> BufferStatus.OFFLINE
+                        else -> BufferStatus.ONLINE
+                      },
+                      description = it?.topic() ?: "",
+                      activity = activity,
+                      highlights = highlights,
+                      hiddenState = state,
+                      matchMode = matchMode
+                    )
+                  }
+                }
+              }
+            }
+          }
+          BufferInfo.Type.StatusBuffer.toInt()  -> {
+            network.liveNetworkInfo().safeSwitchMap { networkInfo ->
+              network.liveConnectionState().map { connectionState ->
+                BufferProps(
+                  info = info,
+                  network = networkInfo,
+                  networkConnectionState = connectionState,
+                  bufferStatus = BufferStatus.OFFLINE,
+                  description = "",
+                  activity = activity,
+                  highlights = highlights,
+                  hiddenState = state,
+                  matchMode = matchMode
+                )
+              }
+            }
+          }
+          else                                  -> Observable.empty()
+        }
+      }
+    }
+
+  fun processInternalBufferList(
+    buffers: Observable<Pair<BufferViewConfig?, List<BufferProps>>>,
+    expandedNetworks: Observable<Map<NetworkId, Boolean>>,
+    selected: Observable<BufferId>,
+    showHandle: Boolean
+  ) =
+    combineLatest(
+      buffers,
+      expandedNetworks,
+      selected
+    ).map { (info, expandedNetworks, selected) ->
+      val (config, list) = info ?: Pair(null, emptyList())
+      val minimumActivity = config?.minimumActivity() ?: Buffer_Activity.NONE
+      list.asSequence().sortedBy { props ->
+        !props.info.type.hasFlag(Buffer_Type.StatusBuffer)
+      }.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { props ->
+        props.network.networkName
+      }).map { props ->
+        BufferListItem(
+          props,
+          BufferState(
+            networkExpanded = expandedNetworks[props.network.networkId]
+                              ?: (props.networkConnectionState == INetwork.ConnectionState.Initialized),
+            selected = selected == props.info.bufferId,
+            showHandle = showHandle && (config?.sortAlphabetically() == false)
+          )
+        )
+      }.filter { (props, state) ->
+        (props.info.type.hasFlag(BufferInfo.Type.StatusBuffer) || state.networkExpanded) &&
+        (minimumActivity.toInt() <= props.bufferActivity.toInt() ||
+         props.info.type.hasFlag(Buffer_Type.StatusBuffer))
+      }.toList()
+    }
+
+  fun processSelectedBuffer(
+    selectedBufferId: Observable<BufferId>,
+    bufferViewConfig: Observable<Optional<BufferViewConfig>>
+  ) = combineLatest(connectedSession, selectedBufferId, bufferViewConfig)
+    .safeSwitchMap { (sessionOptional, buffer, bufferViewConfigOptional) ->
+      val session = sessionOptional.orNull()
+      val bufferSyncer = session?.bufferSyncer
+      val bufferViewConfig = bufferViewConfigOptional.orNull()
+      if (bufferSyncer != null && bufferViewConfig != null) {
+        session.liveNetworks().safeSwitchMap { networks ->
+          val hiddenState = when {
+            bufferViewConfig.removedBuffers().contains(buffer)            ->
+              BufferHiddenState.HIDDEN_PERMANENT
+            bufferViewConfig.temporarilyRemovedBuffers().contains(buffer) ->
+              BufferHiddenState.HIDDEN_TEMPORARY
+            else                                                          ->
+              BufferHiddenState.VISIBLE
+          }
+
+          val info = if (!buffer.isValidId()) networks[NetworkId(-buffer.id)]?.let {
+            BufferInfo(
+              bufferId = buffer,
+              networkId = it.networkId(),
+              groupId = 0,
+              bufferName = it.networkName(),
+              type = Buffer_Type.of(Buffer_Type.StatusBuffer)
+            )
+          } else bufferSyncer.bufferInfo(buffer)
+          if (info != null) {
+            val network = networks[info.networkId]
+            when (info.type.enabledValues().firstOrNull()) {
+              Buffer_Type.StatusBuffer  -> {
+                network?.liveConnectionState()?.map {
+                  SelectedBufferItem(
+                    info,
+                    connectionState = it,
+                    hiddenState = hiddenState
+                  )
+                } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
+              }
+              Buffer_Type.ChannelBuffer -> {
+                network?.liveIrcChannel(info.bufferName)?.mapNullable(IrcChannel.NULL) {
+                  SelectedBufferItem(
+                    info,
+                    joined = it != null,
+                    hiddenState = hiddenState
+                  )
+                } ?: Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
+              }
+              else                      ->
+                Observable.just(SelectedBufferItem(info, hiddenState = hiddenState))
+            }
+          } else {
+            Observable.just(SelectedBufferItem())
+          }
+        }
+      } else {
+        Observable.just(SelectedBufferItem())
+      }
+    }
 }
-- 
GitLab