From 077d57be77e70e37ff11493ac3385ea1b8d26140 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Sun, 25 Feb 2018 03:28:57 +0100
Subject: [PATCH] Properly read and write new format options

- Strikethrough
- Monospace
- Hex colors (only read)
---
 .../quasseldroid_ng/ui/chat/ChatActivity.kt   |  10 +-
 .../util/irc/format/IrcFormatDeserializer.kt  | 209 +++++++++++++---
 .../util/irc/format/IrcFormatSerializer.kt    |  65 ++++-
 .../util/irc/format/spans/IrcHexColorSpan.kt  |   9 +
 .../util/irc/format/spans/IrcMonospaceSpan.kt |   7 +
 .../irc/format/spans/IrcStrikethroughSpan.kt  |   7 +
 app/src/main/res/values/attrs.xml             | 115 +++++++--
 app/src/main/res/values/themes_base.xml       | 230 +++++++++++++++---
 8 files changed, 560 insertions(+), 92 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcHexColorSpan.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcMonospaceSpan.kt
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcStrikethroughSpan.kt

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 3b1f64e2e..dfa0c43ea 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.kt
@@ -1,5 +1,6 @@
 package de.kuschku.quasseldroid_ng.ui.chat
 
+import android.annotation.TargetApi
 import android.app.Activity
 import android.arch.lifecycle.Observer
 import android.arch.lifecycle.ViewModelProviders
@@ -38,6 +39,7 @@ import de.kuschku.quasseldroid_ng.util.helper.editApply
 import de.kuschku.quasseldroid_ng.util.helper.invoke
 import de.kuschku.quasseldroid_ng.util.helper.let
 import de.kuschku.quasseldroid_ng.util.helper.sharedPreferences
+import de.kuschku.quasseldroid_ng.util.irc.format.IrcFormatSerializer
 import de.kuschku.quasseldroid_ng.util.service.ServiceBoundActivity
 import de.kuschku.quasseldroid_ng.util.ui.MaterialContentLoadingProgressBar
 
@@ -72,6 +74,8 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
 
   private lateinit var backlogSettings: BacklogSettings
 
+  private lateinit var ircFormatSerializer: IrcFormatSerializer
+
   private val panelSlideListener: SlidingUpPanelLayout.PanelSlideListener = object :
     SlidingUpPanelLayout.PanelSlideListener {
     override fun onPanelSlide(panel: View?, slideOffset: Float) = Unit
@@ -102,6 +106,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     viewModel = ViewModelProviders.of(this)[QuasselViewModel::class.java]
     viewModel.setBackend(this.backend)
     backlogSettings = Settings.backlog(this)
+    ircFormatSerializer = IrcFormatSerializer(this)
 
     database = QuasselDatabase.Creator.init(application)
 
@@ -162,7 +167,9 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     viewModel.session { session ->
       viewModel.getBuffer().let { bufferId ->
         session.bufferSyncer?.bufferInfo(bufferId)?.also { bufferInfo ->
-          session.rpcHandler?.sendInput(bufferInfo, chatline.text.toString())
+          session.rpcHandler?.sendInput(
+            bufferInfo, ircFormatSerializer.toEscapeCodes(chatline.text)
+          )
         }
       }
     }
@@ -186,6 +193,7 @@ class ChatActivity : ServiceBoundActivity(), SharedPreferences.OnSharedPreferenc
     viewModel.setBuffer(savedInstanceState?.getInt("OPEN_BUFFER", -1) ?: -1)
   }
 
