From 6ec2a1293a58e601349e39c625d6f107c631a3a2 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Fri, 30 Mar 2018 14:00:12 +0200
Subject: [PATCH] Implement color chooser

---
 app/build.gradle.kts                          |   6 +-
 .../quasseldroid/ui/chat/ChatActivity.kt      |  15 +-
 .../quasseldroid/ui/chat/input/Editor.kt      |  79 +-
 .../ui/chat/input/FormatHandler.kt            | 130 ++-
 .../ui/chat/messages/MessageListFragment.kt   |  33 +-
 .../util/irc/format/IrcFormatDeserializer.kt  |   8 +-
 .../format/spans/IrcBackgroundColorSpan.kt    |  16 +-
 .../format/spans/IrcForegroundColorSpan.kt    |  16 +-
 .../format/spans/IrcHexBackgroundColorSpan.kt |   9 -
 .../format/spans/IrcHexForegroundColorSpan.kt |   9 -
 .../util/ui/ColorChooserDialog.java           | 798 ++++++++++++++++++
 app/src/main/res/drawable/bg_transparent.xml  |   5 +
 .../main/res/layout/dialog_colorchooser.xml   |  19 +
 .../res/layout/dialog_colorchooser_custom.xml | 236 ++++++
 .../layout/dialog_colorchooser_presets.xml    |  14 +
 app/src/main/res/layout/layout_history.xml    |  94 ++-
 app/src/main/res/layout/widget_formatting.xml |   4 +-
 app/src/main/res/values-de/strings.xml        |   6 +-
 app/src/main/res/values-large/dimens.xml      |   4 +
 app/src/main/res/values/dimens.xml            |   2 +
 app/src/main/res/values/strings.xml           |   6 +-
 app/src/main/res/values/themes_base.xml       | 246 +++---
 22 files changed, 1535 insertions(+), 220 deletions(-)
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexBackgroundColorSpan.kt
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexForegroundColorSpan.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid/util/ui/ColorChooserDialog.java
 create mode 100644 app/src/main/res/drawable/bg_transparent.xml
 create mode 100644 app/src/main/res/layout/dialog_colorchooser.xml
 create mode 100644 app/src/main/res/layout/dialog_colorchooser_custom.xml
 create mode 100644 app/src/main/res/layout/dialog_colorchooser_presets.xml
 create mode 100644 app/src/main/res/values-large/dimens.xml

diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fc09650aa..a0d68a412 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -142,8 +142,12 @@ dependencies {
 
   // UI
   implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2")
-  implementation("com.afollestad.material-dialogs", "core", "0.9.6.0")
+  withVersion("0.9.6.0") {
+    implementation("com.afollestad.material-dialogs", "core", version)
+    implementation("com.afollestad.material-dialogs", "commons", version)
+  }
   implementation("me.saket", "better-link-movement-method", "2.1.0")
+  implementation("com.nex3z", "flow-layout", "1.2.2")
   implementation(project(":slidingpanel"))
 
   // Quality Assurance
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
index 42b7c8edd..6a74f3d74 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/ChatActivity.kt
@@ -178,6 +178,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
               .title(R.string.label_error_init)
               .content(Html.fromHtml(it.errorString))
               .neutralText(R.string.label_close)
+              .titleColorAttr(R.attr.colorTextPrimary)
+              .backgroundColorAttr(R.attr.colorBackgroundCard)
+              .contentColorAttr(R.attr.colorTextPrimary)
               .build()
               .show()
           is HandshakeMessage.CoreSetupReject   ->
@@ -185,6 +188,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
               .title(R.string.label_error_setup)
               .content(Html.fromHtml(it.errorString))
               .neutralText(R.string.label_close)
+              .titleColorAttr(R.attr.colorTextPrimary)
+              .backgroundColorAttr(R.attr.colorBackgroundCard)
+              .contentColorAttr(R.attr.colorTextPrimary)
               .build()
               .show()
           is HandshakeMessage.ClientLoginReject ->
@@ -220,6 +226,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
                           backend.value.orNull()?.updateUserDataAndLogin(user, pass)
                         }
                       }
+                      .titleColorAttr(R.attr.colorTextPrimary)
+                      .backgroundColorAttr(R.attr.colorBackgroundCard)
+                      .contentColorAttr(R.attr.colorTextPrimary)
                       .build()
                     dialog.customView?.run {
                       val userField = findViewById<EditText>(R.id.user)
@@ -233,6 +242,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
                   }
                 }
               }
+              .titleColorAttr(R.attr.colorTextPrimary)
+              .backgroundColorAttr(R.attr.colorBackgroundCard)
+              .contentColorAttr(R.attr.colorTextPrimary)
               .build()
               .show()
         }
@@ -355,7 +367,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
             .items(R.array.message_filter_types)
             .itemsIds(flags)
             .itemsCallbackMultiChoice(selectedIndices, { _, _, _ -> false })
-            .positiveText(R.string.label_select_multiple)
+            .positiveText(R.string.label_select)
             .negativeText(R.string.label_cancel)
             .onPositive { dialog, _ ->
               val selected = dialog.selectedIndices ?: emptyArray()
@@ -371,6 +383,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
             }.negativeColorAttr(R.attr.colorTextPrimary)
             .backgroundColorAttr(R.attr.colorBackgroundCard)
             .contentColorAttr(R.attr.colorTextPrimary)
+            .titleColorAttr(R.attr.colorTextPrimary)
             .build()
             .show()
         }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
index 4ecee7ba8..b5b2d0750 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/Editor.kt
@@ -2,6 +2,9 @@ package de.kuschku.quasseldroid.ui.chat.input
 
 import android.arch.lifecycle.LiveData
 import android.arch.lifecycle.Observer
+import android.support.annotation.ColorInt
+import android.support.annotation.StringRes
+import android.support.v4.app.FragmentActivity
 import android.support.v7.app.AppCompatActivity
 import android.support.v7.widget.*
 import android.text.Editable
@@ -16,6 +19,7 @@ import de.kuschku.quasseldroid.settings.AppearanceSettings
 import de.kuschku.quasseldroid.settings.AutoCompleteSettings
 import de.kuschku.quasseldroid.ui.chat.ChatActivity
 import de.kuschku.quasseldroid.util.helper.*
+import de.kuschku.quasseldroid.util.ui.ColorChooserDialog
 import de.kuschku.quasseldroid.util.ui.EditTextSelectionChange
 import de.kuschku.quasseldroid.viewmodel.data.AutoCompleteItem
 import io.reactivex.Observable
