diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c3df58fe4c7ed3ba89f2fbf80724bb5988251fac..27c388d7b091225f09db8a6dbc8c5658def3c4e2 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -115,6 +115,8 @@ dependencies {
     exclude(group = "junit", module = "junit")
   }
 
+  implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2")
+
   implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb")
 
   implementation("com.jakewharton", "butterknife", "8.8.1")
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
index f78640484357bb09d9d19bccbad3dc2573c7676c..b05e47731b35f969a2463874a44be90213b9f7db 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselBacklogStorage.kt
@@ -5,7 +5,21 @@ import de.kuschku.libquassel.protocol.Message
 import de.kuschku.libquassel.session.BacklogStorage
 
 class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
-  override fun storeMessages(vararg messages: Message) {
+  override fun storeMessages(vararg messages: Message, initialLoad: Boolean)
+    = storeMessages(messages.asIterable(), initialLoad)
+
+  override fun storeMessages(messages: Iterable<Message>, initialLoad: Boolean) {
+    if (initialLoad)
+      for ((bufferId, bufferMessages) in messages.sortedBy { it.messageId }.groupBy { it.bufferInfo.bufferId }) {
+        val lastMessageId = db.message().findLastByBufferId(bufferId)?.messageId
+        val firstMessage = bufferMessages.firstOrNull()
+        if (lastMessageId != null && firstMessage != null) {
+          if (lastMessageId < firstMessage.messageId) {
+            db.message().clearMessages(bufferId)
+          }
+        }
+      }
+
     for (message in messages) {
       db.message().save(QuasselDatabase.DatabaseMessage(
         messageId = message.messageId,
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
index 205530597579b0935401da396200fabbd4236ceb..723a937a9080793f2e0d3f12071507d98d206bbb 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
@@ -71,6 +71,9 @@ abstract class QuasselDatabase : RoomDatabase() {
     @Query("UPDATE message SET bufferId = :bufferId1 WHERE bufferId = :bufferId2")
     fun merge(@IntRange(from = 0) bufferId1: Int, @IntRange(from = 0) bufferId2: Int)
 
+    @Query("SELECT count(*) FROM message WHERE bufferId = :bufferId")
+    fun bufferSize(@IntRange(from = 0) bufferId: Int): Int
+
     @Query("DELETE FROM message")
     fun clearMessages()
 
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 5867f9f7418b3aa96bc1a8704573b54885ef6023..2babd2c93c8c2673e60871bd4734666513cf3739 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
@@ -87,7 +87,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
 
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
-    val view = inflater.inflate(R.layout.content_chat_list, container, false)
+    val view = inflater.inflate(R.layout.fragment_chat_list, container, false)
     ButterKnife.bind(this, view)
     chatListSpinner.adapter = adapter
     chatListSpinner.onItemSelectedListener = itemSelectedListener
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 5493cc2beab9b2ade206eaa58585cee7192a9c90..589ec88eb9e1f33a155764833e795e978cfa3a51 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
@@ -10,6 +10,7 @@ import android.support.design.widget.Snackbar
 import android.support.v4.widget.DrawerLayout
 import android.support.v7.app.ActionBarDrawerToggle
 import android.support.v7.widget.Toolbar
+import android.view.Gravity
 import android.view.Menu
 import android.view.MenuItem
 import android.widget.Button
@@ -26,11 +27,9 @@ 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.util.AndroidHandlerThread
-import de.kuschku.quasseldroid_ng.util.helper.editApply
-import de.kuschku.quasseldroid_ng.util.helper.map
-import de.kuschku.quasseldroid_ng.util.helper.observeSticky
-import de.kuschku.quasseldroid_ng.util.helper.switchMapRx
+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() {
   var contentMessages: MessageListFragment? = null
@@ -42,6 +41,9 @@ class ChatActivity : ServiceBoundActivity() {
   @BindView(R.id.toolbar)
   lateinit var toolbar: Toolbar
 
+  @BindView(R.id.progressBar)
+  lateinit var progressBar: MaterialContentLoadingProgressBar
+
   @BindView(R.id.buttonSend)
   lateinit var buttonSend: Button
 
@@ -54,6 +56,7 @@ class ChatActivity : ServiceBoundActivity() {
 
   private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager)
   private val state = sessionManager.switchMapRx(SessionManager::state)
+  private val initStatus = sessionManager.switchMapRx(SessionManager::initStatus)
 
   private var snackbar: Snackbar? = null
 
@@ -82,9 +85,14 @@ class ChatActivity : ServiceBoundActivity() {
       println("Changed buffer to $it")
     }
 
-    //drawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close)
-    //actionBar.setDisplayHomeAsUpEnabled(true)
-    //actionBar.setHomeButtonEnabled(true)
+    currentBuffer.observe(this, Observer {
+      if (it != null) {
+        drawerLayout.closeDrawer(Gravity.START, true)
+      }
+    })
+
+    drawerToggle = ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close)
+    drawerToggle.syncState()
 
     backend.observeSticky(this, Observer { backendValue ->
       if (backendValue != null) {
@@ -113,8 +121,8 @@ class ChatActivity : ServiceBoundActivity() {
     })
 
     buttonSend.setOnClickListener {
-      sessionManager.value?.also { sessionManager ->
-        currentBuffer.value?.also { bufferId ->
+      sessionManager { sessionManager ->
+        currentBuffer { bufferId ->
           sessionManager.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
             sessionManager.rpcHandler?.sendInput(bufferInfo, input.text.toString())
           }
@@ -126,14 +134,30 @@ class ChatActivity : ServiceBoundActivity() {
     state.observe(this, Observer {
       val status = it ?: ConnectionState.DISCONNECTED
 
+      if (status == ConnectionState.CONNECTED) {
+        progressBar.progress = 1
+        progressBar.max = 1
+      } else {
+        progressBar.isIndeterminate = status != ConnectionState.INIT
+      }
+
+      progressBar.toggle(status != ConnectionState.CONNECTED && status != ConnectionState.DISCONNECTED)
+
       snackbar?.dismiss()
       snackbar = Snackbar.make(findViewById(R.id.contentMessages), status.name, Snackbar.LENGTH_SHORT)
       snackbar?.show()
     })
+
+    initStatus.observe(this, Observer {
+      val (progress, max) = it ?: 0 to 0
+
+      progressBar.max = max
+      progressBar.progress = progress
+    })
   }
 
   override fun onCreateOptionsMenu(menu: Menu?): Boolean {
-    menuInflater.inflate(R.menu.main, menu)
+    menuInflater.inflate(R.menu.activity_main, menu)
     return super.onCreateOptionsMenu(menu)
   }
 
@@ -143,31 +167,13 @@ class ChatActivity : ServiceBoundActivity() {
         getSharedPreferences(Keys.Status.NAME, Context.MODE_PRIVATE).editApply {
           putBoolean(Keys.Status.reconnect, false)
         }
-        backend.value?.disconnect(true)
+        backend()?.disconnect(true)
         setResult(Activity.RESULT_OK)
         finish()
       }
       true
     }
-    R.id.loadMore -> handler.post {
-      currentBuffer.value?.also { bufferId ->
-        sessionManager.value?.apply {
-          backlogManager?.requestBacklog(
-            bufferId = bufferId,
-            last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1,
-            limit = 20
-          )
-        }
-      }
-    }
-    R.id.clear -> handler.post {
-      currentBuffer.value?.also { bufferId ->
-        sessionManager.value?.apply {
-          backlogStorage.clearMessages(bufferId)
-        }
-      }
-    }
-    else            -> super.onOptionsItemSelected(item)
+    else -> super.onOptionsItemSelected(item)
   }
 
   override fun onDestroy() {
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 7422ad5079b8e760a1f6862a2f31304a0bd44911..3cdfd1a86a9c9905f27cb4b68c61870a6a639a7c 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
@@ -2,35 +2,63 @@ package de.kuschku.quasseldroid_ng.ui.chat
 
 import android.arch.paging.PagedListAdapter
 import android.content.Context
+import android.util.LruCache
 import android.view.LayoutInflater
 import android.view.ViewGroup
+import de.kuschku.libquassel.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Flags
 import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.libquassel.protocol.Message_Types
+import de.kuschku.libquassel.util.hasFlag
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+import de.kuschku.quasseldroid_ng.util.helper.getOrPut
 
 class MessageAdapter(context: Context) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(QuasselDatabase.DatabaseMessage.MessageDiffCallback) {
-  val messageRenderer: MessageRenderer = QuasselMessageRenderer(context)
+  private val messageRenderer: MessageRenderer = QuasselMessageRenderer(context)
+
+  private val messageCache = LruCache<Int, FormattedMessage>(512)
+
+  init {
+    setHasStableIds(true)
+  }
 
   override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) {
-    getItem(position)?.let { messageRenderer.bind(holder, it) }
+    getItem(position)?.let { messageRenderer.bind(holder, messageCache.getOrPut(it.messageId) { messageRenderer.render(it) }) }
   }
 
   override fun getItemViewType(position: Int): Int {
-    return getItem(position)?.type ?: 0
+    val item = getItem(position)
+    if (item != null) {
+      return viewType(Message_Flags.of(item.type), Message_Flags.of(item.flag))
+    } else {
+      return 0
+    }
+  }
+
+  private fun viewType(type: Message_Types, flags: Message_Flags): Int {
+    return (if (flags.hasFlag(Message_Flag.Highlight)) 0x8000 else 0x0000) or (type.value and 0x7FF)
+  }
+
+  override fun getItemId(position: Int): Long {
+    return getItem(position)?.messageId?.toLong() ?: 0L
   }
 
   private fun messageType(viewType: Int): Message_Type?
-    = Message_Type.of(viewType).enabledValues().firstOrNull()
+    = Message_Type.of(viewType and 0x7FF).enabledValues().firstOrNull()
 
-  private fun messageFlags(viewType: Int): Message_Flags
-    = Message_Flags.of()
+  private fun hasHiglight(viewType: Int)
+    = viewType and 0x8000 != 0
 
   override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuasselMessageViewHolder {
-    return QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate(
-      messageRenderer.layout(messageType(viewType), messageFlags(viewType)),
+    val messageType = messageType(viewType)
+    val hasHighlight = hasHiglight(viewType)
+    val viewHolder = QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate(
+      messageRenderer.layout(messageType, hasHighlight),
       parent,
       false
     ))
+    messageRenderer.init(viewHolder, messageType, hasHighlight)
+    return viewHolder
   }
 }
 
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 7007b536ff5b4f106bf3538d9c6b35412395c82b..cccda2cf38769f9f3e79b4cb10ece2b0cb481160 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
@@ -5,7 +5,7 @@ import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
 import android.arch.paging.PagedList
 import android.os.Bundle
-import android.support.v7.widget.DefaultItemAnimator
+import android.support.design.widget.FloatingActionButton
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.view.LayoutInflater
@@ -14,34 +14,52 @@ import android.view.ViewGroup
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
+import de.kuschku.libquassel.session.Backend
+import de.kuschku.libquassel.session.SessionManager
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+import de.kuschku.quasseldroid_ng.util.AndroidHandlerThread
+import de.kuschku.quasseldroid_ng.util.helper.invoke
+import de.kuschku.quasseldroid_ng.util.helper.map
 import de.kuschku.quasseldroid_ng.util.helper.switchMap
+import de.kuschku.quasseldroid_ng.util.helper.toggle
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundFragment
 
 class MessageListFragment : ServiceBoundFragment() {
   val currentBuffer: MutableLiveData<LiveData<BufferId?>?> = MutableLiveData()
+  private val buffer = currentBuffer.switchMap { it }
+
+  private val handler = AndroidHandlerThread("Chat")
 
   private lateinit var database: QuasselDatabase
 
-  @BindView(R.id.messageList)
+  @BindView(R.id.messages)
   lateinit var messageList: RecyclerView
 
+  @BindView(R.id.scrollDown)
+  lateinit var scrollDown: FloatingActionButton
+
+  private val sessionManager: LiveData<SessionManager?> = backend.map(Backend::sessionManager)
+
+  override fun onCreate(savedInstanceState: Bundle?) {
+    handler.onCreate()
+    super.onCreate(savedInstanceState)
+    setHasOptionsMenu(true)
+  }
+
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
-    val view = inflater.inflate(R.layout.content_messages, container, false)
+    val view = inflater.inflate(R.layout.fragment_messages, container, false)
     ButterKnife.bind(this, view)
 
     database = QuasselDatabase.Creator.init(context!!.applicationContext)
-    val data = currentBuffer.switchMap {
-      it.switchMap {
-        database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE,
-          PagedList.Config.Builder()
-            .setPageSize(20)
-            .setEnablePlaceholders(true)
-            .setPrefetchDistance(20)
-            .build()
-        )
-      }
+    val data = buffer.switchMap {
+      database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE,
+        PagedList.Config.Builder()
+          .setPageSize(50)
+          .setEnablePlaceholders(false)
+          .setPrefetchDistance(50)
+          .build()
+      )
     }
 
     val adapter = MessageAdapter(context!!)
@@ -50,10 +68,55 @@ class MessageListFragment : ServiceBoundFragment() {
       adapter.setList(list)
     })
 
+    buffer.observe(this, Observer {
+      handler.post {
+        // Try loading messages when switching to empty buffer
+        if (it != null && database.message().bufferSize(it) == 0) {
+          loadMore()
+        }
+      }
+    })
+
+    var recyclerViewMeasuredHeight = 0
+    val scrollDownListener = object : RecyclerView.OnScrollListener() {
+      override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+        if (!recyclerView.canScrollVertically(-1)) {
+          loadMore()
+        }
+        if (recyclerViewMeasuredHeight == 0)
+          recyclerViewMeasuredHeight = recyclerView.measuredHeight
+        val canScrollDown = recyclerView.canScrollVertically(1)
+        val isScrollingDown = dy > 0
+        val scrollOffsetFromBottom = recyclerView.computeVerticalScrollRange() - recyclerView.computeVerticalScrollOffset() - recyclerViewMeasuredHeight
+        val isMoreThanOneScreenFromBottom = scrollOffsetFromBottom > recyclerViewMeasuredHeight
+        val smartVisibility = scrollDown.visibility == View.VISIBLE || isMoreThanOneScreenFromBottom
+        scrollDown.toggle(canScrollDown && isScrollingDown && smartVisibility)
+      }
+    }
+
     messageList.adapter = adapter
     messageList.layoutManager = LinearLayoutManager(context)
-    messageList.itemAnimator = DefaultItemAnimator()
+    messageList.addOnScrollListener(scrollDownListener)
+
+    scrollDown.setOnClickListener { messageList.scrollToPosition(adapter.itemCount) }
 
     return view
   }
-}
\ No newline at end of file
+
+  private fun loadMore() {
+    handler.post {
+      buffer { bufferId ->
+        backend()?.sessionManager()?.backlogManager?.requestBacklog(
+          bufferId = bufferId,
+          last = database.message().findFirstByBufferId(bufferId)?.messageId ?: -1,
+          limit = 20
+        )
+      }
+    }
+  }
+
+  override fun onDestroy() {
+    handler.onDestroy()
+    super.onDestroy()
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt
index 4ca12c9faffb9d73cdfc7115d9b6f91af702bf97..c351e227358d56c3b28c5568490acb279692d098 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt
@@ -1,13 +1,20 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
 import android.support.annotation.LayoutRes
-import de.kuschku.libquassel.protocol.Message_Flags
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
 
 interface MessageRenderer {
   @LayoutRes
-  fun layout(type: Message_Type?, flags: Message_Flags): Int
+  fun layout(type: Message_Type?, hasHighlight: Boolean): Int
 
-  fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage)
-}
\ No newline at end of file
+  fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage)
+  fun render(message: QuasselDatabase.DatabaseMessage): FormattedMessage
+  fun init(viewHolder: QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) {}
+}
+
+class FormattedMessage(
+  val id: Int,
+  val time: CharSequence,
+  val content: CharSequence
+)
\ 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 751e67e353f08a6349fcb8772529cfccc036d81b..fe5ed6e93d85e3181130cee177aec1996c153416 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
@@ -7,7 +7,6 @@ import android.text.format.DateFormat
 import android.text.style.ForegroundColorSpan
 import android.text.style.StyleSpan
 import de.kuschku.libquassel.protocol.Message.MessageType.*
-import de.kuschku.libquassel.protocol.Message_Flags
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
@@ -18,8 +17,10 @@ import org.threeten.bp.format.DateTimeFormatter
 import java.text.SimpleDateFormat
 
 class QuasselMessageRenderer(context: Context) : MessageRenderer {
-  val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern())
-  val senderColors: IntArray
+  private val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern())
+  private val senderColors: IntArray
+
+  private val zoneId = ZoneId.systemDefault()
 
   init {
     val typedArray = context.obtainStyledAttributes(intArrayOf(
@@ -34,7 +35,7 @@ class QuasselMessageRenderer(context: Context) : MessageRenderer {
     typedArray.recycle()
   }
 
-  override fun layout(type: Message_Type?, flags: Message_Flags)
+  override fun layout(type: Message_Type?, hasHighlight: Boolean)
     = when (type) {
     Nick, Notice, Mode, Join, Part, Quit, Kick, Kill, Server, Info, DayChange, Topic, NetsplitJoin,
     NetsplitQuit, Invite -> R.layout.widget_chatmessage_server
@@ -44,12 +45,29 @@ class QuasselMessageRenderer(context: Context) : MessageRenderer {
     else -> R.layout.widget_chatmessage_plain
   }
 
-  override fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) {
-    holder.time.text = timeFormatter.format(message.time.atZone(ZoneId.systemDefault()))
-    holder.content.text = SpanFormatter.format(
-      "%s: %s",
-      formatNick(message.sender),
-      message.content
+  override fun init(viewHolder: QuasselMessageViewHolder, messageType: Message_Type?, hasHighlight: Boolean) {
+    if (hasHighlight) {
+      val attrs = intArrayOf(R.attr.colorBackgroundHighlight)
+      val colors = viewHolder.itemView.context.obtainStyledAttributes(attrs)
+      viewHolder.itemView.setBackgroundColor(colors.getColor(0, 0))
+      colors.recycle()
+    }
+  }
+
+  override fun bind(holder: QuasselMessageViewHolder, message: FormattedMessage) {
+    holder.time.text = message.time
+    holder.content.text = message.content
+  }
+
+  override fun render(message: QuasselDatabase.DatabaseMessage): FormattedMessage {
+    return FormattedMessage(
+      message.messageId,
+      timeFormatter.format(message.time.atZone(zoneId)),
+      SpanFormatter.format(
+        "%s: %s",
+        formatNick(message.sender),
+        message.content
+      )
     )
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt
index 564df9023dfd882dff907a8b3d916d0a6561fd51..9208d91ddca67ecbeaa1fb9c4329be3342a18761 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/compatibility/AndroidLoggingHandler.kt
@@ -5,9 +5,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler
 
 object AndroidLoggingHandler : LoggingHandler() {
   override fun isLoggable(logLevel: LogLevel, tag: String): Boolean {
-    return Log.isLoggable(tag,
-                          priority(
-                            logLevel))
+    return true || Log.isLoggable(tag, priority(logLevel))
   }
 
   override fun log(logLevel: LogLevel, tag: String, message: String?, throwable: Throwable?) {
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..67d4bf5b061b7becf43a59982078ed62b5aae898
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/FloatingActionButtonHelper.kt
@@ -0,0 +1,8 @@
+package de.kuschku.quasseldroid_ng.util.helper
+
+import android.support.design.widget.FloatingActionButton
+
+fun FloatingActionButton.toggle(visible: Boolean) {
+  if (visible) show()
+  else hide()
+}
\ 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 78f083ab279e9fd7e2c7c19a83b426a454d95334..ea273739ed50b8f1da6decc00d2f2f1154702489 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
@@ -110,3 +110,8 @@ inline fun <T> LiveData<T>.observeForeverSticky(observer: Observer<T>) {
 
 inline fun <T> LiveData<T>.toObservable(lifecycleOwner: LifecycleOwner): Observable<T>
   = Observable.fromPublisher(LiveDataReactiveStreams.toPublisher(lifecycleOwner, this))
+
+
+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
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..772a9f53ef6df686cb3a0ff0e4c818f695bd3cb8
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/LruCacheHelper.kt
@@ -0,0 +1,8 @@
+package de.kuschku.quasseldroid_ng.util.helper
+
+import android.util.LruCache
+
+inline fun <K, V> LruCache<K, V>.getOrPut(key: K, value: () -> V) = get(key) ?: value().let {
+  put(key, it)
+  it
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..87ecb1c85784704d50856c08d9eef5068f651814
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/helper/MaterialContentLoadingProgressBarHelper.kt
@@ -0,0 +1,8 @@
+package de.kuschku.quasseldroid_ng.util.helper
+
+import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
+
+fun MaterialContentLoadingProgressBar.toggle(visible: Boolean) {
+  if (visible) show()
+  else hide()
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bceebb5e097299896b003183cc2eeb543297d0c7
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/MaterialContentLoadingProgressBar.kt
@@ -0,0 +1,109 @@
+package de.kuschku.quasseldroid_ng.util.ui
+
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.View
+import me.zhanghai.android.materialprogressbar.MaterialProgressBar
+
+/**
+ * ContentLoadingProgressBar implements a ProgressBar that waits a minimum time to be
+ * dismissed before showing. Once visible, the progress bar will be visible for
+ * a minimum amount of time to avoid "flashes" in the UI when an event could take
+ * a largely variable time to complete (from none, to a user perceivable amount)
+ */
+class MaterialContentLoadingProgressBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : MaterialProgressBar(context, attrs, 0) {
+  private var mStartTime: Long = -1
+  private var mPostedHide = false
+  private var mPostedShow = false
+  private var mDismissed = false
+
+  private val mDelayedHide = Runnable {
+    mPostedHide = false
+    mStartTime = -1
+    visibility = View.GONE
+  }
+
+  private val mDelayedShow = Runnable {
+    mPostedShow = false
+    if (!mDismissed) {
+      mStartTime = System.currentTimeMillis()
+      visibility = View.VISIBLE
+    }
+  }
+
+  public override fun onAttachedToWindow() {
+    super.onAttachedToWindow()
+    removeCallbacks()
+  }
+
+  public override fun onDetachedFromWindow() {
+    super.onDetachedFromWindow()
+    removeCallbacks()
+  }
+
+  private fun removeCallbacks() {
+    removeCallbacks(mDelayedHide)
+    removeCallbacks(mDelayedShow)
+  }
+
+  /**
+   * Hide the progress view if it is visible. The progress view will not be
+   * hidden until it has been shown for at least a minimum show time. If the
+   * progress view was not yet visible, cancels showing the progress view.
+   */
+  fun hide() {
+    mDismissed = true
+    removeCallbacks(mDelayedShow)
+    val diff = System.currentTimeMillis() - mStartTime
+    if (diff >= MIN_SHOW_TIME || mStartTime == -1L) {
+      // The progress spinner has been shown long enough
+      // OR was not shown yet. If it wasn't shown yet,
+      // it will just never be shown.
+      visibility = View.GONE
+    } else {
+      // The progress spinner is shown, but not long enough,
+      // so put a delayed message in to hide it when its been
+      // shown long enough.
+      if (!mPostedHide) {
+        postDelayed(mDelayedHide, MIN_SHOW_TIME - diff)
+        mPostedHide = true
+      }
+    }
+  }
+
+  /**
+   * Show the progress view after waiting for a minimum delay. If
+   * during that time, hide() is called, the view is never made visible.
+   */
+  fun show() {
+    // Reset the start time.
+    mStartTime = -1
+    mDismissed = false
+    removeCallbacks(mDelayedHide)
+    if (!mPostedShow) {
+      postDelayed(mDelayedShow, MIN_DELAY.toLong())
+      mPostedShow = true
+    }
+  }
+
+  companion object {
+    private val MIN_SHOW_TIME = 500 // ms
+    private val MIN_DELAY = 500 // ms
+  }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 2f9777bab9d8b5fc898cae62823dc263cd76a80a..aece9930fabe1b727e6e867cd4e3905debc0bfc5 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,140 +1,156 @@
 <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
-  xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/drawerLayout"
-  android:layout_width="match_parent"
-  android:layout_height="match_parent"
-  android:fitsSystemWindows="true">
-
-  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:fitsSystemWindows="true"
-    android:orientation="vertical">
+    android:fitsSystemWindows="true">
 
-    <android.support.design.widget.AppBarLayout
-      android:layout_width="match_parent"
-      android:layout_height="wrap_content"
-      android:theme="?attr/actionBarTheme">
-
-      <android.support.v7.widget.Toolbar
-        android:id="@+id/toolbar"
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
-        android:layout_height="?attr/actionBarSize"
-        app:contentInsetStartWithNavigation="0dp"
-        app:popupTheme="@style/Widget.PopupOverlay">
-
-        <LinearLayout
-          android:id="@+id/toolbar_action_area"
-          android:layout_width="fill_parent"
-          android:layout_height="fill_parent"
-          android:background="?attr/selectableItemBackgroundBorderless"
-          android:clickable="true"
-          android:focusable="true"
-          android:focusableInTouchMode="false"
-          android:gravity="center_vertical|start"
-          android:minHeight="?attr/actionBarSize"
-          android:orientation="vertical">
-
-          <LinearLayout
-            android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:fitsSystemWindows="true"
+        android:orientation="vertical">
+
+        <android.support.design.widget.AppBarLayout
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:layout_marginTop="-2dp"
-            android:baselineAligned="false"
-            android:gravity="center_vertical">
-
-            <TextView
-              android:id="@+id/key"
-              style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
-              android:layout_width="16dp"
-              android:layout_height="16dp"
-              android:layout_marginEnd="2dp"
-              android:layout_marginRight="2dp"
-              android:layout_marginTop="2dp"
-              android:gravity="center"
-              android:textSize="16sp"
-              android:visibility="gone" />
-
-            <TextView
-              android:id="@+id/toolbar_title"
-              style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
-              android:layout_width="wrap_content"
-              android:layout_height="wrap_content"
-              android:ellipsize="end"
-              android:gravity="center_vertical"
-              android:singleLine="true"
-              android:text="@string/app_name" />
-
-          </LinearLayout>
-
-          <TextView
-            android:id="@+id/toolbar_subtitle"
-            style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle"
-            android:layout_width="wrap_content"
+            android:theme="?attr/actionBarTheme">
+
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="?attr/actionBarSize">
+
+                <android.support.v7.widget.Toolbar
+                    android:id="@+id/toolbar"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    app:contentInsetStartWithNavigation="0dp"
+                    app:popupTheme="@style/Widget.PopupOverlay">
+
+                    <LinearLayout
+                        android:id="@+id/toolbar_action_area"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:background="?attr/selectableItemBackgroundBorderless"
+                        android:clickable="true"
+                        android:focusable="true"
+                        android:focusableInTouchMode="false"
+                        android:gravity="center_vertical|start"
+                        android:minHeight="?attr/actionBarSize"
+                        android:orientation="vertical">
+
+                        <LinearLayout
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_gravity="center_vertical"
+                            android:layout_marginTop="-2dp"
+                            android:baselineAligned="false"
+                            android:gravity="center_vertical">
+
+                            <TextView
+                                android:id="@+id/key"
+                                style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
+                                android:layout_width="16dp"
+                                android:layout_height="16dp"
+                                android:layout_marginEnd="2dp"
+                                android:layout_marginRight="2dp"
+                                android:layout_marginTop="2dp"
+                                android:gravity="center"
+                                android:textSize="16sp"
+                                android:visibility="gone" />
+
+                            <TextView
+                                android:id="@+id/toolbar_title"
+                                style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
+                                android:layout_width="wrap_content"
+                                android:layout_height="wrap_content"
+                                android:ellipsize="end"
+                                android:gravity="center_vertical"
+                                android:singleLine="true"
+                                android:text="@string/app_name" />
+
+                        </LinearLayout>
+
+                        <TextView
+                            android:id="@+id/toolbar_subtitle"
+                            style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:layout_marginTop="-3dp"
+                            android:ellipsize="end"
+                            android:singleLine="true"
+                            android:visibility="gone" />
+                    </LinearLayout>
+
+                </android.support.v7.widget.Toolbar>
+
+                <de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
+                    android:id="@+id/progressBar"
+                    style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal.NoPadding"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="bottom"
+                    app:mpb_progressStyle="horizontal"
+                    app:mpb_setBothDrawables="true"
+                    app:mpb_useIntrinsicPadding="false"
+                    tools:indeterminate="true" />
+            </FrameLayout>
+
+        </android.support.design.widget.AppBarLayout>
+
+        <fragment
+            android:id="@+id/contentMessages"
+            android:name="de.kuschku.quasseldroid_ng.ui.chat.MessageListFragment"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_weight="1"
+            tools:layout="@layout/fragment_messages" />
+
+        <android.support.v7.widget.CardView
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginTop="-3dp"
-            android:ellipsize="end"
-            android:singleLine="true"
-            android:visibility="gone" />
-        </LinearLayout>
-
-      </android.support.v7.widget.Toolbar>
-
-    </android.support.design.widget.AppBarLayout>
-
-    <fragment
-        android:id="@+id/contentMessages"
-        android:name="de.kuschku.quasseldroid_ng.ui.chat.MessageListFragment"
-        android:layout_width="match_parent"
-        android:layout_height="0dip"
-        android:layout_weight="1"
-        tools:layout="@layout/content_messages" />
+            android:background="?colorBackgroundCard"
+            app:cardElevation="4dp">
 
-    <android.support.v7.widget.CardView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:background="?colorBackgroundCard"
-        app:cardElevation="4dp">
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="?actionBarSize">
 
-      <LinearLayout
-          android:layout_width="match_parent"
-          android:layout_height="?actionBarSize">
+                <EditText
+                    android:id="@+id/input"
+                    android:layout_width="0dip"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1" />
 
-        <EditText
-            android:id="@+id/input"
-            android:layout_width="0dip"
-            android:layout_height="match_parent"
-            android:layout_weight="1" />
-
-        <Button
-            android:id="@+id/buttonSend"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center_vertical"
-            android:text="Send" />
-      </LinearLayout>
+                <Button
+                    android:id="@+id/buttonSend"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical"
+                    android:text="Send" />
+            </LinearLayout>
 
-    </android.support.v7.widget.CardView>
+        </android.support.v7.widget.CardView>
 
-  </LinearLayout>
+    </LinearLayout>
 
-  <include layout="@layout/content_nick_list" />
+    <include layout="@layout/fragment_nick_list" />
 
-  <de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_gravity="start"
-    android:background="?attr/colorBackground"
-    android:fitsSystemWindows="true"
-    app:insetForeground="?attr/colorPrimaryDark">
-
-    <fragment
-      android:id="@+id/chatListFragment"
-      android:name="de.kuschku.quasseldroid_ng.ui.chat.BufferViewConfigFragment"
-      android:layout_width="match_parent"
+    <de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout
+        android:layout_width="match_parent"
         android:layout_height="match_parent"
-        tools:layout="@layout/content_chat_list" />
-  </de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout>
+        android:layout_gravity="start"
+        android:background="?attr/colorBackground"
+        android:fitsSystemWindows="true"
+        app:insetForeground="?attr/colorPrimaryDark">
+
+        <fragment
+            android:id="@+id/chatListFragment"
+            android:name="de.kuschku.quasseldroid_ng.ui.chat.BufferViewConfigFragment"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            tools:layout="@layout/fragment_chat_list" />
+    </de.kuschku.quasseldroid_ng.util.ui.NavigationDrawerLayout>
 </android.support.v4.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/content_messages.xml b/app/src/main/res/layout/content_messages.xml
deleted file mode 100644
index cce02ed3bdf0b4efc2b65bdcac9d5b58bcc1860a..0000000000000000000000000000000000000000
--- a/app/src/main/res/layout/content_messages.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/messageList"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="?attr/colorBackground"
-    tools:listitem="@android:layout/simple_list_item_1" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_chat_list.xml b/app/src/main/res/layout/fragment_chat_list.xml
similarity index 100%
rename from app/src/main/res/layout/content_chat_list.xml
rename to app/src/main/res/layout/fragment_chat_list.xml
diff --git a/app/src/main/res/layout/fragment_messages.xml b/app/src/main/res/layout/fragment_messages.xml
new file mode 100644
index 0000000000000000000000000000000000000000..57e8d5acd3e80559333e322fa2a3c106650377ec
--- /dev/null
+++ b/app/src/main/res/layout/fragment_messages.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout 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="match_parent"
+    android:background="?attr/colorBackground">
+
+    <android.support.v7.widget.RecyclerView
+        android:id="@+id/messages"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:stackFromEnd="true"
+        tools:listitem="@layout/widget_chatmessage_plain" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/scrollDown"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end|bottom"
+        android:layout_marginBottom="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginRight="12dp"
+        android:tint="@color/colorFillDark"
+        app:backgroundTint="#8A808080"
+        app:elevation="0dip"
+        app:fabSize="mini"
+        app:pressedTranslationZ="0dip"
+        app:srcCompat="@drawable/ic_scroll_down" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_nick_list.xml b/app/src/main/res/layout/fragment_nick_list.xml
similarity index 100%
rename from app/src/main/res/layout/content_nick_list.xml
rename to app/src/main/res/layout/fragment_nick_list.xml
diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml
index 0e16b66a13b3b8023fdc9bffba49babe9d03308c..75105c7d9a43893d09f4bba5a6a08fa46e7e8609 100644
--- a/app/src/main/res/layout/widget_chatmessage_plain.xml
+++ b/app/src/main/res/layout/widget_chatmessage_plain.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:clickable="true"
@@ -21,7 +22,8 @@
         android:layout_marginEnd="@dimen/message_horizontal"
         android:layout_marginRight="@dimen/message_horizontal"
         android:textColor="?attr/colorForegroundSecondary"
-        android:typeface="monospace" />
+        android:typeface="monospace"
+        tools:text="[15:55]" />
 
     <TextView
         android:id="@+id/content"
@@ -29,5 +31,6 @@
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:textColor="?attr/colorForeground"
-        android:textIsSelectable="true" />
+        android:textIsSelectable="true"
+        tools:text="justJanne: hiii" />
 </LinearLayout>
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/activity_main.xml
similarity index 51%
rename from app/src/main/res/menu/main.xml
rename to app/src/main/res/menu/activity_main.xml
index 1f23bf3b4c7f8fea7b2a9e95038c10d884dfe046..2079987d43c463a713a0b0cfabb024151a0dd39f 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/activity_main.xml
@@ -1,13 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<menu xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto">
-  <item
-      android:id="@+id/loadMore"
-      android:title="Load More"
-      app:showAsAction="always" />
-  <item
-      android:id="@+id/clear"
-      android:title="Delete History" />
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
   <item
     android:id="@+id/settings"
     android:title="Settings" />
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
index 7d8b5c90251dfce9f03b87770a7245d1ebf24f03..dd915014177d08388eba5bde97940435014a9cae 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BacklogManager.kt
@@ -4,8 +4,7 @@ import de.kuschku.libquassel.protocol.*
 import de.kuschku.libquassel.quassel.syncables.interfaces.IBacklogManager
 import de.kuschku.libquassel.session.BacklogStorage
 import de.kuschku.libquassel.session.SignalProxy
-import de.kuschku.libquassel.util.compatibility.LoggingHandler
-import de.kuschku.libquassel.util.compatibility.log
+import java.util.concurrent.atomic.AtomicInteger
 
 class BacklogManager(
   proxy: SignalProxy,
@@ -15,21 +14,26 @@ class BacklogManager(
     initialized = true
   }
 
+  private var loading = AtomicInteger(-1)
+
+  override fun requestBacklog(bufferId: BufferId, first: MsgId, last: MsgId, limit: Int, additional: Int) {
+    if (loading.getAndSet(bufferId) != bufferId) {
+      super.requestBacklog(bufferId, first, last, limit, additional)
+    }
+  }
+
+  override fun requestBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int) {
+    super.requestBacklogAll(first, last, limit, additional)
+  }
+
   override fun receiveBacklog(bufferId: BufferId, first: MsgId, last: MsgId, limit: Int,
                               additional: Int, messages: QVariantList) {
-    for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) {
-      if (message.bufferInfo.bufferId != bufferId) {
-        // Check if it works here
-        log(LoggingHandler.LogLevel.ERROR, "message has inconsistent bufferid: $bufferId != ${message.bufferInfo.bufferId}")
-      }
-      backlogStorage.storeMessages(message)
-    }
+    loading.compareAndSet(bufferId, -1)
+    backlogStorage.storeMessages(messages.mapNotNull(QVariant_::value), initialLoad = true)
   }
 
   override fun receiveBacklogAll(first: MsgId, last: MsgId, limit: Int, additional: Int,
                                  messages: QVariantList) {
-    for (message: Message in messages.mapNotNull<QVariant_, Message>(QVariant_::value)) {
-      backlogStorage.storeMessages(message)
-    }
+    backlogStorage.storeMessages(messages.mapNotNull(QVariant_::value), initialLoad = true)
   }
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt
index 52aaa577a89c2c703434ff200c0690d626a77d61..6e4c080ece649f9449a662980b54ba85dc7aa909 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/BacklogStorage.kt
@@ -4,7 +4,8 @@ import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.Message
 
 interface BacklogStorage {
-  fun storeMessages(vararg messages: Message)
+  fun storeMessages(vararg messages: Message, initialLoad: Boolean = false)
+  fun storeMessages(messages: Iterable<Message>, initialLoad: Boolean = false)
 
   fun clearMessages(bufferId: BufferId, idRange: IntRange)
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
index 991c3f309374055db48df0e1b3e39617ebb7fe79..2b2a7774022bc8003bcef10e0db77f958f879b74 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ISession.kt
@@ -23,6 +23,7 @@ interface ISession : Closeable {
   val networks: Map<NetworkId, Network>
   val networkConfig: NetworkConfig?
   val rpcHandler: RpcHandler?
+  val initStatus: Observable<Pair<Int, Int>>
 
   companion object {
     val NULL = object : ISession {
@@ -39,6 +40,7 @@ interface ISession : Closeable {
       override val ircListHelper: IrcListHelper? = null
       override val networks: Map<NetworkId, Network> = emptyMap()
       override val networkConfig: NetworkConfig? = null
+      override val initStatus: Observable<Pair<Int, Int>> = Observable.just(0 to 0)
 
       override fun close() = Unit
       override val state = BehaviorSubject.createDefault(ConnectionState.DISCONNECTED)
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
index 3edbb50b579f218a265068d6a6a4c2102ef53c98..07a5916867f0eef3a819a00f9d288dd9da84dd01 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/ProtocolHandler.kt
@@ -30,6 +30,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
 
   abstract fun onInitDone()
 
+  private var totalInitCount = 0
+
   override fun handle(f: SignalProxyMessage): Boolean {
     try {
       if (!super<SignalProxy>.handle(f)) {
@@ -67,6 +69,7 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
   }
 
   private fun checkForInitDone() {
+    onInitStatusChanged(totalInitCount - toInit.size, totalInitCount)
     if (isInitializing && toInit.isEmpty()) {
       isInitializing = false
       onInitDone()
@@ -80,6 +83,8 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
     }
   }
 
+  open fun onInitStatusChanged(progress: Int, total: Int) {}
+
   override fun handle(f: SignalProxyMessage.SyncMessage): Boolean {
     val obj = objectStorage.get(f.className, f.objectName) ?: if (isInitializing) {
       syncQueue.add(f)
@@ -158,6 +163,7 @@ abstract class ProtocolHandler : SignalProxy, AuthHandler, Closeable {
     if (!syncableObject.initialized) {
       if (baseInit) {
         toInit.put(syncableObject, mutableListOf())
+        totalInitCount++
       }
       dispatch(SignalProxyMessage.InitRequest(syncableObject.className, syncableObject.objectName))
     }
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
index fbfbc2061026035ac2d55504cd56cedc186097dd..1faddcd315a5fd675535496e436cfd50d939b546 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -10,6 +10,7 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.DEBUG
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.LogLevel.INFO
 import de.kuschku.libquassel.util.compatibility.log
 import de.kuschku.libquassel.util.hasFlag
+import io.reactivex.subjects.BehaviorSubject
 import org.threeten.bp.Instant
 import javax.net.ssl.X509TrustManager
 
@@ -41,6 +42,8 @@ class Session(
 
   override var rpcHandler: RpcHandler? = RpcHandler(this, backlogStorage)
 
+  override val initStatus = BehaviorSubject.createDefault(0 to 0)
+
   init {
     coreConnection.start()
   }
@@ -93,6 +96,10 @@ class Session(
     return true
   }
 
+  override fun onInitStatusChanged(progress: Int, total: Int) {
+    initStatus.onNext(progress to total)
+  }
+
   override fun onInitDone() {
     coreConnection.setState(ConnectionState.CONNECTED)
     log(INFO, "Session", "Initialization finished")
diff --git a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
index 545a2da0058096c031e76338b6ae0bcb56551017..4de216bde6f6406d2530a6b9b584f851700e04fb 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -107,6 +107,7 @@ class SessionManager(offlineSession: ISession, val backlogStorage: BacklogStorag
   private var inProgressSession = BehaviorSubject.createDefault(ISession.NULL)
   private var lastSession: ISession = offlineSession
   override val state: Observable<ConnectionState> = inProgressSession.switchMap { it.state }
+  override val initStatus: Observable<Pair<Int, Int>> = inProgressSession.switchMap { it.initStatus }
   val session: Observable<ISession> = state.map { connectionState ->
     if (connectionState == ConnectionState.CONNECTED)
       inProgressSession.value