+  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
   override fun onRestoreInstanceState(savedInstanceState: Bundle?,
                                       persistentState: PersistableBundle?) {
     super.onRestoreInstanceState(savedInstanceState, persistentState)
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
index 4a7f9048b..c8e8c56c9 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatDeserializer.kt
@@ -25,20 +25,46 @@ package de.kuschku.quasseldroid_ng.util.irc.format
 import android.content.Context
 import android.text.SpannableStringBuilder
 import android.text.Spanned
-import android.text.style.UnderlineSpan
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.util.helper.styledAttributes
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcBackgroundColorSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcBoldSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcForegroundColorSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcItalicSpan
-import java.util.*
+import de.kuschku.quasseldroid_ng.util.irc.format.spans.*
 
 /**
  * A helper class to turn mIRC formatted Strings into Android’s SpannableStrings with the same
  * color and format codes
  */
 class IrcFormatDeserializer(private val context: Context) {
+  val mircColors = 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
+  ) {
+    IntArray(99) {
+      getColor(it, 0)
+    }
+  }
 
   /**
    * Function to handle mIRC formatted strings
@@ -53,7 +79,10 @@ class IrcFormatDeserializer(private val context: Context) {
     var bold: FormatDescription? = null
     var italic: FormatDescription? = null
     var underline: FormatDescription? = null
+    var strikethrough: FormatDescription? = null
+    var monospace: FormatDescription? = null
     var color: FormatDescription? = null
+    var hexColor: FormatDescription? = null
 
     // Iterating over every character
     var normalCount = 0
@@ -61,7 +90,7 @@ class IrcFormatDeserializer(private val context: Context) {
     while (i < str.length) {
       val character = str[i]
       when (character) {
-        CODE_BOLD      -> {
+        CODE_BOLD          -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -89,7 +118,7 @@ class IrcFormatDeserializer(private val context: Context) {
             italic = FormatDescription(plainText.length, format!!)
           }
         }
-        CODE_UNDERLINE -> {
+        CODE_UNDERLINE     -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -103,7 +132,35 @@ class IrcFormatDeserializer(private val context: Context) {
             underline = FormatDescription(plainText.length, format!!)
           }
         }
-        CODE_COLOR     -> {
+        CODE_STRIKETHROUGH -> {
+          plainText.append(str.substring(i - normalCount, i))
+          normalCount = 0
+
+          // If there is an element on stack with the same code, close it
+          if (strikethrough != null) {
+            if (colorize) strikethrough.apply(plainText, plainText.length)
+            strikethrough = null
+            // Otherwise create a new one
+          } else {
+            val format = fromId(character)
+            strikethrough = FormatDescription(plainText.length, format!!)
+          }
+        }
+        CODE_MONOSPACE     -> {
+          plainText.append(str.substring(i - normalCount, i))
+          normalCount = 0
+
+          // If there is an element on stack with the same code, close it
+          if (monospace != null) {
+            if (colorize) monospace.apply(plainText, plainText.length)
+            monospace = null
+            // Otherwise create a new one
+          } else {
+            val format = fromId(character)
+            monospace = FormatDescription(plainText.length, format!!)
+          }
+        }
+        CODE_COLOR         -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -140,7 +197,28 @@ class IrcFormatDeserializer(private val context: Context) {
             color = null
           }
         }
-        CODE_SWAP      -> {
+        CODE_HEXCOLOR      -> {
+          plainText.append(str.substring(i - normalCount, i))
+          normalCount = 0
+
+          val colorStart = i + 1
+          val colorEnd = findEndOfHexNumber(str, colorStart)
+          // If we have a foreground element
+          if (colorEnd > colorStart) {
+            val foreground = readHexNumber(str, colorStart, colorEnd)
+            // Add new format
+            hexColor = FormatDescription(plainText.length, ColorHexFormat(foreground))
+
+            // i points in front of the next character
+            i = colorEnd - 1
+
+            // Otherwise assume this is a closing tag
+          } else if (hexColor != null) {
+            if (colorize) hexColor.apply(plainText, plainText.length)
+            hexColor = null
+          }
+        }
+        CODE_SWAP          -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -152,7 +230,7 @@ class IrcFormatDeserializer(private val context: Context) {
             )
           }
         }
-        CODE_RESET     -> {
+        CODE_RESET         -> {
           plainText.append(str.substring(i - normalCount, i))
           normalCount = 0
 
@@ -224,7 +302,7 @@ class IrcFormatDeserializer(private val context: Context) {
 
   private class UnderlineIrcFormat : IrcFormat {
     override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
-      editable.setSpan(UnderlineSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+      editable.setSpan(IrcUnderlineSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
     }
 
     override fun id(): Byte {
@@ -232,6 +310,26 @@ class IrcFormatDeserializer(private val context: Context) {
     }
   }
 
+  private class StrikethroughIrcFormat : IrcFormat {
+    override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
+      editable.setSpan(IrcStrikethroughSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+    }
+
+    override fun id(): Byte {
+      return CODE_STRIKETHROUGH.toByte()
+    }
+  }
+
+  private class MonospaceIrcFormat : IrcFormat {
+    override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
+      editable.setSpan(IrcMonospaceSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+    }
+
+    override fun id(): Byte {
+      return CODE_MONOSPACE.toByte()
+    }
+  }
+
   private class BoldIrcFormat : IrcFormat {
     override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
       editable.setSpan(IrcBoldSpan(), from, to, Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
@@ -242,29 +340,32 @@ class IrcFormatDeserializer(private val context: Context) {
     }
   }
 
-  private inner class ColorIrcFormat(val foreground: Byte, val background: Byte) : IrcFormat {
+  private inner class ColorHexFormat(val color: Int) : IrcFormat {
 
     override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
-      val mircColors = context.theme.styledAttributes(
-        R.attr.mircColor0, R.attr.mircColor1, R.attr.mircColor2, R.attr.mircColor3,
-        R.attr.mircColor4, R.attr.mircColor5, R.attr.mircColor6, R.attr.mircColor7,
-        R.attr.mircColor8, R.attr.mircColor9, R.attr.mircColorA, R.attr.mircColorB,
-        R.attr.mircColorC, R.attr.mircColorD, R.attr.mircColorE, R.attr.mircColorF
-      ) {
-        IntArray(16) {
-          getColor(it, 0)
-        }
-      }
+      editable.setSpan(
+        IrcHexColorSpan(color), from, to,
+        Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+      )
+    }
 
-      if (foreground.toInt() != -1 && foreground.toInt() != 99) {
+    override fun id(): Byte {
+      return CODE_HEXCOLOR.toByte()
+    }
+  }
+
+  private inner class ColorIrcFormat(val foreground: Byte, val background: Byte) : IrcFormat {
+
+    override fun applyTo(editable: SpannableStringBuilder, from: Int, to: Int) {
+      if (foreground.toInt() > 0 && foreground.toInt() < mircColors.size) {
         editable.setSpan(
-          IrcForegroundColorSpan(foreground.toInt(), mircColors[foreground % 16]), from, to,
+          IrcForegroundColorSpan(foreground.toInt(), mircColors[foreground.toInt()]), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
-      if (background.toInt() != -1 && background.toInt() != 99) {
+      if (background.toInt() > 0 && background.toInt() < mircColors.size) {
         editable.setSpan(
-          IrcBackgroundColorSpan(background.toInt(), mircColors[background % 16]), from, to,
+          IrcBackgroundColorSpan(background.toInt(), mircColors[background.toInt()]), from, to,
           Spanned.SPAN_INCLUSIVE_EXCLUSIVE
         )
       }
@@ -282,8 +383,11 @@ class IrcFormatDeserializer(private val context: Context) {
   companion object {
     val CODE_BOLD = 0x02.toChar()
     val CODE_COLOR = 0x03.toChar()
+    val CODE_HEXCOLOR = 0x04.toChar()
     val CODE_ITALIC = 0x1D.toChar()
     val CODE_UNDERLINE = 0x1F.toChar()
+    val CODE_STRIKETHROUGH = 0x1E.toChar()
+    val CODE_MONOSPACE = 0x11.toChar()
     val CODE_SWAP = 0x16.toChar()
     val CODE_RESET = 0x0F.toChar()
 
@@ -300,7 +404,23 @@ class IrcFormatDeserializer(private val context: Context) {
       return if (result.isEmpty())
         -1
       else
-        Integer.parseInt(result, 10).toByte()
+        result.toByteOrNull(10) ?: -1
+    }
+
+    /**
+     * Try to read a number from a String in specified bounds
+     *
+     * @param str   String to be read from
+     * @param start Start index (inclusive)
+     * @param end   End index (exclusive)
+     * @return The byte represented by the digits read from the string
+     */
+    fun readHexNumber(str: String, start: Int, end: Int): Int {
+      val result = str.substring(start, end)
+      return if (result.isEmpty())
+        -1
+      else
+        result.toIntOrNull(16) ?: -1
     }
 
     /**
@@ -309,7 +429,7 @@ class IrcFormatDeserializer(private val context: Context) {
      * @return Index of first character that is not a digit
      */
     private fun findEndOfNumber(str: String, start: Int): Int {
-      val validCharCodes = HashSet(Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9'))
+      val validCharCodes = setOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
       val searchFrame = str.substring(start)
       var i = 0
       while (i < 2 && i < searchFrame.length) {
@@ -321,11 +441,34 @@ class IrcFormatDeserializer(private val context: Context) {
       return start + i
     }
 
+    /**
+     * @param str   String to be searched in
+     * @param start Start position (inclusive)
+     * @return Index of first character that is not a digit
+     */
+    private fun findEndOfHexNumber(str: String, start: Int): Int {
+      val validCharCodes = setOf(
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'a', 'b',
+        'c', 'd', 'e', 'f'
+      )
+      val searchFrame = str.substring(start)
+      var i = 0
+      while (i < 6 && i < searchFrame.length) {
+        if (!validCharCodes.contains(searchFrame[i])) {
+          break
+        }
+        i++
+      }
+      return start + i
+    }
+
     private fun fromId(id: Char) = when (id) {
-      CODE_BOLD      -> BoldIrcFormat()
-      CODE_ITALIC    -> ItalicIrcFormat()
-      CODE_UNDERLINE -> UnderlineIrcFormat()
-      else           -> null
+      CODE_BOLD          -> BoldIrcFormat()
+      CODE_ITALIC        -> ItalicIrcFormat()
+      CODE_UNDERLINE     -> UnderlineIrcFormat()
+      CODE_STRIKETHROUGH -> StrikethroughIrcFormat()
+      CODE_MONOSPACE     -> MonospaceIrcFormat()
+      else               -> null
     }
   }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatSerializer.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatSerializer.kt
index 5914434b0..70d7c4348 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatSerializer.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/IrcFormatSerializer.kt
@@ -2,19 +2,44 @@ package de.kuschku.quasseldroid_ng.util.irc.format
 
 import android.content.Context
 import android.text.Spanned
-import android.text.style.BackgroundColorSpan
-import android.text.style.CharacterStyle
-import android.text.style.ForegroundColorSpan
-import android.text.style.UnderlineSpan
+import android.text.style.*
 import de.kuschku.quasseldroid_ng.R
 import de.kuschku.quasseldroid_ng.util.helper.styledAttributes
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcBackgroundColorSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcBoldSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcForegroundColorSpan
-import de.kuschku.quasseldroid_ng.util.irc.format.spans.IrcItalicSpan
+import de.kuschku.quasseldroid_ng.util.irc.format.spans.*
 import java.util.*
 
 class IrcFormatSerializer internal constructor(private val context: Context) {
+  val mircColors = 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
+  ) {
+    IntArray(99) {
+      getColor(it, 0)
+    }
+  }
 
   fun toEscapeCodes(text: Spanned): String {
     val out = StringBuilder()
@@ -30,6 +55,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
     var bold = false
     var underline = false
     var italic = false
+    var strikethrough = false
+    var monospace = false
 
     var i = start
     while (i < end) {
@@ -41,6 +68,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
       var afterBold = false
       var afterUnderline = false
       var afterItalic = false
+      var afterStrikethrough = false
+      var afterMonospace = false
 
       for (aStyle in style) {
         if (text.getSpanFlags(aStyle) and Spanned.SPAN_COMPOSING != 0)
@@ -52,14 +81,18 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
           afterItalic = true
         } else if (aStyle is UnderlineSpan) {
           afterUnderline = true
+        } else if (aStyle is StrikethroughSpan) {
+          afterStrikethrough = true
+        } else if (aStyle is IrcMonospaceSpan) {
+          afterMonospace = true
         } else if (aStyle is IrcForegroundColorSpan) {
           afterForeground = aStyle.mircColor
         } else if (aStyle is IrcBackgroundColorSpan) {
           afterBackground = aStyle.mircColor
         } else if (aStyle is ForegroundColorSpan) {
-          afterForeground = 0
+          afterForeground = -1
         } else if (aStyle is BackgroundColorSpan) {
-          afterBackground = 0
+          afterBackground = -1
         }
       }
 
@@ -75,6 +108,14 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
         out.append(CODE_ITALIC)
       }
 
+      if (afterStrikethrough != strikethrough) {
+        out.append(CODE_STRIKETHROUGH)
+      }
+
+      if (afterMonospace != monospace) {
+        out.append(CODE_MONOSPACE)
+      }
+
       if (afterForeground != foreground || afterBackground != background) {
         if (afterForeground == background && afterBackground == foreground) {
           out.append(CODE_SWAP)
@@ -124,6 +165,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
       bold = afterBold
       italic = afterItalic
       underline = afterUnderline
+      strikethrough = afterStrikethrough
+      monospace = afterMonospace
       background = afterBackground
       foreground = afterForeground
       i = next
@@ -138,6 +181,8 @@ class IrcFormatSerializer internal constructor(private val context: Context) {
     val CODE_COLOR: Char = 0x03.toChar()
     val CODE_ITALIC: Char = 0x1D.toChar()
     val CODE_UNDERLINE: Char = 0x1F.toChar()
+    val CODE_STRIKETHROUGH = 0x1E.toChar()
+    val CODE_MONOSPACE = 0x11.toChar()
     val CODE_SWAP: Char = 0x16.toChar()
     val CODE_RESET: Char = 0x0F.toChar()
   }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcHexColorSpan.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcHexColorSpan.kt
new file mode 100644
index 000000000..ef9e33e96
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcHexColorSpan.kt
@@ -0,0 +1,9 @@
+package de.kuschku.quasseldroid_ng.util.irc.format.spans
+
+import android.support.annotation.ColorInt
+import android.text.style.ForegroundColorSpan
+
+class IrcHexColorSpan(@ColorInt color: Int) : ForegroundColorSpan(color),
+                                              Copyable<IrcHexColorSpan> {
+  override fun copy() = IrcHexColorSpan(foregroundColor)
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcMonospaceSpan.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcMonospaceSpan.kt
new file mode 100644
index 000000000..93be73ccf
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcMonospaceSpan.kt
@@ -0,0 +1,7 @@
+package de.kuschku.quasseldroid_ng.util.irc.format.spans
+
+import android.text.style.TypefaceSpan
+
+class IrcMonospaceSpan : TypefaceSpan("monospace"), Copyable<IrcMonospaceSpan> {
+  override fun copy() = IrcMonospaceSpan()
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcStrikethroughSpan.kt b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcStrikethroughSpan.kt
new file mode 100644
index 000000000..7223229f3
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/irc/format/spans/IrcStrikethroughSpan.kt
@@ -0,0 +1,7 @@
+package de.kuschku.quasseldroid_ng.util.irc.format.spans
+
+import android.text.style.StrikethroughSpan
+
+class IrcStrikethroughSpan : StrikethroughSpan(), Copyable<IrcStrikethroughSpan> {
+  override fun copy() = IrcStrikethroughSpan()
+}
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 2d2d7219d..0af8785e4 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -18,22 +18,105 @@
   <attr name="senderColorF" format="color" />
 
   <!-- mirc colors -->
-  <attr name="mircColor0" format="color" />
-  <attr name="mircColor1" format="color" />
-  <attr name="mircColor2" format="color" />
-  <attr name="mircColor3" format="color" />
-  <attr name="mircColor4" format="color" />
-  <attr name="mircColor5" format="color" />
-  <attr name="mircColor6" format="color" />
-  <attr name="mircColor7" format="color" />
-  <attr name="mircColor8" format="color" />
-  <attr name="mircColor9" format="color" />
-  <attr name="mircColorA" format="color" />
-  <attr name="mircColorB" format="color" />
-  <attr name="mircColorC" format="color" />
-  <attr name="mircColorD" format="color" />
-  <attr name="mircColorE" format="color" />
-  <attr name="mircColorF" format="color" />
+  <attr name="mircColor00" format="color" />
+  <attr name="mircColor01" format="color" />
+  <attr name="mircColor02" format="color" />
+  <attr name="mircColor03" format="color" />
+  <attr name="mircColor04" format="color" />
+  <attr name="mircColor05" format="color" />
+  <attr name="mircColor06" format="color" />
+  <attr name="mircColor07" format="color" />
+  <attr name="mircColor08" format="color" />
+  <attr name="mircColor09" format="color" />
+  <attr name="mircColor10" format="color" />
+  <attr name="mircColor11" format="color" />
+  <attr name="mircColor12" format="color" />
+  <attr name="mircColor13" format="color" />
+  <attr name="mircColor14" format="color" />
+  <attr name="mircColor15" format="color" />
+  <attr name="mircColor16" format="color" />
+  <attr name="mircColor17" format="color" />
+  <attr name="mircColor18" format="color" />
+  <attr name="mircColor19" format="color" />
+  <attr name="mircColor20" format="color" />
+  <attr name="mircColor21" format="color" />
+  <attr name="mircColor22" format="color" />
+  <attr name="mircColor23" format="color" />
+  <attr name="mircColor24" format="color" />
+  <attr name="mircColor25" format="color" />
+  <attr name="mircColor26" format="color" />
+  <attr name="mircColor27" format="color" />
+  <attr name="mircColor28" format="color" />
+  <attr name="mircColor29" format="color" />
+  <attr name="mircColor30" format="color" />
+  <attr name="mircColor31" format="color" />
+  <attr name="mircColor32" format="color" />
+  <attr name="mircColor33" format="color" />
+  <attr name="mircColor34" format="color" />
+  <attr name="mircColor35" format="color" />
+  <attr name="mircColor36" format="color" />
+  <attr name="mircColor37" format="color" />
+  <attr name="mircColor38" format="color" />
+  <attr name="mircColor39" format="color" />
+  <attr name="mircColor40" format="color" />
+  <attr name="mircColor41" format="color" />
+  <attr name="mircColor42" format="color" />
+  <attr name="mircColor43" format="color" />
+  <attr name="mircColor44" format="color" />
+  <attr name="mircColor45" format="color" />
+  <attr name="mircColor46" format="color" />
+  <attr name="mircColor47" format="color" />
+  <attr name="mircColor48" format="color" />
+  <attr name="mircColor49" format="color" />
+  <attr name="mircColor50" format="color" />
+  <attr name="mircColor51" format="color" />
+  <attr name="mircColor52" format="color" />
+  <attr name="mircColor53" format="color" />
+  <attr name="mircColor54" format="color" />
+  <attr name="mircColor55" format="color" />
+  <attr name="mircColor56" format="color" />
+  <attr name="mircColor57" format="color" />
+  <attr name="mircColor58" format="color" />
+  <attr name="mircColor59" format="color" />
+  <attr name="mircColor60" format="color" />
+  <attr name="mircColor61" format="color" />
+  <attr name="mircColor62" format="color" />
+  <attr name="mircColor63" format="color" />
+  <attr name="mircColor64" format="color" />
+  <attr name="mircColor65" format="color" />
+  <attr name="mircColor66" format="color" />
+  <attr name="mircColor67" format="color" />
+  <attr name="mircColor68" format="color" />
+  <attr name="mircColor69" format="color" />
+  <attr name="mircColor70" format="color" />
+  <attr name="mircColor71" format="color" />
+  <attr name="mircColor72" format="color" />
+  <attr name="mircColor73" format="color" />
+  <attr name="mircColor74" format="color" />
+  <attr name="mircColor75" format="color" />
+  <attr name="mircColor76" format="color" />
+  <attr name="mircColor77" format="color" />
+  <attr name="mircColor78" format="color" />
+  <attr name="mircColor79" format="color" />
+  <attr name="mircColor80" format="color" />
+  <attr name="mircColor81" format="color" />
+  <attr name="mircColor82" format="color" />
+  <attr name="mircColor83" format="color" />
+  <attr name="mircColor84" format="color" />
+  <attr name="mircColor85" format="color" />
+  <attr name="mircColor86" format="color" />
+  <attr name="mircColor87" format="color" />
+  <attr name="mircColor88" format="color" />
+  <attr name="mircColor89" format="color" />
+  <attr name="mircColor90" format="color" />
+  <attr name="mircColor91" format="color" />
+  <attr name="mircColor92" format="color" />
+  <attr name="mircColor93" format="color" />
+  <attr name="mircColor94" format="color" />
+  <attr name="mircColor95" format="color" />
+  <attr name="mircColor96" format="color" />
+  <attr name="mircColor97" format="color" />
+  <attr name="mircColor98" format="color" />
 
   <!-- Background and foreground colors for UI -->
   <attr name="colorForeground" format="color" />
diff --git a/app/src/main/res/values/themes_base.xml b/app/src/main/res/values/themes_base.xml
index 6a519ac9a..5f41e0ace 100644
--- a/app/src/main/res/values/themes_base.xml
+++ b/app/src/main/res/values/themes_base.xml
@@ -61,22 +61,105 @@
 
     <item name="cardStyle">@style/CardView.Dark</item>
 
-    <item name="mircColor0">#ffffff</item>
-    <item name="mircColor1">#000000</item>
-    <item name="mircColor2">#000080</item>
-    <item name="mircColor3">#008000</item>
-    <item name="mircColor4">#ff0000</item>
-    <item name="mircColor5">#800000</item>
-    <item name="mircColor6">#800080</item>
-    <item name="mircColor7">#ffa500</item>
-    <item name="mircColor8">#ffff00</item>
-    <item name="mircColor9">#00ff00</item>
-    <item name="mircColorA">#008080</item>
-    <item name="mircColorB">#00ffff</item>
-    <item name="mircColorC">#4169e1</item>
-    <item name="mircColorD">#ff00ff</item>
-    <item name="mircColorE">#808080</item>
-    <item name="mircColorF">#c0c0c0</item>
+    <item name="mircColor00">#ffffff</item>
+    <item name="mircColor01">#000000</item>
+    <item name="mircColor02">#000080</item>
+    <item name="mircColor03">#008000</item>
+    <item name="mircColor04">#ff0000</item>
+    <item name="mircColor05">#800000</item>
+    <item name="mircColor06">#800080</item>
+    <item name="mircColor07">#ffa500</item>
+    <item name="mircColor08">#ffff00</item>
+    <item name="mircColor09">#00ff00</item>
+    <item name="mircColor10">#008080</item>
+    <item name="mircColor11">#00ffff</item>
+    <item name="mircColor12">#4169e1</item>
+    <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="mircColor77">#ffd39c</item>
+    <item name="mircColor78">#ffff9c</item>
+    <item name="mircColor79">#e2ff9c</item>
+    <item name="mircColor80">#9cff9c</item>
+    <item name="mircColor81">#9cffdb</item>
+    <item name="mircColor82">#9cffff</item>
+    <item name="mircColor83">#9cd3ff</item>
+    <item name="mircColor84">#9c9cff</item>
+    <item name="mircColor85">#dc9cff</item>
+    <item name="mircColor86">#ff9cff</item>
+    <item name="mircColor87">#ff94d3</item>
+    <item name="mircColor88">#000000</item>
+    <item name="mircColor89">#131313</item>
+    <item name="mircColor90">#282828</item>
+    <item name="mircColor91">#363636</item>
+    <item name="mircColor92">#4d4d4d</item>
+    <item name="mircColor93">#656565</item>
+    <item name="mircColor94">#818181</item>
+    <item name="mircColor95">#9f9f9f</item>
+    <item name="mircColor96">#bcbcbc</item>
+    <item name="mircColor97">#e2e2e2</item>
+    <item name="mircColor98">#ffffff</item>
   </style>
 
   <style name="Theme.ChatTheme.Auto" parent="Theme.ChatTheme">
@@ -110,22 +193,105 @@
 
     <item name="cardStyle">@style/CardView.Light</item>
 
-    <item name="mircColor0">#ffffff</item>
-    <item name="mircColor1">#000000</item>
-    <item name="mircColor2">#000080</item>
-    <item name="mircColor3">#008000</item>
-    <item name="mircColor4">#ff0000</item>
-    <item name="mircColor5">#800000</item>
-    <item name="mircColor6">#800080</item>
-    <item name="mircColor7">#ffa500</item>
-    <item name="mircColor8">#ffff00</item>
-    <item name="mircColor9">#00ff00</item>
-    <item name="mircColorA">#008080</item>
-    <item name="mircColorB">#00ffff</item>
-    <item name="mircColorC">#4169e1</item>
-    <item name="mircColorD">#ff00ff</item>
-    <item name="mircColorE">#808080</item>
-    <item name="mircColorF">#c0c0c0</item>
+    <item name="mircColor00">#ffffff</item>
+    <item name="mircColor01">#000000</item>
+    <item name="mircColor02">#000080</item>
+    <item name="mircColor03">#008000</item>
+    <item name="mircColor04">#ff0000</item>
+    <item name="mircColor05">#800000</item>
+    <item name="mircColor06">#800080</item>
+    <item name="mircColor07">#ffa500</item>
+    <item name="mircColor08">#ffff00</item>
+    <item name="mircColor09">#00ff00</item>
+    <item name="mircColor10">#008080</item>
+    <item name="mircColor11">#00ffff</item>
+    <item name="mircColor12">#4169e1</item>
+    <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="mircColor77">#ffd39c</item>
+    <item name="mircColor78">#ffff9c</item>
+    <item name="mircColor79">#e2ff9c</item>
+    <item name="mircColor80">#9cff9c</item>
+    <item name="mircColor81">#9cffdb</item>
+    <item name="mircColor82">#9cffff</item>
+    <item name="mircColor83">#9cd3ff</item>
+    <item name="mircColor84">#9c9cff</item>
+    <item name="mircColor85">#dc9cff</item>
+    <item name="mircColor86">#ff9cff</item>
+    <item name="mircColor87">#ff94d3</item>
+    <item name="mircColor88">#000000</item>
+    <item name="mircColor89">#131313</item>
+    <item name="mircColor90">#282828</item>
+    <item name="mircColor91">#363636</item>
+    <item name="mircColor92">#4d4d4d</item>
+    <item name="mircColor93">#656565</item>
+    <item name="mircColor94">#818181</item>
+    <item name="mircColor95">#9f9f9f</item>
+    <item name="mircColor96">#bcbcbc</item>
+    <item name="mircColor97">#e2e2e2</item>
+    <item name="mircColor98">#ffffff</item>
   </style>
 
   <style name="Theme.ChatTheme.Light.Auto" parent="Theme.ChatTheme.Light">
-- 
GitLab