From b01cba18ff2fc7bc7a580a8f8d102522c1eab24d Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Wed, 21 Feb 2018 14:45:36 +0100
Subject: [PATCH] Code cleanup

---
 .../ui/chat/BufferViewConfigFragment.kt       | 206 ++---------
 .../quasseldroid_ng/ui/chat/ChatActivity.kt   |  62 +---
 .../quasseldroid_ng/ui/chat/MessageAdapter.kt |  17 +-
 .../ui/chat/MessageListFragment.kt            |  75 ++--
 .../ui/chat/NickListFragment.kt               |  88 +----
 .../ui/chat/QuasselMessageRenderer.kt         |   1 +
 .../ui/chat/ToolbarFragment.kt                | 126 ++-----
 .../ui/viewmodel/QuasselViewModel.kt          | 325 ++++++++++++++++++
 .../util/helper/LiveDataHelper.kt             |  33 +-
 .../quassel/syncables/BufferViewManager.kt    |   5 +-
 .../libquassel/quassel/syncables/IrcUser.kt   |   2 +-
 11 files changed, 501 insertions(+), 439 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt

diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
index f0de1e7bd..6c5b6b382 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
@@ -1,7 +1,6 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.ViewModelProviders
 import android.os.Bundle
 import android.support.v7.widget.*
 import android.view.LayoutInflater
@@ -10,25 +9,14 @@ import android.view.ViewGroup
 import android.widget.AdapterView
 import butterknife.BindView
 import butterknife.ButterKnife
-import de.kuschku.libquassel.protocol.*
-import de.kuschku.libquassel.quassel.BufferInfo
-import de.kuschku.libquassel.quassel.syncables.*
-import de.kuschku.libquassel.session.Backend
-import de.kuschku.libquassel.session.ISession
-import de.kuschku.libquassel.session.SessionManager
-import de.kuschku.libquassel.util.and
-import de.kuschku.libquassel.util.hasFlag
+import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings
+import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
 import de.kuschku.quasseldroid_ng.util.helper.map
-import de.kuschku.quasseldroid_ng.util.helper.or
-import de.kuschku.quasseldroid_ng.util.helper.switchMap
-import de.kuschku.quasseldroid_ng.util.helper.switchMapRx
 import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
-import io.reactivex.Observable
-import java.util.concurrent.TimeUnit
 
 class BufferViewConfigFragment : ServiceBoundFragment() {
   private val handlerThread = AndroidHandlerThread("ChatList")
@@ -42,37 +30,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
   @BindView(R.id.chatList)
   lateinit var chatList: RecyclerView
 
-  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
-
-  private val sessionManager: LiveData<SessionManager?>
-    = backend.map(Backend::sessionManager)
-  private val bufferViewManager: LiveData<BufferViewManager?>
-    = sessionManager.switchMapRx(SessionManager::session).map(ISession::bufferViewManager)
-  private val networks: LiveData<Map<NetworkId, Network>?>
-    = sessionManager.switchMapRx(SessionManager::session).map(ISession::networks)
-  private val bufferViewConfigs = bufferViewManager.switchMapRx { manager ->
-    manager.live_bufferViewConfigs.map { ids ->
-      ids.mapNotNull { id ->
-        manager.bufferViewConfig(id)
-      }.sortedWith(
-        Comparator { a, b ->
-          (a?.bufferViewName() ?: "").compareTo((b?.bufferViewName() ?: ""), true)
-        }
-      )
-    }
-  }.or(emptyList())
-
-  private val selectedBufferViewConfig = MutableLiveData<BufferViewConfig>()
-
-  private val itemSelectedListener = object : AdapterView.OnItemSelectedListener {
-    override fun onNothingSelected(p0: AdapterView<*>?) {
-      selectedBufferViewConfig.value = null
-    }
-
-    override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
-      selectedBufferViewConfig.value = adapter.getItem(p2)
-    }
-  }
+  private lateinit var viewModel: QuasselViewModel
 
   private var ircFormatDeserializer: IrcFormatDeserializer? = null
   private val renderingSettings = RenderingSettings(
@@ -82,135 +40,12 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     timeFormat = ""
   )
 
