diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0a7df83a40f7ff15b59d8f1e18827da6797b5d7d..e644d7cb882f94f34ed4942cea096605fdbaf943 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -131,6 +131,7 @@ dependencies {
   implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb")
   implementation("org.jetbrains", "annotations", "16.0.1")
   implementation("com.google.code.gson", "gson", "2.8.2")
+  implementation("commons-codec", "commons-codec", "1.11")
   withVersion("8.8.1") {
     implementation("com.jakewharton", "butterknife", version)
     kapt("com.jakewharton", "butterknife-compiler", version)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt b/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
index 1f9c88d883d18ac20b8e9baa7b18901a787f067e..d7c61ae8a82972693b52c1f8a4c2a5dca7a32422 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/QuasseldroidGlideModule.kt
@@ -1,7 +1,14 @@
 package de.kuschku.quasseldroid
 
+import android.content.Context
+import android.util.Log
+import com.bumptech.glide.GlideBuilder
 import com.bumptech.glide.annotation.GlideModule
 import com.bumptech.glide.module.AppGlideModule
 
 @GlideModule
-class QuasseldroidGlideModule : AppGlideModule()
\ No newline at end of file
+class QuasseldroidGlideModule : AppGlideModule() {
+  override fun applyOptions(context: Context, builder: GlideBuilder) {
+    if (!BuildConfig.DEBUG) builder.setLogLevel(Log.ERROR)
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt
index f66f35a67ad4150bde8a666702266f29c3e1c468..169900bebbd85308eaa964a068aa20be8ceb4bf2 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/settings/AutoCompleteSettings.kt
@@ -1,12 +1,13 @@
 package de.kuschku.quasseldroid.settings
 
 data class AutoCompleteSettings(
+  val senderDoubleClick: Boolean = true,
   val button: Boolean = false,
   val doubleTap: Boolean = true,
-  val auto: Boolean = true,
+  val auto: Boolean = false,
   val prefix: Boolean = true
 ) {
   companion object {
     val DEFAULT = AutoCompleteSettings()
   }
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/MessageSettings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/MessageSettings.kt
index 11503a159be2d3eba8a998d8569445cbc7619e69..cbd532fa5ca246a525e60dce108ee73ffdce41f0 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/settings/MessageSettings.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/settings/MessageSettings.kt
@@ -12,7 +12,10 @@ data class MessageSettings(
   val showHostmaskPlain: Boolean = false,
   val nicksOnNewLine: Boolean = false,
   val timeAtEnd: Boolean = false,
-  val showAvatars: Boolean = false,
+  val showRealNames: Boolean = false,
+  val showAvatars: Boolean = true,
+  val showIRCCloudAvatars: Boolean = false,
+  val showGravatarAvatars: Boolean = false,
   val largerEmoji: Boolean = false
 ) {
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt
index abd7bf37475bbe36726a4932a6c0782dabfd1642..250923e70c5dacc70910d340a101ff415f351ed3 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/settings/Settings.kt
@@ -78,10 +78,22 @@ object Settings {
         context.getString(R.string.preference_time_at_end_key),
         MessageSettings.DEFAULT.timeAtEnd
       ),
+      showRealNames = getBoolean(
+        context.getString(R.string.preference_show_realnames_key),
+        MessageSettings.DEFAULT.showRealNames
+      ),
       showAvatars = getBoolean(
         context.getString(R.string.preference_show_avatars_key),
         MessageSettings.DEFAULT.showAvatars
       ),
+      showIRCCloudAvatars = getBoolean(
+        context.getString(R.string.preference_show_irccloud_avatars_key),
+        MessageSettings.DEFAULT.showIRCCloudAvatars
+      ),
+      showGravatarAvatars = getBoolean(
+        context.getString(R.string.preference_show_gravatar_avatars_key),
+        MessageSettings.DEFAULT.showGravatarAvatars
+      ),
       largerEmoji = getBoolean(
         context.getString(R.string.preference_larger_emoji_key),
         MessageSettings.DEFAULT.largerEmoji
@@ -91,21 +103,25 @@ object Settings {
 
   fun autoComplete(context: Context) = context.sharedPreferences {
     AutoCompleteSettings(
+      senderDoubleClick = getBoolean(
+        context.getString(R.string.preference_autocomplete_sender_doubleclick_key),
+        AutoCompleteSettings.DEFAULT.senderDoubleClick
+      ),
       button = getBoolean(
         context.getString(R.string.preference_autocomplete_button_key),
         AutoCompleteSettings.DEFAULT.button
       ),
       doubleTap = getBoolean(
         context.getString(R.string.preference_autocomplete_doubletap_key),
-        AutoCompleteSettings.DEFAULT.button
+        AutoCompleteSettings.DEFAULT.doubleTap
       ),
       auto = getBoolean(
         context.getString(R.string.preference_autocomplete_auto_key),
-        AutoCompleteSettings.DEFAULT.button
+        AutoCompleteSettings.DEFAULT.auto
       ),
       prefix = getBoolean(
         context.getString(R.string.preference_autocomplete_prefix_key),
-        AutoCompleteSettings.DEFAULT.button
+        AutoCompleteSettings.DEFAULT.prefix
       )
     )
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
index 58f5aec05dcb4be159b8bac34697e9404a94d174..72622dfaca4da54f0b4bb19ea3417cca8de5871e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/info/user/UserInfoFragment.kt
@@ -15,8 +15,8 @@ import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.Buffer_Type
 import de.kuschku.libquassel.quassel.syncables.IrcUser
 import de.kuschku.libquassel.util.IrcUserUtils
-import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.ui.chat.ChatActivity
 import de.kuschku.quasseldroid.ui.chat.input.AutoCompleteHelper.Companion.IGNORED_CHARS
 import de.kuschku.quasseldroid.util.AvatarHelper
@@ -84,6 +84,9 @@ class UserInfoFragment : ServiceBoundFragment() {
   @Inject
   lateinit var contentFormatter: ContentFormatter
 
+  @Inject
+  lateinit var messageSettings: MessageSettings
+
   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {
     val view = inflater.inflate(R.layout.fragment_info_user, container, false)
@@ -125,17 +128,11 @@ class UserInfoFragment : ServiceBoundFragment() {
         val initial = rawInitial?.toUpperCase().toString()
         val senderColor = senderColors[senderColorIndex]
 
-        val fallbackDrawable = TextDrawable.builder().buildRect(initial, senderColor)
-
-        val avatarUrl = AvatarHelper.avatar(user = user)
-        if (avatarUrl != null) {
-          GlideApp.with(avatar)
-            .load(avatarUrl)
-            .placeholder(fallbackDrawable)
-            .into(avatar)
-        } else {
-          avatar.setImageDrawable(fallbackDrawable)
-        }
+        avatar.loadAvatars(
+          AvatarHelper.avatar(messageSettings, user, maxOf(avatar.width, avatar.height)),
+          TextDrawable.builder().buildRect(initial, senderColor),
+          crop = false
+        )
 
         nick.text = user.nick()
         realName.text = contentFormatter.format(requireContext(), user.realName())
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt
index 7f295e55e985e74ce0140cc9a12dfd0306466c0b..d7a59d77cb6999164a837a986259ce4332498ba1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteAdapter.kt
@@ -11,15 +11,10 @@ import android.widget.ImageView
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import com.bumptech.glide.request.RequestOptions
-import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.ui.chat.nicks.NickListAdapter.Companion.VIEWTYPE_AWAY
-import de.kuschku.quasseldroid.util.helper.getVectorDrawableCompat
-import de.kuschku.quasseldroid.util.helper.styledAttributes
-import de.kuschku.quasseldroid.util.helper.tint
-import de.kuschku.quasseldroid.util.helper.visibleIf
+import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.ui.SpanFormatter
 import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem
 import de.kuschku.quasseldroid.viewmodel.data.BufferStatus
@@ -112,15 +107,7 @@ class AutoCompleteAdapter(
         nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
         realname.text = data.realname
 
-        if (data.avatarUrl != null) {
-          GlideApp.with(itemView)
-            .load(data.avatarUrl)
-            .apply(RequestOptions.circleCropTransform())
-            .placeholder(data.fallbackDrawable)
-            .into(avatar)
-        } else {
-          avatar.setImageDrawable(data.fallbackDrawable)
-        }
+        avatar.loadAvatars(data.avatarUrls, data.fallbackDrawable)
       }
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt
index 1894073dc7e9cec6445600c030c95a7c48c67981..be99fcca202bfe5301e7f71762c01fa2ddd3bc78 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/AutoCompleteHelper.kt
@@ -44,6 +44,8 @@ class AutoCompleteHelper(
     IntArray(length()) { getColor(it, 0) }
   }
 
+  private val avatarSize = activity.resources.getDimensionPixelSize(R.dimen.avatar_size)
+
   init {
     viewModel.autoCompleteData.toLiveData().observe(activity, Observer {
       val query = it?.first ?: ""
@@ -148,7 +150,7 @@ class AutoCompleteHelper(
                   user.realName(),
                   user.isAway(),
                   network.support("CASEMAPPING"),
-                  AvatarHelper.avatar(user = user)
+                  AvatarHelper.avatar(messageSettings, user, avatarSize)
                 )
               }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
index 0a1872eadb6ad6d99016823f57b6b520707474f1..5723bf24b929fd2e487e9bf10b4d795dd7086df4 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/DisplayMessage.kt
@@ -2,7 +2,6 @@ package de.kuschku.quasseldroid.ui.chat.messages
 
 import de.kuschku.libquassel.protocol.MsgId
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
-import de.kuschku.quasseldroid.util.AvatarHelper
 
 data class DisplayMessage(
   val content: QuasselDatabase.DatabaseMessage,
@@ -32,5 +31,4 @@ data class DisplayMessage(
     isMarkerLine,
     isEmoji
   )
-  val avatarUrl = AvatarHelper.avatar(message = content)
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
index eb54e1f35e4697f8b4a5f9aa188eeb50fbf1051c..6e53f1c924d2c560f83ce4fd353ecb1cc72f6d5e 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageAdapter.kt
@@ -11,14 +11,14 @@ import android.widget.ImageView
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import com.bumptech.glide.request.RequestOptions
 import de.kuschku.libquassel.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Type
 import de.kuschku.libquassel.util.flag.hasFlag
-import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.util.helper.getOrPut
+import de.kuschku.quasseldroid.util.helper.loadAvatars
+import de.kuschku.quasseldroid.util.ui.DoubleClickHelper
 import de.kuschku.quasseldroid.viewmodel.data.FormattedMessage
 import me.saket.bettermovementmethod.BetterLinkMovementMethod
 import javax.inject.Inject
@@ -35,23 +35,28 @@ class MessageAdapter @Inject constructor(
   }) {
   private val movementMethod = BetterLinkMovementMethod.newInstance()
   private var clickListener: ((FormattedMessage) -> Unit)? = null
-  private var selectionListener: ((FormattedMessage) -> Unit)? = null
+  private var longClickListener: ((FormattedMessage) -> Unit)? = null
+  private var doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
   private var expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null
   private var urlLongClickListener: ((TextView, String) -> Boolean)? = null
 
-  fun setOnClickListener(listener: (FormattedMessage) -> Unit) {
+  fun setOnClickListener(listener: ((FormattedMessage) -> Unit)?) {
     this.clickListener = listener
   }
 
-  fun setOnSelectionListener(listener: (FormattedMessage) -> Unit) {
-    this.selectionListener = listener
+  fun setOnLongClickListener(listener: ((FormattedMessage) -> Unit)?) {
+    this.longClickListener = listener
   }
 
-  fun setOnExpansionListener(listener: (QuasselDatabase.DatabaseMessage) -> Unit) {
+  fun setOnDoubleClickListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) {
+    this.doubleClickListener = listener
+  }
+
+  fun setOnExpansionListener(listener: ((QuasselDatabase.DatabaseMessage) -> Unit)?) {
     this.expansionListener = listener
   }
 
-  fun setOnUrlLongClickListener(listener: (TextView, String) -> Boolean) {
+  fun setOnUrlLongClickListener(listener: ((TextView, String) -> Boolean)?) {
     this.urlLongClickListener = listener
   }
 
@@ -121,7 +126,8 @@ class MessageAdapter @Inject constructor(
         false
       ),
       clickListener,
-      selectionListener,
+      longClickListener,
+      doubleClickListener,
       expansionListener,
       movementMethod
     )
@@ -138,7 +144,8 @@ class MessageAdapter @Inject constructor(
   class QuasselMessageViewHolder(
     itemView: View,
     clickListener: ((FormattedMessage) -> Unit)? = null,
-    selectionListener: ((FormattedMessage) -> Unit)? = null,
+    longClickListener: ((FormattedMessage) -> Unit)? = null,
+    doubleClickListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null,
     expansionListener: ((QuasselDatabase.DatabaseMessage) -> Unit)? = null,
     movementMethod: BetterLinkMovementMethod
   ) : RecyclerView.ViewHolder(itemView) {
@@ -167,6 +174,7 @@ class MessageAdapter @Inject constructor(
     var combined: TextView? = null
 
     private var message: FormattedMessage? = null
+    private var original: QuasselDatabase.DatabaseMessage? = null
     private var selectable: Boolean = false
     private var clickable: Boolean = false
 
@@ -181,12 +189,20 @@ class MessageAdapter @Inject constructor(
     private val localLongClickListener = View.OnLongClickListener {
       if (selectable) {
         message?.let {
-          selectionListener?.invoke(it)
+          longClickListener?.invoke(it)
         }
       }
       true
     }
 
+    private val localDoubleClickListener = {
+      if (clickable) {
+        original?.let {
+          doubleClickListener?.invoke(it)
+        }
+      }
+    }
+
     init {
       ButterKnife.bind(this, itemView)
       content?.movementMethod = movementMethod
@@ -194,10 +210,15 @@ class MessageAdapter @Inject constructor(
 
       itemView.setOnClickListener(localClickListener)
       itemView.setOnLongClickListener(localLongClickListener)
+      itemView.setOnTouchListener(DoubleClickHelper(itemView).apply {
+        this.doubleClickListener = localDoubleClickListener
+      })
     }
 
-    fun bind(message: FormattedMessage, selectable: Boolean = true, clickable: Boolean = true) {
+    fun bind(message: FormattedMessage, original: QuasselDatabase.DatabaseMessage,
+             selectable: Boolean = true, clickable: Boolean = true) {
       this.message = message
+      this.original = original
       this.selectable = selectable
       this.clickable = clickable
 
@@ -209,18 +230,7 @@ class MessageAdapter @Inject constructor(
 
       this.itemView.isSelected = message.isSelected
 
-      avatar?.let { avatarView ->
-        if (message.avatarUrl != null) {
-          GlideApp.with(itemView)
-            .load(message.avatarUrl)
-            .apply(RequestOptions.circleCropTransform())
-            .placeholder(message.fallbackDrawable)
-            .into(avatarView)
-        } else {
-          GlideApp.with(itemView).clear(avatarView)
-          avatarView.setImageDrawable(message.fallbackDrawable)
-        }
-      }
+      avatar?.loadAvatars(message.avatarUrls, message.fallbackDrawable)
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
index 68bafbb81bb30fccc5789678ad5731def5beda1e..c30714fa1714613539e7015ef5c9862d6d788617 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/MessageListFragment.kt
@@ -24,12 +24,16 @@ import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.MsgId
 import de.kuschku.libquassel.quassel.syncables.BufferSyncer
 import de.kuschku.libquassel.util.helpers.value
+import de.kuschku.libquassel.util.irc.HostmaskHelper
 import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.AppearanceSettings
+import de.kuschku.quasseldroid.settings.AutoCompleteSettings
 import de.kuschku.quasseldroid.settings.BacklogSettings
 import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.ui.chat.ChatActivity
+import de.kuschku.quasseldroid.util.AvatarHelper
 import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.service.ServiceBoundFragment
 import de.kuschku.quasseldroid.util.ui.LinkLongClickMenuHelper
@@ -55,6 +59,9 @@ class MessageListFragment : ServiceBoundFragment() {
   @Inject
   lateinit var appearanceSettings: AppearanceSettings
 
+  @Inject
+  lateinit var autoCompleteSettings: AutoCompleteSettings
+
   @Inject
   lateinit var backlogSettings: BacklogSettings
 
@@ -183,7 +190,7 @@ class MessageListFragment : ServiceBoundFragment() {
         }
       }
     }
-    adapter.setOnSelectionListener { msg ->
+    adapter.setOnLongClickListener { msg ->
       if (actionMode == null) {
         activity?.startActionMode(actionModeCallback)
       }
@@ -191,6 +198,13 @@ class MessageListFragment : ServiceBoundFragment() {
         actionMode?.finish()
       }
     }
+    if (autoCompleteSettings.senderDoubleClick)
+      adapter.setOnDoubleClickListener { msg ->
+        val intent = Intent(requireContext(), ChatActivity::class.java)
+        intent.type = "text/plain"
+        intent.putExtra(Intent.EXTRA_TEXT, "${HostmaskHelper.nick(msg.sender)}: ")
+        startActivity(intent)
+      }
     adapter.setOnUrlLongClickListener(LinkLongClickMenuHelper())
 
     messageList.adapter = adapter
@@ -371,15 +385,15 @@ class MessageListFragment : ServiceBoundFragment() {
       requireContext().resources.displayMetrics
     ).roundToInt()
 
-    val sizeProvider = FixedPreloadSizeProvider<String>(avatarSize, avatarSize)
+    val sizeProvider = FixedPreloadSizeProvider<List<String>>(avatarSize, avatarSize)
 
-    val preloadModelProvider = object : ListPreloader.PreloadModelProvider<String> {
-      override fun getPreloadItems(position: Int) = adapter[position]?.avatarUrl?.let {
-        mutableListOf(it)
-      } ?: mutableListOf()
+    val preloadModelProvider = object : ListPreloader.PreloadModelProvider<List<String>> {
+      override fun getPreloadItems(position: Int) = listOfNotNull(
+        adapter[position]?.content?.let { AvatarHelper.avatar(messageSettings, it, avatarSize) }
+      )
 
-      override fun getPreloadRequestBuilder(item: String) =
-        GlideApp.with(this@MessageListFragment).load(item).override(avatarSize)
+      override fun getPreloadRequestBuilder(item: List<String>) =
+        GlideApp.with(this@MessageListFragment).loadWithFallbacks(item)?.override(avatarSize)
     }
 
     val preloader = RecyclerViewPreloader(Glide.with(this), preloadModelProvider, sizeProvider, 10)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
index 9c0badeca9da0f80978af3b7edc13dbacaf52e83..b6946bae8711371d83e495aec4b176cb21d5616d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/messages/QuasselMessageRenderer.kt
@@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable
 import android.graphics.drawable.LayerDrawable
 import android.os.Build
 import android.text.SpannableString
+import android.text.SpannableStringBuilder
 import android.text.style.ForegroundColorSpan
 import android.text.style.StyleSpan
 import android.util.TypedValue
@@ -23,6 +24,7 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase
 import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.settings.MessageSettings.ColorizeNicknamesMode
 import de.kuschku.quasseldroid.settings.MessageSettings.ShowPrefixMode
+import de.kuschku.quasseldroid.util.AvatarHelper
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.irc.format.ContentFormatter
@@ -152,11 +154,10 @@ class QuasselMessageRenderer @Inject constructor(
   override fun bind(holder: MessageAdapter.QuasselMessageViewHolder, message: FormattedMessage,
                     original: QuasselDatabase.DatabaseMessage) =
     Message_Type.of(original.type).hasFlag(DayChange).let { isDayChange ->
-      holder.bind(message, !isDayChange, !isDayChange)
+      holder.bind(message, original, !isDayChange, !isDayChange)
     }
 
-  override fun render(context: Context,
-                      message: DisplayMessage): FormattedMessage {
+  override fun render(context: Context, message: DisplayMessage): FormattedMessage {
     context.theme.styledAttributes(
       R.attr.senderColor0, R.attr.senderColor1, R.attr.senderColor2, R.attr.senderColor3,
       R.attr.senderColor4, R.attr.senderColor5, R.attr.senderColor6, R.attr.senderColor7,
@@ -170,20 +171,30 @@ class QuasselMessageRenderer @Inject constructor(
       selfColor = getColor(16, 0)
     }
 
+    val avatarSize = TypedValue.applyDimension(
+      TypedValue.COMPLEX_UNIT_SP,
+      messageSettings.textSize * 2.5f,
+      context.resources.displayMetrics
+    ).roundToInt()
+
     val self = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Self)
     val highlight = Message_Flag.of(message.content.flag).hasFlag(Message_Flag.Highlight)
     return when (Message_Type.of(message.content.type).enabledValues().firstOrNull()) {
       Message_Type.Plain        -> {
-        val nick = SpanFormatter.format(
-          "%s%s",
-          formatPrefix(message.content.senderPrefixes, highlight),
-          formatNick(
+        val realName = contentFormatter.format(context, message.content.realName, highlight)
+        val nick = SpannableStringBuilder().apply {
+          append(formatPrefix(message.content.senderPrefixes, highlight))
+          append(formatNick(
             message.content.sender,
             self,
             highlight,
             messageSettings.showHostmaskPlain && messageSettings.nicksOnNewLine
-          )
-        )
+          ))
+          if (messageSettings.showRealNames) {
+            append(" ")
+            append(realName)
+          }
+        }
         val content = contentFormatter.format(context, message.content.content, highlight)
         val nickName = HostmaskHelper.nick(message.content.sender)
         val senderColorIndex = IrcUserUtils.senderColor(nickName)
@@ -200,8 +211,13 @@ class QuasselMessageRenderer @Inject constructor(
           time = timeFormatter.format(message.content.time.atZone(zoneId)),
           name = nick,
           content = content,
-          combined = SpanFormatter.format("%s: %s", nick, content),
-          avatarUrl = message.avatarUrl,
+          combined = SpannableStringBuilder().apply {
+            append(nick)
+            append(": ")
+            append(content)
+          },
+          realName = realName,
+          avatarUrls = AvatarHelper.avatar(messageSettings, message.content, avatarSize),
           fallbackDrawable = TextDrawable.builder().buildRound(initial, senderColor),
           isMarkerLine = message.isMarkerLine,
           isExpanded = message.isExpanded,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
index 94caac56406a66c7b18d42872902c5b950b9f2d4..e724428ab480206cc470697ecfcbbf6986394889 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
@@ -10,10 +10,9 @@ import android.widget.ImageView
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import com.bumptech.glide.request.RequestOptions
-import de.kuschku.quasseldroid.GlideApp
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.util.helper.loadAvatars
 import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.ui.SpanFormatter
 import de.kuschku.quasseldroid.viewmodel.data.IrcUserItem
@@ -86,16 +85,7 @@ class NickListAdapter(
       nick.text = SpanFormatter.format("%s%s", data.modes, data.displayNick ?: data.nick)
       realname.text = data.realname
 
-      if (data.avatarUrl != null) {
-        GlideApp.with(itemView)
-          .load(data.avatarUrl)
-          .apply(RequestOptions.circleCropTransform())
-          .placeholder(data.fallbackDrawable)
-          .into(avatar)
-      } else {
-        GlideApp.with(itemView).clear(avatar)
-        avatar.setImageDrawable(data.fallbackDrawable)
-      }
+      avatar.loadAvatars(data.avatarUrls, data.fallbackDrawable)
     }
   }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
index e86cb6717ab67ff5129da142586c00520d23c3fb..5aafe6bda25f09866d0818c13ed3472042221d3c 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListFragment.kt
@@ -29,6 +29,8 @@ import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.ui.chat.info.user.UserInfoActivity
+import de.kuschku.quasseldroid.util.AvatarHelper
+import de.kuschku.quasseldroid.util.helper.loadWithFallbacks
 import de.kuschku.quasseldroid.util.helper.styledAttributes
 import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatDeserializer
@@ -73,6 +75,8 @@ class NickListFragment : ServiceBoundFragment() {
       }
     }
 
+    val avatarSize = resources.getDimensionPixelSize(R.dimen.avatar_size)
+
     viewModel.nickData.map {
       it.map {
         val nickName = it.nick
@@ -111,10 +115,11 @@ class NickListFragment : ServiceBoundFragment() {
           },
           realname = ircFormatDeserializer.formatString(
             requireContext(), it.realname.toString(), messageSettings.colorizeMirc
-          )
+          ),
+          avatarUrls = AvatarHelper.avatar(messageSettings, it, avatarSize)
         )
       }.sortedBy {
-        IrcCaseMappers[it.networkCasemapping].toLowerCase(it.nick)
+        IrcCaseMappers.unicode.toLowerCase(it.nick)
       }.sortedBy {
         it.lowestMode
       }
@@ -124,17 +129,15 @@ class NickListFragment : ServiceBoundFragment() {
       nickList.layoutManager.onRestoreInstanceState(getParcelable(KEY_STATE_LIST))
     }
 
-    val avatar_size = resources.getDimensionPixelSize(R.dimen.avatar_size)
-
-    val sizeProvider = FixedPreloadSizeProvider<String>(avatar_size, avatar_size)
+    val sizeProvider = FixedPreloadSizeProvider<List<String>>(avatarSize, avatarSize)
 
-    val preloadModelProvider = object : ListPreloader.PreloadModelProvider<String> {
-      override fun getPreloadItems(position: Int) = nickListAdapter[position]?.avatarUrl?.let {
-        mutableListOf(it)
-      } ?: mutableListOf()
+    val preloadModelProvider = object : ListPreloader.PreloadModelProvider<List<String>> {
+      override fun getPreloadItems(position: Int) = listOfNotNull(
+        nickListAdapter[position]?.let { AvatarHelper.avatar(messageSettings, it) }
+      )
 
-      override fun getPreloadRequestBuilder(item: String) =
-        GlideApp.with(this@NickListFragment).load(item).override(avatar_size)
+      override fun getPreloadRequestBuilder(item: List<String>) =
+        GlideApp.with(this@NickListFragment).loadWithFallbacks(item)?.override(avatarSize)
     }
 
     val preloader = RecyclerViewPreloader(Glide.with(this), preloadModelProvider, sizeProvider, 10)
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt
index 64cbd6ce373b8638e8d00861e6e81da38bd5704f..f5e89cdd33d6b6580af47021dd0fe4bcd5076740 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/coresettings/CoreSettingsFragment.kt
@@ -95,7 +95,7 @@ class CoreSettingsFragment : ServiceBoundFragment() {
     viewModel.identities.switchMap {
       combineLatest(it.values.map(Identity::liveUpdates)).map {
         it.map {
-          SettingsItem(it.id(), it.identityName())
+          SettingsItem(it.id(), it.identityName() ?: "")
         }.sortedBy(SettingsItem::name)
       }
     }.toLiveData().observe(this, Observer {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c949a544c263174985a8da3d4955aacc790b6b1d
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/AvatarHelper.kt
@@ -0,0 +1,71 @@
+package de.kuschku.quasseldroid.util
+
+import de.kuschku.libquassel.quassel.syncables.IrcUser
+import de.kuschku.libquassel.util.irc.HostmaskHelper
+import de.kuschku.libquassel.util.irc.IrcCaseMappers
+import de.kuschku.quasseldroid.persistence.QuasselDatabase
+import de.kuschku.quasseldroid.settings.MessageSettings
+import de.kuschku.quasseldroid.util.backport.codec.Hex
+import de.kuschku.quasseldroid.util.helper.letIf
+import de.kuschku.quasseldroid.util.helper.notBlank
+import de.kuschku.quasseldroid.viewmodel.data.IrcUserItem
+import org.apache.commons.codec.digest.DigestUtils
+
+object AvatarHelper {
+  fun avatar(settings: MessageSettings, message: QuasselDatabase.DatabaseMessage,
+             size: Int? = null) = listOfNotNull(
+    message.avatarUrl.notBlank()?.let { listOf(it) },
+    settings.showIRCCloudAvatars.letIf {
+      ircCloudFallback(HostmaskHelper.user(message.sender), size)
+    },
+    settings.showGravatarAvatars.letIf {
+      gravatarFallback(message.realName, size)
+    }
+  ).flatten()
+
+  fun avatar(settings: MessageSettings, user: IrcUserItem, size: Int? = null) = listOfNotNull(
+    settings.showIRCCloudAvatars.letIf {
+      ircCloudFallback(HostmaskHelper.user(user.hostmask), size)
+    },
+    settings.showGravatarAvatars.letIf {
+      gravatarFallback(user.realname.toString(), size)
+    }
+  ).flatten()
+
+  fun avatar(settings: MessageSettings, user: IrcUser, size: Int? = null) = listOfNotNull(
+    settings.showIRCCloudAvatars.letIf {
+      ircCloudFallback(user.user(), size)
+    },
+    settings.showGravatarAvatars.letIf {
+      gravatarFallback(user.realName(), size)
+    }
+  ).flatten()
+
+  private fun gravatarFallback(realname: String, size: Int?): List<String> {
+    return Regex(Patterns.AUTOLINK_EMAIL_ADDRESS_STR)
+      .findAll(realname)
+      .mapNotNull {
+        it.groups[1]?.value
+      }.map { email ->
+        val hash = Hex.encodeHexString(DigestUtils.md5(IrcCaseMappers.unicode.toLowerCase(email)))
+        if (size == null) {
+          "https://www.gravatar.com/avatar/$hash?d=404"
+        } else {
+          "https://www.gravatar.com/avatar/$hash?d=404&s=${truncateSize(size)}"
+        }
+      }.toList()
+  }
+
+  private fun ircCloudFallback(ident: String, size: Int?): List<String> {
+    val userId = Regex("(?:~?)[us]id(\\d+)").matchEntire(ident)?.groupValues?.lastOrNull()
+                 ?: return emptyList()
+
+    if (size != null) {
+      return listOf("https://static.irccloud-cdn.com/avatar-redirect/w${truncateSize(size)}/$userId")
+    }
+
+    return listOf("https://static.irccloud-cdn.com/avatar-redirect/$userId")
+  }
+
+  private fun truncateSize(originalSize: Int) = if (originalSize > 72) 512 else 72
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt b/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt
index d6f3e59d1964e52aea89b636addccd103a997019..69da950e26f51bfea8370b2812722a3481bee054 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/Patterns.kt
@@ -5,6 +5,9 @@ import java.util.regex.Pattern
 
 @SuppressWarnings("Access")
 object Patterns {
+  @Language("RegExp")
+  const val WORD_BOUNDARY = "(?:\\b|$|^)"
+
   @Language("RegExp")
   const val IPv4 = "(?:(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])\\.){3}(?:[0-1]?[0-9]?[0-9]|2[0-5][0-5])"
 
@@ -41,7 +44,7 @@ object Patterns {
   const val TLD = """(?:$PUNYCODE_TLD|[$TLD_CHAR]{2,63})"""
 
   @Language("RegExp")
-  const val HOST_NAME = """(?:$IRI_LABEL\.)+$TLD.?"""
+  const val HOST_NAME = """(?:$IRI_LABEL\.)+$TLD\.?"""
 
   @Language("RegExp")
   const val LOCAL_HOST_NAME = """(?:$IRI_LABEL\.)*$IRI_LABEL"""
@@ -49,4 +52,32 @@ object Patterns {
   @Language("RegExp")
   const val DOMAIN_NAME_STR = """(?:$LOCAL_HOST_NAME|$HOST_NAME|$IP_ADDRESS_STRING)"""
   val DOMAIN_NAME: Pattern = Pattern.compile(DOMAIN_NAME_STR)
+
+  /**
+   * Regular expression for valid email characters. Does not include some of the valid characters
+   * defined in RFC5321: #&~!^`{}/=$*?|
+   */
+  @Language("RegExp")
+  const val EMAIL_CHAR = """$LABEL_CHAR\+-_%'"""
+
+  /**
+   * Regular expression for local part of an email address. RFC5321 section 4.5.3.1.1 limits
+   * the local part to be at most 64 octets.
+   */
+  @Language("RegExp")
+  const val EMAIL_ADDRESS_LOCAL_PART = """[$EMAIL_CHAR](?:[$EMAIL_CHAR.]{0,62}[$EMAIL_CHAR])?"""
+
+  /**
+   * Regular expression for the domain part of an email address. RFC5321 section 4.5.3.1.2 limits
+   * the domain to be at most 255 octets.
+   */
+  @Language("RegExp")
+  const val EMAIL_ADDRESS_DOMAIN = """(?=.{1,255}(?:\s|$|^))$HOST_NAME"""
+
+  /**
+   * Regular expression pattern to match email addresses. It excludes double quoted local parts
+   * and the special characters #&~!^`{}/=$*?| that are included in RFC5321.
+   */
+  const val AUTOLINK_EMAIL_ADDRESS_STR = """($WORD_BOUNDARY(?:$EMAIL_ADDRESS_LOCAL_PART@$EMAIL_ADDRESS_DOMAIN)$WORD_BOUNDARY)"""
+  val AUTOLINK_EMAIL_ADDRESS = Pattern.compile(AUTOLINK_EMAIL_ADDRESS_STR)
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/backport/codec/Hex.kt b/app/src/main/java/de/kuschku/quasseldroid/util/backport/codec/Hex.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1f927f44aed7ba2c6316baaa318b72ce3c05a31d
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/backport/codec/Hex.kt
@@ -0,0 +1,497 @@
+package de.kuschku.quasseldroid.util.backport.codec
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.commons.codec.*
+import java.nio.ByteBuffer
+import java.nio.charset.Charset
+
+/**
+ * Converts hexadecimal Strings. The charset used for certain operation can be set, the default is set in
+ * [.DEFAULT_CHARSET_NAME]
+ *
+ * This class is thread-safe.
+ *
+ * @since 1.1
+ * @version $Id: Hex.java 1811344 2017-10-06 15:19:57Z ggregory $
+ */
+class Hex : BinaryEncoder, BinaryDecoder {
+
+  /**
+   * Gets the charset.
+   *
+   * @return the charset.
+   * @since 1.7
+   */
+  val charset: Charset
+
+  /**
+   * Gets the charset name.
+   *
+   * @return the charset name.
+   * @since 1.4
+   */
+  val charsetName: String
+    get() = this.charset.name()
+
+  /**
+   * Creates a new codec with the default charset name [.DEFAULT_CHARSET]
+   */
+  constructor() {
+    // use default encoding
+    this.charset = DEFAULT_CHARSET
+  }
+
+  /**
+   * Creates a new codec with the given Charset.
+   *
+   * @param charset
+   * the charset.
+   * @since 1.7
+   */
+  constructor(charset: Charset) {
+    this.charset = charset
+  }
+
+  /**
+   * Creates a new codec with the given charset name.
+   *
+   * @param charsetName
+   * the charset name.
+   * @throws java.nio.charset.UnsupportedCharsetException
+   * If the named charset is unavailable
+   * @since 1.4
+   * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
+   */
+  constructor(charsetName: String) : this(Charset.forName(charsetName)) {}
+
+  /**
+   * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
+   * The returned array will be half the length of the passed array, as it takes two characters to represent any given
+   * byte. An exception is thrown if the passed char array has an odd number of elements.
+   *
+   * @param array
+   * An array of character bytes containing hexadecimal digits
+   * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+   * @throws DecoderException
+   * Thrown if an odd number of characters is supplied to this function
+   * @see .decodeHex
+   */
+  @Throws(DecoderException::class)
+  override fun decode(array: ByteArray): ByteArray {
+    return decodeHex(String(array, charset).toCharArray())
+  }
+
+  /**
+   * Converts a buffer of character bytes representing hexadecimal values into an array of bytes of those same values.
+   * The returned array will be half the length of the passed array, as it takes two characters to represent any given
+   * byte. An exception is thrown if the passed char array has an odd number of elements.
+   *
+   * @param buffer
+   * An array of character bytes containing hexadecimal digits
+   * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+   * @throws DecoderException
+   * Thrown if an odd number of characters is supplied to this function
+   * @see .decodeHex
+   * @since 1.11
+   */
+  @Throws(DecoderException::class)
+  fun decode(buffer: ByteBuffer): ByteArray {
+    return decodeHex(String(buffer.array(), charset).toCharArray())
+  }
+
+  /**
+   * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
+   * same values. The returned array will be half the length of the passed String or array, as it takes two characters
+   * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
+   *
+   * @param object
+   * A String, ByteBuffer, byte[], or an array of character bytes containing hexadecimal digits
+   * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
+   * @throws DecoderException
+   * Thrown if an odd number of characters is supplied to this function or the object is not a String or
+   * char[]
+   * @see .decodeHex
+   */
+  @Throws(DecoderException::class)
+  override fun decode(`object`: Any): Any {
+    return if (`object` is String) {
+      decode(`object`.toCharArray())
+    } else if (`object` is ByteArray) {
+      decode(`object`)
+    } else if (`object` is ByteBuffer) {
+      decode(`object`)
+    } else {
+      try {
+        decodeHex(`object` as CharArray)
+      } catch (e: ClassCastException) {
+        throw DecoderException(e.message, e)
+      }
+
+    }
+  }
+
+  /**
+   * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
+   * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
+   * represent any given byte.
+   *
+   *
+   * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
+   * [.getCharset].
+   *
+   *
+   * @param array
+   * a byte[] to convert to Hex characters
+   * @return A byte[] containing the bytes of the lower-case hexadecimal characters
+   * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid.
+   * @see .encodeHex
+   */
+  override fun encode(array: ByteArray): ByteArray {
+    return encodeHexString(array).toByteArray(this.charset)
+  }
+
+  /**
+   * Converts byte buffer into an array of bytes for the characters representing the hexadecimal values of each
+   * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
+   * represent any given byte.
+   *
+   *
+   * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
+   * [.getCharset].
+   *
+   *
+   * @param array
+   * a byte buffer to convert to Hex characters
+   * @return A byte[] containing the bytes of the lower-case hexadecimal characters
+   * @see .encodeHex
+   * @since 1.11
+   */
+  fun encode(array: ByteBuffer): ByteArray {
+    return encodeHexString(array).toByteArray(this.charset)
+  }
+
+  /**
+   * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
+   * byte in order. The returned array will be double the length of the passed String or array, as it takes two
+   * characters to represent any given byte.
+   *
+   *
+   * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
+   * [.getCharset].
+   *
+   *
+   * @param object
+   * a String, ByteBuffer, or byte[] to convert to Hex characters
+   * @return A char[] containing lower-case hexadecimal characters
+   * @throws EncoderException
+   * Thrown if the given object is not a String or byte[]
+   * @see .encodeHex
+   */
+  @Throws(EncoderException::class)
+  override fun encode(`object`: Any): Any {
+    val byteArray: ByteArray
+    if (`object` is String) {
+      byteArray = `object`.toByteArray(this.charset)
+    } else if (`object` is ByteBuffer) {
+      byteArray = `object`.array()
+    } else {
+      try {
+        byteArray = `object` as ByteArray
+      } catch (e: ClassCastException) {
+        throw EncoderException(e.message, e)
+      }
+
+    }
+    return encodeHex(byteArray)
+  }
+
+  /**
+   * Returns a string representation of the object, which includes the charset name.
+   *
+   * @return a string representation of the object.
+   */
+  override fun toString(): String {
+    return super.toString() + "[charsetName=" + this.charset + "]"
+  }
+
+  companion object {
+
+    /**
+     * Default charset is [Charsets.UTF_8]
+     *
+     * @since 1.7
+     */
+    val DEFAULT_CHARSET = Charsets.UTF_8
+
+    /**
+     * Default charset name is [CharEncoding.UTF_8]
+     *
+     * @since 1.4
+     */
+    val DEFAULT_CHARSET_NAME = CharEncoding.UTF_8
+
+    /**
+     * Used to build output as Hex
+     */
+    private val DIGITS_LOWER = charArrayOf('0',
+                                           '1',
+                                           '2',
+                                           '3',
+                                           '4',
+                                           '5',
+                                           '6',
+                                           '7',
+                                           '8',
+                                           '9',
+                                           'a',
+                                           'b',
+                                           'c',
+                                           'd',
+                                           'e',
+                                           'f')
+
+    /**
+     * Used to build output as Hex
+     */
+    private val DIGITS_UPPER = charArrayOf('0',
+                                           '1',
+                                           '2',
+                                           '3',
+                                           '4',
+                                           '5',
+                                           '6',
+                                           '7',
+                                           '8',
+                                           '9',
+                                           'A',
+                                           'B',
+                                           'C',
+                                           'D',
+                                           'E',
+                                           'F')
+
+    /**
+     * Converts a String representing hexadecimal values into an array of bytes of those same values. The
+     * returned array will be half the length of the passed String, as it takes two characters to represent any given
+     * byte. An exception is thrown if the passed String has an odd number of elements.
+     *
+     * @param data
+     * A String containing hexadecimal digits
+     * @return A byte array containing binary data decoded from the supplied char array.
+     * @throws DecoderException
+     * Thrown if an odd number or illegal of characters is supplied
+     * @since 1.11
+     */
+    @Throws(DecoderException::class)
+    fun decodeHex(data: String): ByteArray {
+      return decodeHex(data.toCharArray())
+    }
+
+    /**
+     * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
+     * returned array will be half the length of the passed array, as it takes two characters to represent any given
+     * byte. An exception is thrown if the passed char array has an odd number of elements.
+     *
+     * @param data
+     * An array of characters containing hexadecimal digits
+     * @return A byte array containing binary data decoded from the supplied char array.
+     * @throws DecoderException
+     * Thrown if an odd number or illegal of characters is supplied
+     */
+    @Throws(DecoderException::class)
+    fun decodeHex(data: CharArray): ByteArray {
+
+      val len = data.size
+
+      if (len and 0x01 != 0) {
+        throw DecoderException("Odd number of characters.")
+      }
+
+      val out = ByteArray(len shr 1)
+
+      // two characters form the hex value.
+      var i = 0
+      var j = 0
+      while (j < len) {
+        var f = toDigit(data[j], j) shl 4
+        j++
+        f = f or toDigit(data[j], j)
+        j++
+        out[i] = (f and 0xFF).toByte()
+        i++
+      }
+
+      return out
+    }
+
+    /**
+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+     * The returned array will be double the length of the passed array, as it takes two characters to represent any
+     * given byte.
+     *
+     * @param data
+     * a byte[] to convert to Hex characters
+     * @param toLowerCase
+     * `true` converts to lowercase, `false` to uppercase
+     * @return A char[] containing hexadecimal characters in the selected case
+     * @since 1.4
+     */
+    @JvmOverloads
+    fun encodeHex(data: ByteArray, toLowerCase: Boolean = true): CharArray {
+      return encodeHex(data, if (toLowerCase) DIGITS_LOWER else DIGITS_UPPER)
+    }
+
+    /**
+     * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
+     * The returned array will be double the length of the passed array, as it takes two characters to represent any
+     * given byte.
+     *
+     * @param data
+     * a byte buffer to convert to Hex characters
+     * @param toLowerCase
+     * `true` converts to lowercase, `false` to uppercase
+     * @return A char[] containing hexadecimal characters in the selected case
+     * @since 1.11
+     */
+    @JvmOverloads
+    fun encodeHex(data: ByteBuffer, toLowerCase: Boolean = true): CharArray {
+      return encodeHex(data, if (toLowerCase) DIGITS_LOWER else DIGITS_UPPER)
+    }
+
+    /**
+     * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
+     * The returned array will be double the length of the passed array, as it takes two characters to represent any
+     * given byte.
+     *
+     * @param data
+     * a byte[] to convert to Hex characters
+     * @param toDigits
+     * the output alphabet (must contain at least 16 chars)
+     * @return A char[] containing the appropriate characters from the alphabet
+     * For best results, this should be either upper- or lower-case hex.
+     * @since 1.4
+     */
+    protected fun encodeHex(data: ByteArray, toDigits: CharArray): CharArray {
+      val l = data.size
+      val out = CharArray(l shl 1)
+      // two characters form the hex value.
+      var i = 0
+      var j = 0
+      while (i < l) {
+        out[j++] = toDigits[(0xF0 and data[i].toInt()).ushr(4)]
+        out[j++] = toDigits[0x0F and data[i].toInt()]
+        i++
+      }
+      return out
+    }
+
+    /**
+     * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order.
+     * The returned array will be double the length of the passed array, as it takes two characters to represent any
+     * given byte.
+     *
+     * @param data
+     * a byte buffer to convert to Hex characters
+     * @param toDigits
+     * the output alphabet (must be at least 16 characters)
+     * @return A char[] containing the appropriate characters from the alphabet
+     * For best results, this should be either upper- or lower-case hex.
+     * @since 1.11
+     */
+    protected fun encodeHex(data: ByteBuffer, toDigits: CharArray): CharArray {
+      return encodeHex(data.array(), toDigits)
+    }
+
+    /**
+     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
+     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+     *
+     * @param data
+     * a byte[] to convert to Hex characters
+     * @return A String containing lower-case hexadecimal characters
+     * @since 1.4
+     */
+    fun encodeHexString(data: ByteArray): String {
+      return String(encodeHex(data))
+    }
+
+    /**
+     * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
+     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+     *
+     * @param data
+     * a byte[] to convert to Hex characters
+     * @param toLowerCase
+     * `true` converts to lowercase, `false` to uppercase
+     * @return A String containing lower-case hexadecimal characters
+     * @since 1.11
+     */
+    fun encodeHexString(data: ByteArray, toLowerCase: Boolean): String {
+      return String(encodeHex(data, toLowerCase))
+    }
+
+    /**
+     * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
+     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+     *
+     * @param data
+     * a byte buffer to convert to Hex characters
+     * @return A String containing lower-case hexadecimal characters
+     * @since 1.11
+     */
+    fun encodeHexString(data: ByteBuffer): String {
+      return String(encodeHex(data))
+    }
+
+    /**
+     * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
+     * String will be double the length of the passed array, as it takes two characters to represent any given byte.
+     *
+     * @param data
+     * a byte buffer to convert to Hex characters
+     * @param toLowerCase
+     * `true` converts to lowercase, `false` to uppercase
+     * @return A String containing lower-case hexadecimal characters
+     * @since 1.11
+     */
+    fun encodeHexString(data: ByteBuffer, toLowerCase: Boolean): String {
+      return String(encodeHex(data, toLowerCase))
+    }
+
+    /**
+     * Converts a hexadecimal character to an integer.
+     *
+     * @param ch
+     * A character to convert to an integer digit
+     * @param index
+     * The index of the character in the source
+     * @return An integer
+     * @throws DecoderException
+     * Thrown if ch is an illegal hex character
+     */
+    @Throws(DecoderException::class)
+    protected fun toDigit(ch: Char, index: Int): Int {
+      val digit = Character.digit(ch, 16)
+      if (digit == -1) {
+        throw DecoderException("Illegal hexadecimal character $ch at index $index")
+      }
+      return digit
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/AnyHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/AnyHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ac9f12f5b305d0bbca44fed4ca2f2753ac5b1283
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/AnyHelper.kt
@@ -0,0 +1,3 @@
+package de.kuschku.quasseldroid.util.helper
+
+inline fun <T> T.letIf(condition: Boolean, f: (T) -> T) = if (condition) f(this) else this
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/BooleanHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/BooleanHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..8bb2713f0c827421c22f51e15a369a9959fa17b4
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/BooleanHelper.kt
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid.util.helper
+
+inline fun <R> Boolean.letIf(block: () -> R): R? {
+  return if (this) block() else null
+}
+
+inline fun <R> Boolean.letUnless(block: () -> R): R? {
+  return if (this) null else block()
+}
+
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/CharSequenceHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/CharSequenceHelper.kt
index cefe3ead65f49b72d8fea4a8161495cc7b5c617e..22c209d7176deaceff2524ffbca95d25378c1e55 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/helper/CharSequenceHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/CharSequenceHelper.kt
@@ -157,3 +157,5 @@ fun CharSequence.lastWordIndices(cursor: Int = this.length,
     null
   }
 }
+
+inline fun <T : CharSequence> T?.notBlank(): T? = if (this.isNullOrBlank()) null else this
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/helper/GlideHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/helper/GlideHelper.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0676bb48c51ddf0214934076ef4611d8abe068ae
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/helper/GlideHelper.kt
@@ -0,0 +1,33 @@
+package de.kuschku.quasseldroid.util.helper
+
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import com.bumptech.glide.RequestBuilder
+import de.kuschku.quasseldroid.GlideApp
+import de.kuschku.quasseldroid.GlideRequest
+import de.kuschku.quasseldroid.GlideRequests
+
+fun GlideRequests.loadWithFallbacks(urls: List<String>): GlideRequest<Drawable>? {
+  fun fold(url: String, fallback: RequestBuilder<Drawable>?): GlideRequest<Drawable> {
+    return this.load(url).let {
+      if (fallback != null) it.error(fallback) else it
+    }
+  }
+
+  return urls.foldRight(null, ::fold)
+}
+
+fun ImageView.loadAvatars(urls: List<String>, fallback: Drawable? = null, crop: Boolean = true) {
+  if (urls.isNotEmpty()) {
+    GlideApp.with(this)
+      .loadWithFallbacks(urls)
+      ?.letIf(crop) {
+        it.optionalCircleCrop()
+      }
+      ?.placeholder(fallback)
+      ?.into(this)
+  } else {
+    GlideApp.with(this).clear(this)
+    setImageDrawable(fallback)
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/DoubleClickHelper.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DoubleClickHelper.kt
index 917f84c55a6b95f0b70a4f41b22bd9b514b2ddef..1cf9eac785c609df64923de2ce515ec56b92009d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/ui/DoubleClickHelper.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DoubleClickHelper.kt
@@ -3,22 +3,23 @@ package de.kuschku.quasseldroid.util.ui
 import android.view.GestureDetector
 import android.view.MotionEvent
 import android.view.View
-import android.widget.EditText
 
-class DoubleClickHelper(editText: EditText) : View.OnTouchListener {
+class DoubleClickHelper(view: View) : View.OnTouchListener {
   var doubleClickListener: (() -> Unit)? = null
 
   private val gestureDetector = GestureDetector(
-    editText.context, object : GestureDetector.SimpleOnGestureListener() {
-    override fun onDoubleTap(e: MotionEvent?): Boolean {
-      doubleClickListener?.invoke()
-      return true
-    }
+    view.context,
+    object : GestureDetector.SimpleOnGestureListener() {
+      override fun onDoubleTap(e: MotionEvent?): Boolean {
+        doubleClickListener?.invoke()
+        return true
+      }
 
-    override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
-      return true
+      override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
+        return true
+      }
     }
-  })
+  )
 
   override fun onTouch(v: View?, event: MotionEvent?) = gestureDetector.onTouchEvent(event)
 }
diff --git a/app/src/main/res/layout/fragment_info_user.xml b/app/src/main/res/layout/fragment_info_user.xml
index dc3ea1234ee73ab713ed9c5b4e0ff9cb4c98ec99..a58b9fef85912746f99a5c7660e69784a05cc9b0 100644
--- a/app/src/main/res/layout/fragment_info_user.xml
+++ b/app/src/main/res/layout/fragment_info_user.xml
@@ -22,7 +22,7 @@
         android:layout_height="0dp"
         android:contentDescription="@string/label_avatar"
         android:scaleType="centerCrop"
-        app:layout_constraintDimensionRatio="H,1.77:1"
+        app:layout_constraintDimensionRatio="H,3:2"
         tools:src="@tools:sample/avatars" />
 
     </android.support.constraint.ConstraintLayout>
diff --git a/app/src/main/res/values-de/strings_preferences.xml b/app/src/main/res/values-de/strings_preferences.xml
index a3f8d985968830e1b4a09bfd6c21a2ffa8c28522..e483c79b60707e62e73f2ab0aa04cbf6ecb53063 100644
--- a/app/src/main/res/values-de/strings_preferences.xml
+++ b/app/src/main/res/values-de/strings_preferences.xml
@@ -52,8 +52,16 @@
   <string name="preference_nicks_on_new_line_title">Separate Spitznamen</string>
   <string name="preference_nicks_on_new_line_summary">Zeigt Spitznamen in einer eigenen Zeile an</string>
 
+  <string name="preference_show_realnames_title">Echtnamen anzeigen</string>
+
   <string name="preference_show_avatars_title">Avatare anzeigen</string>
 
+  <string name="preference_show_irccloud_avatars_title">IRCCloud Avatare anzeigen</string>
+  <string name="preference_show_irccloud_avatars_summary">Zeigt für Nutzer ohne Avatare den IRCCloud-Avatar an, falls verfügbar</string>
+
+  <string name="preference_show_gravatar_avatars_title">Gravatar Avatare anzeigen</string>
+  <string name="preference_show_gravatar_avatars_summary">Zeigt für Nutzer ohne Avatare den Gravatar-Avatar an, falls verfügbar</string>
+
   <string name="preference_time_at_end_title">Rechts-Ausgerichtete Zeit</string>
   <string name="preference_time_at_end_summary">Zeigt die Zeit rechts in Nachrichten an</string>
 
@@ -63,6 +71,9 @@
 
   <string name="preference_autocomplete_title">Autovervollständigung</string>
 
+  <string name="preference_autocomplete_sender_doubleclick_title">Sender-Autovervollständigung</string>
+  <string name="preference_autocomplete_sender_doubleclick_summary">Vervollständigt den Sender einer Nachricht, wenn auf diese doppelgeklickt wird</string>
+
   <string name="preference_autocomplete_button_title">Autovervollständigungsknopf</string>
   <string name="preference_autocomplete_button_summary">Zeigt einen Knopf um Namen und Chats zu vervollständigen</string>
 
diff --git a/app/src/main/res/values/strings_preferences.xml b/app/src/main/res/values/strings_preferences.xml
index 4a557e59569b0b84b076e1fa38a76220a4a07812..2538fcc4689ab28802f7cbc388534f5d4565645e 100644
--- a/app/src/main/res/values/strings_preferences.xml
+++ b/app/src/main/res/values/strings_preferences.xml
@@ -111,9 +111,20 @@
   <string name="preference_nicks_on_new_line_title">Separate Nicknames</string>
   <string name="preference_nicks_on_new_line_summary">Shows nicknames on a separate line</string>
 
+  <string name="preference_show_realnames_key" translatable="false">show_realnames</string>
+  <string name="preference_show_realnames_title">Show Realnames</string>
+
   <string name="preference_show_avatars_key" translatable="false">show_avatars</string>
   <string name="preference_show_avatars_title">Show Avatars</string>
 
+  <string name="preference_show_irccloud_avatars_key" translatable="false">show_irccloud_avatars</string>
+  <string name="preference_show_irccloud_avatars_title">Show IRCCloud Avatars</string>
+  <string name="preference_show_irccloud_avatars_summary">Shows for users without avatar their IRCCloud fallback, if available</string>
+
+  <string name="preference_show_gravatar_avatars_key" translatable="false">show_gravatar_avatars</string>
+  <string name="preference_show_gravatar_avatars_title">Show Gravatar Avatars</string>
+  <string name="preference_show_gravatar_avatars_summary">Shows for users without avatar their Gravatar fallback, if available</string>
+
   <string name="preference_time_at_end_key" translatable="false">time_at_end</string>
   <string name="preference_time_at_end_title">Right-Aligned Timestamps</string>
   <string name="preference_time_at_end_summary">Aligns timestamps at the end of each message</string>
@@ -125,6 +136,10 @@
 
   <string name="preference_autocomplete_title">Autocomplete</string>
 
+  <string name="preference_autocomplete_sender_doubleclick_key" translatable="false">autocomplete_sender_doubleclick</string>
+  <string name="preference_autocomplete_sender_doubleclick_title">Sender Autocomplete</string>
+  <string name="preference_autocomplete_sender_doubleclick_summary">Doubleclick on a message to autocomplete its sender</string>
+
   <string name="preference_autocomplete_button_key" translatable="false">autocomplete_button</string>
   <string name="preference_autocomplete_button_title">Autocomplete Button</string>
   <string name="preference_autocomplete_button_summary">Shows a button on the left of the input line that triggers tabcomplete</string>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 12b68e147ab95db7eaf741d2c6864eb709f63132..04da8934330c278dcf7c4d8aa1433d1bb376e984 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -69,17 +69,35 @@
       android:title="@string/preference_show_prefix_title" />
 
     <SwitchPreference
-      android:defaultValue="false"
+      android:defaultValue="true"
       android:key="@string/preference_nicks_on_new_line_key"
       android:summary="@string/preference_nicks_on_new_line_summary"
       android:title="@string/preference_nicks_on_new_line_title" />
 
     <SwitchPreference
-      android:defaultValue="false"
+      android:defaultValue="true"
+      android:dependency="@string/preference_nicks_on_new_line_key"
+      android:key="@string/preference_show_realnames_key"
+      android:title="@string/preference_show_realnames_title" />
+
+    <SwitchPreference
+      android:defaultValue="true"
       android:dependency="@string/preference_nicks_on_new_line_key"
       android:key="@string/preference_show_avatars_key"
       android:title="@string/preference_show_avatars_title" />
 
+    <SwitchPreference
+      android:defaultValue="false"
+      android:dependency="@string/preference_show_avatars_key"
+      android:key="@string/preference_show_irccloud_avatars_key"
+      android:title="@string/preference_show_irccloud_avatars_title" />
+
+    <SwitchPreference
+      android:defaultValue="false"
+      android:dependency="@string/preference_show_avatars_key"
+      android:key="@string/preference_show_gravatar_avatars_key"
+      android:title="@string/preference_show_gravatar_avatars_title" />
+
     <SwitchPreference
       android:defaultValue="false"
       android:key="@string/preference_hostmask_actions_key"
@@ -110,6 +128,12 @@
 
   <PreferenceCategory android:title="@string/preference_autocomplete_title">
 
+    <SwitchPreference
+      android:defaultValue="true"
+      android:key="@string/preference_autocomplete_sender_doubleclick_key"
+      android:summary="@string/preference_autocomplete_sender_doubleclick_summary"
+      android:title="@string/preference_autocomplete_sender_doubleclick_title" />
+
     <SwitchPreference
       android:defaultValue="false"
       android:key="@string/preference_autocomplete_button_key"
@@ -123,7 +147,7 @@
       android:title="@string/preference_autocomplete_doubletap_title" />
 
     <SwitchPreference
-      android:defaultValue="true"
+      android:defaultValue="false"
       android:key="@string/preference_autocomplete_auto_key"
       android:summary="@string/preference_autocomplete_auto_summary"
       android:title="@string/preference_autocomplete_auto_title" />
diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
index f628ca91486060038bf3a5f005e110390d63fd07..fadb2eb3704f418a24dc98a9ae75e01c78a0b281 100644
--- a/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/protocol/Message.kt
@@ -13,6 +13,8 @@ class Message(
   val bufferInfo: BufferInfo,
   val sender: String,
   val senderPrefixes: String,
+  val realName: String,
+  val avatarUrl: String,
   val content: String
 ) {
   enum class MessageType(override val bit: Int) :
diff --git a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt
index 89155782cc04014dc0113ba06c7adeeeb9a62ea5..ebe0b3ff9afe3a123683188d927788f113b07e8b 100644
--- a/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/protocol/primitive/serializer/MessageSerializer.kt
@@ -10,20 +10,30 @@ import java.nio.ByteBuffer
 object MessageSerializer : Serializer<Message> {
   override fun serialize(buffer: ChainedByteBuffer, data: Message, features: QuasselFeatures) {
     IntSerializer.serialize(buffer, data.messageId, features)
-    IntSerializer.serialize(buffer, data.time.epochSecond.toInt(), features)
+    if (features.hasFeature(ExtendedFeature.LongMessageTime))
+      LongSerializer.serialize(buffer, data.time.toEpochMilli(), features)
+    else
+      IntSerializer.serialize(buffer, data.time.epochSecond.toInt(), features)
     IntSerializer.serialize(buffer, data.type.toInt(), features)
     ByteSerializer.serialize(buffer, data.flag.toByte(), features)
     BufferInfoSerializer.serialize(buffer, data.bufferInfo, features)
     StringSerializer.UTF8.serialize(buffer, data.sender, features)
     if (features.hasFeature(ExtendedFeature.SenderPrefixes))
       StringSerializer.UTF8.serialize(buffer, data.senderPrefixes, features)
+    if (features.hasFeature(ExtendedFeature.RichMessages))
+      StringSerializer.UTF8.serialize(buffer, data.realName, features)
+    if (features.hasFeature(ExtendedFeature.RichMessages))
+      StringSerializer.UTF8.serialize(buffer, data.avatarUrl, features)
     StringSerializer.UTF8.serialize(buffer, data.content, features)
   }
 
   override fun deserialize(buffer: ByteBuffer, features: QuasselFeatures): Message {
     return Message(
       messageId = IntSerializer.deserialize(buffer, features),
-      time = Instant.ofEpochSecond(IntSerializer.deserialize(buffer, features).toLong()),
+      time = if (features.hasFeature(ExtendedFeature.LongMessageTime))
+        Instant.ofEpochMilli(LongSerializer.deserialize(buffer, features))
+      else
+        Instant.ofEpochSecond(IntSerializer.deserialize(buffer, features).toLong()),
       type = Message.MessageType.of(IntSerializer.deserialize(buffer, features)),
       flag = Message.MessageFlag.of(
         ByteSerializer.deserialize(buffer, features).toInt()
@@ -32,6 +42,10 @@ object MessageSerializer : Serializer<Message> {
       sender = StringSerializer.UTF8.deserialize(buffer, features) ?: "",
       senderPrefixes = if (features.hasFeature(ExtendedFeature.SenderPrefixes))
         StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "",
+      realName = if (features.hasFeature(ExtendedFeature.RichMessages))
+        StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "",
+      avatarUrl = if (features.hasFeature(ExtendedFeature.RichMessages))
+        StringSerializer.UTF8.deserialize(buffer, features) ?: "" else "",
       content = StringSerializer.UTF8.deserialize(buffer, features) ?: ""
     )
   }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt
index c9d0a62b382091b473f07457d17731562eb6c48d..d30e67652853f66b496c9874e7c94acb43799e42 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/ExtendedFeature.kt
@@ -26,10 +26,14 @@ enum class ExtendedFeature {
   /** Supports RPC call disconnectFromCore to remotely disconnect a client */
   RemoteDisconnect,
   /** Transmit features as list of strings */
-  ExtendedFeatures;
+  ExtendedFeatures,
+  /** Serialize message time as 64-bit */
+  LongMessageTime,
+  /** Real Name and Avatar URL in backlog */
+  RichMessages;
 
   companion object {
     private val map = values().associateBy(ExtendedFeature::name)
     fun of(name: String) = map[name]
   }
-}
\ No newline at end of file
+}
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/LegacyFeature.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/LegacyFeature.kt
index 9243d109fefcd94a1ed43f0193affaa744063508..de9c099f96cdf95c449274722c75cb98cd828533 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/LegacyFeature.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/LegacyFeature.kt
@@ -58,13 +58,14 @@ enum class LegacyFeature(override val bit: Int) :
       ExtendedFeature.VerifyServerSSL        -> LegacyFeature.VerifyServerSSL
       ExtendedFeature.CustomRateLimits       -> LegacyFeature.CustomRateLimits
       ExtendedFeature.DccFileTransfer        -> LegacyFeature.DccFileTransfer
-      ExtendedFeature.AwayFormatTimestamp    -> LegacyFeature.AwayFormatTimestamp
-      ExtendedFeature.Authenticators         -> LegacyFeature.Authenticators
-      ExtendedFeature.BufferActivitySync     -> LegacyFeature.BufferActivitySync
-      ExtendedFeature.CoreSideHighlights     -> LegacyFeature.CoreSideHighlights
-      ExtendedFeature.SenderPrefixes         -> LegacyFeature.SenderPrefixes
-      ExtendedFeature.RemoteDisconnect       -> LegacyFeature.RemoteDisconnect
-      ExtendedFeature.ExtendedFeatures       -> LegacyFeature.ExtendedFeatures
+      ExtendedFeature.AwayFormatTimestamp -> LegacyFeature.AwayFormatTimestamp
+      ExtendedFeature.Authenticators      -> LegacyFeature.Authenticators
+      ExtendedFeature.BufferActivitySync  -> LegacyFeature.BufferActivitySync
+      ExtendedFeature.CoreSideHighlights  -> LegacyFeature.CoreSideHighlights
+      ExtendedFeature.SenderPrefixes      -> LegacyFeature.SenderPrefixes
+      ExtendedFeature.RemoteDisconnect    -> LegacyFeature.RemoteDisconnect
+      ExtendedFeature.ExtendedFeatures    -> LegacyFeature.ExtendedFeatures
+      else                                -> null
     }
   }
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt
index 6bce7edbe5761f273cd2ddbe6327cce49ea6a1a3..965f4905bf47e19be8b2912390a4d992b58c84e1 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/QuasselFeatures.kt
@@ -9,11 +9,11 @@ class QuasselFeatures(
 ) {
   constructor(legacyFeatures: Legacy_Features?, extendedFeatures: Collection<String>) : this(
     legacyFeatures?.enabledValues()?.map(Legacy_Feature::toExtended).orEmpty() union
-      extendedFeatures.mapNotNull { ExtendedFeature.of(it) },
+      extendedFeatures.mapNotNull(ExtendedFeature.Companion::of),
     extendedFeatures.filter { ExtendedFeature.of(it) == null }.toSet()
   )
 
-  fun toInt() = LegacyFeature.of(enabledFeatures.map(LegacyFeature.Companion::fromExtended))
+  fun toInt() = LegacyFeature.of(enabledFeatures.mapNotNull(LegacyFeature.Companion::fromExtended))
 
   fun toStringList() = enabledFeatures.map(ExtendedFeature::name)
 
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
index e1b29a81974fb2db0140e403eeabfa11f252234b..3209bbd9c9c3999ad687a83cfafca46606a25443 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferSyncer.kt
@@ -19,32 +19,38 @@ class BufferSyncer constructor(
     markerLine(buffer)
   }.distinctUntilChanged()
 
-  fun liveLastSeenMsgs(): Observable<Map<BufferId, MsgId>> = live_lastSeenMsg
+  fun liveLastSeenMsgs(): Observable<Map<BufferId, MsgId>> =
+    live_lastSeenMsg.map { _lastSeenMsg.toMap() }
 
   fun markerLine(buffer: BufferId): MsgId = _markerLines[buffer] ?: 0
-  fun liveMarkerLine(
-    buffer: BufferId): Observable<MsgId> = live_markerLines.map { markerLine(buffer) }.distinctUntilChanged()
+  fun liveMarkerLine(buffer: BufferId): Observable<MsgId> =
+    live_markerLines.map { markerLine(buffer) }.distinctUntilChanged()
 
-  fun liveMarkerLines(): Observable<Map<BufferId, MsgId>> = live_markerLines
+  fun liveMarkerLines(): Observable<Map<BufferId, MsgId>> =
+    live_markerLines.map { _markerLines.toMap() }
 
-  fun activity(buffer: BufferId): Message_Types = _bufferActivities[buffer] ?: Message_Types.of()
-  fun liveActivity(
-    buffer: BufferId): Observable<Message_Types> = live_bufferActivities.map { activity(buffer) }.distinctUntilChanged()
+  fun activity(buffer: BufferId): Message_Types =
+    _bufferActivities[buffer] ?: Message_Types.of()
 
-  fun liveActivities(): Observable<Map<BufferId, Message_Types>> = live_bufferActivities
+  fun liveActivity(buffer: BufferId): Observable<Message_Types> =
+    live_bufferActivities.map { activity(buffer) }.distinctUntilChanged()
+
+  fun liveActivities(): Observable<Map<BufferId, Message_Types>> =
+    live_bufferActivities.map { _bufferActivities.toMap() }
 
   fun highlightCount(buffer: BufferId): Int = _highlightCounts[buffer] ?: 0
-  fun liveHighlightCount(
-    buffer: BufferId): Observable<Int> = live_highlightCounts.map { highlightCount(buffer) }.distinctUntilChanged()
+  fun liveHighlightCount(buffer: BufferId): Observable<Int> =
+    live_highlightCounts.map { highlightCount(buffer) }.distinctUntilChanged()
 
-  fun liveHighlightCounts(): Observable<Map<BufferId, Int>> = live_highlightCounts
+  fun liveHighlightCounts(): Observable<Map<BufferId, Int>> =
+    live_highlightCounts.map { _highlightCounts.toMap() }
 
   fun bufferInfo(bufferId: BufferId) = _bufferInfos[bufferId]
-  fun liveBufferInfo(
-    bufferId: BufferId) = live_bufferInfos.map { bufferInfo(bufferId) }.distinctUntilChanged()
+  fun liveBufferInfo(bufferId: BufferId) =
+    live_bufferInfos.map { bufferInfo(bufferId) }.distinctUntilChanged()
 
-  fun bufferInfos(): Collection<BufferInfo> = _bufferInfos.values
-  fun liveBufferInfos(): Observable<Map<BufferId, BufferInfo>> = live_bufferInfos
+  fun bufferInfos(): Collection<BufferInfo> = _bufferInfos.values.toList()
+  fun liveBufferInfos(): Observable<Map<BufferId, BufferInfo>> = live_bufferInfos.map { _bufferInfos.toMap() }
 
   override fun toVariantMap(): QVariantMap = mapOf(
     "Activities" to QVariant.of(initActivities(), Type.QVariantList),
@@ -102,7 +108,7 @@ class BufferSyncer constructor(
     }.forEach { (buffer, activity) ->
       setBufferActivity(buffer, activity)
     }
-    live_bufferActivities.onNext(_bufferActivities)
+    live_bufferActivities.onNext(Unit)
   }
 
   override fun initSetHighlightCounts(data: QVariantList) {
@@ -111,7 +117,7 @@ class BufferSyncer constructor(
     }.forEach { (buffer, count) ->
       setHighlightCount(buffer, count)
     }
-    live_highlightCounts.onNext(_highlightCounts)
+    live_highlightCounts.onNext(Unit)
   }
 
   override fun initSetLastSeenMsg(data: QVariantList) {
@@ -120,7 +126,7 @@ class BufferSyncer constructor(
     }.forEach { (buffer, msgId) ->
       setLastSeenMsg(buffer, msgId)
     }
-    live_lastSeenMsg.onNext(_lastSeenMsg)
+    live_lastSeenMsg.onNext(Unit)
   }
 
   override fun initSetMarkerLines(data: QVariantList) {
@@ -129,29 +135,29 @@ class BufferSyncer constructor(
     }.forEach { (buffer, msgId) ->
       setMarkerLine(buffer, msgId)
     }
-    live_markerLines.onNext(_markerLines)
+    live_markerLines.onNext(Unit)
   }
 
   fun initSetBufferInfos(infos: QVariantList?) {
     _bufferInfos.clear()
     infos?.mapNotNull { it.value<BufferInfo>() }?.forEach { _bufferInfos[it.bufferId] = it }
-    live_bufferInfos.onNext(_bufferInfos)
+    live_bufferInfos.onNext(Unit)
   }
 
   override fun mergeBuffersPermanently(buffer1: BufferId, buffer2: BufferId) {
-    _lastSeenMsg.remove(buffer2);live_lastSeenMsg.onNext(_lastSeenMsg)
-    _markerLines.remove(buffer2);live_markerLines.onNext(_markerLines)
-    _bufferActivities.remove(buffer2);live_bufferActivities.onNext(_bufferActivities)
-    _highlightCounts.remove(buffer2);live_highlightCounts.onNext(_highlightCounts)
-    _bufferInfos.remove(buffer2);live_bufferInfos.onNext(_bufferInfos)
+    _lastSeenMsg.remove(buffer2);live_lastSeenMsg.onNext(Unit)
+    _markerLines.remove(buffer2);live_markerLines.onNext(Unit)
+    _bufferActivities.remove(buffer2);live_bufferActivities.onNext(Unit)
+    _highlightCounts.remove(buffer2);live_highlightCounts.onNext(Unit)
+    _bufferInfos.remove(buffer2);live_bufferInfos.onNext(Unit)
   }
 
   override fun removeBuffer(buffer: BufferId) {
-    _lastSeenMsg.remove(buffer);live_lastSeenMsg.onNext(_lastSeenMsg)
-    _markerLines.remove(buffer);live_markerLines.onNext(_markerLines)
-    _bufferActivities.remove(buffer);live_bufferActivities.onNext(_bufferActivities)
-    _highlightCounts.remove(buffer);live_highlightCounts.onNext(_highlightCounts)
-    _bufferInfos.remove(buffer);live_bufferInfos.onNext(_bufferInfos)
+    _lastSeenMsg.remove(buffer);live_lastSeenMsg.onNext(Unit)
+    _markerLines.remove(buffer);live_markerLines.onNext(Unit)
+    _bufferActivities.remove(buffer);live_bufferActivities.onNext(Unit)
+    _highlightCounts.remove(buffer);live_highlightCounts.onNext(Unit)
+    _bufferInfos.remove(buffer);live_bufferInfos.onNext(Unit)
     session.backlogManager?.removeBuffer(buffer)
   }
 
@@ -159,7 +165,7 @@ class BufferSyncer constructor(
     val bufferInfo = _bufferInfos[buffer]
     if (bufferInfo != null) {
       _bufferInfos[buffer] = bufferInfo.copy(bufferName = newName)
-      live_bufferInfos.onNext(_bufferInfos)
+      live_bufferInfos.onNext(Unit)
     }
   }
 
@@ -167,7 +173,7 @@ class BufferSyncer constructor(
     val oldInfo = _bufferInfos[info.bufferId]
     if (info != oldInfo) {
       _bufferInfos[info.bufferId] = info
-      live_bufferInfos.onNext(_bufferInfos)
+      live_bufferInfos.onNext(Unit)
 
       if (oldInfo == null) {
         session.bufferViewManager?.handleBuffer(info, this)
@@ -182,7 +188,7 @@ class BufferSyncer constructor(
     val oldLastSeenMsg = lastSeenMsg(buffer)
     if (oldLastSeenMsg < msgId) {
       _lastSeenMsg[buffer] = msgId
-      live_lastSeenMsg.onNext(_lastSeenMsg)
+      live_lastSeenMsg.onNext(Unit)
       super.setLastSeenMsg(buffer, msgId)
     }
   }
@@ -192,7 +198,7 @@ class BufferSyncer constructor(
       return
 
     _markerLines[buffer] = msgId
-    live_markerLines.onNext(_markerLines)
+    live_markerLines.onNext(Unit)
     super.setMarkerLine(buffer, msgId)
   }
 
@@ -200,13 +206,13 @@ class BufferSyncer constructor(
     val flags = Message_Types.of<Message_Type>(activity)
     super.setBufferActivity(buffer, activity)
     _bufferActivities[buffer] = flags
-    live_bufferActivities.onNext(_bufferActivities)
+    live_bufferActivities.onNext(Unit)
   }
 
   override fun setHighlightCount(buffer: BufferId, count: Int) {
     super.setHighlightCount(buffer, count)
     _highlightCounts[buffer] = count
-    live_highlightCounts.onNext(_highlightCounts)
+    live_highlightCounts.onNext(Unit)
   }
 
   fun all(
@@ -236,17 +242,17 @@ class BufferSyncer constructor(
   ) = all(bufferName, bufferId, networkId, type, groupId).firstOrNull()
 
   private val _lastSeenMsg: MutableMap<BufferId, MsgId> = mutableMapOf()
-  private val live_lastSeenMsg = BehaviorSubject.createDefault(mapOf<BufferId, MsgId>())
+  private val live_lastSeenMsg = BehaviorSubject.createDefault(Unit)
 
   private val _markerLines: MutableMap<BufferId, MsgId> = mutableMapOf()
-  private val live_markerLines = BehaviorSubject.createDefault(mapOf<BufferId, MsgId>())
+  private val live_markerLines = BehaviorSubject.createDefault(Unit)
 
   private val _bufferActivities: MutableMap<BufferId, Message_Types> = mutableMapOf()
-  private val live_bufferActivities = BehaviorSubject.createDefault(mapOf<BufferId, Message_Types>())
+  private val live_bufferActivities = BehaviorSubject.createDefault(Unit)
 
   private val _highlightCounts: MutableMap<BufferId, Int> = mutableMapOf()
-  private val live_highlightCounts = BehaviorSubject.createDefault(mapOf<BufferId, Int>())
+  private val live_highlightCounts = BehaviorSubject.createDefault(Unit)
 
   private val _bufferInfos = mutableMapOf<BufferId, BufferInfo>()
-  private val live_bufferInfos = BehaviorSubject.createDefault(mapOf<BufferId, BufferInfo>())
+  private val live_bufferInfos = BehaviorSubject.createDefault(Unit)
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
index 1cee19a4808883b14c8cec7405852036a79840ce..deb0381b6740af46dfab73e9ca7213ff7e2dc5c5 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/BufferViewConfig.kt
@@ -58,17 +58,17 @@ class BufferViewConfig constructor(
 
   override fun initSetBufferList(buffers: QVariantList) {
     _buffers = buffers.mapNotNull { it.value<BufferId?>() }.toMutableList()
-    live_buffers.onNext(_buffers)
+    live_buffers.onNext(Unit)
   }
 
   override fun initSetRemovedBuffers(buffers: QVariantList) {
     _removedBuffers = buffers.mapNotNull { it.value<BufferId?>() }.toMutableSet()
-    live_removedBuffers.onNext(_removedBuffers)
+    live_removedBuffers.onNext(Unit)
   }
 
   override fun initSetTemporarilyRemovedBuffers(buffers: QVariantList) {
     _temporarilyRemovedBuffers = buffers.mapNotNull { it.value<BufferId?>() }.toMutableSet()
-    live_temporarilyRemovedBuffers.onNext(_temporarilyRemovedBuffers)
+    live_temporarilyRemovedBuffers.onNext(Unit)
   }
 
   override fun initSetProperties(properties: QVariantMap) {
@@ -90,16 +90,16 @@ class BufferViewConfig constructor(
 
     if (_removedBuffers.contains(bufferId)) {
       _removedBuffers.remove(bufferId)
-      live_removedBuffers.onNext(_removedBuffers)
+      live_removedBuffers.onNext(Unit)
     }
 
     if (_temporarilyRemovedBuffers.contains(bufferId)) {
       _temporarilyRemovedBuffers.remove(bufferId)
-      live_temporarilyRemovedBuffers.onNext(_temporarilyRemovedBuffers)
+      live_temporarilyRemovedBuffers.onNext(Unit)
     }
 
     _buffers.add(minOf(maxOf(pos, 0), _buffers.size), bufferId)
-    live_buffers.onNext(_buffers)
+    live_buffers.onNext(Unit)
   }
 
   override fun moveBuffer(bufferId: BufferId, pos: Int) {
@@ -119,37 +119,37 @@ class BufferViewConfig constructor(
       _buffers.add(bufferId, targetPos - 1)
     }
 
-    live_buffers.onNext(_buffers)
+    live_buffers.onNext(Unit)
   }
 
   override fun removeBuffer(bufferId: BufferId) {
     if (_buffers.contains(bufferId)) {
       _buffers.remove(bufferId)
-      live_buffers.onNext(_buffers)
+      live_buffers.onNext(Unit)
     }
 
     if (_removedBuffers.contains(bufferId)) {
       _removedBuffers.remove(bufferId)
-      live_removedBuffers.onNext(_removedBuffers)
+      live_removedBuffers.onNext(Unit)
     }
 
     _temporarilyRemovedBuffers.add(bufferId)
-    live_temporarilyRemovedBuffers.onNext(_temporarilyRemovedBuffers)
+    live_temporarilyRemovedBuffers.onNext(Unit)
   }
 
   override fun removeBufferPermanently(bufferId: BufferId) {
     if (_buffers.contains(bufferId)) {
       _buffers.remove(bufferId)
-      live_buffers.onNext(_buffers)
+      live_buffers.onNext(Unit)
     }
 
     if (_temporarilyRemovedBuffers.contains(bufferId)) {
       _temporarilyRemovedBuffers.remove(bufferId)
-      live_temporarilyRemovedBuffers.onNext(_temporarilyRemovedBuffers)
+      live_temporarilyRemovedBuffers.onNext(Unit)
     }
 
     _removedBuffers.add(bufferId)
-    live_removedBuffers.onNext(_removedBuffers)
+    live_removedBuffers.onNext(Unit)
   }
 
   fun bufferViewId() = _bufferViewId
@@ -164,14 +164,21 @@ class BufferViewConfig constructor(
   fun minimumActivity() = _minimumActivity
   fun showSearch() = _showSearch
 
-  fun buffers(): List<BufferId> = _buffers
-  fun removedBuffers(): Set<BufferId> = _removedBuffers
-  fun temporarilyRemovedBuffers(): Set<BufferId> = _temporarilyRemovedBuffers
+  fun buffers(): List<BufferId> = _buffers.toList()
+  fun removedBuffers(): Set<BufferId> = _removedBuffers.toSet()
+  fun temporarilyRemovedBuffers(): Set<BufferId> = _temporarilyRemovedBuffers.toSet()
 
-  fun liveUpdates(): Observable<BufferViewConfig> = live_config
-  fun liveBuffers(): Observable<List<BufferId>> = live_buffers
-  fun liveRemovedBuffers(): Observable<Set<BufferId>> = live_removedBuffers
-  fun liveTemporarilyRemovedBuffers(): Observable<Set<BufferId>> = live_temporarilyRemovedBuffers
+  fun liveUpdates(): Observable<BufferViewConfig> =
+    live_config.map { this }
+
+  fun liveBuffers(): Observable<List<BufferId>> =
+    live_buffers.map { buffers() }
+
+  fun liveRemovedBuffers(): Observable<Set<BufferId>> =
+    live_removedBuffers.map { removedBuffers() }
+
+  fun liveTemporarilyRemovedBuffers(): Observable<Set<BufferId>> =
+    live_temporarilyRemovedBuffers.map { temporarilyRemovedBuffers() }
 
   fun copy(): BufferViewConfig {
     val config = BufferViewConfig(this.bufferViewId(), SignalProxy.NULL)
@@ -238,60 +245,60 @@ class BufferViewConfig constructor(
   private var _bufferViewName: String = ""
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _networkId: NetworkId = 0
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _addNewBuffersAutomatically: Boolean = true
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _sortAlphabetically: Boolean = true
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _hideInactiveBuffers: Boolean = false
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _hideInactiveNetworks: Boolean = false
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _disableDecoration: Boolean = false
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _allowedBufferTypes: Buffer_Types = Buffer_Type.of(*Buffer_Type.validValues)
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _minimumActivity: Buffer_Activities = Buffer_Activities.of(0)
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _showSearch: Boolean = false
     set(value) {
       field = value
-      live_config.onNext(this)
+      live_config.onNext(Unit)
     }
   private var _buffers: MutableList<BufferId> = mutableListOf()
   private var _removedBuffers: MutableSet<BufferId> = mutableSetOf()
   private var _temporarilyRemovedBuffers: MutableSet<BufferId> = mutableSetOf()
-  private val live_config = BehaviorSubject.createDefault(this)
-  private val live_buffers = BehaviorSubject.createDefault<List<BufferId>>(emptyList())
-  private val live_removedBuffers = BehaviorSubject.createDefault<Set<BufferId>>(emptySet())
-  private val live_temporarilyRemovedBuffers = BehaviorSubject.createDefault<Set<BufferId>>(emptySet())
+  private val live_config = BehaviorSubject.createDefault(Unit)
+  private val live_buffers = BehaviorSubject.createDefault(Unit)
+  private val live_removedBuffers = BehaviorSubject.createDefault(Unit)
+  private val live_temporarilyRemovedBuffers = BehaviorSubject.createDefault(Unit)
 
   object NameComparator : Comparator<BufferViewConfig> {
     override fun compare(a: BufferViewConfig?, b: BufferViewConfig?) =
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Identity.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Identity.kt
index 21d0ff4639127ba271cd41715597d14cfaaab7f1..2c673f0efc3a368c48f616519db7735706684413 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Identity.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/Identity.kt
@@ -99,7 +99,7 @@ class Identity constructor(
     super.setAutoAwayEnabled(enabled)
   }
 
-  override fun setAutoAwayReason(reason: String) {
+  override fun setAutoAwayReason(reason: String?) {
     _autoAwayReason = reason
     super.setAutoAwayReason(reason)
   }
@@ -114,7 +114,7 @@ class Identity constructor(
     super.setAutoAwayTime(time)
   }
 
-  override fun setAwayNick(awayNick: String) {
+  override fun setAwayNick(awayNick: String?) {
     _awayNick = awayNick
     super.setAwayNick(awayNick)
   }
@@ -124,7 +124,7 @@ class Identity constructor(
     super.setAwayNickEnabled(enabled)
   }
 
-  override fun setAwayReason(awayReason: String) {
+  override fun setAwayReason(awayReason: String?) {
     _awayReason = awayReason
     super.setAwayReason(awayReason)
   }
@@ -139,7 +139,7 @@ class Identity constructor(
     super.setDetachAwayEnabled(enabled)
   }
 
-  override fun setDetachAwayReason(reason: String) {
+  override fun setDetachAwayReason(reason: String?) {
     _detachAwayReason = reason
     super.setDetachAwayReason(reason)
   }
@@ -154,17 +154,17 @@ class Identity constructor(
     super.setId(id)
   }
 
-  override fun setIdent(ident: String) {
+  override fun setIdent(ident: String?) {
     _ident = ident
     super.setIdent(ident)
   }
 
-  override fun setIdentityName(name: String) {
+  override fun setIdentityName(name: String?) {
     _identityName = name
     super.setIdentityName(name)
   }
 
-  override fun setKickReason(reason: String) {
+  override fun setKickReason(reason: String?) {
     _kickReason = reason
     super.setKickReason(reason)
   }
@@ -174,17 +174,17 @@ class Identity constructor(
     super.setNicks(nicks)
   }
 
-  override fun setPartReason(reason: String) {
+  override fun setPartReason(reason: String?) {
     _partReason = reason
     super.setPartReason(reason)
   }
 
-  override fun setQuitReason(reason: String) {
+  override fun setQuitReason(reason: String?) {
     _quitReason = reason
     super.setQuitReason(reason)
   }
 
-  override fun setRealName(realName: String) {
+  override fun setRealName(realName: String?) {
     _realName = realName
     super.setRealName(realName)
   }
@@ -196,12 +196,12 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _identityName: String = "<isEmpty>"
+  private var _identityName: String? = "<empty>"
     set(value) {
       field = value
       _change.onNext(Unit)
     }
-  private var _realName: String = ""
+  private var _realName: String? = ""
     set(value) {
       field = value
       _change.onNext(Unit)
@@ -211,7 +211,7 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _awayNick: String = ""
+  private var _awayNick: String? = ""
     set(value) {
       field = value
       _change.onNext(Unit)
@@ -221,7 +221,7 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _awayReason: String = "Gone fishing."
+  private var _awayReason: String? = "Gone fishing."
     set(value) {
       field = value
       _change.onNext(Unit)
@@ -241,7 +241,7 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _autoAwayReason: String = "Not here. No, really. not here!"
+  private var _autoAwayReason: String? = "Not here. No, really. not here!"
     set(value) {
       field = value
       _change.onNext(Unit)
@@ -256,7 +256,7 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _detachAwayReason: String = "All Quassel clients vanished from the face of the earth..."
+  private var _detachAwayReason: String? = "All Quassel clients vanished from the face of the earth..."
     set(value) {
       field = value
       _change.onNext(Unit)
@@ -266,22 +266,22 @@ class Identity constructor(
       field = value
       _change.onNext(Unit)
     }
-  private var _ident: String = "quassel"
+  private var _ident: String? = "quassel"
     set(value) {
       field = value
       _change.onNext(Unit)
     }
-  private var _kickReason: String = "Kindergarten is elsewhere!"
+  private var _kickReason: String? = "Kindergarten is elsewhere!"
     set(value) {
       field = value
       _change.onNext(Unit)
     }
-  private var _partReason: String = "http://quassel-irc.org - Chat comfortably. Anywhere."
+  private var _partReason: String? = "http://quassel-irc.org - Chat comfortably. Anywhere."
     set(value) {
       field = value
       _change.onNext(Unit)
     }
-  private var _quitReason: String = "http://quassel-irc.org - Chat comfortably. Anywhere."
+  private var _quitReason: String? = "http://quassel-irc.org - Chat comfortably. Anywhere."
     set(value) {
       field = value
       _change.onNext(Unit)
diff --git a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/interfaces/IIdentity.kt b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/interfaces/IIdentity.kt
index d9314996131b576ae72ab17f95c524959bbea5a1..52b1389a5bcc582b777515a1d3bfcd36bdd0dbf8 100644
--- a/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/interfaces/IIdentity.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/quassel/syncables/interfaces/IIdentity.kt
@@ -22,7 +22,7 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setAutoAwayReason(reason: String) {
+  fun setAutoAwayReason(reason: String?) {
     SYNC("setAutoAwayReason", ARG(reason, Type.QString))
   }
 
@@ -37,7 +37,7 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setAwayNick(awayNick: String) {
+  fun setAwayNick(awayNick: String?) {
     SYNC("setAwayNick", ARG(awayNick, Type.QString))
   }
 
@@ -47,7 +47,7 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setAwayReason(awayReason: String) {
+  fun setAwayReason(awayReason: String?) {
     SYNC("setAwayReason", ARG(awayReason, Type.QString))
   }
 
@@ -62,7 +62,7 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setDetachAwayReason(reason: String) {
+  fun setDetachAwayReason(reason: String?) {
     SYNC("setDetachAwayReason", ARG(reason, Type.QString))
   }
 
@@ -77,17 +77,17 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setIdent(ident: String) {
+  fun setIdent(ident: String?) {
     SYNC("setIdent", ARG(ident, Type.QString))
   }
 
   @Slot
-  fun setIdentityName(name: String) {
+  fun setIdentityName(name: String?) {
     SYNC("setIdentityName", ARG(name, Type.QString))
   }
 
   @Slot
-  fun setKickReason(reason: String) {
+  fun setKickReason(reason: String?) {
     SYNC("setKickReason", ARG(reason, Type.QString))
   }
 
@@ -97,17 +97,17 @@ interface IIdentity : ISyncableObject {
   }
 
   @Slot
-  fun setPartReason(reason: String) {
+  fun setPartReason(reason: String?) {
     SYNC("setPartReason", ARG(reason, Type.QString))
   }
 
   @Slot
-  fun setQuitReason(reason: String) {
+  fun setQuitReason(reason: String?) {
     SYNC("setQuitReason", ARG(reason, Type.QString))
   }
 
   @Slot
-  fun setRealName(realName: String) {
+  fun setRealName(realName: String?) {
     SYNC("setRealName", ARG(realName, Type.QString))
   }
 
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 fd6a46f21d40a6dc74594e5298813e5531870c84..23439087d6a7ea6be3e42cede53bfa4f2ed35cb0 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/Session.kt
@@ -47,14 +47,14 @@ class Session(
 
   override val identities = mutableMapOf<IdentityId, Identity>()
   private val live_identities = BehaviorSubject.createDefault(Unit)
-  override fun liveIdentities(): Observable<Map<IdentityId, Identity>> = live_identities.map { identities }
+  override fun liveIdentities(): Observable<Map<IdentityId, Identity>> = live_identities.map { identities.toMap() }
 
   override val ignoreListManager = IgnoreListManager(this)
   override val ircListHelper = IrcListHelper(this)
 
   override val networks = mutableMapOf<NetworkId, Network>()
   private val live_networks = BehaviorSubject.createDefault(Unit)
-  override fun liveNetworks(): Observable<Map<NetworkId, Network>> = live_networks.map { networks }
+  override fun liveNetworks(): Observable<Map<NetworkId, Network>> = live_networks.map { networks.toMap() }
 
   override val networkConfig = NetworkConfig(this)
 
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 7e0ded8ca24909d4b8cf0bf595fc21f4207cca44..c10ac00b82414e48f54e23df9ebe5cb25e80c4bf 100644
--- a/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/session/SessionManager.kt
@@ -8,7 +8,6 @@ import de.kuschku.libquassel.util.compatibility.LoggingHandler
 import de.kuschku.libquassel.util.compatibility.LoggingHandler.Companion.log
 import de.kuschku.libquassel.util.helpers.or
 import io.reactivex.BackpressureStrategy
-import io.reactivex.Flowable
 import io.reactivex.Observable
 import io.reactivex.functions.BiFunction
 import io.reactivex.subjects.BehaviorSubject
@@ -40,8 +39,11 @@ class SessionManager(
     else
       lastSession
   }
-  val error: Flowable<HandshakeMessage>
-    get() = inProgressSession.toFlowable(BackpressureStrategy.LATEST).switchMap(ISession::error)
+  val error: Observable<HandshakeMessage>
+    get() = inProgressSession
+      .toFlowable(BackpressureStrategy.LATEST)
+      .switchMap(ISession::error)
+      .toObservable()
 
   val connectionProgress: Observable<Triple<ConnectionState, Int, Int>> = Observable.combineLatest(
     state, initStatus,
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/HandlerService.kt b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/HandlerService.kt
index 837d07626494f3877e84272818c6520ddd605a5a..921d55c597fc9d714edf1e46984595bfca62f714 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/HandlerService.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/HandlerService.kt
@@ -1,6 +1,12 @@
 package de.kuschku.libquassel.util.compatibility
 
+import io.reactivex.Scheduler
+
 interface HandlerService {
+  val scheduler: Scheduler
+
+  var exceptionHandler: Thread.UncaughtExceptionHandler?
+
   fun serialize(f: () -> Unit)
   fun deserialize(f: () -> Unit)
   fun write(f: () -> Unit)
@@ -8,6 +14,4 @@ interface HandlerService {
   fun backendDelayed(delayMillis: Long, f: () -> Unit)
 
   fun quit()
-
-  var exceptionHandler: Thread.UncaughtExceptionHandler?
 }
diff --git a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/reference/JavaHandlerService.kt b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/reference/JavaHandlerService.kt
index 32b4befe9ab9e61d0fc77017d936ff85e8666045..3d1dca3435f52f066894aff0eb54fdef4a1c52be 100644
--- a/lib/src/main/java/de/kuschku/libquassel/util/compatibility/reference/JavaHandlerService.kt
+++ b/lib/src/main/java/de/kuschku/libquassel/util/compatibility/reference/JavaHandlerService.kt
@@ -1,9 +1,12 @@
 package de.kuschku.libquassel.util.compatibility.reference
 
 import de.kuschku.libquassel.util.compatibility.HandlerService
+import io.reactivex.schedulers.Schedulers
 import java.util.concurrent.Executors
 
 class JavaHandlerService : HandlerService {
+  override val scheduler = Schedulers.computation()
+
   override fun backendDelayed(delayMillis: Long, f: () -> Unit) = backend(f)
 
   private val serializeExecutor = Executors.newSingleThreadExecutor()
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
index 2c79f758b54bd447d84d50170f5814c61261835e..37c141583d2adca8acc0cb6eb1806d4bab563528 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselBacklogStorage.kt
@@ -29,6 +29,8 @@ class QuasselBacklogStorage(private val db: QuasselDatabase) : BacklogStorage {
           bufferId = message.bufferInfo.bufferId,
           sender = message.sender,
           senderPrefixes = message.senderPrefixes,
+          realName = message.realName,
+          avatarUrl = message.avatarUrl,
           content = message.content
         )
       )
diff --git a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
index 857f984fa85d75a81ec5ca64702dd9eb70521ab4..672f16b1add585c736ce92570c9d8a935f9daea1 100644
--- a/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
+++ b/persistence/src/main/java/de/kuschku/quasseldroid/persistence/QuasselDatabase.kt
@@ -16,7 +16,7 @@ import de.kuschku.quasseldroid.persistence.QuasselDatabase.Filtered
 import io.reactivex.Flowable
 import org.threeten.bp.Instant
 
-@Database(entities = [DatabaseMessage::class, Filtered::class], version = 5)
+@Database(entities = [DatabaseMessage::class, Filtered::class], version = 6)
 @TypeConverters(DatabaseMessage.MessageTypeConverters::class)
 abstract class QuasselDatabase : RoomDatabase() {
   abstract fun message(): MessageDao
@@ -31,6 +31,8 @@ abstract class QuasselDatabase : RoomDatabase() {
     var bufferId: Int,
     var sender: String,
     var senderPrefixes: String,
+    var realName: String,
+    var avatarUrl: String,
     var content: String
   ) {
     class MessageTypeConverters {
@@ -46,7 +48,7 @@ abstract class QuasselDatabase : RoomDatabase() {
         type
       )}, flag=${Message_Flag.of(
         flag
-      )}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')"
+      )}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', realName='$realName', avatarUrl='$avatarUrl', content='$content')"
     }
   }
 
@@ -181,6 +183,12 @@ abstract class QuasselDatabase : RoomDatabase() {
                   database.execSQL("drop table message;")
                   database.execSQL("create table message (messageId INTEGER not null primary key, time INTEGER not null, type INTEGER not null, flag INTEGER not null, bufferId INTEGER not null, sender TEXT not null, senderPrefixes TEXT not null, content TEXT not null);")
                 }
+              },
+              object : Migration(5, 6) {
+                override fun migrate(database: SupportSQLiteDatabase) {
+                  database.execSQL("drop table message;")
+                  database.execSQL("create table message (messageId INTEGER not null primary key, time INTEGER not null, type INTEGER not null, flag INTEGER not null, bufferId INTEGER not null, sender TEXT not null, senderPrefixes TEXT not null, realName TEXT not null, avatarUrl TEXT not null, content TEXT not null);")
+                }
               }
             ).build()
           }
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
index 9b6eb6569be05909d384730d3d14b2558b781083..f0863ce12d7f24e41a15560cce07d51278ac0f70 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/util/helper/ObservableHelper.kt
@@ -2,23 +2,28 @@ package de.kuschku.quasseldroid.util.helper
 
 import android.arch.lifecycle.LiveData
 import android.arch.lifecycle.LiveDataReactiveStreams
+import de.kuschku.libquassel.util.compatibility.HandlerService
 import io.reactivex.*
 import io.reactivex.functions.BiFunction
 import io.reactivex.schedulers.Schedulers
 
 inline fun <T> Observable<T>.toLiveData(
-  strategy: BackpressureStrategy = BackpressureStrategy.LATEST
-): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
-  subscribeOn(Schedulers.computation()).toFlowable(strategy)
-)
+  strategy: BackpressureStrategy = BackpressureStrategy.LATEST,
+  handlerService: HandlerService? = null,
+  scheduler: Scheduler = handlerService?.scheduler ?: Schedulers.computation()
+): LiveData<T> =
+  LiveDataReactiveStreams.fromPublisher(subscribeOn(scheduler).toFlowable(strategy))
 
-inline fun <T> Maybe<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
-  subscribeOn(Schedulers.computation()).toFlowable()
-)
+inline fun <T> Maybe<T>.toLiveData(
+  handlerService: HandlerService? = null,
+  scheduler: Scheduler = handlerService?.scheduler ?: Schedulers.computation()
+): LiveData<T> =
+  LiveDataReactiveStreams.fromPublisher(subscribeOn(scheduler).toFlowable())
 
-inline fun <T> Flowable<T>.toLiveData(): LiveData<T> = LiveDataReactiveStreams.fromPublisher(
-  subscribeOn(Schedulers.computation())
-)
+inline fun <T> Flowable<T>.toLiveData(
+  handlerService: HandlerService? = null,
+  scheduler: Scheduler = handlerService?.scheduler ?: Schedulers.computation()
+): LiveData<T> = LiveDataReactiveStreams.fromPublisher(subscribeOn(scheduler))
 
 inline fun <reified A, B> combineLatest(
   a: ObservableSource<A>,
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt
index f77ff9678282a6611322875b2d555ca1ea2b5fc5..afb77af7cf2368839179aeee2bc75ed79928f522 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/EditorViewModel.kt
@@ -84,10 +84,7 @@ class EditorViewModel : ViewModel() {
                         lowestMode,
                         user.realName(),
                         user.isAway(),
-                        network.support("CASEMAPPING"),
-                        Regex("[us]id(\\d+)").matchEntire(user.user())?.groupValues?.lastOrNull()?.let {
-                          "https://www.irccloud.com/avatar-redirect/$it"
-                        }
+                        network.support("CASEMAPPING")
                       )
                     }
                   }
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
index 232fbd13f678ac722b11dc91d1a7c434e6faeebd..576041eb936da856c60fa526867db829e6e28a34 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/QuasselViewModel.kt
@@ -20,8 +20,6 @@ import de.kuschku.libquassel.util.flag.hasFlag
 import de.kuschku.libquassel.util.helpers.*
 import de.kuschku.libquassel.util.irc.IrcCaseMappers
 import de.kuschku.quasseldroid.util.helper.combineLatest
-import de.kuschku.quasseldroid.util.helper.switchMapNotNull
-import de.kuschku.quasseldroid.util.helper.toLiveData
 import de.kuschku.quasseldroid.viewmodel.data.*
 import io.reactivex.Observable
 import io.reactivex.subjects.BehaviorSubject
@@ -41,13 +39,11 @@ class QuasselViewModel : ViewModel() {
   val expandedMessages = BehaviorSubject.createDefault(emptySet<MsgId>())
 
   val buffer = BehaviorSubject.createDefault(-1)
-  val buffer_liveData = buffer.toLiveData()
 
   val bufferViewConfigId = BehaviorSubject.createDefault(-1)
 
   val MAX_RECENT_MESSAGES = 20
   val recentlySentMessages = BehaviorSubject.createDefault(emptyList<CharSequence>())
-  val recentlySentMessages_liveData = recentlySentMessages.toLiveData()
   fun addRecentlySentMessage(message: CharSequence) {
     recentlySentMessages.onNext(
       listOf(message) + recentlySentMessages.value
@@ -58,12 +54,10 @@ class QuasselViewModel : ViewModel() {
 
   val backend = backendWrapper.switchMap { it }
   val sessionManager = backend.mapMap(Backend::sessionManager)
-  val sessionManager_liveData = sessionManager.toLiveData()
   val session = sessionManager.mapSwitchMap(SessionManager::session)
 
   val connectionProgress = sessionManager.mapSwitchMap(SessionManager::connectionProgress)
     .mapOrElse(Triple(ConnectionState.DISCONNECTED, 0, 0))
-  val connectionProgress_liveData = connectionProgress.toLiveData()
 
   val bufferViewManager = session.mapMapNullable(ISession::bufferViewManager)
 
@@ -73,9 +67,7 @@ class QuasselViewModel : ViewModel() {
     }
   }
 
-  val errors = sessionManager.toLiveData().switchMapNotNull {
-    it.orNull()?.error?.toLiveData()
-  }
+  val errors = sessionManager.mapSwitchMap(SessionManager::error)
 
   val networkConfig = session.map {
     it.map(ISession::networkConfig)
@@ -94,6 +86,12 @@ class QuasselViewModel : ViewModel() {
     it.liveBufferInfos().map(Map<BufferId, BufferInfo>::values)
   }.mapOrElse(emptyList())
 
+  val network = combineLatest(bufferSyncer,
+                              networks,
+                              buffer).map { (bufferSyncer, networks, buffer) ->
+    Optional.ofNullable(bufferSyncer.orNull()?.bufferInfo(buffer)?.let { networks[it.networkId] })
+  }
+
   /**
    * An observable of the changes of the markerline, as pairs of `(old, new)`
    */
@@ -103,7 +101,6 @@ class QuasselViewModel : ViewModel() {
       currentSession.bufferSyncer?.liveMarkerLine(currentBuffer) ?: Observable.empty()
     }
   }
-  val markerLine_liveData = markerLine.toLiveData()
 
   // Remove orElse
   val lag: Observable<Long> = session.mapSwitchMap(ISession::lag).mapOrElse(0)
@@ -198,11 +195,9 @@ class QuasselViewModel : ViewModel() {
                       network.modesToPrefixes(userModes),
                       lowestMode,
                       user.realName(),
+                      user.hostMask(),
                       user.isAway(),
-                      network.support("CASEMAPPING"),
-                      Regex("[us]id(\\d+)").matchEntire(user.user())?.groupValues?.lastOrNull()?.let {
-                        "https://www.irccloud.com/avatar-redirect/$it"
-                      }
+                      network.support("CASEMAPPING")
                     )
                   }
                 }
@@ -285,7 +280,6 @@ class QuasselViewModel : ViewModel() {
         Observable.just(SelectedBufferItem())
       }
     }
-  val selectedBuffer_liveData = selectedBuffer.toLiveData()
 
   val bufferList: Observable<Pair<BufferViewConfig?, List<BufferProps>>?> =
     combineLatest(session, bufferViewConfig, showHidden)
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt
index fe384dcd53e41394f03109afc578e4faeb3e8001..f7764526384fe29dcb27c9e3e746bd2017ad3b68 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/AutoCompleteItem.kt
@@ -22,7 +22,7 @@ sealed class AutoCompleteItem(open val name: String) : Comparable<AutoCompleteIt
     val realname: CharSequence,
     val away: Boolean,
     val networkCasemapping: String?,
-    val avatarUrl: String? = null,
+    val avatarUrls: List<String> = emptyList(),
     val fallbackDrawable: Drawable? = null,
     val displayNick: CharSequence? = null
   ) : AutoCompleteItem(nick)
@@ -33,4 +33,4 @@ sealed class AutoCompleteItem(open val name: String) : Comparable<AutoCompleteIt
     val bufferStatus: BufferStatus,
     val description: CharSequence
   ) : AutoCompleteItem(info.bufferName ?: "")
-}
\ No newline at end of file
+}
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt
index fd7c7865b90276fd6f68e6822a7d4cf57709874c..567f2b73586824f002673bd876bea9a390d7aacd 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/FormattedMessage.kt
@@ -9,8 +9,9 @@ class FormattedMessage(
   val content: CharSequence? = null,
   val combined: CharSequence,
   val fallbackDrawable: Drawable? = null,
-  val avatarUrl: String? = null,
+  val realName: CharSequence? = null,
+  val avatarUrls: List<String> = emptyList(),
   val isSelected: Boolean,
   val isExpanded: Boolean,
   val isMarkerLine: Boolean
-)
\ No newline at end of file
+)
diff --git a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/IrcUserItem.kt b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/IrcUserItem.kt
index de3ec6672def2a3d39cba4b9880905ff2823fa19..d7f8080daa9e04ccda65e6ba6f357aaaf43f0803 100644
--- a/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/IrcUserItem.kt
+++ b/viewmodel/src/main/java/de/kuschku/quasseldroid/viewmodel/data/IrcUserItem.kt
@@ -7,9 +7,10 @@ data class IrcUserItem(
   val modes: String,
   val lowestMode: Int,
   val realname: CharSequence,
+  val hostmask: String,
   val away: Boolean,
   val networkCasemapping: String?,
-  val avatarUrl: String? = null,
+  val avatarUrls: List<String> = emptyList(),
   val fallbackDrawable: Drawable? = null,
   val displayNick: CharSequence? = null
-)
\ No newline at end of file
+)