diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index bce3a7a922f0b9926fadb4be8b117029f7a6508f..c3df58fe4c7ed3ba89f2fbf80724bb5988251fac 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -16,7 +16,7 @@ plugins {
 
 android {
   compileSdkVersion(26)
-  buildToolsVersion("26.0.2")
+  buildToolsVersion("27.0.2")
 
   signingConfigs {
     val signing = project.rootProject.properties("signing.properties")
@@ -88,21 +88,21 @@ android {
   }
 }
 
-val appCompatVersion = "26.1.0"
-val appArchVersion = "1.0.0-rc1"
+val appCompatVersion = "27.0.2"
+val appArchVersion = "1.0.0"
 dependencies {
-  implementation(kotlin("stdlib", "1.1.61"))
+  implementation(kotlin("stdlib", "1.2.0"))
 
   implementation(appCompat("appcompat-v7"))
   implementation(appCompat("design"))
   implementation(appCompat("customtabs"))
   implementation(appCompat("cardview-v7"))
   implementation(appCompat("recyclerview-v7"))
-  implementation("com.android.support.constraint:constraint-layout:1.0.2")
+  implementation(appCompat("constraint", "constraint-layout", version = "1.0.2"))
 
-  implementation("com.github.StephenVinouze.AdvancedRecyclerView:core:1.1.6")
+  implementation("com.github.StephenVinouze.AdvancedRecyclerView", "core", "1.1.6")
 
-  implementation("io.reactivex.rxjava2:rxjava:2.1.3")
+  implementation("io.reactivex.rxjava2", "rxjava", "2.1.3")
 
   implementation(appArch("lifecycle", "extensions"))
   implementation(appArch("lifecycle", "reactivestreams"))
@@ -115,21 +115,25 @@ dependencies {
     exclude(group = "junit", module = "junit")
   }
 
-  implementation("org.threeten:threetenbp:1.3.6")
+  implementation("org.threeten", "threetenbp", "1.3.6", classifier = "no-tzdb")
 
-  implementation("com.jakewharton:butterknife:8.8.1")
-  kapt("com.jakewharton:butterknife-compiler:8.8.1")
+  implementation("com.jakewharton", "butterknife", "8.8.1")
+  kapt("com.jakewharton", "butterknife-compiler", "8.8.1")
 
-  implementation(project(":lib"))
+  implementation(project(":lib")) {
+    exclude(group = "org.threeten", module = "threetenbp")
+  }
   implementation(project(":malheur"))
 
+  debugImplementation("com.squareup.leakcanary", "leakcanary-android", "1.5.1")
+
   testImplementation(appArch("persistence.room", "testing"))
-  testImplementation("junit:junit:4.12")
+  testImplementation("junit", "junit", "4.12")
 
-  androidTestImplementation("com.android.support.test:runner:1.0.1")
-  androidTestImplementation("com.android.support.test:rules:1.0.1")
+  androidTestImplementation("com.android.support.test", "runner", "1.0.1")
+  androidTestImplementation("com.android.support.test", "rules", "1.0.1")
 
-  androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.1")
+  androidTestImplementation("com.android.support.test.espresso", "espresso-core", "3.0.1")
 }
 
 tasks.withType(KotlinCompile::class.java) {
@@ -168,8 +172,12 @@ fun Project.properties(fileName: String): Properties? {
  * @param module simple name of the AppCompat module, for example "cardview-v7".
  * @param version optional desired version, null implies [appCompatVersion].
  */
-fun appCompat(module: String, version: String? = null)
-  = "com.android.support:$module:${version ?: appCompatVersion}"
+fun appCompat(module: String, submodule: String? = null, version: String? = null)
+  = if (submodule != null) {
+  "com.android.support.$module:$submodule:${version ?: appCompatVersion}"
+} else {
+  "com.android.support:$module:${version ?: appCompatVersion}"
+}
 
 /**
  * Builds the dependency notation for the named AppArch [module] at the given [version].
diff --git a/app/src/main/assets/org/threeten/bp/TZDB.dat b/app/src/main/assets/org/threeten/bp/TZDB.dat
new file mode 100644
index 0000000000000000000000000000000000000000..5677059dc8b992b60ae88f75a82f14e80a1cbf70
Binary files /dev/null and b/app/src/main/assets/org/threeten/bp/TZDB.dat differ
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
index f03aecc1fa6e3e7f99ea3cb73a5eb69efc5c4548..6395cd02568f858bba7c46edab6c0197f8ff6da1 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasseldroidNG.kt
@@ -5,7 +5,9 @@ import android.content.pm.ShortcutInfo
 import android.content.pm.ShortcutManager
 import android.graphics.drawable.Icon
 import android.os.Build
+import com.squareup.leakcanary.LeakCanary
 import de.kuschku.malheur.CrashHandler
+import de.kuschku.quasseldroid_ng.util.backport.AndroidThreeTenBackport
 import de.kuschku.quasseldroid_ng.util.compatibility.AndroidCompatibilityUtils
 import de.kuschku.quasseldroid_ng.util.compatibility.AndroidLoggingHandler
 import de.kuschku.quasseldroid_ng.util.compatibility.AndroidStreamChannelFactory
@@ -13,7 +15,12 @@ import de.kuschku.quasseldroid_ng.util.helper.systemService
 
 class QuasseldroidNG : Application() {
   override fun onCreate() {
-    println("QuasseldroidNG::onCreate")
+    if (LeakCanary.isInAnalyzerProcess(this)) {
+      // This process is dedicated to LeakCanary for heap analysis.
+      // You should not init your app in this process.
+      return
+    }
+    LeakCanary.install(this)
 
     CrashHandler.init(
       application = this,
@@ -26,6 +33,8 @@ class QuasseldroidNG : Application() {
     AndroidLoggingHandler.inject()
     AndroidStreamChannelFactory.inject()
 
+    AndroidThreeTenBackport.init(this)
+
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
       systemService<ShortcutManager>().dynamicShortcuts = listOf(
         ShortcutInfo.Builder(this, "id1")
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
index 89d9c7a8e09c5ffdb3422dc9822f0fb5d9cf3ab6..205530597579b0935401da396200fabbd4236ceb 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/persistence/QuasselDatabase.kt
@@ -4,6 +4,7 @@ import android.arch.paging.LivePagedListProvider
 import android.arch.persistence.room.*
 import android.content.Context
 import android.support.annotation.IntRange
+import android.support.v7.recyclerview.extensions.DiffCallback
 import de.kuschku.libquassel.protocol.Message_Flag
 import de.kuschku.libquassel.protocol.Message_Type
 import org.threeten.bp.Instant
@@ -37,6 +38,14 @@ abstract class QuasselDatabase : RoomDatabase() {
         type)}, flag=${Message_Flag.of(
         flag)}, bufferId=$bufferId, sender='$sender', senderPrefixes='$senderPrefixes', content='$content')"
     }
+
+    object MessageDiffCallback : DiffCallback<DatabaseMessage>() {
+      override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
+        = oldItem == newItem
+
+      override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
+        = oldItem.messageId == newItem.messageId
+    }
   }
 
   @Dao
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
index bcb8e99ed04b6f9f9a085921c12cab136f20d35e..5867f9f7418b3aa96bc1a8704573b54885ef6023 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigFragment.kt
@@ -92,7 +92,7 @@ class BufferViewConfigFragment : ServiceBoundFragment() {
     chatListSpinner.adapter = adapter
     chatListSpinner.onItemSelectedListener = itemSelectedListener
 
-    chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post, activity::runOnUiThread, clickListener)
+    chatList.adapter = BufferListAdapter(this, bufferList, handlerThread::post, activity!!::runOnUiThread, clickListener)
     chatList.layoutManager = LinearLayoutManager(context)
     chatList.itemAnimator = DefaultItemAnimator()
     return view
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
index 17638610cfceeb6839614da6bd6bfac630ac3776..9b40316e0e8eed4a658023e8512ccbdd2589bec8 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
@@ -62,7 +62,6 @@ class ChatActivity : ServiceBoundActivity() {
   private lateinit var database: QuasselDatabase
 
   override fun onCreate(savedInstanceState: Bundle?) {
-    println("ChatActivity::onCreate")
     handler.onCreate()
     super.onCreate(savedInstanceState)
     setContentView(R.layout.activity_main)
@@ -128,7 +127,7 @@ class ChatActivity : ServiceBoundActivity() {
       val status = it ?: ConnectionState.DISCONNECTED
 
       snackbar?.dismiss()
-      snackbar = Snackbar.make(window.decorView.rootView, status.name, Snackbar.LENGTH_SHORT)
+      snackbar = Snackbar.make(findViewById(R.id.contentMessages), status.name, Snackbar.LENGTH_SHORT)
       snackbar?.show()
     })
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7422ad5079b8e760a1f6862a2f31304a0bd44911
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageAdapter.kt
@@ -0,0 +1,36 @@
+package de.kuschku.quasseldroid_ng.ui.chat
+
+import android.arch.paging.PagedListAdapter
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import de.kuschku.libquassel.protocol.Message_Flags
+import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+
+class MessageAdapter(context: Context) : PagedListAdapter<QuasselDatabase.DatabaseMessage, QuasselMessageViewHolder>(QuasselDatabase.DatabaseMessage.MessageDiffCallback) {
+  val messageRenderer: MessageRenderer = QuasselMessageRenderer(context)
+
+  override fun onBindViewHolder(holder: QuasselMessageViewHolder, position: Int) {
+    getItem(position)?.let { messageRenderer.bind(holder, it) }
+  }
+
+  override fun getItemViewType(position: Int): Int {
+    return getItem(position)?.type ?: 0
+  }
+
+  private fun messageType(viewType: Int): Message_Type?
+    = Message_Type.of(viewType).enabledValues().firstOrNull()
+
+  private fun messageFlags(viewType: Int): Message_Flags
+    = Message_Flags.of()
+
+  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QuasselMessageViewHolder {
+    return QuasselMessageViewHolder(LayoutInflater.from(parent.context).inflate(
+      messageRenderer.layout(messageType(viewType), messageFlags(viewType)),
+      parent,
+      false
+    ))
+  }
+}
+
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
index 3e98bb186f298b1b7272ad92fe01703b18134b5c..7007b536ff5b4f106bf3538d9c6b35412395c82b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageListFragment.kt
@@ -4,16 +4,13 @@ import android.arch.lifecycle.LiveData
 import android.arch.lifecycle.MutableLiveData
 import android.arch.lifecycle.Observer
 import android.arch.paging.PagedList
-import android.arch.paging.PagedListAdapter
 import android.os.Bundle
-import android.support.v7.recyclerview.extensions.DiffCallback
 import android.support.v7.widget.DefaultItemAnimator
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
-import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
 import de.kuschku.libquassel.protocol.BufferId
@@ -27,8 +24,6 @@ class MessageListFragment : ServiceBoundFragment() {
 
   private lateinit var database: QuasselDatabase
 
-  private val adapter = MessageAdapter()
-
   @BindView(R.id.messageList)
   lateinit var messageList: RecyclerView
 
@@ -36,7 +31,7 @@ class MessageListFragment : ServiceBoundFragment() {
     val view = inflater.inflate(R.layout.content_messages, container, false)
     ButterKnife.bind(this, view)
 
-    database = QuasselDatabase.Creator.init(context.applicationContext)
+    database = QuasselDatabase.Creator.init(context!!.applicationContext)
     val data = currentBuffer.switchMap {
       it.switchMap {
         database.message().findByBufferIdPaged(it).create(Int.MAX_VALUE,
@@ -49,6 +44,8 @@ class MessageListFragment : ServiceBoundFragment() {
       }
     }
 
+    val adapter = MessageAdapter(context!!)
+
     data.observe(this, Observer { list ->
       adapter.setList(list)
     })
@@ -59,33 +56,4 @@ class MessageListFragment : ServiceBoundFragment() {
 
     return view
   }
-}
-
-class MessageAdapter : PagedListAdapter<QuasselDatabase.DatabaseMessage, MessageViewHolder>(MessageDiffCallback) {
-  override fun onBindViewHolder(holder: MessageViewHolder?, position: Int) {
-    holder?.bind(getItem(position))
-  }
-
-  override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MessageViewHolder {
-    return MessageViewHolder(LayoutInflater.from(parent?.context).inflate(android.R.layout.simple_list_item_1, parent, false))
-  }
-}
-
-object MessageDiffCallback : DiffCallback<QuasselDatabase.DatabaseMessage>() {
-  override fun areContentsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
-    = oldItem == newItem
-
-  override fun areItemsTheSame(oldItem: QuasselDatabase.DatabaseMessage, newItem: QuasselDatabase.DatabaseMessage)
-    = oldItem.messageId == newItem.messageId
-}
-
-class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
-  fun bind(message: QuasselDatabase.DatabaseMessage?) {
-    val text = (itemView as TextView)
-    if (message == null) {
-      text.text = "null"
-    } else {
-      text.text = "[${message.time}] <${message.senderPrefixes}${message.sender}> ${message.content}"
-    }
-  }
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4ca12c9faffb9d73cdfc7115d9b6f91af702bf97
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MessageRenderer.kt
@@ -0,0 +1,13 @@
+package de.kuschku.quasseldroid_ng.ui.chat
+
+import android.support.annotation.LayoutRes
+import de.kuschku.libquassel.protocol.Message_Flags
+import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+
+interface MessageRenderer {
+  @LayoutRes
+  fun layout(type: Message_Type?, flags: Message_Flags): Int
+
+  fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage)
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt
new file mode 100644
index 0000000000000000000000000000000000000000..751e67e353f08a6349fcb8772529cfccc036d81b
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageRenderer.kt
@@ -0,0 +1,74 @@
+package de.kuschku.quasseldroid_ng.ui.chat
+
+import android.content.Context
+import android.graphics.Typeface
+import android.text.SpannableString
+import android.text.format.DateFormat
+import android.text.style.ForegroundColorSpan
+import android.text.style.StyleSpan
+import de.kuschku.libquassel.protocol.Message.MessageType.*
+import de.kuschku.libquassel.protocol.Message_Flags
+import de.kuschku.libquassel.protocol.Message_Type
+import de.kuschku.quasseldroid_ng.R
+import de.kuschku.quasseldroid_ng.persistence.QuasselDatabase
+import de.kuschku.quasseldroid_ng.util.quassel.IrcUserUtils
+import de.kuschku.quasseldroid_ng.util.ui.SpanFormatter
+import org.threeten.bp.ZoneId
+import org.threeten.bp.format.DateTimeFormatter
+import java.text.SimpleDateFormat
+
+class QuasselMessageRenderer(context: Context) : MessageRenderer {
+  val timeFormatter = DateTimeFormatter.ofPattern((DateFormat.getTimeFormat(context) as SimpleDateFormat).toLocalizedPattern())
+  val senderColors: IntArray
+
+  init {
+    val typedArray = context.obtainStyledAttributes(intArrayOf(
+      R.attr.senderColor0, R.attr.senderColor1, R.attr.senderColor2, R.attr.senderColor3,
+      R.attr.senderColor4, R.attr.senderColor5, R.attr.senderColor6, R.attr.senderColor7,
+      R.attr.senderColor8, R.attr.senderColor9, R.attr.senderColorA, R.attr.senderColorB,
+      R.attr.senderColorC, R.attr.senderColorD, R.attr.senderColorE, R.attr.senderColorF
+    ))
+    senderColors = IntArray(16) {
+      typedArray.getColor(it, 0)
+    }
+    typedArray.recycle()
+  }
+
+  override fun layout(type: Message_Type?, flags: Message_Flags)
+    = when (type) {
+    Nick, Notice, Mode, Join, Part, Quit, Kick, Kill, Server, Info, DayChange, Topic, NetsplitJoin,
+    NetsplitQuit, Invite -> R.layout.widget_chatmessage_server
+    Error -> R.layout.widget_chatmessage_error
+    Action -> R.layout.widget_chatmessage_action
+    Plain -> R.layout.widget_chatmessage_plain
+    else -> R.layout.widget_chatmessage_plain
+  }
+
+  override fun bind(holder: QuasselMessageViewHolder, message: QuasselDatabase.DatabaseMessage) {
+    holder.time.text = timeFormatter.format(message.time.atZone(ZoneId.systemDefault()))
+    holder.content.text = SpanFormatter.format(
+      "%s: %s",
+      formatNick(message.sender),
+      message.content
+    )
+  }
+
+  private fun formatNick(sender: String): CharSequence {
+    val nick = IrcUserUtils.nick(sender)
+    val senderColor = IrcUserUtils.senderColor(nick)
+    val spannableString = SpannableString(nick)
+    spannableString.setSpan(
+      ForegroundColorSpan(senderColors[senderColor % senderColors.size]),
+      0,
+      nick.length,
+      SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
+    )
+    spannableString.setSpan(
+      StyleSpan(Typeface.BOLD),
+      0,
+      nick.length,
+      SpannableString.SPAN_INCLUSIVE_EXCLUSIVE
+    )
+    return spannableString
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cf54d42c9a66edf32cdc43eee9178c64b1ee5aa4
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/QuasselMessageViewHolder.kt
@@ -0,0 +1,22 @@
+package de.kuschku.quasseldroid_ng.ui.chat
+
+import android.support.v7.widget.RecyclerView
+import android.text.method.LinkMovementMethod
+import android.view.View
+import android.widget.TextView
+import butterknife.BindView
+import butterknife.ButterKnife
+import de.kuschku.quasseldroid_ng.R
+
+class QuasselMessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+  @BindView(R.id.time)
+  lateinit var time: TextView
+
+  @BindView(R.id.content)
+  lateinit var content: TextView
+
+  init {
+    ButterKnife.bind(this, itemView)
+    content.movementMethod = LinkMovementMethod.getInstance()
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
index 5465c59950e162e1edffc5708fe05d9d2614c3d1..9a943d1eb390accff12ecd4d879a870a6e853460 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/SetupActivity.kt
@@ -160,13 +160,13 @@ abstract class SetupActivity : AppCompatActivity() {
       list.add(fragment)
     }
 
-    override fun instantiateItem(container: ViewGroup?, position: Int): Any {
+    override fun instantiateItem(container: ViewGroup, position: Int): Any {
       val fragment = super.instantiateItem(container, position)
       storeNewFragment(position, fragment as SlideFragment)
       return fragment
     }
 
-    override fun destroyItem(container: ViewGroup?, position: Int, `object`: Any?) {
+    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
       retainedFragments.get(position)?.getData(result)
       retainedFragments.remove(position)
       super.destroyItem(container, position, `object`)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt
index ec5e12e1cc664ea2d12ebfffb2c1920e0655690c..cb6891877f190de016e533effed7cfb945ef35d5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionActivity.kt
@@ -8,7 +8,7 @@ import android.os.Bundle
 import de.kuschku.quasseldroid_ng.Keys
 import de.kuschku.quasseldroid_ng.ui.chat.ChatActivity
 import de.kuschku.quasseldroid_ng.ui.setup.SetupActivity
-import de.kuschku.quasseldroid_ng.util.helper.editCommit
+import de.kuschku.quasseldroid_ng.util.helper.editApply
 
 class AccountSelectionActivity : SetupActivity() {
   companion object {
@@ -23,7 +23,7 @@ class AccountSelectionActivity : SetupActivity() {
 
   private lateinit var statusPreferences: SharedPreferences
   override fun onDone(data: Bundle) {
-    statusPreferences.editCommit {
+    statusPreferences.editApply {
       putLong(Keys.Status.selectedAccount, data.getLong(Keys.Status.selectedAccount, -1))
       putBoolean(Keys.Status.reconnect, true)
     }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt
index a0198fb1a170477e261631d23d5526e75c8d1f4a..86b30513313eeac312f5dece8c2b0ac59ca9fd88 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/setup/accounts/AccountSelectionSlide.kt
@@ -76,7 +76,7 @@ class AccountSelectionSlide : SlideFragment() {
   override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
     super.onActivityResult(requestCode, resultCode, data)
     if (requestCode == REQUEST_CREATE_FIRST && resultCode == Activity.RESULT_CANCELED) {
-      activity.finish()
+      activity?.finish()
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt
new file mode 100644
index 0000000000000000000000000000000000000000..04019a0f0942c1c90ca1305b541570f15f22514e
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/backport/AndroidThreeTenBackport.kt
@@ -0,0 +1,38 @@
+package de.kuschku.quasseldroid_ng.util.backport
+
+import android.content.Context
+import org.threeten.bp.zone.TzdbZoneRulesProvider
+import org.threeten.bp.zone.ZoneRulesProvider
+import java.io.IOException
+import java.io.InputStream
+import java.util.concurrent.atomic.AtomicBoolean
+
+
+object AndroidThreeTenBackport {
+  private val initialized = AtomicBoolean()
+
+  fun init(context: Context) {
+    if (initialized.getAndSet(true)) {
+      return
+    }
+
+    val provider: TzdbZoneRulesProvider
+    var inputStream: InputStream? = null
+    try {
+      inputStream = context.assets.open("org/threeten/bp/TZDB.dat")
+      provider = TzdbZoneRulesProvider(inputStream)
+    } catch (e: IOException) {
+      throw IllegalStateException("TZDB.dat missing from assets.", e)
+    } finally {
+      if (inputStream != null) {
+        try {
+          inputStream.close()
+        } catch (ignored: IOException) {
+        }
+
+      }
+    }
+
+    ZoneRulesProvider.registerProvider(provider)
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9a734a320aa2e1e0965175e9bda5962ed5597ed1
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/CRCUtils.kt
@@ -0,0 +1,44 @@
+package de.kuschku.quasseldroid_ng.util.quassel
+
+object CRCUtils {
+  fun qChecksum(data: ByteArray): Int {
+    var crc = 0xffff
+    val crcHighBitMask = 0x8000
+
+    for (b in data) {
+      val c = reflect(b.toInt(), 8)
+      var j = 0x80
+      while (j > 0) {
+        var highBit = crc and crcHighBitMask
+        crc = crc shl 1
+        if (c and j > 0) {
+          highBit = highBit xor crcHighBitMask
+        }
+        if (highBit > 0) {
+          crc = crc xor 0x1021
+        }
+        j = j shr 1
+      }
+    }
+
+    crc = reflect(crc, 16)
+    crc = crc xor 0xffff
+    crc = crc and 0xffff
+
+    return crc
+  }
+
+  private fun reflect(crc: Int, n: Int): Int {
+    var j = 1
+    var crcout = 0
+    var i = 1 shl n - 1
+    while (i > 0) {
+      if (crc and i > 0) {
+        crcout = crcout or j
+      }
+      j = j shl 1
+      i = i shr 1
+    }
+    return crcout
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..f9a348d9bff80e07ebd5feaf2be9da92bcc0cc06
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/quassel/IrcUserUtils.kt
@@ -0,0 +1,53 @@
+package de.kuschku.quasseldroid_ng.util.quassel
+
+import java.util.*
+
+object IrcUserUtils {
+  fun senderColor(nick: String): Int {
+    return 0xf and CRCUtils.qChecksum(
+      nick.trimEnd('_').toLowerCase(Locale.US).toByteArray(Charsets.ISO_8859_1)
+    )
+  }
+
+  fun nick(hostmask: String): String {
+    return hostmask.substring(
+      0,
+      hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: hostmask.length
+    )
+  }
+
+  fun user(hostmask: String): String {
+    return hostmask.substring(
+      hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: 0,
+      hostmask.lastIndex('@') ?: hostmask.length
+    )
+  }
+
+  fun host(hostmask: String): String {
+    return hostmask.substring(
+      hostmask.lastIndex('@') ?: 0
+    )
+  }
+
+  fun mask(hostmask: String): String {
+    return hostmask.substring(
+      hostmask.lastIndex('!', hostmask.lastIndex('@')) ?: 0
+    )
+  }
+
+  private fun String.firstIndex(char: Char, startIndex: Int? = null, ignoreCase: Boolean = false): Int? {
+    val lastIndex = indexOf(char, startIndex ?: 0, ignoreCase)
+    if (lastIndex < 0)
+      return null
+    else
+      return lastIndex
+  }
+
+  private fun String.lastIndex(char: Char, startIndex: Int? = null, ignoreCase: Boolean = false): Int? {
+    val lastIndex = lastIndexOf(char, startIndex ?: lastIndex, ignoreCase)
+    if (lastIndex < 0)
+      return null
+    else
+      return lastIndex
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6dff17f6a0658cc7a35a67884aae5800da4913fb
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/BackendServiceConnection.kt
@@ -0,0 +1,52 @@
+package de.kuschku.quasseldroid_ng.util.service
+
+import android.arch.lifecycle.MutableLiveData
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.IBinder
+import de.kuschku.libquassel.session.Backend
+import de.kuschku.quasseldroid_ng.service.QuasselService
+
+class BackendServiceConnection : ServiceConnection {
+  var bound = false
+  val backend = MutableLiveData<Backend?>()
+
+  var context: Context? = null
+
+  override fun onServiceDisconnected(component: ComponentName?) {
+    bound = false
+    when (component) {
+      ComponentName(context, QuasselService::class.java) -> {
+        backend.value = null
+      }
+    }
+  }
+
+  override fun onServiceConnected(component: ComponentName?, binder: IBinder?) {
+    bound = true
+    when (component) {
+      ComponentName(context, QuasselService::class.java) ->
+        if (binder is QuasselService.QuasselBinder) {
+          backend.value = binder.backend
+        }
+    }
+  }
+
+  fun start(intent: Intent = Intent(context, QuasselService::class.java)) {
+    context?.startService(intent)
+  }
+
+  fun bind(intent: Intent = Intent(context, QuasselService::class.java), flags: Int = 0) {
+    context?.bindService(intent, this, flags)
+  }
+
+  fun stop(intent: Intent = Intent(context, QuasselService::class.java)) {
+    context?.stopService(intent)
+  }
+
+  fun unbind() {
+    if (bound) context?.unbindService(this)
+  }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
index 9c8598786de164fedea30630a279ef0864e30b46..ef9518e587f9c76478494ed742ebefb0943da03d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundActivity.kt
@@ -1,49 +1,30 @@
 package de.kuschku.quasseldroid_ng.util.service
 
-import android.arch.lifecycle.MutableLiveData
-import android.content.ComponentName
-import android.content.Intent
-import android.content.ServiceConnection
+import android.arch.lifecycle.LiveData
 import android.os.Bundle
-import android.os.IBinder
 import android.support.annotation.ColorRes
 import android.support.annotation.DrawableRes
 import android.support.v7.app.AppCompatActivity
 import de.kuschku.libquassel.session.Backend
 import de.kuschku.quasseldroid_ng.R
-import de.kuschku.quasseldroid_ng.service.QuasselService
 import de.kuschku.quasseldroid_ng.util.helper.updateRecentsHeaderIfExisting
 
 abstract class ServiceBoundActivity : AppCompatActivity() {
-  protected val backend = MutableLiveData<Backend?>()
   @DrawableRes
   protected val icon: Int = R.mipmap.ic_launcher
   @ColorRes
   protected val recentsHeaderColor: Int = R.color.colorPrimaryDark
 
-  private val connection = object : ServiceConnection {
-    override fun onServiceDisconnected(component: ComponentName?) {
-      when (component) {
-        ComponentName(application, QuasselService::class.java) -> {
-          backend.value = null
-        }
-      }
-    }
+  private val connection = BackendServiceConnection()
 
-    override fun onServiceConnected(component: ComponentName?, binder: IBinder?) {
-      when (component) {
-        ComponentName(application, QuasselService::class.java) ->
-          if (binder is QuasselService.QuasselBinder) {
-            backend.value = binder.backend
-          }
-      }
-    }
-  }
+  val backend: LiveData<Backend?>
+    get() = connection.backend
 
   override fun onCreate(savedInstanceState: Bundle?) {
+    connection.context = this
     setTheme(R.style.Theme_ChatTheme_Quassel_Light)
     super.onCreate(savedInstanceState)
-    startService(Intent(this, QuasselService::class.java))
+    connection.start()
     updateRecentsHeader()
   }
 
@@ -56,17 +37,17 @@ abstract class ServiceBoundActivity : AppCompatActivity() {
   }
 
   override fun onStart() {
-    bindService(Intent(this, QuasselService::class.java), connection, 0)
+    connection.bind()
     super.onStart()
   }
 
   override fun onStop() {
     super.onStop()
-    unbindService(connection)
+    connection.unbind()
   }
 
   protected fun stopService() {
-    unbindService(connection)
-    stopService(Intent(this, QuasselService::class.java))
+    connection.unbind()
+    connection.stop()
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt
index 87c260f11ad0bbbb6047a1306d7c26acc1bb1269..6d42d72272571339f6295c978227b2800fd4263a 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/service/ServiceBoundFragment.kt
@@ -1,49 +1,29 @@
 package de.kuschku.quasseldroid_ng.util.service
 
-import android.arch.lifecycle.MutableLiveData
-import android.content.ComponentName
-import android.content.Intent
-import android.content.ServiceConnection
+import android.arch.lifecycle.LiveData
 import android.os.Bundle
-import android.os.IBinder
 import android.support.v4.app.Fragment
 import de.kuschku.libquassel.session.Backend
-import de.kuschku.quasseldroid_ng.service.QuasselService
 
 abstract class ServiceBoundFragment : Fragment() {
-  protected val backend = MutableLiveData<Backend?>()
+  private var connection = BackendServiceConnection()
 
-  private val connection = object : ServiceConnection {
-    override fun onServiceDisconnected(component: ComponentName?) {
-      when (component) {
-        ComponentName(context.applicationContext, QuasselService::class.java) -> {
-          backend.value = null
-        }
-      }
-    }
-
-    override fun onServiceConnected(component: ComponentName?, binder: IBinder?) {
-      when (component) {
-        ComponentName(context.applicationContext, QuasselService::class.java) ->
-          if (binder is QuasselService.QuasselBinder) {
-            backend.value = binder.backend
-          }
-      }
-    }
-  }
+  val backend: LiveData<Backend?>
+    get() = connection.backend
 
   override fun onCreate(savedInstanceState: Bundle?) {
+    connection.context = context
     super.onCreate(savedInstanceState)
-    context.startService(Intent(context, QuasselService::class.java))
+    connection.start()
   }
 
   override fun onStart() {
-    context.bindService(Intent(context, QuasselService::class.java), connection, 0)
+    connection.bind()
     super.onStart()
   }
 
   override fun onStop() {
     super.onStop()
-    context.unbindService(connection)
+    connection.unbind()
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java
new file mode 100644
index 0000000000000000000000000000000000000000..31f342ec75649868892b4b6cc928c25fe3a3e590
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ui/SpanFormatter.java
@@ -0,0 +1,140 @@
+/*
+ * QuasselDroid - Quassel client for Android
+ * Copyright (C) 2016 Janne Koschinski
+ * Copyright (C) 2016 Ken Børge Viktil
+ * Copyright (C) 2016 Magnus Fjell
+ * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+* Copyright © 2014 George T. Steel
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package de.kuschku.quasseldroid_ng.util.ui;
+
+import android.support.annotation.NonNull;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.SpannedString;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Provides {@link String#format} style functions that work with {@link Spanned} strings and preserve formatting.
+ *
+ * @author George T. Steel
+ */
+public class SpanFormatter {
+    private static final Pattern FORMAT_SEQUENCE = Pattern.compile("%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])");
+
+    private SpanFormatter() {
+    }
+
+    /**
+     * Version of {@link String#format(String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting.
+     * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved.
+     * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result.
+     * Any duplicates will appear as text only.
+     *
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args   the list of arguments passed to the formatter. If there are
+     *               more arguments than required by {@code format},
+     *               additional arguments are ignored.
+     * @return the formatted string (with spans).
+     */
+    @NonNull
+    public static SpannedString format(@NonNull CharSequence format, Object... args) {
+        return format(Locale.getDefault(), format, args);
+    }
+
+    /**
+     * Version of {@link String#format(Locale, String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting.
+     * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved.
+     * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result.
+     * Any duplicates will appear as text only.
+     *
+     * @param locale the locale to apply; {@code null} value means no localization.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args   the list of arguments passed to the formatter.
+     * @return the formatted string (with spans).
+     * @see String#format(Locale, String, Object...)
+     */
+    @NonNull
+    public static SpannedString format(@NonNull Locale locale, @NonNull CharSequence format, Object... args) {
+        SpannableStringBuilder out = new SpannableStringBuilder(format);
+
+        int i = 0;
+        int argAt = -1;
+
+        while (i < out.length()) {
+            Matcher m = FORMAT_SEQUENCE.matcher(out);
+            if (!m.find(i)) break;
+            i = m.start();
+            int exprEnd = m.end();
+
+            String argTerm = m.group(1);
+            String modTerm = m.group(2);
+            String typeTerm = m.group(3);
+
+            CharSequence cookedArg;
+
+            if (typeTerm.equals("%")) {
+                cookedArg = "%";
+            } else {
+                int argIdx;
+                switch (argTerm) {
+                    case "":
+                        argIdx = ++argAt;
+                        break;
+                    case "<":
+                        argIdx = argAt;
+                        break;
+                    default:
+                        argIdx = Integer.parseInt(argTerm.substring(0, argTerm.length() - 1)) - 1;
+                        break;
+                }
+
+                Object argItem = args[argIdx];
+
+                if (typeTerm.equals("s") && argItem instanceof Spanned) {
+                    cookedArg = (Spanned) argItem;
+                } else {
+                    cookedArg = String.format(locale, "%" + modTerm + typeTerm, argItem);
+                }
+            }
+
+            out.replace(i, exprEnd, cookedArg);
+            i += cookedArg.length();
+        }
+
+        return new SpannedString(out);
+    }
+}
diff --git a/app/src/main/res/layout/widget_chatmessage_action.xml b/app/src/main/res/layout/widget_chatmessage_action.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b4271dd33221a05181e3973922f9022c1a81fab3
--- /dev/null
+++ b/app/src/main/res/layout/widget_chatmessage_action.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ QuasselDroid - Quassel client for Android
+  ~ Copyright (C) 2016 Janne Koschinski
+  ~ Copyright (C) 2016 Ken Børge Viktil
+  ~ Copyright (C) 2016 Magnus Fjell
+  ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify it
+  ~ under the terms of the GNU General Public License as published by the Free
+  ~ Software Foundation, either version 3 of the License, or (at your option)
+  ~ any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along
+  ~ with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="top"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/message_vertical"
+    android:paddingEnd="@dimen/message_horizontal"
+    android:paddingLeft="@dimen/message_horizontal"
+    android:paddingRight="@dimen/message_horizontal"
+    android:paddingStart="@dimen/message_horizontal"
+    android:paddingTop="@dimen/message_vertical"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall">
+
+    <TextView
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/message_horizontal"
+        android:layout_marginRight="@dimen/message_horizontal"
+        android:textColor="?attr/colorForegroundSecondary"
+        android:typeface="monospace" />
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textColor="?attr/colorForegroundAction"
+        android:textIsSelectable="true"
+        android:textStyle="italic" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_chatmessage_error.xml b/app/src/main/res/layout/widget_chatmessage_error.xml
new file mode 100644
index 0000000000000000000000000000000000000000..44e42f1cdb0b6377b64c206663f679b84e05e92c
--- /dev/null
+++ b/app/src/main/res/layout/widget_chatmessage_error.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/colorBackgroundSecondary"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="top"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/message_vertical"
+    android:paddingEnd="@dimen/message_horizontal"
+    android:paddingLeft="@dimen/message_horizontal"
+    android:paddingRight="@dimen/message_horizontal"
+    android:paddingStart="@dimen/message_horizontal"
+    android:paddingTop="@dimen/message_vertical"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall">
+
+    <TextView
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/message_horizontal"
+        android:layout_marginRight="@dimen/message_horizontal"
+        android:textColor="?attr/colorForegroundSecondary"
+        android:typeface="monospace" />
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textColor="?attr/colorForegroundError"
+        android:textIsSelectable="true"
+        android:textStyle="italic" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_chatmessage_plain.xml b/app/src/main/res/layout/widget_chatmessage_plain.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e16b66a13b3b8023fdc9bffba49babe9d03308c
--- /dev/null
+++ b/app/src/main/res/layout/widget_chatmessage_plain.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="top"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/message_vertical"
+    android:paddingEnd="@dimen/message_horizontal"
+    android:paddingLeft="@dimen/message_horizontal"
+    android:paddingRight="@dimen/message_horizontal"
+    android:paddingStart="@dimen/message_horizontal"
+    android:paddingTop="@dimen/message_vertical"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall">
+
+    <TextView
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/message_horizontal"
+        android:layout_marginRight="@dimen/message_horizontal"
+        android:textColor="?attr/colorForegroundSecondary"
+        android:typeface="monospace" />
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textColor="?attr/colorForeground"
+        android:textIsSelectable="true" />
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_chatmessage_server.xml b/app/src/main/res/layout/widget_chatmessage_server.xml
new file mode 100644
index 0000000000000000000000000000000000000000..40613e00c91fb0cb192e2d0ea4c613ad70c6ed30
--- /dev/null
+++ b/app/src/main/res/layout/widget_chatmessage_server.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/colorBackgroundSecondary"
+    android:clickable="true"
+    android:focusable="true"
+    android:gravity="top"
+    android:orientation="horizontal"
+    android:paddingBottom="@dimen/message_vertical"
+    android:paddingEnd="@dimen/message_horizontal"
+    android:paddingLeft="@dimen/message_horizontal"
+    android:paddingRight="@dimen/message_horizontal"
+    android:paddingStart="@dimen/message_horizontal"
+    android:paddingTop="@dimen/message_vertical"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall">
+
+    <TextView
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/message_horizontal"
+        android:layout_marginRight="@dimen/message_horizontal"
+        android:textColor="?attr/colorForegroundSecondary"
+        android:typeface="monospace" />
+
+    <TextView
+        android:id="@+id/content"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:textColor="?attr/colorForegroundSecondary"
+        android:textIsSelectable="true"
+        android:textStyle="italic" />
+</LinearLayout>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 4b085309263b5803199bd1e9b8e414b5185a313c..dd9f8a6b1b62f1f62373afc32a44a902a4490c33 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,3 +1,6 @@
 <resources>
-  <dimen name="navigation_drawer_max_width">320dp</dimen>
+    <dimen name="navigation_drawer_max_width">320dp</dimen>
+
+    <dimen name="message_horizontal">8dp</dimen>
+    <dimen name="message_vertical">2dp</dimen>
 </resources>
diff --git a/build.gradle.kts b/build.gradle.kts
index 98bb71717474b50f7acc652588459e641a24d567..b5ff130acd1899c464feb09dd105c80c449b321a 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,8 +5,8 @@ buildscript {
   }
   dependencies {
     classpath("com.android.tools.build:gradle:3.0.1")
-    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.61")
-    classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.1.61")
+    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.0")
+    classpath("org.jetbrains.kotlin:kotlin-android-extensions:1.2.0")
   }
 }
 
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
index c39ec3973c9563cc4c82e70b456fcdc9b98a4b42..0161971e9748fe1809634f4e8fcea0e19b7466e4 100644
--- a/lib/build.gradle.kts
+++ b/lib/build.gradle.kts
@@ -11,10 +11,10 @@ plugins {
 
 val appCompatVersion = "26.1.0"
 dependencies {
-  implementation(kotlin("stdlib", "1.1.61"))
+  implementation(kotlin("stdlib", "1.2.0"))
 
   implementation(appCompat("support-annotations"))
-  implementation("org.threeten:threetenbp:1.3.6")
+  implementation("org.threeten", "threetenbp", "1.3.6")
   implementation("io.reactivex.rxjava2:rxjava:2.1.3")
 
   implementation(project(":invokerannotations"))
diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts
index d1bdc445064a7639f85e74e2460bca97167d728f..38e8818bd602e17a811c4cdebdfd25ca911beaab 100644
--- a/malheur/build.gradle.kts
+++ b/malheur/build.gradle.kts
@@ -1,7 +1,3 @@
-import org.gradle.kotlin.dsl.apply
-import org.gradle.kotlin.dsl.dependencies
-import org.gradle.kotlin.dsl.kotlin
-
 plugins {
   id("com.android.library")
   kotlin("android")
@@ -10,7 +6,7 @@ plugins {
 
 android {
   compileSdkVersion(26)
-  buildToolsVersion("26.0.2")
+  buildToolsVersion("27.0.2")
 
   defaultConfig {
     minSdkVersion(9)
@@ -21,7 +17,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.1.61"))
+  implementation(kotlin("stdlib", "1.2.0"))
 
-  implementation("com.google.code.gson:gson:2.2.4")
+  implementation("com.google.code.gson", "gson", "2.2.4")
 }