-  private val adapter = BufferViewConfigAdapter(this, bufferViewConfigs)
-
-  private val bufferIdList = selectedBufferViewConfig.switchMapRx(BufferViewConfig::live_buffers)
-
-  private val bufferList: LiveData<List<BufferListAdapter.BufferProps>?> = sessionManager.switchMap { manager ->
-    selectedBufferViewConfig.switchMapRx { config ->
-      config.live_config.debounce(16, TimeUnit.MILLISECONDS).switchMap { currentConfig ->
-        config.live_buffers.switchMap { ids ->
-          val bufferSyncer = manager.bufferSyncer
-          if (bufferSyncer != null) {
-            bufferSyncer.liveBufferInfos().switchMap {
-              Observable.combineLatest(
-                ids.mapNotNull { id ->
-                  bufferSyncer.bufferInfo(id)
-                }.filter {
-                  currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId
-                }.filter {
-                  (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
-                  it.type.hasFlag(Buffer_Type.StatusBuffer)
-                }.mapNotNull {
-                  val network = manager.networks[it.networkId]
-                  if (network == null) {
-                    null
-                  } else {
-                    it to network
-                  }
-                }.map { (info, network) ->
-                  bufferSyncer.liveActivity(info.bufferId).switchMap { activity ->
-                    bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
-                      when {
-                        highlights > 0                               -> Buffer_Activity.Highlight
-                        activity.hasFlag(Message.MessageType.Plain) ||
-                        activity.hasFlag(Message.MessageType.Notice) ||
-                        activity.hasFlag(Message.MessageType.Action) -> Buffer_Activity.NewMessage
-                        activity.isNotEmpty()                        -> Buffer_Activity.OtherActivity
-                        else                                         -> Buffer_Activity.NoActivity
-                      }
-                    }
-                  }.switchMap { activity ->
-                    when (info.type.toInt()) {
-                      BufferInfo.Type.QueryBuffer.toInt()   -> {
-                        network.liveIrcUser(info.bufferName).switchMap { user ->
-                          user.live_away.switchMap { away ->
-                            user.live_realName.map { realName ->
-                              BufferListAdapter.BufferProps(
-                                info = info,
-                                network = network.networkInfo(),
-                                bufferStatus = when {
-                                  user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE
-                                  away                 -> BufferListAdapter.BufferStatus.AWAY
-                                  else                 -> BufferListAdapter.BufferStatus.ONLINE
-                                },
-                                description = ircFormatDeserializer?.formatString(
-                                  realName, renderingSettings.colorizeMirc
-                                ) ?: realName,
-                                activity = activity
-                              )
-                            }
-                          }
-                        }
-                      }
-                      BufferInfo.Type.ChannelBuffer.toInt() -> {
-                        network.liveIrcChannel(
-                          info.bufferName
-                        ).switchMap { channel ->
-                          channel.live_topic.map { topic ->
-                            BufferListAdapter.BufferProps(
-                              info = info,
-                              network = network.networkInfo(),
-                              bufferStatus = when (channel) {
-                                IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE
-                                else            -> BufferListAdapter.BufferStatus.ONLINE
-                              },
-                              description = ircFormatDeserializer?.formatString(
-                                topic, renderingSettings.colorizeMirc
-                              ) ?: topic,
-                              activity = activity
-                            )
-                          }
-                        }
-                      }
-                      BufferInfo.Type.StatusBuffer.toInt()  -> {
-                        network.liveConnectionState.map {
-                          BufferListAdapter.BufferProps(
-                            info = info,
-                            network = network.networkInfo(),
-                            bufferStatus = BufferListAdapter.BufferStatus.OFFLINE,
-                            description = "",
-                            activity = activity
-                          )
-                        }
-                      }
-                      else                                  -> Observable.just(
-                        BufferListAdapter.BufferProps(
-                          info = info,
-                          network = network.networkInfo(),
-                          bufferStatus = BufferListAdapter.BufferStatus.OFFLINE,
-                          description = "",
-                          activity = activity
-                        )
-                      )
-                    }
-                  }
-                }, { array: Array<Any> ->
-                  array.toList() as List<BufferListAdapter.BufferProps>
-                }
-              ).map { list ->
-                list.filter {
-                  config.minimumActivity().value <= it.activity.bit ||
-                  it.info.type.hasFlag(Buffer_Type.StatusBuffer)
-                }.filter {
-                  (!config.hideInactiveBuffers()) ||
-                  it.bufferStatus != BufferListAdapter.BufferStatus.OFFLINE ||
-                  it.info.type.hasFlag(Buffer_Type.StatusBuffer)
-                }
-              }
-            }
-          } else {
-            Observable.empty()
-          }
-        }
-      }
-    }
-  }
-
   override fun onCreate(savedInstanceState: Bundle?) {
     handlerThread.onCreate()
     super.onCreate(savedInstanceState)
 
+    viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
+
     if (ircFormatDeserializer == null) {
       ircFormatDeserializer = IrcFormatDeserializer(context!!)
     }
@@ -220,12 +55,31 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
                             savedInstanceState: Bundle?): View? {
     val view = inflater.inflate(R.layout.fragment_chat_list, container, false)
     ButterKnife.bind(this, view)
+
+    val adapter = BufferViewConfigAdapter(this, viewModel.bufferViewConfigs)
+
     chatListSpinner.adapter = adapter
-    chatListSpinner.onItemSelectedListener = itemSelectedListener
+    chatListSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+      override fun onNothingSelected(p0: AdapterView<*>?) {
+        viewModel.setBufferViewConfig(null)
+      }
+
+      override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
+        viewModel.setBufferViewConfig(adapter.getItem(p2))
+      }
+    }
 
     chatList.adapter = BufferListAdapter(
       this,
-      bufferList,
+      viewModel.bufferList.map {
+        it.map {
+          it.copy(
+            description = ircFormatDeserializer?.formatString(
+              it.description.toString(), renderingSettings.colorizeMirc
+            ) ?: it.description
+          )
+        }
+      },
       handlerThread::post,
       activity!!::runOnUiThread,
       clickListener
@@ -241,11 +95,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     super.onDestroy()
   }
 