@@ -104,9 +108,15 @@ class Editor(
   @BindView(R.id.action_format_foreground)
   lateinit var foregroundButton: View
 
+  @BindView(R.id.action_format_foreground_preview)
+  lateinit var foregroundButtonPreview: View
+
   @BindView(R.id.action_format_background)
   lateinit var backgroundButton: View
 
+  @BindView(R.id.action_format_background_preview)
+  lateinit var backgroundButtonPreview: View
+
   @BindView(R.id.action_format_clear)
   lateinit var clearButton: View
 
@@ -225,12 +235,30 @@ class Editor(
     TooltipCompat.setTooltipText(monospaceButton, monospaceButton.contentDescription)
 
     foregroundButton.setOnClickListener {
-
+      showColorChooser(
+        activity,
+        R.string.label_foreground,
+        formatHandler.foregroundColor(chatline.selection)
+        ?: formatHandler.defaultForegroundColor
+      ) { color ->
+        formatHandler.toggleForeground(chatline.selection, color,
+                                       formatHandler.mircColorMap[color])
+        updateButtons(chatline.selection)
+      }
     }
     TooltipCompat.setTooltipText(foregroundButton, foregroundButton.contentDescription)
 
     backgroundButton.setOnClickListener {
-
+      showColorChooser(
+        activity,
+        R.string.label_background,
+        formatHandler.backgroundColor(chatline.selection)
+        ?: formatHandler.defaultBackgroundColor
+      ) { color ->
+        formatHandler.toggleBackground(chatline.selection, color,
+                                       formatHandler.mircColorMap[color])
+        updateButtons(chatline.selection)
+      }
     }
     TooltipCompat.setTooltipText(backgroundButton, backgroundButton.contentDescription)
 
@@ -262,12 +290,59 @@ class Editor(
     }
   }
 
+  private fun showColorChooser(
+    activity: FragmentActivity, @StringRes title: Int, @ColorInt preselect: Int, f: (Int?) -> Unit
+  ) {
+    var selectedColor: Int? = null
+    ColorChooserDialog.Builder(chatline.context, title)
+      .customColors(intArrayOf(
+        formatHandler.mircColors[0],
+        formatHandler.mircColors[1],
+        formatHandler.mircColors[2],
+        formatHandler.mircColors[3],
+        formatHandler.mircColors[4],
+        formatHandler.mircColors[5],
+        formatHandler.mircColors[6],
+        formatHandler.mircColors[7],
+        formatHandler.mircColors[8],
+        formatHandler.mircColors[9],
+        formatHandler.mircColors[10],
+        formatHandler.mircColors[11],
+        formatHandler.mircColors[12],
+        formatHandler.mircColors[13],
+        formatHandler.mircColors[14],
+        formatHandler.mircColors[15]
+      ), null)
+      .doneButton(R.string.label_select)
+      .cancelButton(R.string.label_reset)
+      .backButton(R.string.label_back)
+      .customButton(R.string.label_colors_custom)
+      .presetsButton(R.string.label_colors_mirc)
+      .preselect(preselect)
+      .dynamicButtonColor(false)
+      .allowUserColorInputAlpha(false)
+      .callback(object : ColorChooserDialog.ColorCallback {
+        override fun onColorSelection(dialog: ColorChooserDialog, color: Int) {
+          selectedColor = color
+        }
+
+        override fun onColorChooserDismissed(dialog: ColorChooserDialog) {
+          f(selectedColor)
+        }
+      })
+      .show(activity)
+  }
+
   fun updateButtons(selection: IntRange) {
     boldButton.isSelected = formatHandler.isBold(selection)
     italicButton.isSelected = formatHandler.isItalic(selection)
     underlineButton.isSelected = formatHandler.isUnderline(selection)
     strikethroughButton.isSelected = formatHandler.isStrikethrough(selection)
     monospaceButton.isSelected = formatHandler.isMonospace(selection)
+    foregroundButtonPreview.setBackgroundColor(formatHandler.foregroundColor(selection)
+                                               ?: formatHandler.defaultForegroundColor)
+    backgroundButtonPreview.setBackgroundColor(formatHandler.backgroundColor(selection)
+                                               ?: formatHandler.defaultBackgroundColor)
   }
 
   fun onStart() {
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt
index f7c0d8547..af1987c58 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/input/FormatHandler.kt
@@ -1,25 +1,60 @@
 package de.kuschku.quasseldroid.ui.chat.input
 
 import android.graphics.Typeface
+import android.support.annotation.ColorInt
 import android.text.Editable
 import android.text.SpannableString
 import android.text.Spanned
-import android.text.style.StrikethroughSpan
-import android.text.style.StyleSpan
-import android.text.style.TypefaceSpan
-import android.text.style.UnderlineSpan
+import android.text.style.*
 import android.widget.EditText
+import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.ui.chat.ChatActivity
-import de.kuschku.quasseldroid.util.helper.lastWordIndices
-import de.kuschku.quasseldroid.util.helper.lineSequence
-import de.kuschku.quasseldroid.util.helper.selection
-import de.kuschku.quasseldroid.util.helper.without
+import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.irc.format.IrcFormatSerializer
 import de.kuschku.quasseldroid.util.irc.format.spans.*
 
 class FormatHandler(
   private val editText: EditText
 ) {
+  val mircColors = editText.context.theme.styledAttributes(
+    R.attr.mircColor00, R.attr.mircColor01, R.attr.mircColor02, R.attr.mircColor03,
+    R.attr.mircColor04, R.attr.mircColor05, R.attr.mircColor06, R.attr.mircColor07,
+    R.attr.mircColor08, R.attr.mircColor09, R.attr.mircColor10, R.attr.mircColor11,
+    R.attr.mircColor12, R.attr.mircColor13, R.attr.mircColor14, R.attr.mircColor15,
+    R.attr.mircColor16, R.attr.mircColor17, R.attr.mircColor18, R.attr.mircColor19,
+    R.attr.mircColor20, R.attr.mircColor21, R.attr.mircColor22, R.attr.mircColor23,
+    R.attr.mircColor24, R.attr.mircColor25, R.attr.mircColor26, R.attr.mircColor27,
+    R.attr.mircColor28, R.attr.mircColor29, R.attr.mircColor30, R.attr.mircColor31,
+    R.attr.mircColor32, R.attr.mircColor33, R.attr.mircColor34, R.attr.mircColor35,
+    R.attr.mircColor36, R.attr.mircColor37, R.attr.mircColor38, R.attr.mircColor39,
+    R.attr.mircColor40, R.attr.mircColor41, R.attr.mircColor42, R.attr.mircColor43,
+    R.attr.mircColor44, R.attr.mircColor45, R.attr.mircColor46, R.attr.mircColor47,
+    R.attr.mircColor48, R.attr.mircColor49, R.attr.mircColor50, R.attr.mircColor51,
+    R.attr.mircColor52, R.attr.mircColor53, R.attr.mircColor54, R.attr.mircColor55,
+    R.attr.mircColor56, R.attr.mircColor57, R.attr.mircColor58, R.attr.mircColor59,
+    R.attr.mircColor60, R.attr.mircColor61, R.attr.mircColor62, R.attr.mircColor63,
+    R.attr.mircColor64, R.attr.mircColor65, R.attr.mircColor66, R.attr.mircColor67,
+    R.attr.mircColor68, R.attr.mircColor69, R.attr.mircColor70, R.attr.mircColor71,
+    R.attr.mircColor72, R.attr.mircColor73, R.attr.mircColor74, R.attr.mircColor75,
+    R.attr.mircColor76, R.attr.mircColor77, R.attr.mircColor78, R.attr.mircColor79,
+    R.attr.mircColor80, R.attr.mircColor81, R.attr.mircColor82, R.attr.mircColor83,
+    R.attr.mircColor84, R.attr.mircColor85, R.attr.mircColor86, R.attr.mircColor87,
+    R.attr.mircColor88, R.attr.mircColor89, R.attr.mircColor90, R.attr.mircColor91,
+    R.attr.mircColor92, R.attr.mircColor93, R.attr.mircColor94, R.attr.mircColor95,
+    R.attr.mircColor96, R.attr.mircColor97, R.attr.mircColor98
+  ) {
+    (0..98).map { getColor(it, 0) }
+  }
+  val mircColorMap = mircColors.withIndex().map { (key, value) -> key to value }.toMap()
+
+  val defaultForegroundColor = editText.context.theme.styledAttributes(R.attr.colorForeground) {
+    getColor(0, 0)
+  }
+
+  val defaultBackgroundColor = editText.context.theme.styledAttributes(R.attr.colorBackground) {
+    getColor(0, 0)
+  }
+
   private val serializer = IrcFormatSerializer(editText.context)
   val formattedText: Sequence<String>
     get() = editText.text.lineSequence().map { serializer.toEscapeCodes(SpannableString(it)) }
@@ -144,14 +179,93 @@ class FormatHandler(
     }
   }
 
+  fun foregroundColors(range: IntRange) = editText.text.spans<ForegroundColorSpan>(range)
+  fun foregroundColor(range: IntRange) = foregroundColors(range).singleOrNull()?.foregroundColor
+  fun toggleForeground(range: IntRange, @ColorInt color: Int? = null, mircColor: Int? = null) {
+    editText.text.removeSpans<ForegroundColorSpan, IrcForegroundColorSpan<*>>(range) { span ->
+      val mirc = mircColorMap[span.foregroundColor]
+      when {
+        span is IrcForegroundColorSpan<*> -> span
+        mirc != null                      -> IrcForegroundColorSpan.MIRC(mirc, span.foregroundColor)
+        else                              -> IrcForegroundColorSpan.HEX(span.foregroundColor)
+      }
+    }
+
+    if (color != null) {
+      if (mircColor != null) {
+        editText.text.setSpan(
+          IrcForegroundColorSpan.MIRC(mircColor, color),
+          range.start,
+          range.last + 1,
+          Spanned.SPAN_INCLUSIVE_INCLUSIVE
+        )
+      } else {
+        editText.text.setSpan(
+          IrcForegroundColorSpan.HEX(color),
+          range.start,
+          range.last + 1,
+          Spanned.SPAN_INCLUSIVE_INCLUSIVE
+        )
+      }
+    }
+  }
+
+  fun backgroundColors(range: IntRange) = editText.text.spans<BackgroundColorSpan>(range)
+  fun backgroundColor(range: IntRange) = backgroundColors(range).singleOrNull()?.backgroundColor
+  fun toggleBackground(range: IntRange, @ColorInt color: Int? = null, mircColor: Int? = null) {
+    editText.text.removeSpans<BackgroundColorSpan, IrcBackgroundColorSpan<*>>(range) { span ->
+      val mirc = mircColorMap[span.backgroundColor]
+      when {
+        span is IrcBackgroundColorSpan<*> -> span
+        mirc != null                      -> IrcBackgroundColorSpan.MIRC(mirc, span.backgroundColor)
+        else                              -> IrcBackgroundColorSpan.HEX(span.backgroundColor)
+      }
+    }
+
+    if (color != null) {
+      if (mircColor != null) {
+        editText.text.setSpan(
+          IrcBackgroundColorSpan.MIRC(mircColor, color),
+          range.start,
+          range.last + 1,
+          Spanned.SPAN_INCLUSIVE_INCLUSIVE
+        )
+      } else {
+        editText.text.setSpan(
+          IrcBackgroundColorSpan.HEX(color),
+          range.start,
+          range.last + 1,
+          Spanned.SPAN_INCLUSIVE_INCLUSIVE
+        )
+      }
+    }
+  }
+
   fun clearFormatting(range: IntRange) {
     toggleBold(range, false)
     toggleItalic(range, false)
     toggleUnderline(range, false)
     toggleStrikethrough(range, false)
     toggleMonospace(range, false)
+    toggleForeground(range, null, null)
+    toggleBackground(range, null, null)
   }
 
+  private inline fun <reified U> Spanned.spans(range: IntRange) =
+    getSpans(range.start, range.endInclusive + 1, U::class.java).filter {
+      getSpanFlags(it) and Spanned.SPAN_COMPOSING == 0 &&
+      (getSpanEnd(it) != range.start ||
+       getSpanFlags(it) and 0x02 != 0)
+    }
+
+  private inline fun <reified U> Spanned.spans(range: IntRange, f: (U) -> Boolean) =
+    getSpans(range.start, range.last + 1, U::class.java).filter {
+      f(it) &&
+      getSpanFlags(it) and Spanned.SPAN_COMPOSING == 0 &&
+      (getSpanEnd(it) != range.start ||
+       getSpanFlags(it) and 0x02 != 0)
+    }
+
   private inline fun <reified U> Spanned.hasSpans(range: IntRange) =
     getSpans(range.start, range.endInclusive + 1, U::class.java).any {
       getSpanFlags(it) and Spanned.SPAN_COMPOSING == 0 &&
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 7e0ef6577..41cfe09b6 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
@@ -11,6 +11,7 @@ import android.support.design.widget.FloatingActionButton
 import android.support.v4.widget.SwipeRefreshLayout
 import android.support.v7.widget.LinearLayoutManager
 import android.support.v7.widget.RecyclerView
+import android.text.SpannableStringBuilder
 import android.view.*
 import butterknife.BindView
 import butterknife.ButterKnife
@@ -63,36 +64,36 @@ class MessageListFragment : ServiceBoundFragment() {
   private val actionModeCallback = object : ActionMode.Callback {
     override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?) = when (item?.itemId) {
       R.id.action_copy  -> {
-        val data = viewModel.selectedMessages.value.values.sortedBy {
+        val builder = SpannableStringBuilder()
+        viewModel.selectedMessages.value.values.sortedBy {
           it.id
-        }.joinToString("\n") {
-          SpanFormatter.format(
-            getString(R.string.message_format_copy),
-            it.time,
-            it.content
-          )
+        }.map {
+          SpanFormatter.format(getString(R.string.message_format_copy), it.time, it.content)
+        }.forEach {
+          builder.append(it)
+          builder.append("\n")
         }
 
         val clipboard = requireActivity().systemService<ClipboardManager>()
-        val clip = ClipData.newPlainText(null, data)
+        val clip = ClipData.newPlainText(null, builder)
         clipboard.primaryClip = clip
         actionMode?.finish()
         true
       }
       R.id.action_share -> {
-        val data = viewModel.selectedMessages.value.values.sortedBy {
+        val builder = SpannableStringBuilder()
+        viewModel.selectedMessages.value.values.sortedBy {
           it.id
-        }.joinToString("\n") {
-          SpanFormatter.format(
-            getString(R.string.message_format_copy),
-            it.time,
-            it.content
-          )
+        }.map {
+          SpanFormatter.format(getString(R.string.message_format_copy), it.time, it.content)
+        }.forEach {
+          builder.append(it)
+          builder.append("\n")
         }
 
         val intent = Intent(Intent.ACTION_SEND)
         intent.type = "text/plain"
-        intent.putExtra(Intent.EXTRA_TEXT, data)
+        intent.putExtra(Intent.EXTRA_TEXT, builder)
         requireContext().startActivity(
           Intent.createChooser(
             intent,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
index 8b36cb6c8..ea59e13db 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/IrcFormatDeserializer.kt
@@ -350,13 +350,13 @@ class IrcFormatDeserializer @Inject constructor() {
     override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
       if (foreground >= 0) {
         editable.setSpan(
-          IrcHexForegroundColorSpan(foreground or 0xFFFFFF.inv()), from, to,
+          IrcForegroundColorSpan.HEX(foreground or 0xFFFFFF.inv()), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
       if (background >= 0) {
         editable.setSpan(
-          IrcHexBackgroundColorSpan(background or 0xFFFFFF.inv()), from, to,
+          IrcBackgroundColorSpan.HEX(background or 0xFFFFFF.inv()), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
@@ -369,13 +369,13 @@ class IrcFormatDeserializer @Inject constructor() {
     override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
       if (foreground.toInt() >= 0 && foreground.toInt() < mircColors.size) {
         editable.setSpan(
-          IrcForegroundColorSpan(foreground.toInt(), mircColors[foreground.toInt()]), from, to,
+          IrcForegroundColorSpan.MIRC(foreground.toInt(), mircColors[foreground.toInt()]), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
       if (background.toInt() >= 0 && background.toInt() < mircColors.size) {
         editable.setSpan(
-          IrcBackgroundColorSpan(background.toInt(), mircColors[background.toInt()]), from, to,
+          IrcBackgroundColorSpan.MIRC(background.toInt(), mircColors[background.toInt()]), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcBackgroundColorSpan.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcBackgroundColorSpan.kt
index 934efb741..8a4562753 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcBackgroundColorSpan.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcBackgroundColorSpan.kt
@@ -3,9 +3,15 @@ package de.kuschku.quasseldroid.util.irc.format.spans
 import android.support.annotation.ColorInt
 import android.text.style.BackgroundColorSpan
 
-class IrcBackgroundColorSpan(
-  val mircColor: Int,
-  @ColorInt color: Int
-) : BackgroundColorSpan(color), Copyable<IrcBackgroundColorSpan> {
-  override fun copy() = IrcBackgroundColorSpan(mircColor, backgroundColor)
+sealed class IrcBackgroundColorSpan<T : IrcBackgroundColorSpan<T>>(@ColorInt color: Int) :
+  BackgroundColorSpan(color), Copyable<T> {
+  class MIRC(val mircColor: Int, @ColorInt color: Int) :
+    IrcBackgroundColorSpan<MIRC>(color), Copyable<MIRC> {
+    override fun copy() = MIRC(mircColor, backgroundColor)
+  }
+
+  class HEX(@ColorInt color: Int) :
+    IrcBackgroundColorSpan<HEX>(color), Copyable<HEX> {
+    override fun copy() = HEX(backgroundColor)
+  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcForegroundColorSpan.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcForegroundColorSpan.kt
index 12641c5c8..36acd5aae 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcForegroundColorSpan.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcForegroundColorSpan.kt
@@ -3,9 +3,15 @@ package de.kuschku.quasseldroid.util.irc.format.spans
 import android.support.annotation.ColorInt
 import android.text.style.ForegroundColorSpan
 
-class IrcForegroundColorSpan(
-  val mircColor: Int,
-  @ColorInt color: Int
-) : ForegroundColorSpan(color), Copyable<IrcForegroundColorSpan> {
-  override fun copy() = IrcForegroundColorSpan(mircColor, foregroundColor)
+sealed class IrcForegroundColorSpan<T : IrcForegroundColorSpan<T>>(@ColorInt color: Int) :
+  ForegroundColorSpan(color), Copyable<T> {
+  class MIRC(val mircColor: Int, @ColorInt color: Int) :
+    IrcForegroundColorSpan<MIRC>(color), Copyable<MIRC> {
+    override fun copy() = MIRC(mircColor, foregroundColor)
+  }
+
+  class HEX(@ColorInt color: Int) :
+    IrcForegroundColorSpan<HEX>(color), Copyable<HEX> {
+    override fun copy() = HEX(foregroundColor)
+  }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexBackgroundColorSpan.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexBackgroundColorSpan.kt
deleted file mode 100644
index 2269de782..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexBackgroundColorSpan.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package de.kuschku.quasseldroid.util.irc.format.spans
-
-import android.support.annotation.ColorInt
-import android.text.style.BackgroundColorSpan
-
-class IrcHexBackgroundColorSpan(@ColorInt color: Int) : BackgroundColorSpan(color),
-                                                        Copyable<IrcHexBackgroundColorSpan> {
-  override fun copy() = IrcHexBackgroundColorSpan(backgroundColor)
-}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexForegroundColorSpan.kt b/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexForegroundColorSpan.kt
deleted file mode 100644
index f3d680b1c..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid/util/irc/format/spans/IrcHexForegroundColorSpan.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package de.kuschku.quasseldroid.util.irc.format.spans
-
-import android.support.annotation.ColorInt
-import android.text.style.ForegroundColorSpan
-
-class IrcHexForegroundColorSpan(@ColorInt color: Int) : ForegroundColorSpan(color),
-                                                        Copyable<IrcHexForegroundColorSpan> {
-  override fun copy() = IrcHexForegroundColorSpan(foregroundColor)
-}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/ColorChooserDialog.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ColorChooserDialog.java
new file mode 100644
index 000000000..811ff4d82
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/ColorChooserDialog.java
@@ -0,0 +1,798 @@
+package de.kuschku.quasseldroid.util.ui;
+
+import android.annotation.SuppressLint;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringDef;
+import android.support.annotation.StringRes;
+import android.support.v4.app.DialogFragment;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.content.res.ResourcesCompat;
+import android.text.Editable;
+import android.text.InputFilter;
+import android.text.TextWatcher;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.EditText;
+import android.widget.GridView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.afollestad.materialdialogs.Theme;
+import com.afollestad.materialdialogs.color.CircleView;
+import com.afollestad.materialdialogs.internal.MDTintHelper;
+import com.afollestad.materialdialogs.util.DialogUtils;
+
+import java.io.Serializable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Locale;
+
+import de.kuschku.quasseldroid.R;
+
+/**
+ * @author Aidan Follestad (afollestad)
+ */
+@SuppressWarnings({"FieldCanBeLocal", "ConstantConditions"})
+public class ColorChooserDialog extends DialogFragment
+  implements View.OnClickListener, View.OnLongClickListener {
+
+  public static final String TAG_PRIMARY = "[MD_COLOR_CHOOSER]";
+  public static final String TAG_ACCENT = "[MD_COLOR_CHOOSER]";
+  public static final String TAG_CUSTOM = "[MD_COLOR_CHOOSER]";
+  private int[] colorsTop;
+  @Nullable
+  private int[][] colorsSub;
+  private int circleSize;
+  private GridView grid;
+  private View colorChooserCustomFrame;
+  private EditText customColorHex;
+  private View customColorIndicator;
+  private TextWatcher customColorTextWatcher;
+  private SeekBar customSeekA;
+  private TextView customSeekAValue;
+  private SeekBar customSeekR;
+  private TextView customSeekRValue;
+  private SeekBar customSeekG;
+  private TextView customSeekGValue;
+  private SeekBar customSeekB;
+  private TextView customSeekBValue;
+  private SeekBar.OnSeekBarChangeListener customColorRgbListener;
+  private int selectedCustomColor;
+
+  public ColorChooserDialog() {
+  }
+
+  @Nullable
+  public static ColorChooserDialog findVisible(
+    @NonNull FragmentManager fragmentManager, @ColorChooserTag String tag) {
+    Fragment frag = fragmentManager.findFragmentByTag(tag);
+    if (frag != null && frag instanceof ColorChooserDialog) {
+      return (ColorChooserDialog) frag;
+    }
+    return null;
+  }
+
+  private void generateColors() {
+    Builder builder = getBuilder();
+    colorsTop = builder.colorsTop;
+    colorsSub = builder.colorsSub;
+  }
+
+  @Override
+  public void onSaveInstanceState(Bundle outState) {
+    super.onSaveInstanceState(outState);
+    outState.putInt("top_index", topIndex());
+    outState.putBoolean("in_sub", isInSub());
+    outState.putInt("sub_index", subIndex());
+    outState.putBoolean(
+      "in_custom",
+      colorChooserCustomFrame != null && colorChooserCustomFrame.getVisibility() == View.VISIBLE);
+  }
+
+  private boolean isInSub() {
+    return getArguments().getBoolean("in_sub", false);
+  }
+
+  private void isInSub(boolean value) {
+    getArguments().putBoolean("in_sub", value);
+  }
+
+  private int topIndex() {
+    return getArguments().getInt("top_index", -1);
+  }
+
+  private void topIndex(int value) {
+    if (value > -1) {
+      findSubIndexForColor(value, colorsTop[value]);
+    }
+    getArguments().putInt("top_index", value);
+  }
+
+  private int subIndex() {
+    if (colorsSub == null) {
+      return -1;
+    }
+    return getArguments().getInt("sub_index", -1);
+  }
+
+  private void subIndex(int value) {
+    if (colorsSub == null) {
+      return;
+    }
+    getArguments().putInt("sub_index", value);
+  }
+
+  @StringRes
+  public int getTitle() {
+    Builder builder = getBuilder();
+    int title;
+    if (isInSub()) {
+      title = builder.titleSub;
+    } else {
+      title = builder.title;
+    }
+    if (title == 0) {
+      title = builder.title;
+    }
+    return title;
+  }
+
+  public String tag() {
+    Builder builder = getBuilder();
+    if (builder.tag != null) {
+      return builder.tag;
+    } else {
+      return super.getTag();
+    }
+  }
+
+  @Override
+  public void onClick(View v) {
+    if (v.getTag() != null) {
+      final String[] tag = ((String) v.getTag()).split(":");
+      final int index = Integer.parseInt(tag[0]);
+      final MaterialDialog dialog = (MaterialDialog) getDialog();
+      final Builder builder = getBuilder();
+
+      if (isInSub()) {
+        subIndex(index);
+      } else {
+        topIndex(index);
+        if (colorsSub != null && index < colorsSub.length) {
+          dialog.setActionButton(DialogAction.NEGATIVE, builder.backBtn);
+          isInSub(true);
+        }
+      }
+
+      if (builder.allowUserCustom) {
+        selectedCustomColor = getSelectedColor();
+      }
+      invalidateDynamicButtonColors();
+      invalidate();
+    }
+  }
+
+  @Override
+  public boolean onLongClick(View v) {
+    if (v.getTag() != null) {
+      final String[] tag = ((String) v.getTag()).split(":");
+      final int color = Integer.parseInt(tag[1]);
+      ((CircleView) v).showHint(color);
+      return true;
+    }
+    return false;
+  }
+
+  private void invalidateDynamicButtonColors() {
+    final MaterialDialog dialog = (MaterialDialog) getDialog();
+    if (dialog == null) {
+      return;
+    }
+    final Builder builder = getBuilder();
+    if (builder.dynamicButtonColor) {
+      int selectedColor = getSelectedColor();
+      if (Color.alpha(selectedColor) < 64
+        || (Color.red(selectedColor) > 247
+        && Color.green(selectedColor) > 247
+        && Color.blue(selectedColor) > 247)) {
+        // Once we get close to white or transparent,
+        // the action buttons and seekbars will be a very light gray.
+        selectedColor = Color.parseColor("#DEDEDE");
+      }
+
+      if (getBuilder().dynamicButtonColor) {
+        dialog.getActionButton(DialogAction.POSITIVE).setTextColor(selectedColor);
+        dialog.getActionButton(DialogAction.NEGATIVE).setTextColor(selectedColor);
+        dialog.getActionButton(DialogAction.NEUTRAL).setTextColor(selectedColor);
+      }
+
+      if (customSeekR != null) {
+        if (customSeekA.getVisibility() == View.VISIBLE) {
+          MDTintHelper.setTint(customSeekA, selectedColor);
+        }
+        MDTintHelper.setTint(customSeekR, selectedColor);
+        MDTintHelper.setTint(customSeekG, selectedColor);
+        MDTintHelper.setTint(customSeekB, selectedColor);
+      }
+    }
+  }
+
+  @ColorInt
+  private int getSelectedColor() {
+    if (colorChooserCustomFrame != null
+      && colorChooserCustomFrame.getVisibility() == View.VISIBLE) {
+      return selectedCustomColor;
+    }
+
+    int color = 0;
+    if (subIndex() > -1) {
+      color = colorsSub[topIndex()][subIndex()];
+    } else if (topIndex() > -1) {
+      color = colorsTop[topIndex()];
+    }
+    return color;
+  }
+
+  private void findSubIndexForColor(int topIndex, int color) {
+    if (colorsSub == null || colorsSub.length - 1 < topIndex) {
+      return;
+    }
+    int[] subColors = colorsSub[topIndex];
+    for (int subIndex = 0; subIndex < subColors.length; subIndex++) {
+      if (subColors[subIndex] == color) {
+        subIndex(subIndex);
+        break;
+      }
+    }
+  }
+
+  @NonNull
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState) {
+    if (getArguments() == null || !getArguments().containsKey("builder")) {
+      throw new IllegalStateException(
+        "ColorChooserDialog should be created using its Builder interface.");
+    }
+    generateColors();
+
+    int preselectColor;
+    boolean foundPreselectColor = false;
+
+    if (savedInstanceState != null) {
+      preselectColor = getSelectedColor();
+    } else {
+      if (getBuilder().setPreselectionColor) {
+        preselectColor = getBuilder().preselectColor;
+        if (preselectColor != 0) {
+          for (int topIndex = 0; topIndex < colorsTop.length; topIndex++) {
+            if (colorsTop[topIndex] == preselectColor) {
+              topIndex(topIndex);
+              if (colorsSub != null) {
+                findSubIndexForColor(topIndex, preselectColor);
+              } else {
+                subIndex(5);
+              }
+              break;
+            }
+
+            if (colorsSub != null) {
+              for (int subIndex = 0; subIndex < colorsSub[topIndex].length; subIndex++) {
+                if (colorsSub[topIndex][subIndex] == preselectColor) {
+                  foundPreselectColor = true;
+                  topIndex(topIndex);
+                  subIndex(subIndex);
+                  break;
+                }
+              }
+              if (foundPreselectColor) {
+                break;
+              }
+            }
+          }
+        }
+      } else {
+        preselectColor = Color.BLACK;
+      }
+    }
+
+    circleSize = getResources().getDimensionPixelSize(R.dimen.colorchooser_circlesize);
+    final Builder builder = getBuilder();
+
+    MaterialDialog.Builder bd =
+      new MaterialDialog.Builder(getActivity())
+        .title(getTitle())
+        .autoDismiss(false)
+        .customView(R.layout.dialog_colorchooser, false)
+        .negativeText(builder.cancelBtn)
+        .positiveText(builder.doneBtn)
+        .neutralText(builder.allowUserCustom ? builder.customBtn : 0)
+        .typeface(builder.mediumFont, builder.regularFont)
+        .titleColorAttr(R.attr.colorTextPrimary)
+        .backgroundColorAttr(R.attr.colorBackgroundCard)
+        .contentColorAttr(R.attr.colorTextPrimary)
+        .onPositive(
+          new MaterialDialog.SingleButtonCallback() {
+            @Override
+            public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+              builder.callback.onColorSelection(ColorChooserDialog.this, getSelectedColor());
+              dismiss();
+            }
+          })
+        .onNegative(
+          new MaterialDialog.SingleButtonCallback() {
+            @Override
+            public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+              if (isInSub()) {
+                dialog.setActionButton(DialogAction.NEGATIVE, getBuilder().cancelBtn);
+                isInSub(false);
+                subIndex(-1); // Do this to avoid ArrayIndexOutOfBoundsException
+                invalidate();
+              } else {
+                dialog.cancel();
+              }
+            }
+          })
+        .onNeutral(
+          new MaterialDialog.SingleButtonCallback() {
+            @Override
+            public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+              toggleCustom(dialog);
+            }
+          })
+        .showListener(
+          new DialogInterface.OnShowListener() {
+            @Override
+            public void onShow(DialogInterface dialog) {
+              invalidateDynamicButtonColors();
+            }
+          });
+
+    if (builder.theme != null) {
+      bd.theme(builder.theme);
+    }
+
+    final MaterialDialog dialog = bd.build();
+    final View v = dialog.getCustomView();
+    grid = v.findViewById(R.id.md_grid);
+
+    if (builder.allowUserCustom) {
+      selectedCustomColor = preselectColor;
+      colorChooserCustomFrame = v.findViewById(R.id.md_colorChooserCustomFrame);
+      customColorHex = v.findViewById(R.id.md_hexInput);
+      customColorIndicator = v.findViewById(R.id.md_colorIndicator);
+      customSeekA = v.findViewById(R.id.md_colorA);
+      customSeekAValue = v.findViewById(R.id.md_colorAValue);
+      customSeekR = v.findViewById(R.id.md_colorR);
+      customSeekRValue = v.findViewById(R.id.md_colorRValue);
+      customSeekG = v.findViewById(R.id.md_colorG);
+      customSeekGValue = v.findViewById(R.id.md_colorGValue);
+      customSeekB = v.findViewById(R.id.md_colorB);
+      customSeekBValue = v.findViewById(R.id.md_colorBValue);
+
+      if (!builder.allowUserCustomAlpha) {
+        v.findViewById(R.id.md_colorALabel).setVisibility(View.GONE);
+        customSeekA.setVisibility(View.GONE);
+        customSeekAValue.setVisibility(View.GONE);
+        customColorHex.setHint("2196F3");
+        customColorHex.setFilters(new InputFilter[]{new InputFilter.LengthFilter(6)});
+      } else {
+        customColorHex.setHint("FF2196F3");
+        customColorHex.setFilters(new InputFilter[]{new InputFilter.LengthFilter(8)});
+      }
+    }
+
+    invalidate();
+    return dialog;
+  }
+
+  @Override
+  public void onDismiss(DialogInterface dialog) {
+    super.onDismiss(dialog);
+    if (getBuilder().callback != null) {
+      getBuilder().callback.onColorChooserDismissed(this);
+    }
+  }
+
+  private void toggleCustom(MaterialDialog dialog) {
+    if (dialog == null) {
+      dialog = (MaterialDialog) getDialog();
+    }
+    if (grid.getVisibility() == View.VISIBLE) {
+      dialog.setTitle(getBuilder().customBtn);
+      dialog.setActionButton(DialogAction.NEUTRAL, getBuilder().presetsBtn);
+      dialog.setActionButton(DialogAction.NEGATIVE, getBuilder().cancelBtn);
+      grid.setVisibility(View.INVISIBLE);
+      colorChooserCustomFrame.setVisibility(View.VISIBLE);
+
+      customColorTextWatcher =
+        new TextWatcher() {
+          @Override
+          public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+          }
+
+          @Override
+          public void onTextChanged(CharSequence s, int start, int before, int count) {
+            try {
+              selectedCustomColor = Color.parseColor("#" + s.toString());
+            } catch (IllegalArgumentException e) {
+              selectedCustomColor = Color.BLACK;
+            }
+            customColorIndicator.setBackgroundColor(selectedCustomColor);
+            if (customSeekA.getVisibility() == View.VISIBLE) {
+              int alpha = Color.alpha(selectedCustomColor);
+              customSeekA.setProgress(alpha);
+              customSeekAValue.setText(String.format(Locale.US, "%d", alpha));
+            }
+            int red = Color.red(selectedCustomColor);
+            customSeekR.setProgress(red);
+            int green = Color.green(selectedCustomColor);
+            customSeekG.setProgress(green);
+            int blue = Color.blue(selectedCustomColor);
+            customSeekB.setProgress(blue);
+            isInSub(false);
+            topIndex(-1);
+            subIndex(-1);
+            invalidateDynamicButtonColors();
+          }
+
+          @Override
+          public void afterTextChanged(Editable s) {
+          }
+        };
+
+      customColorHex.addTextChangedListener(customColorTextWatcher);
+      customColorRgbListener =
+        new SeekBar.OnSeekBarChangeListener() {
+
+          @SuppressLint("DefaultLocale")
+          @Override
+          public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+            if (fromUser) {
+              if (getBuilder().allowUserCustomAlpha) {
+                int color =
+                  Color.argb(
+                    customSeekA.getProgress(),
+                    customSeekR.getProgress(),
+                    customSeekG.getProgress(),
+                    customSeekB.getProgress());
+                customColorHex.setText(String.format("%08X", color));
+              } else {
+                int color =
+                  Color.rgb(
+                    customSeekR.getProgress(),
+                    customSeekG.getProgress(),
+                    customSeekB.getProgress());
+                customColorHex.setText(String.format("%06X", 0xFFFFFF & color));
+              }
+            }
+            customSeekAValue.setText(String.format("%d", customSeekA.getProgress()));
+            customSeekRValue.setText(String.format("%d", customSeekR.getProgress()));
+            customSeekGValue.setText(String.format("%d", customSeekG.getProgress()));
+            customSeekBValue.setText(String.format("%d", customSeekB.getProgress()));
+          }
+
+          @Override
+          public void onStartTrackingTouch(SeekBar seekBar) {
+          }
+
+          @Override
+          public void onStopTrackingTouch(SeekBar seekBar) {
+          }
+        };
+
+      customSeekR.setOnSeekBarChangeListener(customColorRgbListener);
+      customSeekG.setOnSeekBarChangeListener(customColorRgbListener);
+      customSeekB.setOnSeekBarChangeListener(customColorRgbListener);
+      if (customSeekA.getVisibility() == View.VISIBLE) {
+        customSeekA.setOnSeekBarChangeListener(customColorRgbListener);
+        customColorHex.setText(String.format("%08X", selectedCustomColor));
+      } else {
+        customColorHex.setText(String.format("%06X", 0xFFFFFF & selectedCustomColor));
+      }
+    } else {
+      dialog.setTitle(getBuilder().title);
+      dialog.setActionButton(DialogAction.NEUTRAL, getBuilder().customBtn);
+      if (isInSub()) {
+        dialog.setActionButton(DialogAction.NEGATIVE, getBuilder().backBtn);
+      } else {
+        dialog.setActionButton(DialogAction.NEGATIVE, getBuilder().cancelBtn);
+      }
+      grid.setVisibility(View.VISIBLE);
+      colorChooserCustomFrame.setVisibility(View.GONE);
+      customColorHex.removeTextChangedListener(customColorTextWatcher);
+      customColorTextWatcher = null;
+      customSeekR.setOnSeekBarChangeListener(null);
+      customSeekG.setOnSeekBarChangeListener(null);
+      customSeekB.setOnSeekBarChangeListener(null);
+      customColorRgbListener = null;
+    }
+  }
+
+  private void invalidate() {
+    if (grid.getAdapter() == null) {
+      grid.setAdapter(new ColorGridAdapter());
+      grid.setSelector(
+        ResourcesCompat.getDrawable(getResources(), R.drawable.bg_transparent, null));
+    } else {
+      ((BaseAdapter) grid.getAdapter()).notifyDataSetChanged();
+    }
+    if (getDialog() != null) {
+      getDialog().setTitle(getTitle());
+    }
+  }
+
+  private Builder getBuilder() {
+    if (getArguments() == null || !getArguments().containsKey("builder")) {
+      return null;
+    }
+    return (Builder) getArguments().getSerializable("builder");
+  }
+
+  private void dismissIfNecessary(FragmentManager fragmentManager, String tag) {
+    Fragment frag = fragmentManager.findFragmentByTag(tag);
+    if (frag != null) {
+      ((DialogFragment) frag).dismiss();
+      fragmentManager.beginTransaction().remove(frag).commit();
+    }
+  }
+
+  @NonNull
+  public ColorChooserDialog show(FragmentManager fragmentManager) {
+    String tag;
+    Builder builder = getBuilder();
+    if (builder.colorsTop != null) {
+      tag = TAG_CUSTOM;
+    } else {
+      tag = TAG_PRIMARY;
+    }
+    dismissIfNecessary(fragmentManager, tag);
+    show(fragmentManager, tag);
+    return this;
+  }
+
+  @NonNull
+  public ColorChooserDialog show(FragmentActivity fragmentActivity) {
+    return show(fragmentActivity.getSupportFragmentManager());
+  }
+
+  @Retention(RetentionPolicy.SOURCE)
+  @StringDef({TAG_PRIMARY, TAG_ACCENT, TAG_CUSTOM})
+  public @interface ColorChooserTag {
+  }
+
+  public interface ColorCallback {
+
+    void onColorSelection(@NonNull ColorChooserDialog dialog, @ColorInt int selectedColor);
+
+    void onColorChooserDismissed(@NonNull ColorChooserDialog dialog);
+  }
+
+  public static class Builder implements Serializable {
+
+    @NonNull
+    final transient Context context;
+    @StringRes
+    final int title;
+    @Nullable
+    String mediumFont;
+    @Nullable
+    String regularFont;
+    @StringRes
+    int titleSub;
+    @ColorInt
+    int preselectColor;
+    @StringRes
+    int doneBtn = R.string.md_done_label;
+    @StringRes
+    int backBtn = R.string.md_back_label;
+    @StringRes
+    int cancelBtn = R.string.md_cancel_label;
+    @StringRes
+    int customBtn = R.string.md_custom_label;
+    @StringRes
+    int presetsBtn = R.string.md_presets_label;
+    @Nullable
+    int[] colorsTop;
+    @Nullable
+    int[][] colorsSub;
+    @Nullable
+    String tag;
+    @Nullable
+    Theme theme;
+
+    ColorCallback callback;
+
+    boolean dynamicButtonColor = true;
+    boolean allowUserCustom = true;
+    boolean allowUserCustomAlpha = true;
+    boolean setPreselectionColor = false;
+
+    public Builder(@NonNull Context context, @StringRes int title) {
+      this.context = context;
+      this.title = title;
+    }
+
+    @NonNull
+    public Builder typeface(@Nullable String medium, @Nullable String regular) {
+      this.mediumFont = medium;
+      this.regularFont = regular;
+      return this;
+    }
+
+    @NonNull
+    public Builder titleSub(@StringRes int titleSub) {
+      this.titleSub = titleSub;
+      return this;
+    }
+
+    @NonNull
+    public Builder tag(@Nullable String tag) {
+      this.tag = tag;
+      return this;
+    }
+
+    @NonNull
+    public Builder theme(@NonNull Theme theme) {
+      this.theme = theme;
+      return this;
+    }
+
+    @NonNull
+    public Builder preselect(@ColorInt int preselect) {
+      preselectColor = preselect;
+      setPreselectionColor = true;
+      return this;
+    }
+
+    @NonNull
+    public Builder doneButton(@StringRes int text) {
+      doneBtn = text;
+      return this;
+    }
+
+    @NonNull
+    public Builder backButton(@StringRes int text) {
+      backBtn = text;
+      return this;
+    }
+
+    @NonNull
+    public Builder cancelButton(@StringRes int text) {
+      cancelBtn = text;
+      return this;
+    }
+
+    @NonNull
+    public Builder customButton(@StringRes int text) {
+      customBtn = text;
+      return this;
+    }
+
+    @NonNull
+    public Builder presetsButton(@StringRes int text) {
+      presetsBtn = text;
+      return this;
+    }
+
+    @NonNull
+    public Builder dynamicButtonColor(boolean enabled) {
+      dynamicButtonColor = enabled;
+      return this;
+    }
+
+    @NonNull
+    public Builder customColors(@NonNull int[] topLevel, @Nullable int[][] subLevel) {
+      colorsTop = topLevel;
+      colorsSub = subLevel;
+      return this;
+    }
+
+    @NonNull
+    public Builder customColors(@ArrayRes int topLevel, @Nullable int[][] subLevel) {
+      colorsTop = DialogUtils.getColorArray(context, topLevel);
+      colorsSub = subLevel;
+      return this;
+    }
+
+    @NonNull
+    public Builder allowUserColorInput(boolean allow) {
+      allowUserCustom = allow;
+      return this;
+    }
+
+    @NonNull
+    public Builder allowUserColorInputAlpha(boolean allow) {
+      allowUserCustomAlpha = allow;
+      return this;
+    }
+
+    @NonNull
+    public ColorChooserDialog build() {
+      ColorChooserDialog dialog = new ColorChooserDialog();
+      Bundle args = new Bundle();
+      args.putSerializable("builder", this);
+      dialog.setArguments(args);
+      return dialog;
+    }
+
+    public Builder callback(ColorCallback callback) {
+      this.callback = callback;
+      return this;
+    }
+
+    @NonNull
+    public ColorChooserDialog show(FragmentManager fragmentManager) {
+      ColorChooserDialog dialog = build();
+      dialog.show(fragmentManager);
+      return dialog;
+    }
+
+    @NonNull
+    public ColorChooserDialog show(FragmentActivity fragmentActivity) {
+      return show(fragmentActivity.getSupportFragmentManager());
+    }
+  }
+
+  private class ColorGridAdapter extends BaseAdapter {
+
+    ColorGridAdapter() {
+    }
+
+    @Override
+    public int getCount() {
+      if (isInSub()) {
+        return colorsSub[topIndex()].length;
+      } else {
+        return colorsTop.length;
+      }
+    }
+
+    @Override
+    public Object getItem(int position) {
+      if (isInSub()) {
+        return colorsSub[topIndex()][position];
+      } else {
+        return colorsTop[position];
+      }
+    }
+
+    @Override
+    public long getItemId(int position) {
+      return position;
+    }
+
+    @SuppressLint("DefaultLocale")
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+      if (convertView == null) {
+        convertView = new CircleView(getContext());
+        convertView.setLayoutParams(new GridView.LayoutParams(circleSize, circleSize));
+      }
+      CircleView child = (CircleView) convertView;
+      @ColorInt final int color = isInSub() ? colorsSub[topIndex()][position] : colorsTop[position];
+      child.setBackgroundColor(color);
+      if (isInSub()) {
+        child.setSelected(subIndex() == position);
+      } else {
+        child.setSelected(topIndex() == position);
+      }
+      child.setTag(String.format("%d:%d", position, color));
+      child.setOnClickListener(ColorChooserDialog.this);
+      child.setOnLongClickListener(ColorChooserDialog.this);
+      return convertView;
+    }
+  }
+}
diff --git a/app/src/main/res/drawable/bg_transparent.xml b/app/src/main/res/drawable/bg_transparent.xml
new file mode 100644
index 000000000..261a644cd
--- /dev/null
+++ b/app/src/main/res/drawable/bg_transparent.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+  android:shape="rectangle">
+  <solid android:color="@android:color/transparent" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_colorchooser.xml b/app/src/main/res/layout/dialog_colorchooser.xml
new file mode 100644
index 000000000..53dde1694
--- /dev/null
+++ b/app/src/main/res/layout/dialog_colorchooser.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent">
+
+  <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <include layout="@layout/dialog_colorchooser_presets" />
+
+    <include
+      layout="@layout/dialog_colorchooser_custom"
+      android:visibility="gone" />
+
+  </FrameLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_colorchooser_custom.xml b/app/src/main/res/layout/dialog_colorchooser_custom.xml
new file mode 100644
index 000000000..6417ccbbd
--- /dev/null
+++ b/app/src/main/res/layout/dialog_colorchooser_custom.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  xmlns:tools="http://schemas.android.com/tools"
+  android:id="@+id/md_colorChooserCustomFrame"
+  android:layout_width="match_parent"
+  android:layout_height="wrap_content"
+  android:orientation="vertical"
+  android:paddingBottom="@dimen/md_title_frame_margin_bottom"
+  android:paddingTop="@dimen/md_title_frame_margin_bottom">
+
+  <View
+    android:id="@+id/md_colorIndicator"
+    android:layout_width="match_parent"
+    android:layout_height="120dp"
+    tools:background="@color/md_material_blue_600" />
+
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center_horizontal"
+    android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+    android:gravity="center">
+
+    <TextView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_marginEnd="2dp"
+      android:layout_marginRight="2dp"
+      android:digits="0123456789abcdefABCDEF"
+      android:text="#"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_title_textsize"
+      tools:ignore="HardcodedText,TextViewEdits" />
+
+    <EditText
+      android:id="@+id/md_hexInput"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:digits="0123456789abcdefABCDEF"
+      android:focusable="true"
+      android:hint="FF0099CC"
+      android:textColor="?colorTextPrimary"
+      android:textColorHint="?colorTextSecondary"
+      android:textSize="@dimen/md_title_textsize"
+      tools:ignore="HardcodedText" />
+
+  </LinearLayout>
+
+  <RelativeLayout
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+    android:paddingLeft="@dimen/md_dialog_frame_margin"
+    android:paddingRight="@dimen/md_dialog_frame_margin">
+
+    <!-- Alpha -->
+
+    <TextView
+      android:id="@+id/md_colorALabel"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorA"
+      android:layout_alignTop="@+id/md_colorA"
+      android:layout_marginEnd="4dp"
+      android:layout_marginRight="4dp"
+      android:text="A"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <SeekBar
+      android:id="@+id/md_colorA"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+      android:layout_toEndOf="@+id/md_colorALabel"
+      android:layout_toLeftOf="@+id/md_colorAValue"
+      android:layout_toRightOf="@+id/md_colorALabel"
+      android:layout_toStartOf="@+id/md_colorAValue"
+      android:focusable="true"
+      android:max="255" />
+
+    <TextView
+      android:id="@+id/md_colorAValue"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorA"
+      android:layout_alignParentEnd="true"
+      android:layout_alignParentRight="true"
+      android:layout_alignTop="@+id/md_colorA"
+      android:layout_marginLeft="4dp"
+      android:layout_marginStart="4dp"
+      android:gravity="center"
+      android:minWidth="24dp"
+      android:text="0"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <!-- Red -->
+
+    <TextView
+      android:id="@+id/md_colorRLabel"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorR"
+      android:layout_alignTop="@+id/md_colorR"
+      android:layout_marginEnd="4dp"
+      android:layout_marginRight="4dp"
+      android:text="R"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <SeekBar
+      android:id="@+id/md_colorR"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_below="@+id/md_colorA"
+      android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+      android:layout_toEndOf="@+id/md_colorRLabel"
+      android:layout_toLeftOf="@+id/md_colorRValue"
+      android:layout_toRightOf="@+id/md_colorRLabel"
+      android:layout_toStartOf="@+id/md_colorRValue"
+      android:focusable="true"
+      android:max="255" />
+
+    <TextView
+      android:id="@+id/md_colorRValue"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorR"
+      android:layout_alignParentEnd="true"
+      android:layout_alignParentRight="true"
+      android:layout_alignTop="@+id/md_colorR"
+      android:layout_marginLeft="4dp"
+      android:layout_marginStart="4dp"
+      android:gravity="center"
+      android:minWidth="24dp"
+      android:text="0"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <!-- Green -->
+
+    <TextView
+      android:id="@+id/md_colorGLabel"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorG"
+      android:layout_alignTop="@+id/md_colorG"
+      android:layout_marginEnd="4dp"
+      android:layout_marginRight="4dp"
+      android:text="G"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <SeekBar
+      android:id="@+id/md_colorG"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_below="@+id/md_colorR"
+      android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+      android:layout_toEndOf="@+id/md_colorGLabel"
+      android:layout_toLeftOf="@+id/md_colorGValue"
+      android:layout_toRightOf="@+id/md_colorGLabel"
+      android:layout_toStartOf="@+id/md_colorGValue"
+      android:focusable="true"
+      android:max="255" />
+
+    <TextView
+      android:id="@+id/md_colorGValue"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorG"
+      android:layout_alignParentEnd="true"
+      android:layout_alignParentRight="true"
+      android:layout_alignTop="@+id/md_colorG"
+      android:layout_marginLeft="4dp"
+      android:layout_marginStart="4dp"
+      android:gravity="center"
+      android:minWidth="24dp"
+      android:text="0"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <!-- Blue -->
+
+    <TextView
+      android:id="@+id/md_colorBLabel"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorB"
+      android:layout_alignTop="@+id/md_colorB"
+      android:layout_marginEnd="4dp"
+      android:layout_marginRight="4dp"
+      android:text="B"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+    <SeekBar
+      android:id="@+id/md_colorB"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_below="@+id/md_colorG"
+      android:layout_marginTop="@dimen/md_title_frame_margin_bottom"
+      android:layout_toEndOf="@+id/md_colorBLabel"
+      android:layout_toLeftOf="@+id/md_colorBValue"
+      android:layout_toRightOf="@+id/md_colorBLabel"
+      android:layout_toStartOf="@+id/md_colorBValue"
+      android:max="255" />
+
+    <TextView
+      android:id="@+id/md_colorBValue"
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_alignBottom="@+id/md_colorB"
+      android:layout_alignParentEnd="true"
+      android:layout_alignParentRight="true"
+      android:layout_alignTop="@+id/md_colorB"
+      android:layout_marginLeft="4dp"
+      android:layout_marginStart="4dp"
+      android:gravity="center"
+      android:minWidth="24dp"
+      android:text="0"
+      android:textColor="?colorTextPrimary"
+      android:textSize="@dimen/md_content_textsize"
+      tools:ignore="HardcodedText" />
+
+  </RelativeLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_colorchooser_presets.xml b/app/src/main/res/layout/dialog_colorchooser_presets.xml
new file mode 100644
index 000000000..5efc0a4ec
--- /dev/null
+++ b/app/src/main/res/layout/dialog_colorchooser_presets.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.afollestad.materialdialogs.color.FillGridView xmlns:android="http://schemas.android.com/apk/res/android"
+  android:id="@+id/md_grid"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:clipToPadding="false"
+  android:columnWidth="@dimen/colorchooser_circlesize"
+  android:gravity="center"
+  android:horizontalSpacing="8dp"
+  android:numColumns="auto_fit"
+  android:orientation="vertical"
+  android:padding="16dp"
+  android:stretchMode="columnWidth"
+  android:verticalSpacing="8dp" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_history.xml b/app/src/main/res/layout/layout_history.xml
index e42496899..2a35012d5 100644
--- a/app/src/main/res/layout/layout_history.xml
+++ b/app/src/main/res/layout/layout_history.xml
@@ -1,57 +1,59 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/card_panel"
-  style="?attr/cardStyle"
   android:layout_width="fill_parent"
-  android:layout_height="fill_parent"
-  android:layout_marginBottom="16dp"
-  android:layout_marginLeft="16dp"
-  android:layout_marginRight="16dp"
-  android:layout_marginTop="16dp"
-  app:cardBackgroundColor="?attr/colorBackgroundCard">
-
-  <LinearLayout
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+  android:layout_height="fill_parent">
+
+  <android.support.v7.widget.CardView
+    style="?attr/cardStyle"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:layout_marginBottom="16dp"
+    android:layout_marginLeft="16dp"
+    android:layout_marginRight="16dp"
+    android:layout_marginTop="16dp"
+    app:cardBackgroundColor="?attr/colorBackgroundCard">
 
     <LinearLayout
       android:layout_width="match_parent"
-      android:layout_height="48dp"
-      android:orientation="horizontal">
-
-      <TextView
-        android:layout_width="0dip"
-        android:layout_height="match_parent"
-        android:layout_weight="1"
-        android:gravity="center_vertical|fill_horizontal"
-        android:paddingLeft="16dp"
-        android:paddingRight="16dp"
-        android:text="@string/label_input_history"
-        android:textAppearance="@style/TextAppearance.AppCompat.Body2"
-        android:textColor="?attr/colorForegroundSecondary" />
+      android:layout_height="match_parent"
+      android:orientation="vertical">
 
-      <android.support.v7.widget.AppCompatImageView
-        android:id="@+id/close"
-        android:layout_width="48dp"
+      <LinearLayout
+        android:layout_width="match_parent"
         android:layout_height="48dp"
-        android:layout_gravity="top"
-        android:background="?attr/selectableItemBackgroundBorderless"
-        android:padding="12dp"
-        android:scaleType="fitXY"
-        app:srcCompat="@drawable/ic_close"
-        app:tint="?attr/colorForegroundSecondary" />
-
+        android:orientation="horizontal">
+
+        <TextView
+          android:layout_width="0dip"
+          android:layout_height="match_parent"
+          android:layout_weight="1"
+          android:gravity="center_vertical|fill_horizontal"
+          android:paddingLeft="16dp"
+          android:paddingRight="16dp"
+          android:text="@string/label_input_history"
+          android:textAppearance="@style/TextAppearance.AppCompat.Body2"
+          android:textColor="?attr/colorForegroundSecondary" />
+
+        <android.support.v7.widget.AppCompatImageView
+          android:id="@+id/send"
+          android:layout_width="48dp"
+          android:layout_height="48dp"
+          android:layout_gravity="top"
+          android:background="?attr/selectableItemBackgroundBorderless"
+          android:padding="12dp"
+          android:scaleType="fitXY"
+          app:srcCompat="@drawable/ic_close"
+          app:tint="?attr/colorForegroundSecondary" />
+      </LinearLayout>
+
+      <android.support.v7.widget.RecyclerView
+        android:id="@+id/msg_history"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        tools:listitem="@layout/widget_history_message" />
     </LinearLayout>
-
-    <android.support.v7.widget.RecyclerView
-      android:id="@+id/msg_history"
-      android:layout_width="fill_parent"
-      android:layout_height="fill_parent"
-      tools:listitem="@layout/widget_history_message" />
-
-  </LinearLayout>
-
-</android.support.v7.widget.CardView>
\ No newline at end of file
+  </android.support.v7.widget.CardView>
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_formatting.xml b/app/src/main/res/layout/widget_formatting.xml
index e619482d9..ba916fcea 100644
--- a/app/src/main/res/layout/widget_formatting.xml
+++ b/app/src/main/res/layout/widget_formatting.xml
@@ -76,7 +76,7 @@
       app:tint="?colorControlNormal" />
 
     <View
-      android:id="@+id/ic_format_foreground_preview"
+      android:id="@+id/action_format_foreground_preview"
       android:layout_width="match_parent"
       android:layout_height="4dp"
       android:layout_gravity="center_horizontal|bottom"
@@ -105,7 +105,7 @@
       app:tint="?colorControlNormal" />
 
     <View
-      android:id="@+id/ic_format_background_preview"
+      android:id="@+id/action_format_background_preview"
       android:layout_width="match_parent"
       android:layout_height="4dp"
       android:layout_gravity="center_horizontal|bottom"
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 26f6295db..1ae9cfc6e 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -8,9 +8,12 @@
   <string name="connection_service_description">Hält eine Verbindung zum Core, um Benachrichtigungen und die Übertragung von Nachrichten zu ermöglichen</string>
 
   <string name="label_about">Über</string>
+  <string name="label_back">Zurück</string>
   <string name="label_buffer_name">Chatname</string>
   <string name="label_cancel">Abbrechen</string>
   <string name="label_close">Schließen</string>
+  <string name="label_colors_custom">Anpassen</string>
+  <string name="label_colors_mirc">mIRC</string>
   <string name="label_connect">Verbinden</string>
   <string name="label_contributors">Mitwirkende</string>
   <string name="label_copy">Kopieren</string>
@@ -29,12 +32,13 @@
   <string name="label_license">Lizenz</string>
   <string name="label_new_account">Account hinzufügen</string>
   <string name="label_no">Nein</string>
+  <string name="label_reset">Zurücksetzen</string>
   <string name="label_open">Öffnen</string>
   <string name="label_part">Verlassen</string>
   <string name="label_placeholder">Nachricht schreiben…</string>
   <string name="label_rename">Umbenennen</string>
   <string name="label_save">Speichern</string>
-  <string name="label_select_multiple">Auswählen</string>
+  <string name="label_select">Auswählen</string>
   <string name="label_settings">Einstellungen</string>
   <string name="label_share">Teilen</string>
   <string name="label_share_crashreport">Absturzbericht Teilen</string>
diff --git a/app/src/main/res/values-large/dimens.xml b/app/src/main/res/values-large/dimens.xml
new file mode 100644
index 000000000..e9d3194ca
--- /dev/null
+++ b/app/src/main/res/values-large/dimens.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <dimen name="colorchooser_circlesize">64dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 685b30aea..5535c2b7c 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -9,4 +9,6 @@
   <dimen name="max_content_width">480dp</dimen>
 
   <dimen name="button_corner_radius">2dp</dimen>
+
+  <dimen name="colorchooser_circlesize">56dp</dimen>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 91c25cd95..86c826ad9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,9 +8,12 @@
   <string name="connection_service_description">Keeps a connection to your core to allow for notifications, and to transmit messages.</string>
 
   <string name="label_about">About</string>
+  <string name="label_back">Back</string>
   <string name="label_buffer_name">Buffer Name</string>
   <string name="label_cancel">Cancel</string>
   <string name="label_close">Close</string>
+  <string name="label_colors_custom">Custom</string>
+  <string name="label_colors_mirc">mIRC</string>
   <string name="label_connect">Connect</string>
   <string name="label_contributors">Contributors</string>
   <string name="label_copy">Copy</string>
@@ -29,12 +32,13 @@
   <string name="label_license">License</string>
   <string name="label_new_account">New Account</string>
   <string name="label_no">No</string>
+  <string name="label_reset">Reset</string>
   <string name="label_open">Open</string>
   <string name="label_part">Part</string>
   <string name="label_placeholder">Write a message…</string>
   <string name="label_rename">Rename</string>
   <string name="label_save">Save</string>
-  <string name="label_select_multiple">Select</string>
+  <string name="label_select">Select</string>
   <string name="label_settings">Settings</string>
   <string name="label_share">Share</string>
   <string name="label_share_crashreport">Share Crash Report</string>
diff --git a/app/src/main/res/values/themes_base.xml b/app/src/main/res/values/themes_base.xml
index 8d37a28cd..90530c4b2 100644
--- a/app/src/main/res/values/themes_base.xml
+++ b/app/src/main/res/values/themes_base.xml
@@ -83,78 +83,91 @@
     <item name="mircColor13">#ff00ff</item>
     <item name="mircColor14">#808080</item>
     <item name="mircColor15">#c0c0c0</item>
+
     <item name="mircColor16">#470000</item>
-    <item name="mircColor17">#472100</item>
-    <item name="mircColor18">#474700</item>
-    <item name="mircColor19">#324700</item>
-    <item name="mircColor20">#004700</item>
-    <item name="mircColor21">#00472c</item>
-    <item name="mircColor22">#004747</item>
-    <item name="mircColor23">#002747</item>
-    <item name="mircColor24">#000047</item>
-    <item name="mircColor25">#2e0047</item>
-    <item name="mircColor26">#470047</item>
-    <item name="mircColor27">#47002a</item>
     <item name="mircColor28">#740000</item>
-    <item name="mircColor29">#743a00</item>
-    <item name="mircColor30">#747400</item>
-    <item name="mircColor31">#517400</item>
-    <item name="mircColor32">#007400</item>
-    <item name="mircColor33">#007449</item>
-    <item name="mircColor34">#007474</item>
-    <item name="mircColor35">#004074</item>
-    <item name="mircColor36">#000074</item>
-    <item name="mircColor37">#4b0074</item>
-    <item name="mircColor38">#740074</item>
-    <item name="mircColor39">#740045</item>
     <item name="mircColor40">#b50000</item>
-    <item name="mircColor41">#b56300</item>
-    <item name="mircColor42">#b5b500</item>
-    <item name="mircColor43">#7db500</item>
-    <item name="mircColor44">#00b500</item>
-    <item name="mircColor45">#00b571</item>
-    <item name="mircColor46">#00b5b5</item>
-    <item name="mircColor47">#0063b5</item>
-    <item name="mircColor48">#0000b5</item>
-    <item name="mircColor49">#7500b5</item>
-    <item name="mircColor50">#b500b5</item>
-    <item name="mircColor51">#b5006b</item>
     <item name="mircColor52">#ff0000</item>
-    <item name="mircColor53">#ff8c00</item>
-    <item name="mircColor54">#ffff00</item>
-    <item name="mircColor55">#b2ff00</item>
-    <item name="mircColor56">#00ff00</item>
-    <item name="mircColor57">#00ffa0</item>
-    <item name="mircColor58">#00ffff</item>
-    <item name="mircColor59">#008cff</item>
-    <item name="mircColor60">#0000ff</item>
-    <item name="mircColor61">#a500ff</item>
-    <item name="mircColor62">#ff00ff</item>
-    <item name="mircColor63">#ff0098</item>
     <item name="mircColor64">#ff5959</item>
-    <item name="mircColor65">#ffb459</item>
-    <item name="mircColor66">#ffff71</item>
-    <item name="mircColor67">#cfff60</item>
-    <item name="mircColor68">#6fff6f</item>
-    <item name="mircColor69">#65ffc9</item>
-    <item name="mircColor70">#6dffff</item>
-    <item name="mircColor71">#59b4ff</item>
-    <item name="mircColor72">#5959ff</item>
-    <item name="mircColor73">#c459ff</item>
-    <item name="mircColor74">#ff66ff</item>
-    <item name="mircColor75">#ff59bc</item>
     <item name="mircColor76">#ff9c9c</item>
+
+    <item name="mircColor17">#472100</item>
+    <item name="mircColor29">#743a00</item>
+    <item name="mircColor41">#b56300</item>
+    <item name="mircColor53">#ff8c00</item>
+    <item name="mircColor65">#ffb459</item>
     <item name="mircColor77">#ffd39c</item>
+
+    <item name="mircColor18">#474700</item>
+    <item name="mircColor30">#747400</item>
+    <item name="mircColor42">#b5b500</item>
+    <item name="mircColor54">#ffff00</item>
+    <item name="mircColor66">#ffff71</item>
     <item name="mircColor78">#ffff9c</item>
+
+    <item name="mircColor19">#324700</item>
+    <item name="mircColor31">#517400</item>
+    <item name="mircColor43">#7db500</item>
+    <item name="mircColor55">#b2ff00</item>
+    <item name="mircColor67">#cfff60</item>
     <item name="mircColor79">#e2ff9c</item>
+
+    <item name="mircColor20">#004700</item>
+    <item name="mircColor32">#007400</item>
+    <item name="mircColor44">#00b500</item>
+    <item name="mircColor56">#00ff00</item>
+    <item name="mircColor68">#6fff6f</item>
     <item name="mircColor80">#9cff9c</item>
+
+    <item name="mircColor21">#00472c</item>
+    <item name="mircColor33">#007449</item>
+    <item name="mircColor45">#00b571</item>
+    <item name="mircColor57">#00ffa0</item>
+    <item name="mircColor69">#65ffc9</item>
     <item name="mircColor81">#9cffdb</item>
+
+    <item name="mircColor22">#004747</item>
+    <item name="mircColor34">#007474</item>
+    <item name="mircColor46">#00b5b5</item>
+    <item name="mircColor58">#00ffff</item>
+    <item name="mircColor70">#6dffff</item>
     <item name="mircColor82">#9cffff</item>
+
+    <item name="mircColor23">#002747</item>
+    <item name="mircColor35">#004074</item>
+    <item name="mircColor47">#0063b5</item>
+    <item name="mircColor59">#008cff</item>
+    <item name="mircColor71">#59b4ff</item>
     <item name="mircColor83">#9cd3ff</item>
+
+    <item name="mircColor24">#000047</item>
+    <item name="mircColor36">#000074</item>
+    <item name="mircColor48">#0000b5</item>
+    <item name="mircColor60">#0000ff</item>
+    <item name="mircColor72">#5959ff</item>
     <item name="mircColor84">#9c9cff</item>
+
+    <item name="mircColor25">#2e0047</item>
+    <item name="mircColor37">#4b0074</item>
+    <item name="mircColor49">#7500b5</item>
+    <item name="mircColor61">#a500ff</item>
+    <item name="mircColor73">#c459ff</item>
     <item name="mircColor85">#dc9cff</item>
+
+    <item name="mircColor26">#470047</item>
+    <item name="mircColor38">#740074</item>
+    <item name="mircColor50">#b500b5</item>
+    <item name="mircColor62">#ff00ff</item>
+    <item name="mircColor74">#ff66ff</item>
     <item name="mircColor86">#ff9cff</item>
+
+    <item name="mircColor27">#47002a</item>
+    <item name="mircColor39">#740045</item>
+    <item name="mircColor51">#b5006b</item>
+    <item name="mircColor63">#ff0098</item>
+    <item name="mircColor75">#ff59bc</item>
     <item name="mircColor87">#ff94d3</item>
+
     <item name="mircColor88">#000000</item>
     <item name="mircColor89">#131313</item>
     <item name="mircColor90">#282828</item>
@@ -217,78 +230,91 @@
     <item name="mircColor13">#ff00ff</item>
     <item name="mircColor14">#808080</item>
     <item name="mircColor15">#c0c0c0</item>
+
     <item name="mircColor16">#470000</item>
-    <item name="mircColor17">#472100</item>
-    <item name="mircColor18">#474700</item>
-    <item name="mircColor19">#324700</item>
-    <item name="mircColor20">#004700</item>
-    <item name="mircColor21">#00472c</item>
-    <item name="mircColor22">#004747</item>
-    <item name="mircColor23">#002747</item>
-    <item name="mircColor24">#000047</item>
-    <item name="mircColor25">#2e0047</item>
-    <item name="mircColor26">#470047</item>
-    <item name="mircColor27">#47002a</item>
     <item name="mircColor28">#740000</item>
-    <item name="mircColor29">#743a00</item>
-    <item name="mircColor30">#747400</item>
-    <item name="mircColor31">#517400</item>
-    <item name="mircColor32">#007400</item>
-    <item name="mircColor33">#007449</item>
-    <item name="mircColor34">#007474</item>
-    <item name="mircColor35">#004074</item>
-    <item name="mircColor36">#000074</item>
-    <item name="mircColor37">#4b0074</item>
-    <item name="mircColor38">#740074</item>
-    <item name="mircColor39">#740045</item>
     <item name="mircColor40">#b50000</item>
-    <item name="mircColor41">#b56300</item>
-    <item name="mircColor42">#b5b500</item>
-    <item name="mircColor43">#7db500</item>
-    <item name="mircColor44">#00b500</item>
-    <item name="mircColor45">#00b571</item>
-    <item name="mircColor46">#00b5b5</item>
-    <item name="mircColor47">#0063b5</item>
-    <item name="mircColor48">#0000b5</item>
-    <item name="mircColor49">#7500b5</item>
-    <item name="mircColor50">#b500b5</item>
-    <item name="mircColor51">#b5006b</item>
     <item name="mircColor52">#ff0000</item>
-    <item name="mircColor53">#ff8c00</item>
-    <item name="mircColor54">#ffff00</item>
-    <item name="mircColor55">#b2ff00</item>
-    <item name="mircColor56">#00ff00</item>
-    <item name="mircColor57">#00ffa0</item>
-    <item name="mircColor58">#00ffff</item>
-    <item name="mircColor59">#008cff</item>
-    <item name="mircColor60">#0000ff</item>
-    <item name="mircColor61">#a500ff</item>
-    <item name="mircColor62">#ff00ff</item>
-    <item name="mircColor63">#ff0098</item>
     <item name="mircColor64">#ff5959</item>
-    <item name="mircColor65">#ffb459</item>
-    <item name="mircColor66">#ffff71</item>
-    <item name="mircColor67">#cfff60</item>
-    <item name="mircColor68">#6fff6f</item>
-    <item name="mircColor69">#65ffc9</item>
-    <item name="mircColor70">#6dffff</item>
-    <item name="mircColor71">#59b4ff</item>
-    <item name="mircColor72">#5959ff</item>
-    <item name="mircColor73">#c459ff</item>
-    <item name="mircColor74">#ff66ff</item>
-    <item name="mircColor75">#ff59bc</item>
     <item name="mircColor76">#ff9c9c</item>
+
+    <item name="mircColor17">#472100</item>
+    <item name="mircColor29">#743a00</item>
+    <item name="mircColor41">#b56300</item>
+    <item name="mircColor53">#ff8c00</item>
+    <item name="mircColor65">#ffb459</item>
     <item name="mircColor77">#ffd39c</item>
+
+    <item name="mircColor18">#474700</item>
+    <item name="mircColor30">#747400</item>
+    <item name="mircColor42">#b5b500</item>
+    <item name="mircColor54">#ffff00</item>
+    <item name="mircColor66">#ffff71</item>
     <item name="mircColor78">#ffff9c</item>
+
+    <item name="mircColor19">#324700</item>
+    <item name="mircColor31">#517400</item>
+    <item name="mircColor43">#7db500</item>
+    <item name="mircColor55">#b2ff00</item>
+    <item name="mircColor67">#cfff60</item>
     <item name="mircColor79">#e2ff9c</item>
+
+    <item name="mircColor20">#004700</item>
+    <item name="mircColor32">#007400</item>
+    <item name="mircColor44">#00b500</item>
+    <item name="mircColor56">#00ff00</item>
+    <item name="mircColor68">#6fff6f</item>
     <item name="mircColor80">#9cff9c</item>
+
+    <item name="mircColor21">#00472c</item>
+    <item name="mircColor33">#007449</item>
+    <item name="mircColor45">#00b571</item>
+    <item name="mircColor57">#00ffa0</item>
+    <item name="mircColor69">#65ffc9</item>
     <item name="mircColor81">#9cffdb</item>
+
+    <item name="mircColor22">#004747</item>
+    <item name="mircColor34">#007474</item>
+    <item name="mircColor46">#00b5b5</item>
+    <item name="mircColor58">#00ffff</item>
+    <item name="mircColor70">#6dffff</item>
     <item name="mircColor82">#9cffff</item>
+
+    <item name="mircColor23">#002747</item>
+    <item name="mircColor35">#004074</item>
+    <item name="mircColor47">#0063b5</item>
+    <item name="mircColor59">#008cff</item>
+    <item name="mircColor71">#59b4ff</item>
     <item name="mircColor83">#9cd3ff</item>
+
+    <item name="mircColor24">#000047</item>
+    <item name="mircColor36">#000074</item>
+    <item name="mircColor48">#0000b5</item>
+    <item name="mircColor60">#0000ff</item>
+    <item name="mircColor72">#5959ff</item>
     <item name="mircColor84">#9c9cff</item>
+
+    <item name="mircColor25">#2e0047</item>
+    <item name="mircColor37">#4b0074</item>
+    <item name="mircColor49">#7500b5</item>
+    <item name="mircColor61">#a500ff</item>
+    <item name="mircColor73">#c459ff</item>
     <item name="mircColor85">#dc9cff</item>
+
+    <item name="mircColor26">#470047</item>
+    <item name="mircColor38">#740074</item>
+    <item name="mircColor50">#b500b5</item>
+    <item name="mircColor62">#ff00ff</item>
+    <item name="mircColor74">#ff66ff</item>
     <item name="mircColor86">#ff9cff</item>
+
+    <item name="mircColor27">#47002a</item>
+    <item name="mircColor39">#740045</item>
+    <item name="mircColor51">#b5006b</item>
+    <item name="mircColor63">#ff0098</item>
+    <item name="mircColor75">#ff59bc</item>
     <item name="mircColor87">#ff94d3</item>
+
     <item name="mircColor88">#000000</item>
     <item name="mircColor89">#131313</item>
     <item name="mircColor90">#282828</item>
-- 
GitLab