-  val clickListeners = mutableListOf<(BufferId) -> Unit>()
-
   private val clickListener: ((BufferId) -> Unit)? = {
-    for (clickListener in clickListeners) {
-      clickListener.invoke(it)
-    }
+    viewModel.setBuffer(it)
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
index d1f3c74c0..c62795a30 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
@@ -1,9 +1,8 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
 import android.app.Activity
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
+import android.arch.lifecycle.ViewModelProviders
 import android.content.Context
 import android.os.Bundle
 import android.support.design.widget.Snackbar
@@ -17,26 +16,19 @@ import android.widget.Button
 import android.widget.EditText
 import butterknife.BindView
 import butterknife.ButterKnife
-import de.kuschku.libquassel.protocol.BufferId
-import de.kuschku.libquassel.session.Backend
 import de.kuschku.libquassel.session.ConnectionState
-import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.session.SocketAddress
 import de.kuschku.quasseldroid_ng.Keys
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.AccountDatabase
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
 import de.kuschku.quasseldroid_ng.util.helper.*
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
 
 class ChatActivity : ServiceBoundActivity() {
-  private var contentMessages: MessageListFragment? = null
-  private var chatListFragment: BufferViewConfigFragment? = null
-  private var nickListFragment: NickListFragment? = null
-  private var toolbarFragment: ToolbarFragment? = null
-
   @BindView(R.id.drawerLayout)
   lateinit var drawerLayout: DrawerLayout
 
@@ -56,14 +48,10 @@ class ChatActivity : ServiceBoundActivity() {
 
   private val handler = AndroidHandlerThread("Chat")
 
-  private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager)
-  private val state = sessionManager.switchMapRx(SessionManager::state)
-  private val initStatus = sessionManager.switchMapRx(SessionManager::initStatus)
+  private lateinit var viewModel: QuasselViewModel
 
   private var snackbar: Snackbar? = null
 
-  private val currentBuffer = MutableLiveData<BufferId>()
-
   private lateinit var database: QuasselDatabase
 
   override fun onCreate(savedInstanceState: Bundle?) {
@@ -72,34 +60,14 @@ class ChatActivity : ServiceBoundActivity() {
     setContentView(R.layout.activity_main)
     ButterKnife.bind(this)
 
-    database = QuasselDatabase.Creator.init(application)
+    viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java]
+    viewModel.setBackend(this.backend)
 
-    contentMessages = supportFragmentManager.findFragmentById(
-      R.id.contentMessages
-    ) as? MessageListFragment
-    chatListFragment = supportFragmentManager.findFragmentById(
-      R.id.chatListFragment
-    ) as? BufferViewConfigFragment
-    nickListFragment = supportFragmentManager.findFragmentById(
-      R.id.nickListFragment
-    ) as? NickListFragment
-    toolbarFragment = supportFragmentManager.findFragmentById(
-      R.id.toolbarFragment
-    ) as? ToolbarFragment
+    database = QuasselDatabase.Creator.init(application)
 
     setSupportActionBar(toolbar)
 
-    chatListFragment?.currentBuffer?.value = currentBuffer
-    nickListFragment?.currentBuffer?.value = currentBuffer
-    contentMessages?.currentBuffer?.value = currentBuffer
-    toolbarFragment?.currentBuffer?.value = currentBuffer
-
-    chatListFragment?.clickListeners?.add {
-      currentBuffer.value = it
-      println("Changed buffer to $it")
-    }
-
-    currentBuffer.observe(
+    viewModel.getBuffer().observe(
       this, Observer {
       if (it != null) {
         drawerLayout.closeDrawer(Gravity.START, true)
@@ -145,17 +113,17 @@ class ChatActivity : ServiceBoundActivity() {
     )
 
     buttonSend.setOnClickListener {
-      sessionManager { sessionManager ->
-        currentBuffer { bufferId ->
-          sessionManager.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
-            sessionManager.rpcHandler?.sendInput(bufferInfo, input.text.toString())
+      viewModel.session { session ->
+        viewModel.getBuffer().let { bufferId ->
+          session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
+            session.rpcHandler?.sendInput(bufferInfo, input.text.toString())
           }
         }
       }
       input.text.clear()
     }
 
-    state.observe(
+    viewModel.connectionState.observe(
       this, Observer {
       val status = it ?: ConnectionState.DISCONNECTED
 
@@ -180,7 +148,7 @@ class ChatActivity : ServiceBoundActivity() {
     }
     )
 
-    initStatus.observe(
+    viewModel.initState.observe(
       this, Observer {
       val (progress, max) = it ?: 0 to 0
 
@@ -192,12 +160,12 @@ class ChatActivity : ServiceBoundActivity() {
 
   override fun onSaveInstanceState(outState: Bundle?) {
     super.onSaveInstanceState(outState)
-    outState?.putInt("OPEN_BUFFER", currentBuffer.value ?: -1)
+    outState?.putInt("OPEN_BUFFER", viewModel.getBuffer().value ?: -1)
   }
 
   override fun onRestoreInstanceState(savedInstanceState: Bundle?) {
     super.onRestoreInstanceState(savedInstanceState)
-    currentBuffer.value = savedInstanceState?.getInt("OPEN_BUFFER", -1)
+    viewModel.setBuffer(savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1)
   }
 
   override fun onCreateOptionsMenu(menu: Menu?): Boolean {
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt
index 02e301411..ab634fac3 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt
@@ -16,7 +16,8 @@ import de.kuschku.quasseldroid_ng.util.helper.getOrPut
 
 class MessageAdapter(
   context: Context,
-  private val markerLine: LiveData<Pair<MsgId, MsgId>?>
+  private val markerLine: LiveData<Pair<MsgId, MsgId>?>,
+  var markerLinePosition: Pair<MsgId, MsgId>? = null
 ) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(
   object : DiffCallback<QuasselDatabase.DatabaseMessage>() {
     override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
@@ -26,8 +27,8 @@ class MessageAdapter(
     override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage,
                                     newItem: QuasselDatabase.DatabaseMessage)
       = DatabaseMessage.MessageDiffCallback.areContentsTheSame(oldItem, newItem) &&
-        oldItem.messageId != markerLine.value?.first &&
-        oldItem.messageId != markerLine.value?.second
+        oldItem.messageId != markerLinePosition?.first &&
+        oldItem.messageId != markerLinePosition?.second
   }
 ) {
   private val messageRenderer: MessageRenderer = QuasselMessageRenderer(
@@ -42,6 +43,10 @@ class MessageAdapter(
 
   private val messageCache = LruCache<Int, FormattedMessage>(512)
 
+  fun clearCache() {
+    messageCache.evictAll()
+  }
+
   override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) {
     getItem(position)?.let {
       messageRenderer.bind(
@@ -100,8 +105,10 @@ class MessageAdapter(
     return viewHolder
   }
 
-  operator fun get(position: Int): QuasselDatabase.DatabaseMessage? {
-    return getItem(position)
+  operator fun get(position: Int) = if (position in 0 until itemCount) {
+    getItem(position)
+  } else {
+    null
   }
 }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
index 6ed8b5ce5..9177811e4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
@@ -1,8 +1,8 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
 import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
+import android.arch.lifecycle.ViewModelProviders
 import android.arch.paging.LivePagedListBuilder
 import android.arch.paging.PagedList
 import android.os.Bundle
@@ -15,40 +15,31 @@ import android.view.ViewGroup
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
-import de.kuschku.libquassel.protocol.MsgId
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.libquassel.session.SessionManager
+import de.kuschku.libquassel.util.compatibility.LoggingHandler
+import de.kuschku.libquassel.util.compatibility.log
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
 import de.kuschku.quasseldroid_ng.util.helper.*
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
-import io.reactivex.Observable
-import io.reactivex.functions.BiFunction
 
 class MessageListFragment : ServiceBoundFragment() {
-  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
-  private val buffer = currentBuffer.switchMap { it }
+  private lateinit var viewModel: QuasselViewModel
 
   private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager)
 
-  val markerLine = buffer.switchMap { buffer ->
-    sessionManager.switchMapRx { manager ->
-      manager.session.switchMap { session ->
-        val raw = session.bufferSyncer?.liveMarkerLine(buffer)
-        Observable.zip(
-          Observable.just(-1).concatWith(raw),
-          raw,
-          BiFunction<MsgId, MsgId, Pair<MsgId, MsgId>> { a, b -> a to b }
-        )
-      }
-    }
-  }
+  private var lastBuffer: BufferId? = null
 
   private val handler = AndroidHandlerThread("Chat")
 
   private lateinit var database: QuasselDatabase
 
+  private lateinit var linearLayoutManager: LinearLayoutManager
+  private lateinit var adapter: MessageAdapter
+
   @BindView(R.id.messages)
   lateinit var messageList: RecyclerView
 
@@ -58,6 +49,8 @@ class MessageListFragment : ServiceBoundFragment() {
   override fun onCreate(savedInstanceState: Bundle?) {
     handler.onCreate()
     super.onCreate(savedInstanceState)
+
+    viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
     setHasOptionsMenu(true)
   }
 
@@ -76,10 +69,9 @@ class MessageListFragment : ServiceBoundFragment() {
     val view = inflater.inflate(R.layout.fragment_messages, container, false)
     ButterKnife.bind(this, view)
 
-    val adapter = MessageAdapter(context!!, markerLine)
-
+    adapter = MessageAdapter(context!!, viewModel.markerLine)
     messageList.adapter = adapter
-    val linearLayoutManager = LinearLayoutManager(context)
+    linearLayoutManager = LinearLayoutManager(context)
     linearLayoutManager.reverseLayout = true
     messageList.layoutManager = linearLayoutManager
     messageList.itemAnimator = null
@@ -98,7 +90,7 @@ class MessageListFragment : ServiceBoundFragment() {
     )
 
     database = QuasselDatabase.Creator.init(context!!.applicationContext)
-    val data = buffer.switchMap {
+    val data = viewModel.getBuffer().switchMapNotNull {
       LivePagedListBuilder(
         database.message().findByBufferIdPaged(it),
         PagedList.Config.Builder()
@@ -114,7 +106,7 @@ class MessageListFragment : ServiceBoundFragment() {
 
     handler.post {
       val database = QuasselDatabase.Creator.init(this.context!!)
-      sessionManager.zip(buffer).zip(data).observe(
+      sessionManager.zip(viewModel.getBuffer()).zip(data).observe(
         this, Observer {
         handler.post {
           val session = it?.first?.first
@@ -134,13 +126,19 @@ class MessageListFragment : ServiceBoundFragment() {
       )
     }
 
-    markerLine.observe(this, Observer { adapter.notifyDataSetChanged() })
+    viewModel.markerLine.observe(
+      this, Observer {
+      log(LoggingHandler.LogLevel.ERROR, "DEBUG", "$it")
+      adapter.markerLinePosition = it
+      adapter.notifyDataSetChanged()
+    }
+    )
 
     data.observe(
       this, Observer { list ->
-      val findFirstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
+      val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
       adapter.setList(list)
-      if (findFirstVisibleItemPosition < 2) {
+      if (firstVisibleItemPosition < 2) {
         activity?.runOnUiThread {
           messageList.smoothScrollToPosition(0)
         }
@@ -155,9 +153,17 @@ class MessageListFragment : ServiceBoundFragment() {
     }
     )
 
-    buffer.observe(
+    viewModel.getBuffer().observe(
       this, Observer {
+      val previous = lastBuffer
+      val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
+      val messageId = adapter[firstVisibleItemPosition]?.messageId
+
       handler.post {
+        val bufferSyncer = sessionManager.value?.bufferSyncer
+        if (previous != null && messageId != null)
+          bufferSyncer?.requestSetMarkerLine(previous, messageId)
+
         // Try loading messages when switching to isEmpty buffer
         if (it != null) {
           if (database.message().bufferSize(it) == 0) {
@@ -166,6 +172,8 @@ class MessageListFragment : ServiceBoundFragment() {
           activity?.runOnUiThread {
             messageList.scrollToPosition(0)
           }
+
+          lastBuffer = it
         }
       }
     }
@@ -179,9 +187,20 @@ class MessageListFragment : ServiceBoundFragment() {
     return view
   }
 
+  override fun onPause() {
+    val previous = lastBuffer
+    val firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition()
+    val messageId = adapter[firstVisibleItemPosition]?.messageId
+    val bufferSyncer = sessionManager.value?.bufferSyncer
+    if (previous != null && messageId != null)
+      bufferSyncer?.requestSetMarkerLine(previous, messageId)
+
+    super.onPause()
+  }
+
   private fun loadMore() {
     handler.post {
-      buffer { bufferId ->
+      viewModel.getBuffer().let { bufferId ->
         backend()?.sessionManager()?.backlogManager?.requestBacklog(
           bufferId = bufferId,
           last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1,
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt
index be9bd31ff..f532c3802 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/NickListFragment.kt
@@ -1,7 +1,6 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.ViewModelProviders
 import android.os.Bundle
 import android.support.v7.widget.DefaultItemAnimator
 import android.support.v7.widget.LinearLayoutManager
@@ -11,24 +10,17 @@ import android.view.View
 import android.view.ViewGroup
 import butterknife.BindView
 import butterknife.ButterKnife
-import de.kuschku.libquassel.protocol.BufferId
-import de.kuschku.libquassel.protocol.Buffer_Type
-import de.kuschku.libquassel.session.Backend
-import de.kuschku.libquassel.session.SessionManager
-import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings
+import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
 import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
 import de.kuschku.quasseldroid_ng.util.helper.map
-import de.kuschku.quasseldroid_ng.util.helper.switchMap
-import de.kuschku.quasseldroid_ng.util.helper.switchMapRx
 import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
-import io.reactivex.Observable
-import io.reactivex.Observable.zip
-import io.reactivex.functions.BiFunction
 
 class NickListFragment : ServiceBoundFragment() {
+  private lateinit var viewModel: QuasselViewModel
+
   private val handlerThread = AndroidHandlerThread("NickList")
 
   @BindView(R.id.nickList)
@@ -42,64 +34,12 @@ class NickListFragment : ServiceBoundFragment() {
     timeFormat = ""
   )
 
-  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
-  val buffer = currentBuffer.switchMap { it }
-
-  private val sessionManager: LiveData<SessionManager?>
-    = backend.map(Backend::sessionManager)
-
-  private val ircChannel: LiveData<List<NickListAdapter.IrcUserItem>?>
-    = sessionManager.switchMapRx(SessionManager::session).switchMap { session ->
-    buffer.switchMapRx {
-      val bufferSyncer = session.bufferSyncer
-      val bufferInfo = bufferSyncer?.bufferInfo(it)
-      if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
-        val network = session.networks[bufferInfo.networkId]
-        val ircChannel = network?.ircChannel(bufferInfo.bufferName)
-        if (ircChannel != null) {
-          Observable.combineLatest(
-            ircChannel.ircUsers().map { user ->
-              zip(
-                user.live_realName, user.live_away,
-                BiFunction<String, Boolean, Pair<String, Boolean>> { a, b -> Pair(a, b) }
-              ).map { (realName, away) ->
-                val userModes = ircChannel.userModes(user)
-                val prefixModes = network.prefixModes()
-
-                val lowestMode = userModes.mapNotNull {
-                  prefixModes.indexOf(it)
-                }.min() ?: prefixModes.size
-
-                NickListAdapter.IrcUserItem(
-                  user.nick(),
-                  network.modesToPrefixes(userModes),
-                  lowestMode,
-                  ircFormatDeserializer?.formatString(
-                    realName, renderingSettings.colorizeMirc
-                  ) ?: realName,
-                  away,
-                  network.support("CASEMAPPING")
-                )
-              }
-            }, { array: Array<Any> ->
-              array.toList() as List<NickListAdapter.IrcUserItem>
-            }
-          )
-        } else {
-          Observable.just(emptyList())
-        }
-      } else {
-        Observable.just(emptyList())
-      }
-    }
-  }
-
-  private val nicks: LiveData<List<NickListAdapter.IrcUserItem>?> = ircChannel
-
   override fun onCreate(savedInstanceState: Bundle?) {
     handlerThread.onCreate()
     super.onCreate(savedInstanceState)
 
+    viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
+
     if (ircFormatDeserializer == null) {
       ircFormatDeserializer = IrcFormatDeserializer(context!!)
     }
@@ -112,7 +52,15 @@ class NickListFragment : ServiceBoundFragment() {
 
     nickList.adapter = NickListAdapter(
       this,
-      nicks,
+      viewModel.nickData.map {
+        it.map {
+          it.copy(
+            realname = ircFormatDeserializer?.formatString(
+              it.realname.toString(), renderingSettings.colorizeMirc
+            ) ?: it.realname
+          )
+        }
+      },
       handlerThread::post,
       activity!!::runOnUiThread,
       clickListener
@@ -129,11 +77,7 @@ class NickListFragment : ServiceBoundFragment() {
     super.onDestroy()
   }
 
-  val clickListeners = mutableListOf<(String) -> Unit>()
-
   private val clickListener: ((String) -> Unit)? = {
-    for (clickListener in clickListeners) {
-      clickListener.invoke(it)
-    }
+    // TODO
   }
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt
index 48d1f87ec..6b237e590 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt
@@ -285,6 +285,7 @@ class QuasselMessageRenderer(
       text.setSpan(URLSpan(result.value), result.range.start, result.range.endInclusive, Spanned.SPAN_INCLUSIVE_INCLUSIVE)
     }
     */
+
     return text
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
index 0e3176638..bd82faddf 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ToolbarFragment.kt
@@ -1,8 +1,7 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
-import android.arch.lifecycle.LiveData
-import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
+import android.arch.lifecycle.ViewModelProviders
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -10,21 +9,19 @@ import android.view.ViewGroup
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.quassel.BufferInfo
 import de.kuschku.libquassel.quassel.syncables.interfaces.INetwork
-import de.kuschku.libquassel.session.Backend
-import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.ui.settings.data.DisplaySettings
 import de.kuschku.quasseldroid_ng.ui.settings.data.RenderingSettings
-import de.kuschku.quasseldroid_ng.util.helper.*
+import de.kuschku.quasseldroid_ng.ui.viewmodel.QuasselViewModel
+import de.kuschku.quasseldroid_ng.util.helper.visibleIf
+import de.kuschku.quasseldroid_ng.util.helper.zip
 import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatDeserializer
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
 import de.kuschku.quasseldroid_ng.util.ui.SpanFormatter
-import io.reactivex.Observable
 
 class ToolbarFragment : ServiceBoundFragment() {
   @BindView(R.id.toolbar_title)
@@ -33,14 +30,7 @@ class ToolbarFragment : ServiceBoundFragment() {
   @BindView(R.id.toolbar_subtitle)
   lateinit var toolbarSubtitle: TextView
 
-  val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
-  val buffer = currentBuffer.switchMap { it }
-
-  private val sessionManager: LiveData<SessionManager?>
-    = backend.map(Backend::sessionManager)
-
-  private val lag: LiveData<Long?>
-    = sessionManager.switchMapRx { it.session.switchMap { it.lag } }
+  private lateinit var viewModel: QuasselViewModel
 
   private val displaySettings = DisplaySettings(
     showLag = true
@@ -54,91 +44,6 @@ class ToolbarFragment : ServiceBoundFragment() {
     timeFormat = ""
   )
 
-  private val bufferData: LiveData<BufferData?> = sessionManager.switchMap { manager ->
-    buffer.switchMapRx { id ->
-      manager.session.switchMap {
-        val bufferSyncer = it.bufferSyncer
-        if (bufferSyncer != null) {
-          bufferSyncer.liveBufferInfos().switchMap {
-            val info = bufferSyncer.bufferInfo(id)
-            val network = manager.networks[info?.networkId]
-            if (info == null) {
-              Observable.just(
-                BufferData(
-                  description = "Info was null"
-                )
-              )
-            } else if (network == null) {
-              Observable.just(
-                BufferData(
-                  description = "Network was null"
-                )
-              )
-            } else {
-              when (info.type.toInt()) {
-                BufferInfo.Type.QueryBuffer.toInt()   -> {
-                  network.liveIrcUser(info.bufferName).switchMap { user ->
-                    user.live_realName.map { realName ->
-                      BufferData(
-                        info = info,
-                        network = network.networkInfo(),
-                        description = ircFormatDeserializer?.formatString(
-                          realName, renderingSettings.colorizeMirc
-                        ) ?: realName
-                      )
-                    }
-                  }
-                }
-                BufferInfo.Type.ChannelBuffer.toInt() -> {
-                  network.liveIrcChannel(
-                    info.bufferName
-                  ).switchMap { channel ->
-                    channel.live_topic.map { topic ->
-                      BufferData(
-                        info = info,
-                        network = network.networkInfo(),
-                        description = ircFormatDeserializer?.formatString(
-                          topic, renderingSettings.colorizeMirc
-                        ) ?: topic
-                      )
-                    }
-                  }
-                }
-                BufferInfo.Type.StatusBuffer.toInt()  -> {
-                  network.liveConnectionState.map {
-                    BufferData(
-                      info = info,
-                      network = network.networkInfo()
-                    )
-                  }
-                }
-                else                                  -> Observable.just(
-                  BufferData(
-                    description = "type is unknown: ${info.type.toInt()}"
-                  )
-                )
-              }
-            }
-          }
-        } else {
-          Observable.just(
-            BufferData(
-              description = "buffersyncer was null"
-            )
-          )
-        }
-      }
-    }
-  }
-
-  private val isSecure: LiveData<Boolean?> = sessionManager.switchMapRx(
-    SessionManager::session
-  ).switchMapRx { session ->
-    session.state.map { state ->
-      session.sslSession != null
-    }
-  }
-
   var title: CharSequence?
     get() = toolbarTitle.text
     set(value) {
@@ -157,6 +62,9 @@ class ToolbarFragment : ServiceBoundFragment() {
 
   override fun onCreate(savedInstanceState: Bundle?) {
     super.onCreate(savedInstanceState)
+
+    viewModel = ViewModelProviders.of(activity!!)[QuasselViewModel::class.java]
+
     if (ircFormatDeserializer == null) {
       ircFormatDeserializer = IrcFormatDeserializer(context!!)
     }
@@ -168,7 +76,7 @@ class ToolbarFragment : ServiceBoundFragment() {
     val view = inflater.inflate(R.layout.fragment_toolbar, container, false)
     ButterKnife.bind(this, view)
 
-    bufferData.zip(isSecure, lag).observe(
+    viewModel.bufferData.zip(viewModel.isSecure, viewModel.lag).observe(
       this, Observer {
       if (it != null) {
         val (data, isSecure, lag) = it
@@ -179,13 +87,17 @@ class ToolbarFragment : ServiceBoundFragment() {
         }
 
         if (lag == 0L || !displaySettings.showLag) {
-          this.subtitle = data?.description
+          this.subtitle = colorizeDescription(data?.description)
         } else {
-          val description = data?.description
+          val description = colorizeDescription(data?.description)
           if (description.isNullOrBlank()) {
             this.subtitle = "Lag: ${lag}ms"
           } else {
-            this.subtitle = SpanFormatter.format("Lag: %dms | %s", lag, description)
+            this.subtitle = SpanFormatter.format(
+              "Lag: %dms | %s",
+              lag,
+              colorizeDescription(data?.description)
+            )
           }
         }
       }
@@ -195,10 +107,14 @@ class ToolbarFragment : ServiceBoundFragment() {
     return view
   }
 
+  private fun colorizeDescription(description: String?)
+    = ircFormatDeserializer?.formatString(description, renderingSettings.colorizeMirc)
+      ?: description
+
   data class BufferData(
     val info: BufferInfo? = null,
     val network: INetwork.NetworkInfo? = null,
-    val description: CharSequence? = null
+    val description: String? = null
   )
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt
new file mode 100644
index 000000000..e22fc8877
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/viewmodel/QuasselViewModel.kt
@@ -0,0 +1,325 @@
+package de.kuschku.quasseldroid_ng.ui.viewmodel
+
+import android.arch.lifecycle.LiveData
+import android.arch.lifecycle.MutableLiveData
+import android.arch.lifecycle.ViewModel
+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
+import de.kuschku.libquassel.quassel.BufferInfo
+import de.kuschku.libquassel.quassel.syncables.BufferViewConfig
+import de.kuschku.libquassel.quassel.syncables.IrcChannel
+import de.kuschku.libquassel.quassel.syncables.IrcUser
+import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.session.ISession
+import de.kuschku.libquassel.session.SessionManager
+import de.kuschku.libquassel.util.and
+import de.kuschku.libquassel.util.hasFlag
+import de.kuschku.quasseldroid_ng.ui.chat.BufferListAdapter
+import de.kuschku.quasseldroid_ng.ui.chat.NickListAdapter
+import de.kuschku.quasseldroid_ng.ui.chat.ToolbarFragment
+import de.kuschku.quasseldroid_ng.util.helper.*
+import io.reactivex.Observable
+import io.reactivex.functions.BiFunction
+import java.util.concurrent.TimeUnit
+
+class QuasselViewModel : ViewModel() {
+  private val backendWrapper = MutableLiveData<LiveData<Backend?>>()
+  fun setBackend(backendWrapper: LiveData<Backend?>) {
+    this.backendWrapper.value = backendWrapper
+  }
+
+  private val buffer = MutableLiveData<BufferId>()
+  fun getBuffer(): LiveData<BufferId> = buffer
+  fun setBuffer(buffer: BufferId) {
+    this.buffer.value = buffer
+  }
+
+  private val bufferViewConfig = MutableLiveData<BufferViewConfig?>()
+  fun getBufferViewConfig(): LiveData<BufferViewConfig?> = bufferViewConfig
+  fun setBufferViewConfig(bufferViewConfig: BufferViewConfig?) {
+    this.bufferViewConfig.value = bufferViewConfig
+  }
+
+  val backend = backendWrapper.switchMap { it }
+  val sessionManager = backend.map { it.sessionManager() }
+  val session = sessionManager.switchMapRx { it.session }
+
+  val connectionState = sessionManager.switchMapRx { it.state }
+  val initState = sessionManager.switchMapRx { it.initStatus }
+
+  private val bufferViewManager = session.map(ISession::bufferViewManager)
+
+  private var lastMarkerLine = -1
+  /**
+   * An observable of the changes of the markerline, as pairs of `(old, new)`
+   */
+  val markerLine = session.switchMap { currentSession ->
+    buffer.switchMapRx { currentBuffer ->
+      // Get a stream of the latest marker line
+      val raw = currentSession.bufferSyncer?.liveMarkerLine(currentBuffer)
+
+      // Turn it into a pair of changes
+      val changes = raw?.map {
+        val previous = lastMarkerLine
+        if (it != lastMarkerLine)
+          lastMarkerLine = it
+        previous to it
+      }
+
+      // Only return when there was an actual change
+      val distinct = changes?.filter {
+        it.first != it.second
+      }
+
+      distinct
+    }
+  }
+
+  val lag: LiveData<Long?> = sessionManager.switchMapRx { it.session.switchMap { it.lag } }
+
+  val isSecure: LiveData<Boolean?> = sessionManager.switchMapRx(SessionManager::session)
+    .switchMapRx { session ->
+      session.state.map { _ ->
+        session.sslSession != null
+      }
+    }
+
+  val bufferData = session.zip(buffer).switchMapRx { (session, id) ->
+    val bufferSyncer = session?.bufferSyncer
+    if (bufferSyncer != null) {
+      bufferSyncer.liveBufferInfos().switchMap {
+        val info = bufferSyncer.bufferInfo(id)
+        val network = session.networks[info?.networkId]
+        if (info == null) {
+          Observable.just(
+            ToolbarFragment.BufferData(
+              description = "Info was null"
+            )
+          )
+        } else if (network == null) {
+          Observable.just(
+            ToolbarFragment.BufferData(
+              description = "Network was null"
+            )
+          )
+        } else {
+          when (info.type.toInt()) {
+            BufferInfo.Type.QueryBuffer.toInt()   -> {
+              network.liveIrcUser(info.bufferName).switchMap { user ->
+                user.live_realName.map { realName ->
+                  ToolbarFragment.BufferData(
+                    info = info,
+                    network = network.networkInfo(),
+                    description = realName
+                  )
+                }
+              }
+            }
+            BufferInfo.Type.ChannelBuffer.toInt() -> {
+              network.liveIrcChannel(
+                info.bufferName
+              ).switchMap { channel ->
+                channel.live_topic.map { topic ->
+                  ToolbarFragment.BufferData(
+                    info = info,
+                    network = network.networkInfo(),
+                    description = topic
+                  )
+                }
+              }
+            }
+            BufferInfo.Type.StatusBuffer.toInt()  -> {
+              network.liveConnectionState.map {
+                ToolbarFragment.BufferData(
+                  info = info,
+                  network = network.networkInfo()
+                )
+              }
+            }
+            else                                  -> Observable.just(
+              ToolbarFragment.BufferData(
+                description = "type is unknown: ${info.type.toInt()}"
+              )
+            )
+          }
+        }
+      }
+    } else {
+      Observable.just(
+        ToolbarFragment.BufferData(
+          description = "buffersyncer was null"
+        )
+      )
+    }
+  }
+
+  val nickData = session.zip(buffer).switchMapRx { (session, buffer) ->
+    val bufferSyncer = session?.bufferSyncer
+    val bufferInfo = bufferSyncer?.bufferInfo(buffer)
+    if (bufferInfo?.type?.hasFlag(Buffer_Type.ChannelBuffer) == true) {
+      val network = session.networks[bufferInfo.networkId]
+      val ircChannel = network?.ircChannel(bufferInfo.bufferName)
+      if (ircChannel != null) {
+        Observable.combineLatest(
+          ircChannel.ircUsers().map { user ->
+            Observable.zip(
+              user.live_realName, user.live_away,
+              BiFunction<String, Boolean, Pair<String, Boolean>> { a, b -> Pair(a, b) }
+            ).map { (realName, away) ->
+              val userModes = ircChannel.userModes(user)
+              val prefixModes = network.prefixModes()
+
+              val lowestMode = userModes.mapNotNull {
+                prefixModes.indexOf(it)
+              }.min() ?: prefixModes.size
+
+              NickListAdapter.IrcUserItem(
+                user.nick(),
+                network.modesToPrefixes(userModes),
+                lowestMode,
+                realName,
+                away,
+                network.support("CASEMAPPING")
+              )
+            }
+          }, { array: Array<Any> ->
+            array.toList() as List<NickListAdapter.IrcUserItem>
+          }
+        )
+      } else {
+        Observable.just(emptyList())
+      }
+    } else {
+      Observable.just(emptyList())
+    }
+  }
+
+  val bufferViewConfigs = bufferViewManager.switchMapRx { manager ->
+    manager.liveBufferViewConfigs().map { ids ->
+      ids.mapNotNull { id ->
+        manager.bufferViewConfig(id)
+      }.sortedWith(
+        Comparator { a, b ->
+          (a?.bufferViewName() ?: "").compareTo((b?.bufferViewName() ?: ""), true)
+        }
+      )
+    }
+  }.or(emptyList())
+
+  val bufferList = session.zip(bufferViewConfig).switchMapRx { (session, config) ->
+    val bufferSyncer = session?.bufferSyncer
+    if (bufferSyncer != null && config != null) {
+      config.live_config.debounce(16, TimeUnit.MILLISECONDS).switchMap { currentConfig ->
+        config.live_buffers.switchMap { ids ->
+          bufferSyncer.liveBufferInfos().switchMap {
+            Observable.combineLatest(
+              ids.mapNotNull { id ->
+                bufferSyncer.bufferInfo(id)
+              }.filter {
+                currentConfig.networkId() <= 0 || currentConfig.networkId() == it.networkId
+              }.filter {
+                (currentConfig.allowedBufferTypes() and it.type).isNotEmpty() ||
+                it.type.hasFlag(Buffer_Type.StatusBuffer)
+              }.mapNotNull {
+                val network = session.networks[it.networkId]
+                if (network == null) {
+                  null
+                } else {
+                  it to network
+                }
+              }.map { (info, network) ->
+                bufferSyncer.liveActivity(info.bufferId).switchMap { activity ->
+                  bufferSyncer.liveHighlightCount(info.bufferId).map { highlights ->
+                    when {
+                      highlights > 0                               -> Buffer_Activity.Highlight
+                      activity.hasFlag(Message.MessageType.Plain) ||
+                      activity.hasFlag(Message.MessageType.Notice) ||
+                      activity.hasFlag(Message.MessageType.Action) -> Buffer_Activity.NewMessage
+                      activity.isNotEmpty()                        -> Buffer_Activity.OtherActivity
+                      else                                         -> Buffer_Activity.NoActivity
+                    }
+                  }
+                }.switchMap { activity ->
+                  when (info.type.toInt()) {
+                    BufferInfo.Type.QueryBuffer.toInt()   -> {
+                      network.liveIrcUser(info.bufferName).switchMap { user ->
+                        user.live_away.switchMap { away ->
+                          user.live_realName.map { realName ->
+                            BufferListAdapter.BufferProps(
+                              info = info,
+                              network = network.networkInfo(),
+                              bufferStatus = when {
+                                user == IrcUser.NULL -> BufferListAdapter.BufferStatus.OFFLINE
+                                away                 -> BufferListAdapter.BufferStatus.AWAY
+                                else                 -> BufferListAdapter.BufferStatus.ONLINE
+                              },
+                              description = realName,
+                              activity = activity
+                            )
+                          }
+                        }
+                      }
+                    }
+                    BufferInfo.Type.ChannelBuffer.toInt() -> {
+                      network.liveIrcChannel(
+                        info.bufferName
+                      ).switchMap { channel ->
+                        channel.live_topic.map { topic ->
+                          BufferListAdapter.BufferProps(
+                            info = info,
+                            network = network.networkInfo(),
+                            bufferStatus = when (channel) {
+                              IrcChannel.NULL -> BufferListAdapter.BufferStatus.OFFLINE
+                              else            -> BufferListAdapter.BufferStatus.ONLINE
+                            },
+                            description = topic,
+                            activity = activity
+                          )
+                        }
+                      }
+                    }
+                    BufferInfo.Type.StatusBuffer.toInt()  -> {
+                      network.liveConnectionState.map {
+                        BufferListAdapter.BufferProps(
+                          info = info,
+                          network = network.networkInfo(),
+                          bufferStatus = BufferListAdapter.BufferStatus.OFFLINE,
+                          description = "",
+                          activity = activity
+                        )
+                      }
+                    }
+                    else                                  -> Observable.just(
+                      BufferListAdapter.BufferProps(
+                        info = info,
+                        network = network.networkInfo(),
+                        bufferStatus = BufferListAdapter.BufferStatus.OFFLINE,
+                        description = "",
+                        activity = activity
+                      )
+                    )
+                  }
+                }
+              }, { array: Array<Any> ->
+                array.toList() as List<BufferListAdapter.BufferProps>
+              }
+            ).map { list ->
+              list.filter {
+                config.minimumActivity().value <= it.activity.bit ||
+                it.info.type.hasFlag(Buffer_Type.StatusBuffer)
+              }.filter {
+                (!config.hideInactiveBuffers()) ||
+                it.bufferStatus != BufferListAdapter.BufferStatus.OFFLINE ||
+                it.info.type.hasFlag(Buffer_Type.StatusBuffer)
+              }
+            }
+          }
+        }
+      }
+    } else {
+      Observable.just(emptyList())
+    }
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
index a72e74e0e..5c81c9898 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LiveDataHelper.kt
@@ -32,6 +32,33 @@ inline fun <X, Y> LiveData<X?>.switchMap(
   return result
 }
 
+@MainThread
+inline fun <X, Y> LiveData<X>.switchMapNotNull(
+  crossinline func: (X) -> LiveData<Y>?
+): LiveData<Y> {
+  val result = MediatorLiveData<Y>()
+  result.addSource(
+    this, object : Observer<X> {
+    internal var mSource: LiveData<Y>? = null
+
+    override fun onChanged(x: X?) {
+      val newLiveData = if (x == null) null else func(x)
+      if (mSource === newLiveData) {
+        return
+      }
+      mSource?.let(result::removeSource)
+      mSource = newLiveData
+      if (newLiveData != null) {
+        result.addSource(newLiveData) { y -> result.value = y }
+      } else {
+        result.value = null
+      }
+    }
+  }
+  )
+  return result
+}
+
 @MainThread
 inline fun <X, Y> LiveData<X?>.switchMapRx(
   strategy: BackpressureStrategy,
@@ -66,7 +93,7 @@ inline fun <X, Y> LiveData<out X?>.switchMapRx(
 ): LiveData<Y?> = switchMapRx(BackpressureStrategy.LATEST, func)
 
 @MainThread
-inline fun <X, Y> LiveData<X?>.map(
+inline fun <X, Y> LiveData<out X?>.map(
   crossinline func: (X) -> Y?
 ): LiveData<Y?> {
   val result = MediatorLiveData<Y?>()
@@ -118,4 +145,6 @@ inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observa
 
 inline operator fun <T> LiveData<T>.invoke() = value
 
-inline operator fun <T, U> LiveData<T?>.invoke(f: (T) -> U?) = value?.let(f)
\ No newline at end of file
+inline operator fun <T, U> LiveData<T?>.invoke(f: (T) -> U?) = value?.let(f)
+
+inline fun <T, U> LiveData<T>.let(f: (T) -> U?) = value?.let(f)
\ No newline at end of file
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt
index 0de820eb5..49f16ebbb 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewManager.kt
@@ -3,6 +3,7 @@ package de.kuschku.libquassel.quassel.syncables
 import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBufferViewManager
 import de.kuschku.libquassel.session.SignalProxy
+import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
 
 class BufferViewManager constructor(
@@ -24,6 +25,8 @@ class BufferViewManager constructor(
 
   fun bufferViewConfigs() = _bufferViewConfigs.values
 
+  fun liveBufferViewConfigs(): Observable<Set<Int>> = live_bufferViewConfigs
+
   override fun initSetBufferViewIds(bufferViewIds: QVariantList) {
     bufferViewIds
       .mapNotNull { it.value<Int>() }
@@ -57,6 +60,6 @@ class BufferViewManager constructor(
   private val _bufferViewConfigs: MutableMap<Int, BufferViewConfig>
     = mutableMapOf()
 
-  val live_bufferViewConfigs: BehaviorSubject<Set<Int>>
+  private val live_bufferViewConfigs: BehaviorSubject<Set<Int>>
     = BehaviorSubject.createDefault<Set<Int>>(emptySet())
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
index 7069bc149..c50dd6400 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/IrcUser.kt
@@ -250,7 +250,7 @@ class IrcUser(
   }
 
   override fun quit() {
-    for (channel in _channels) {
+    for (channel in _channels.toList()) {
       channel.part(this)
     }
     _channels.clear()
-- 
GitLab