From 42fa2d263d1770dc81983373d9bc208e58e6233a Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Sat, 30 Jan 2016 20:42:08 +0100
Subject: [PATCH] Fixed a lot of bugs Improved UI to show buffer status Added
 notification manager

---
 app/build.gradle                              |  43 +-
 app/proguard-rules.pro                        |   7 +-
 .../java/de/kuschku/libquassel/Client.java    |   3 +-
 .../de/kuschku/libquassel/CoreConnection.java |   5 +-
 .../kuschku/libquassel/IProtocolHandler.java  |   6 +
 .../kuschku/libquassel/ProtocolHandler.java   |   8 +
 .../serializers/HeartbeatSerializer.java      |   3 +-
 .../kuschku/libquassel/localtypes/Buffer.java |   2 +-
 .../libquassel/localtypes/ChannelBuffer.java  |   4 +-
 .../localtypes/NotificationManager.java       |  65 +++
 .../libquassel/localtypes/QueryBuffer.java    |   6 +-
 .../libquassel/localtypes/StatusBuffer.java   |   4 +-
 .../backlogmanagers/BacklogFilter.java        |  46 ++-
 .../backlogmanagers/SimpleBacklogManager.java |   5 +-
 .../kuschku/libquassel/message/Message.java   |  20 +-
 .../serializers/SessionStateSerializer.java   |   3 +-
 .../objects/types/SessionState.java           |   7 +-
 .../primitives/types/BufferInfo.java          |  13 +
 .../BufferViewConfigSerializer.java           |   2 +-
 .../syncables/types/BufferViewConfig.java     | 131 ++++--
 .../libquassel/syncables/types/Identity.java  |   6 +-
 .../syncables/types/IrcChannel.java           |  11 +-
 .../libquassel/syncables/types/Network.java   |  10 +-
 .../quasseldroid_ng/ui/chat/ChatActivity.java | 378 +++++++++++++-----
 .../ui/chat/chatview/ChatMessageRenderer.java | 193 ++-------
 .../ui/chat/drawer/BufferItem.java            |  26 +-
 .../{ => drawer}/BufferViewConfigWrapper.java |  16 +-
 .../ui/chat/drawer/NetworkItem.java           |  48 ++-
 .../ui/editor/AdvancedEditor.java             | 116 ++++++
 .../quasseldroid_ng/ui/editor/BoldSpan.java   |  10 +
 .../ui/editor/FormattingHelper.java           | 114 ++++++
 .../quasseldroid_ng/ui/editor/ItalicSpan.java |  10 +
 .../quasseldroid_ng/ui/theme/ThemeUtil.java   | 139 +++++++
 .../de/kuschku/util/irc/IrcFormatHelper.java  |  12 +-
 .../util/observables/ContentComparable.java   |   3 +-
 .../lists/ObservableComparableSortedList.java |   4 +-
 .../lists/ObservableSortedList.java           |  16 +-
 app/src/main/res/layout/activity_chat.xml     |   2 +-
 app/src/main/res/layout/slider_main.xml       |  68 +---
 app/src/main/res/layout/widget_editor.xml     |  68 ++++
 app/src/main/res/menu/chat.xml                |  15 +
 app/src/main/res/menu/formatting.xml          |   5 +
 app/src/main/res/values/attrs.xml             |   1 +
 app/src/main/res/values/dimens.xml            |   1 +
 app/src/main/res/values/strings.xml           | 114 +-----
 app/src/main/res/values/styles.xml            |   2 +
 46 files changed, 1211 insertions(+), 560 deletions(-)
 rename app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/{ => drawer}/BufferViewConfigWrapper.java (88%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/BoldSpan.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/ItalicSpan.java
 create mode 100644 app/src/main/res/layout/widget_editor.xml
 create mode 100644 app/src/main/res/menu/chat.xml

diff --git a/app/build.gradle b/app/build.gradle
index c05a7e9d8..372d60648 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -46,6 +46,7 @@ if (versionPropsFile.canRead()) {
   throw new GradleException("Could not read version.properties!"+versionPropsFile.toString())
 }
 
+
 def rawVersionName = "0.2.0"
 
 android {
@@ -61,20 +62,9 @@ android {
     }
     buildTypes {
         release {
-            //minifyEnabled true
-            //shrinkResources true
-            //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
-
-            applicationVariants.all { variant ->
-                variant.outputs.each { output ->
-                    if ('assemble' in runTasks || 'assembleRelease' in runTasks || 'aR' in runTasks) {
-                        def fileName = output.outputFile.name
-                                .replace(".apk", String.format("-%s-build%d.apk", rawVersionName, versionCode))
-                                .replace("app-", "QuasselDroidNG-")
-                        output.outputFile = new File(output.outputFile.parent, fileName)
-                    }
-                }
-            }
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
     compileOptions {
@@ -93,6 +83,31 @@ android {
         exclude 'META-INF/dependencies.txt'
         exclude 'META-INF/LGPL2.1'
     }
+    lintOptions {
+        abortOnError false
+    }
+}
+
+def runTasks = gradle.startParameter.taskNames
+if ('assemble' in runTasks || 'assembleRelease' in runTasks || 'aR' in runTasks) {
+    android {
+        buildTypes {
+            release {
+                //minifyEnabled true
+                //shrinkResources true
+                //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+
+                applicationVariants.all { variant ->
+                    variant.outputs.each { output ->
+                        def fileName = output.outputFile.name
+                                .replace(".apk", String.format("-%s-build%d.apk", rawVersionName, versionBuild))
+                                .replace("app-", "QuasselDroidNG-")
+                        output.outputFile = new File(output.outputFile.parent, fileName)
+                    }
+                }
+            }
+        }
+    }
 }
 
 
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 59129d0e1..7f2953eaa 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -26,13 +26,16 @@
 -dontwarn sun.misc.Unsafe
 -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
 -dontwarn javax.annotation.processing.ProcessingEnvironment
+-dontwarn com.nineoldandroids.view.animation.AnimatorProxy
 
--keepclasseswithmembernames class **.libquassel.** {
+-keepclasseswithmembernames class de.kuschku.** {
     <methods>;
+    <fields>;
 }
 
--keepclassmembers class **.libquassel.** {
+-keepclassmembers class de.kuschku.** {
     <methods>;
+    <fields>;
 }
 
 
diff --git a/app/src/main/java/de/kuschku/libquassel/Client.java b/app/src/main/java/de/kuschku/libquassel/Client.java
index dc24c9aae..62fe02b78 100644
--- a/app/src/main/java/de/kuschku/libquassel/Client.java
+++ b/app/src/main/java/de/kuschku/libquassel/Client.java
@@ -54,7 +54,7 @@ public class Client {
     @NonNull
     private final BacklogManager backlogManager;
     @NonNull
-    private final NotificationManager notificationManager = new NotificationManager();
+    private final NotificationManager notificationManager = new NotificationManager(this);
     @NonNull
     private final BusProvider busProvider;
     private long lag;
@@ -197,6 +197,7 @@ public class Client {
 
     public void setState(@Nullable SessionState state) {
         this.state = state;
+        Log.e("DEBUG", String.valueOf(this.state));
     }
 
     @NonNull
diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
index 23387e781..71baec598 100644
--- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
+++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
@@ -281,12 +281,13 @@ public class CoreConnection {
                 assertNotNull(client);
 
                 while (running) {
-                    busProvider.dispatch(new Heartbeat(DateTime.now()));
+                    Heartbeat heartbeat = new Heartbeat(DateTime.now());
+                    Log.e("heartbeat", String.valueOf(heartbeat));
+                    busProvider.dispatch(heartbeat);
 
                     Thread.sleep(30 * 1000);
                 }
             } catch (InterruptedException e) {
-                e.printStackTrace();
             }
         }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
index ef0fdb7e7..203282cb7 100644
--- a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
+++ b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
@@ -2,6 +2,8 @@ package de.kuschku.libquassel;
 
 import android.support.annotation.NonNull;
 
+import de.kuschku.libquassel.functions.types.Heartbeat;
+import de.kuschku.libquassel.functions.types.HeartbeatReply;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.functions.types.InitRequestFunction;
 import de.kuschku.libquassel.functions.types.RpcCallFunction;
@@ -31,6 +33,10 @@ public interface IProtocolHandler {
 
     void onEvent(SessionInit message);
 
+    void onEvent(Heartbeat message);
+
+    void onEventMainThread(HeartbeatReply message);
+
     @NonNull
     Client getClient();
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java
index 6cafde2d2..82bfaae07 100644
--- a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java
+++ b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java
@@ -5,6 +5,8 @@ import android.util.Log;
 
 import org.joda.time.DateTime;
 
+import java.util.List;
+
 import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.events.HandshakeFailedEvent;
@@ -25,6 +27,7 @@ import de.kuschku.libquassel.objects.types.SessionInit;
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.syncables.SyncableRegistry;
 import de.kuschku.libquassel.syncables.types.BufferViewConfig;
+import de.kuschku.libquassel.syncables.types.Identity;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
 import de.kuschku.util.AndroidAssert;
 import de.kuschku.util.ReflectionUtils;
@@ -135,6 +138,9 @@ public class ProtocolHandler implements IProtocolHandler {
         for (int NetworkId : client.getState().NetworkIds) {
             client.sendInitRequest("Network", String.valueOf(NetworkId), true);
         }
+        for (Identity identity : client.getState().Identities) {
+            identity.init(null, busProvider, client);
+        }
         for (BufferInfo info : message.SessionState.BufferInfos) {
             final int initialBacklogCount = 10;
             client.getBacklogManager().requestBacklog(info.id, -1, -1, initialBacklogCount, 0);
@@ -146,6 +152,8 @@ public class ProtocolHandler implements IProtocolHandler {
     }
 
     public void onEventMainThread(@NonNull HeartbeatReply heartbeat) {
+        Log.e("heartbeatreply", String.valueOf(heartbeat));
+
         long roundtrip = DateTime.now().getMillis() - heartbeat.dateTime.getMillis();
         long lag = (long) (roundtrip * 0.5);
 
diff --git a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java
index ec2e4c59d..38d20cd72 100644
--- a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java
@@ -9,6 +9,7 @@ import java.util.List;
 
 import de.kuschku.libquassel.functions.FunctionType;
 import de.kuschku.libquassel.functions.types.Heartbeat;
+import de.kuschku.libquassel.primitives.QMetaType;
 import de.kuschku.libquassel.primitives.types.QVariant;
 
 import static de.kuschku.util.AndroidAssert.assertTrue;
@@ -30,7 +31,7 @@ public class HeartbeatSerializer implements FunctionSerializer<Heartbeat> {
     public List serialize(@NonNull Heartbeat data) {
         return Arrays.asList(
                 FunctionType.HEARTBEAT.id,
-                new QVariant<>(data.dateTime)
+                new QVariant<>(QMetaType.Type.QDateTime, data.dateTime)
         );
     }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java
index 40b7c73c8..6c61d0a3f 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java
@@ -12,5 +12,5 @@ public interface Buffer {
     @Nullable
     String getName();
 
-    boolean isActive();
+    BufferInfo.BufferStatus getStatus();
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
index 2865b1a63..41f855121 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
@@ -30,8 +30,8 @@ public class ChannelBuffer implements Buffer {
     }
 
     @Override
-    public boolean isActive() {
-        return channel != null;
+    public BufferInfo.BufferStatus getStatus() {
+        return channel == null ? BufferInfo.BufferStatus.OFFLINE : BufferInfo.BufferStatus.ONLINE;
     }
 
     @Nullable
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
index 5dd652996..9ae407ba0 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
@@ -2,17 +2,82 @@ package de.kuschku.libquassel.localtypes;
 
 import android.util.SparseArray;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.message.Message;
+import de.kuschku.libquassel.syncables.types.Identity;
+import de.kuschku.libquassel.syncables.types.Network;
 import de.kuschku.util.observables.lists.ObservableComparableSortedList;
 
 public class NotificationManager {
     private SparseArray<ObservableComparableSortedList<Message>> notifications = new SparseArray<>();
+    private List<HighlightRule> highlights = new ArrayList<>();
+    private Client client;
+
+    public NotificationManager(Client client) {
+        this.client = client;
+    }
 
     public ObservableComparableSortedList<Message> getNotifications(int bufferid) {
+        if (notifications.get(bufferid) == null)
+            notifications.put(bufferid, new ObservableComparableSortedList<>(Message.class));
+
         return notifications.get(bufferid);
     }
 
     public void init(int id) {
         notifications.put(id, new ObservableComparableSortedList<>(Message.class));
     }
+
+    public void receiveMessage(Message message) {
+        if (checkMessage(message)) {
+            getNotifications(message.bufferInfo.id).add(message);
+        }
+    }
+
+    public boolean checkMessage(Message message) {
+        Buffer buffer = client.getBuffer(message.bufferInfo.id);
+        if (buffer == null) return false;
+        Network network = client.getNetwork(buffer.getInfo().networkId);
+        if (network == null) return false;
+        Identity identity = client.getIdentity(network.getIdentityId());
+        if (identity == null) return false;
+
+        for (String nick : identity.getNicks()) {
+            if (message.content.contains(nick))
+                return true;
+        }
+        for (HighlightRule rule : highlights) {
+            if (rule.matches(message.content, buffer.getName()))
+                return true;
+        }
+        return false;
+    }
+
+    public void receiveMessages(List<Message> messages) {
+        for (Message message : messages) {
+            receiveMessage(message);
+        }
+    }
+
+    private class HighlightRule {
+        public final Pattern rule;
+        public final Pattern channelRule;
+        public final boolean invertChannelRule;
+        public final boolean caseSensitive;
+
+        public HighlightRule(String rule, String channelRule, boolean invertChannelRule, boolean caseSensitive) {
+            this.rule = rule.isEmpty() ? Pattern.compile(".*") : Pattern.compile(rule);
+            this.channelRule = channelRule.isEmpty() ? Pattern.compile(".*") : Pattern.compile(channelRule);
+            this.invertChannelRule = invertChannelRule;
+            this.caseSensitive = caseSensitive;
+        }
+
+        public boolean matches(String message, String channelName) {
+            return (invertChannelRule ^ channelRule.matcher(channelName).matches() && rule.matcher(message).matches());
+        }
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
index b64c6be50..c07356b14 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
@@ -30,8 +30,10 @@ public class QueryBuffer implements Buffer {
     }
 
     @Override
-    public boolean isActive() {
-        return user != null;
+    public BufferInfo.BufferStatus getStatus() {
+        return  (user == null) ?    BufferInfo.BufferStatus.OFFLINE :
+                (user.isAway()) ?   BufferInfo.BufferStatus.AWAY :
+                                    BufferInfo.BufferStatus.ONLINE;
     }
 
     @Nullable
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
index bc25d633c..c28a09c25 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
@@ -30,8 +30,8 @@ public class StatusBuffer implements Buffer {
     }
 
     @Override
-    public boolean isActive() {
-        return network.isConnected();
+    public BufferInfo.BufferStatus getStatus() {
+        return network.isConnected() ? BufferInfo.BufferStatus.ONLINE : BufferInfo.BufferStatus.OFFLINE;
     }
 
     @NonNull
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java
index 370099559..d36073bf8 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java
@@ -1,9 +1,9 @@
 package de.kuschku.libquassel.localtypes.backlogmanagers;
 
 import android.support.annotation.NonNull;
-import android.util.Log;
 
 import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -47,7 +47,7 @@ public class BacklogFilter implements UICallback {
         int id = -1;
         while (now.isAfter(earliestMessage)) {
             filtered.add(new Message(
-                    id,
+                    (int) DateTimeUtils.toJulianDay(now.getMillis()),
                     now,
                     Message.Type.DayChange,
                     new Message.Flags(false, false, false, false, false),
@@ -79,21 +79,22 @@ public class BacklogFilter implements UICallback {
         updateAdd();
     }
 
-    private void updateRemove() {
+    public void update() {
+        updateAdd();
+        updateRemove();
+    }
+
+    public void updateRemove() {
         for (Message message : unfiltered) {
-            if (filterItem(message) && filtered.contains(message)) {
-                String simpleName = getClass().getSimpleName();
-                Log.e(simpleName, "Filtered: "+message);
+            if (filterItem(message)) {
                 filtered.remove(message);
             }
         }
     }
 
-    private void updateAdd() {
+    public void updateAdd() {
         for (Message message : unfiltered) {
-            if (!filterItem(message) && !filtered.contains(message)) {
-                String simpleName = getClass().getSimpleName();
-                Log.e(simpleName, "Unfiltered: "+message);
+            if (!filterItem(message)) {
                 filtered.add(message);
             }
         }
@@ -134,4 +135,29 @@ public class BacklogFilter implements UICallback {
             notifyItemRemoved(i);
         }
     }
+
+    public void setFilters(int filters) {
+        Set<Message.Type> removed = new HashSet<>();
+        for (Message.Type type : filteredTypes) {
+            if ((filters & type.value) == 0)
+                removed.add(type);
+        }
+        for (Message.Type type : removed) {
+            removeFilter(type);
+        }
+
+        for (Message.Type type : Message.Type.values()) {
+            if ((filters & type.value) != 0) {
+                addFilter(type);
+            }
+        }
+    }
+
+    public int getFilters() {
+        int filters = 0x00000000;
+        for (Message.Type type : filteredTypes) {
+            filters |= type.value;
+        }
+        return filters;
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
index 2abd74a6d..e8236cc7a 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
@@ -4,7 +4,6 @@ import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
-import android.util.Log;
 import android.util.SparseArray;
 
 import com.google.common.collect.Lists;
@@ -19,13 +18,11 @@ import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.functions.types.SyncFunction;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.primitives.types.QVariant;
-import de.kuschku.util.AndroidAssert;
 import de.kuschku.util.observables.AutoScroller;
 import de.kuschku.util.observables.callbacks.wrappers.AdapterUICallbackWrapper;
 import de.kuschku.util.observables.lists.ObservableComparableSortedList;
 import de.kuschku.util.observables.lists.ObservableSortedList;
 
-import static de.kuschku.util.AndroidAssert.*;
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
 public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> {
@@ -60,6 +57,7 @@ public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> {
 
     public void receiveBacklog(@IntRange(from = 0) int bufferId, int from, int to, int count, int extra, @NonNull List<Message> messages) {
         get(bufferId).addAll(messages);
+        client.getNotificationManager().receiveMessages(messages);
 
         busProvider.sendEvent(new BacklogReceivedEvent(bufferId));
     }
@@ -70,6 +68,7 @@ public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> {
         assertNotNull(messages);
 
         messages.add(message);
+        client.getNotificationManager().receiveMessage(message);
     }
 
     public void bind(@IntRange(from = 0) int bufferId, @NonNull RecyclerView.Adapter adapter, @Nullable AutoScroller scroller) {
diff --git a/app/src/main/java/de/kuschku/libquassel/message/Message.java b/app/src/main/java/de/kuschku/libquassel/message/Message.java
index 430442535..627698d1b 100644
--- a/app/src/main/java/de/kuschku/libquassel/message/Message.java
+++ b/app/src/main/java/de/kuschku/libquassel/message/Message.java
@@ -52,20 +52,13 @@ public class Message implements ContentComparable<Message> {
     }
 
     @Override
-    public boolean equalsContent(@NonNull Message message) {
-        return messageId == message.messageId &&
-                time.equals(message.time) &&
-                type == message.type &&
-                flags.equals(message.flags) &&
-                bufferInfo.equals(message.bufferInfo) &&
-                sender.equals(message.sender) &&
-                content.equals(message.content);
+    public boolean areContentsTheSame(@NonNull Message message) {
+        return this == message;
     }
 
     @Override
-    public boolean equals(@Nullable Object o) {
-        return this == o || !(o == null || getClass() != o.getClass()) && messageId == ((Message) o).messageId;
-
+    public boolean areItemsTheSame(Message other) {
+        return this.messageId == other.messageId;
     }
 
     @Override
@@ -75,7 +68,10 @@ public class Message implements ContentComparable<Message> {
 
     @Override
     public int compareTo(@NonNull Message another) {
-        return this.time.compareTo(another.time);
+        if (this.type != Type.DayChange && another.type != Type.DayChange)
+            return this.messageId - another.messageId;
+        else
+            return this.time.compareTo(another.time);
     }
 
     public enum Type {
diff --git a/app/src/main/java/de/kuschku/libquassel/objects/serializers/SessionStateSerializer.java b/app/src/main/java/de/kuschku/libquassel/objects/serializers/SessionStateSerializer.java
index dce6e2f24..ce63ca596 100644
--- a/app/src/main/java/de/kuschku/libquassel/objects/serializers/SessionStateSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/objects/serializers/SessionStateSerializer.java
@@ -13,6 +13,7 @@ import de.kuschku.libquassel.functions.types.UnpackedFunction;
 import de.kuschku.libquassel.objects.types.SessionState;
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.types.Identity;
 
 @SuppressWarnings({"unchecked", "ConstantConditions"})
 public class SessionStateSerializer implements ObjectSerializer<SessionState> {
@@ -43,7 +44,7 @@ public class SessionStateSerializer implements ObjectSerializer<SessionState> {
     @Override
     public SessionState fromLegacy(@NonNull Map<String, QVariant> map) {
         return new SessionState(
-                (List<Map<String, QVariant>>) map.get("Identities").or(new ArrayList<>()),
+                (List<Identity>) map.get("Identities").or(new ArrayList<>()),
                 (List<BufferInfo>) map.get("BufferInfos").or(new ArrayList<>()),
                 (List<Integer>) map.get("NetworkIds").or(new ArrayList<>())
         );
diff --git a/app/src/main/java/de/kuschku/libquassel/objects/types/SessionState.java b/app/src/main/java/de/kuschku/libquassel/objects/types/SessionState.java
index 4c3f67024..bbd365392 100644
--- a/app/src/main/java/de/kuschku/libquassel/objects/types/SessionState.java
+++ b/app/src/main/java/de/kuschku/libquassel/objects/types/SessionState.java
@@ -3,20 +3,19 @@ package de.kuschku.libquassel.objects.types;
 import android.support.annotation.NonNull;
 
 import java.util.List;
-import java.util.Map;
 
 import de.kuschku.libquassel.primitives.types.BufferInfo;
-import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.types.Identity;
 
 public class SessionState {
     @NonNull
-    public final List<Map<String, QVariant>> Identities;
+    public final List<Identity> Identities;
     @NonNull
     public final List<BufferInfo> BufferInfos;
     @NonNull
     public final List<Integer> NetworkIds;
 
-    public SessionState(@NonNull List<Map<String, QVariant>> identities, @NonNull List<BufferInfo> bufferInfos,
+    public SessionState(@NonNull List<Identity> identities, @NonNull List<BufferInfo> bufferInfos,
                         @NonNull List<Integer> networkIds) {
         this.Identities = identities;
         this.BufferInfos = bufferInfos;
diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/types/BufferInfo.java b/app/src/main/java/de/kuschku/libquassel/primitives/types/BufferInfo.java
index 28a4ba658..f392d7b94 100644
--- a/app/src/main/java/de/kuschku/libquassel/primitives/types/BufferInfo.java
+++ b/app/src/main/java/de/kuschku/libquassel/primitives/types/BufferInfo.java
@@ -65,4 +65,17 @@ public class BufferInfo {
             }
         }
     }
+
+    public enum BufferStatus {
+        OFFLINE,
+        AWAY,
+        ONLINE
+    }
+
+    public enum ContentStatus {
+        NONE,
+        ACTIVITY,
+        MESSAGES,
+        HIGHLIGHTS
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewConfigSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewConfigSerializer.java
index 03c997d10..7c7b08056 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewConfigSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewConfigSerializer.java
@@ -33,7 +33,7 @@ public class BufferViewConfigSerializer implements ObjectSerializer<BufferViewCo
         map.data.put("bufferViewName", new QVariant<>(data.getBufferViewName()));
         map.data.put("TemporarilyRemovedBuffers", new QVariant<>(data.getTemporarilyRemovedBuffers()));
         map.data.put("hideInactiveNetworks", new QVariant<>(data.isHideInactiveNetworks()));
-        map.data.put("BufferList", new QVariant<>(data.getBufferList()));
+        map.data.put("BufferList", new QVariant<>(data.getBuffers()));
         map.data.put("allowedBufferTypes", new QVariant<>(data.getAllowedBufferTypes()));
         map.data.put("sortAlphabetically", new QVariant<>(data.isSortAlphabetically()));
         map.data.put("disableDecoration", new QVariant<>(data.isDisableDecoration()));
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java
index b0fa90823..177f3d878 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java
@@ -1,7 +1,7 @@
 package de.kuschku.libquassel.syncables.types;
 
-import android.net.*;
 import android.support.annotation.NonNull;
+import android.util.Log;
 
 import java.util.Collections;
 import java.util.List;
@@ -11,20 +11,18 @@ import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.primitives.types.QVariant;
-import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer;
 import de.kuschku.libquassel.syncables.serializers.BufferViewConfigSerializer;
-import de.kuschku.util.AndroidAssert;
 import de.kuschku.util.observables.callbacks.ElementCallback;
 import de.kuschku.util.observables.lists.IObservableList;
 import de.kuschku.util.observables.lists.ObservableElementList;
 
-import static de.kuschku.util.AndroidAssert.*;
+import static de.kuschku.util.AndroidAssert.assertNotNull;
 
 public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     private String bufferViewName;
-    private List<Integer> TemporarilyRemovedBuffers;
+    private List<Integer> temporarilyRemovedBuffers;
     private boolean hideInactiveNetworks;
-    private IObservableList<ElementCallback<Integer>, Integer> BufferList;
+    private IObservableList<ElementCallback<Integer>, Integer> buffers;
     private IObservableList<ElementCallback<Integer>, Integer> NetworkList = new ObservableElementList<>();
     private int allowedBufferTypes;
     private boolean sortAlphabetically;
@@ -33,14 +31,14 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     private int networkId;
     private int minimumActivity;
     private boolean hideInactiveBuffers;
-    private List<Integer> RemovedBuffers;
+    private List<Integer> removedBuffers;
     private Client client;
 
     public BufferViewConfig(String bufferViewName, List<Integer> temporarilyRemovedBuffers, boolean hideInactiveNetworks, @NonNull List<Integer> bufferList, int allowedBufferTypes, boolean sortAlphabetically, boolean disableDecoration, boolean addNewBuffersAutomatically, int networkId, int minimumActivity, boolean hideInactiveBuffers, List<Integer> removedBuffers) {
         this.bufferViewName = bufferViewName;
-        this.TemporarilyRemovedBuffers = temporarilyRemovedBuffers;
+        this.temporarilyRemovedBuffers = temporarilyRemovedBuffers;
         this.hideInactiveNetworks = hideInactiveNetworks;
-        this.BufferList = new ObservableElementList<>(bufferList);
+        this.buffers = new ObservableElementList<>(bufferList);
         this.allowedBufferTypes = allowedBufferTypes;
         this.sortAlphabetically = sortAlphabetically;
         this.disableDecoration = disableDecoration;
@@ -48,7 +46,7 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         this.networkId = networkId;
         this.minimumActivity = minimumActivity;
         this.hideInactiveBuffers = hideInactiveBuffers;
-        this.RemovedBuffers = removedBuffers;
+        this.removedBuffers = removedBuffers;
     }
 
     public String getBufferViewName() {
@@ -60,11 +58,11 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     }
 
     public List<Integer> getTemporarilyRemovedBuffers() {
-        return TemporarilyRemovedBuffers;
+        return temporarilyRemovedBuffers;
     }
 
     public void setTemporarilyRemovedBuffers(List<Integer> temporarilyRemovedBuffers) {
-        TemporarilyRemovedBuffers = temporarilyRemovedBuffers;
+        this.temporarilyRemovedBuffers = temporarilyRemovedBuffers;
     }
 
     public boolean isHideInactiveNetworks() {
@@ -75,16 +73,23 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         this.hideInactiveNetworks = hideInactiveNetworks;
     }
 
-    public IObservableList<ElementCallback<Integer>, Integer> getBufferList() {
-        return BufferList;
+
+    public void SYNC_setHideInactiveNetworks(boolean hideInactiveNetworks) {
+        if (this.hideInactiveNetworks == hideInactiveNetworks) return;
+        setHideInactiveNetworks(hideInactiveBuffers);
+        sync("setHideInactiveBuffers", new Object[]{hideInactiveNetworks});
+    }
+
+    public IObservableList<ElementCallback<Integer>, Integer> getBuffers() {
+        return buffers;
     }
 
-    public void setBufferList(IObservableList<ElementCallback<Integer>, Integer> bufferList) {
-        BufferList = bufferList;
+    public void setBuffers(IObservableList<ElementCallback<Integer>, Integer> buffers) {
+        this.buffers = buffers;
     }
 
     public void setBufferList(@NonNull List<Integer> bufferList) {
-        BufferList = new ObservableElementList<>(bufferList);
+        buffers = new ObservableElementList<>(bufferList);
     }
 
     public int getAllowedBufferTypes() {
@@ -125,10 +130,11 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
 
     public void setNetworkId(int networkId) {
         this.networkId = networkId;
-        if (this.networkId != -1) {
-            this.NetworkList.addAll(client.getNetworks());
+        if (this.networkId != 0) {
+            if (client.getNetworks().contains(networkId))
+                this.NetworkList.add(networkId);
         } else {
-            this.NetworkList.retainAll(Collections.singletonList(networkId));
+            this.NetworkList.addAll(client.getNetworks());
         }
     }
 
@@ -148,12 +154,18 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         this.hideInactiveBuffers = hideInactiveBuffers;
     }
 
+    public void SYNC_setHideInactiveBuffers(boolean hideInactiveBuffers) {
+        if (this.hideInactiveBuffers == hideInactiveBuffers) return;
+        setHideInactiveBuffers(hideInactiveBuffers);
+        sync("setHideInactiveBuffers", new Object[]{hideInactiveBuffers});
+    }
+
     public List<Integer> getRemovedBuffers() {
-        return RemovedBuffers;
+        return removedBuffers;
     }
 
     public void setRemovedBuffers(List<Integer> removedBuffers) {
-        RemovedBuffers = removedBuffers;
+        this.removedBuffers = removedBuffers;
     }
 
     @NonNull
@@ -161,9 +173,9 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     public String toString() {
         return "BufferViewConfig{" +
                 "bufferViewName='" + bufferViewName + '\'' +
-                ", TemporarilyRemovedBuffers=" + TemporarilyRemovedBuffers +
+                ", temporarilyRemovedBuffers=" + temporarilyRemovedBuffers +
                 ", hideInactiveNetworks=" + hideInactiveNetworks +
-                ", BufferList=" + BufferList +
+                ", buffers=" + buffers +
                 ", allowedBufferTypes=" + allowedBufferTypes +
                 ", sortAlphabetically=" + sortAlphabetically +
                 ", disableDecoration=" + disableDecoration +
@@ -171,7 +183,7 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
                 ", networkId=" + networkId +
                 ", minimumActivity=" + minimumActivity +
                 ", hideInactiveBuffers=" + hideInactiveBuffers +
-                ", RemovedBuffers=" + RemovedBuffers +
+                ", removedBuffers=" + removedBuffers +
                 '}';
     }
 
@@ -186,9 +198,9 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     @Override
     public void update(BufferViewConfig from) {
         this.bufferViewName = from.bufferViewName;
-        this.TemporarilyRemovedBuffers = from.TemporarilyRemovedBuffers;
+        this.temporarilyRemovedBuffers = from.temporarilyRemovedBuffers;
         this.hideInactiveNetworks = from.hideInactiveNetworks;
-        this.BufferList = from.BufferList;
+        this.buffers = from.buffers;
         this.allowedBufferTypes = from.allowedBufferTypes;
         this.sortAlphabetically = from.sortAlphabetically;
         this.disableDecoration = from.disableDecoration;
@@ -196,7 +208,7 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         this.networkId = from.networkId;
         this.minimumActivity = from.minimumActivity;
         this.hideInactiveBuffers = from.hideInactiveBuffers;
-        this.RemovedBuffers = from.RemovedBuffers;
+        this.removedBuffers = from.removedBuffers;
     }
 
     @Override
@@ -204,8 +216,22 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         update(BufferViewConfigSerializer.get().fromDatastream(from));
     }
 
-    public void addBuffer(int bufferId, int position) {
-        BufferList.add(position, bufferId);
+    public void addBuffer(int bufferId, int pos) {
+        if (buffers.contains(bufferId))
+            return;
+
+        if (pos < 0)
+            pos = 0;
+        if (pos > buffers.size())
+            pos = buffers.size();
+
+        if (removedBuffers.contains(bufferId))
+            removedBuffers.remove(removedBuffers.indexOf(bufferId));
+
+        if (temporarilyRemovedBuffers.contains(bufferId))
+            temporarilyRemovedBuffers.remove(temporarilyRemovedBuffers.indexOf(bufferId));
+
+        buffers.add(pos, bufferId);
     }
 
     public void SYNC_addBuffer(int bufferId, int position) {
@@ -213,8 +239,36 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         sync("addBuffer", new Object[]{bufferId, position});
     }
 
+    public void moveBuffer(int bufferId, int pos) {
+        if (!buffers.contains(bufferId))
+            return;
+
+        if (pos < 0)
+            pos = 0;
+        if (pos >= buffers.size())
+            pos = buffers.size() - 1;
+
+        int index = buffers.indexOf(bufferId);
+        if (pos == index)
+            return;
+        if (pos > index)
+            pos--;
+
+        buffers.remove(index);
+        buffers.add(pos, bufferId);
+    }
+
+    public void SYNC_moveBuffer(int bufferId, int position) {
+        moveBuffer(bufferId, position);
+        sync("moveBuffer", new Object[]{bufferId, position});
+    }
+
     public void removeBuffer(int bufferId) {
-        if (BufferList.contains(bufferId)) BufferList.remove(BufferList.indexOf(bufferId));
+        if (buffers.contains(bufferId))
+            buffers.remove(buffers.indexOf(bufferId));
+        if (removedBuffers.contains(bufferId))
+            removedBuffers.remove(removedBuffers.indexOf(bufferId));
+        temporarilyRemovedBuffers.add(bufferId);
     }
 
     public void SYNC_removeBuffer(int bufferId) {
@@ -222,6 +276,21 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
         sync("removeBuffer", new Object[]{bufferId});
     }
 
+    public void removeBufferPermanently(int bufferId) {
+        if (buffers.contains(bufferId))
+            buffers.remove(buffers.indexOf(bufferId));
+        if (temporarilyRemovedBuffers.contains(bufferId))
+            temporarilyRemovedBuffers.remove(temporarilyRemovedBuffers.indexOf(bufferId));
+        removedBuffers.add(bufferId);
+    }
+
+    public void SYNC_removeBufferPermanently(int bufferId) {
+        removeBufferPermanently(bufferId);
+        sync("removeBufferPermanently", new Object[]{bufferId});
+    }
+
+
+
     @NonNull
     public IObservableList<ElementCallback<Integer>, Integer> getNetworkList() {
         return NetworkList;
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java
index dd43c8f90..49406768c 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java
@@ -1,6 +1,8 @@
 package de.kuschku.libquassel.syncables.types;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
 
 import java.util.List;
 import java.util.Map;
@@ -258,8 +260,8 @@ public class Identity extends SyncableObject<Identity> {
     }
 
     @Override
-    public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
-        client.addIdentity(Integer.valueOf(function.objectName), this);
+    public void init(@Nullable InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+        client.addIdentity(getIdentityId(), this);
     }
 
     @Override
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java
index 853a559cb..ab47a9728 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java
@@ -44,23 +44,32 @@ public class IrcChannel extends SyncableObject<IrcChannel> {
         this.encrypted = encrypted;
     }
 
+    @NonNull
     public Map<String, List<String>> getA_ChanModes() {
         if (ChanModes.get("A") == null) ChanModes.put("A", new HashMap<>());
         return (Map<String, List<String>>) ChanModes.get("A");
     }
 
+    @NonNull
     public Map<String, String> getB_ChanModes() {
         if (ChanModes.get("B") == null) ChanModes.put("B", new HashMap<>());
         return (Map<String, String>) ChanModes.get("B");
     }
 
+    @NonNull
     public Map<String, String> getC_ChanModes() {
         if (ChanModes.get("C") == null) ChanModes.put("C", new HashMap<>());
         return (Map<String, String>) ChanModes.get("C");
     }
 
+    @NonNull
     public Set<String> getD_ChanModes() {
-        if (ChanModes.get("D") == null) ChanModes.put("D", new HashSet<>());
+        if (ChanModes.get("D") instanceof String) {
+            List<String> list = Arrays.asList(((String) ChanModes.get("D")).split(""));
+            ChanModes.put("D", new HashSet<>(list));
+        } else if (ChanModes.get("D") == null) {
+            ChanModes.put("D", new HashSet<>());
+        }
         return (Set<String>) ChanModes.get("D");
     }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java
index 834955523..bb7ed09a3 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java
@@ -18,6 +18,7 @@ import de.kuschku.libquassel.localtypes.Buffer;
 import de.kuschku.libquassel.objects.types.NetworkServer;
 import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.libquassel.syncables.serializers.NetworkSerializer;
+import de.kuschku.util.irc.IrcUserUtils;
 import de.kuschku.util.observables.ContentComparable;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
@@ -156,7 +157,7 @@ public class Network extends SyncableObject<Network> implements ContentComparabl
     }
 
     public void addIrcUser(String sender) {
-        client.sendInitRequest("IrcUser", getObjectName() + "/" + sender);
+        client.sendInitRequest("IrcUser", getObjectName() + "/" + IrcUserUtils.getNick(sender));
     }
 
     @Nullable
@@ -543,7 +544,12 @@ public class Network extends SyncableObject<Network> implements ContentComparabl
     }
 
     @Override
-    public boolean equalsContent(@NonNull Network other) {
+    public boolean areContentsTheSame(@NonNull Network other) {
+        return this == other;
+    }
+
+    @Override
+    public boolean areItemsTheSame(Network other) {
         return networkId == other.networkId;
     }
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java
index 700611bc3..38fb5b2e5 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java
@@ -4,15 +4,14 @@ import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
-import android.support.design.widget.CoordinatorLayout;
 import android.support.design.widget.Snackbar;
-import android.support.v4.widget.NestedScrollView;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.ActionMenuView;
@@ -22,17 +21,18 @@ import android.support.v7.widget.DefaultItemAnimator;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
-import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ScrollView;
-import android.widget.Scroller;
 
 import com.afollestad.materialdialogs.MaterialDialog;
 import com.google.common.base.Splitter;
 import com.mikepenz.fastadapter.FastAdapter;
 import com.mikepenz.fastadapter.IExpandable;
+import com.mikepenz.fastadapter.IIdentifyable;
 import com.mikepenz.fastadapter.IItem;
 import com.mikepenz.fastadapter.adapters.ItemAdapter;
 import com.mikepenz.materialdrawer.AccountHeader;
@@ -42,9 +42,12 @@ import com.mikepenz.materialdrawer.DrawerBuilder;
 import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
 import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
 import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
-import com.sothree.slidinguppanel.ScrollableViewHelper;
 import com.sothree.slidinguppanel.SlidingUpPanelLayout;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.Map;
 
 import aspm.annotations.BooleanPreference;
@@ -59,6 +62,8 @@ import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.events.LagChangedEvent;
 import de.kuschku.libquassel.localtypes.Buffer;
+import de.kuschku.libquassel.localtypes.ChannelBuffer;
+import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogFilter;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.syncables.types.BufferViewConfig;
 import de.kuschku.libquassel.syncables.types.BufferViewManager;
@@ -67,10 +72,14 @@ import de.kuschku.quasseldroid_ng.R;
 import de.kuschku.quasseldroid_ng.service.ClientBackgroundThread;
 import de.kuschku.quasseldroid_ng.service.QuasselService;
 import de.kuschku.quasseldroid_ng.ui.chat.chatview.MessageAdapter;
+import de.kuschku.quasseldroid_ng.ui.chat.drawer.BufferViewConfigWrapper;
+import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem;
+import de.kuschku.quasseldroid_ng.ui.editor.AdvancedEditor;
 import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
 import de.kuschku.quasseldroid_ng.ui.theme.AppTheme;
 import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil;
 import de.kuschku.util.ServerAddress;
+import de.kuschku.util.backports.Stream;
 import de.kuschku.util.instancestateutil.Storable;
 import de.kuschku.util.instancestateutil.Store;
 import de.kuschku.util.keyboardutils.DialogKeyboardUtil;
@@ -86,8 +95,10 @@ public class ChatActivity extends AppCompatActivity {
     Toolbar toolbar;
     @Bind(R.id.sliding_layout)
     SlidingUpPanelLayout slidingLayout;
+    @Bind(R.id.sliding_layout_history)
+    SlidingUpPanelLayout slidingLayoutHistory;
 
-    @Bind(R.id.chatlineScroller)
+    @Bind(R.id.chatline_scroller)
     ScrollView chatlineScroller;
     @Bind(R.id.chatline)
     AppCompatEditText chatline;
@@ -102,8 +113,10 @@ public class ChatActivity extends AppCompatActivity {
     @Bind(R.id.messages)
     RecyclerView messages;
 
-    @Bind(R.id.amvMenu)
+    @Bind(R.id.formatting_menu)
     ActionMenuView formattingMenu;
+    @Bind(R.id.formatting_toolbar)
+    Toolbar formattingToolbar;
 
     @PreferenceWrapper(BuildConfig.APPLICATION_ID)
     public static abstract class Settings {
@@ -141,8 +154,8 @@ public class ChatActivity extends AppCompatActivity {
         }
 
         private void disconnect() {
-            if (binder != null) binder.stopBackgroundThread();
-            if (context.getProvider() != null) context.getProvider().event.unregister(this);
+            if (context.getProvider() != null)
+                context.getProvider().event.unregister(this);
             context.setProvider(null);
             context.setClient(null);
         }
@@ -155,6 +168,7 @@ public class ChatActivity extends AppCompatActivity {
     private AccountHeader accountHeader;
     private Drawer drawerLeft;
     private BufferViewConfigWrapper wrapper;
+    private AdvancedEditor editor;
 
     private ServiceConnection serviceConnection = new ServiceConnection() {
         @UiThread
@@ -162,7 +176,16 @@ public class ChatActivity extends AppCompatActivity {
             if (service instanceof QuasselService.LocalBinder) {
                 ChatActivity.this.binder = (QuasselService.LocalBinder) service;
                 if (binder.getBackgroundThread() != null) {
-                    connectToThread(binder.getBackgroundThread());
+                    ClientBackgroundThread backgroundThread = binder.getBackgroundThread();
+                    assertNotNull(backgroundThread);
+
+                    serviceInterface.disconnect();
+
+                    backgroundThread.provider.event.register(ChatActivity.this);
+                    context.setClient(backgroundThread.handler.client);
+                    context.setProvider(backgroundThread.provider);
+                    updateBufferViewConfigs();
+                    updateSubTitle();
                 }
             }
         }
@@ -176,67 +199,77 @@ public class ChatActivity extends AppCompatActivity {
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
-        context.setSettings(new WrappedSettings(this));
-        AppTheme theme = AppTheme.themeFromString(context.getSettings().theme.get());
-        setTheme(theme.themeId);
-        context.setThemeUtil(new ThemeUtil(this, theme));
+        setupContext();
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_chat);
         ButterKnife.bind(this);
         setSupportActionBar(toolbar);
 
-        connectToService();
+        Intent intent = new Intent(this, QuasselService.class);
+        startService(intent);
 
-        accountHeader = new AccountHeaderBuilder()
-                .withActivity(this)
-                .withCompactStyle(true)
-                .withHeaderBackground(R.drawable.bg)
-                .withSavedInstance(savedInstanceState)
-                .withProfileImagesVisible(false)
-                .withOnAccountHeaderListener((view, profile, current) -> {
-                    if (!current) {
-                        selectBufferViewConfig((int) profile.getIdentifier());
-                    }
-                    return true;
-                })
-                .build();
+        setupHeader(savedInstanceState);
+
+        setupDrawer(savedInstanceState);
+
+        setupEditor();
+
+        setupContent();
+
+        setupHistory();
+
+        initLoader();
+    }
+
+    private void setupContext() {
+        context.setSettings(new WrappedSettings(this));
+        AppTheme theme = AppTheme.themeFromString(context.getSettings().theme.get());
+        setTheme(theme.themeId);
+        context.setThemeUtil(new ThemeUtil(this, theme));
+    }
+
+    private void setupEditorLayout() {
+        slidingLayout.setAntiDragView(R.id.card_panel);
+        slidingLayout.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
+            @Override
+            public void onPanelSlide(View panel, float slideOffset) {
 
-        drawerLeft = new DrawerBuilder()
-                .withActivity(this)
-                .withToolbar(toolbar)
-                .withAccountHeader(accountHeader)
-                .withSavedInstance(savedInstanceState)
-                .withTranslucentStatusBar(true)
-                .build();
-        drawerLeft.addStickyFooterItem(new PrimaryDrawerItem().withIcon(R.drawable.ic_server_light).withName("(Re-)Connect").withIdentifier(-1));
-        drawerLeft.addStickyFooterItem(new SecondaryDrawerItem().withName("Settings").withIdentifier(-2));
-        drawerLeft.setOnDrawerItemClickListener((view, position, drawerItem) -> {
-            long identifier = drawerItem.getIdentifier();
-            if (identifier == -1) {
-                showConnectDialog();
-                return false;
-            } else if (identifier == -2) {
-                showThemeDialog();
-                return false;
-            } else {
-                if (((IExpandable) drawerItem).getSubItems() != null) {
-                    drawerLeft.getAdapter().toggleExpandable(position);
-                    return true;
-                } else {
-                    selectBuffer((int) drawerItem.getIdentifier());
-                    return false;
-                }
             }
-        });
 
-        getMenuInflater().inflate(R.menu.formatting,formattingMenu.getMenu());
+            @Override
+            public void onPanelCollapsed(View panel) {
+                setChatlineExpanded(false);
+            }
+
+            @Override
+            public void onPanelExpanded(View panel) {
+                setChatlineExpanded(true);
+            }
 
-        messages.setItemAnimator(new DefaultItemAnimator());
-        messages.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true));
-        messageAdapter = new MessageAdapter(this, context, new AutoScroller(messages));
-        messages.setAdapter(messageAdapter);
+            @Override
+            public void onPanelAnchored(View panel) {
+
+            }
+
+            @Override
+            public void onPanelHidden(View panel) {
+
+            }
+        });
+        setChatlineExpanded(slidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED);
+    }
+
+    private void initLoader() {
+        swipeView.setEnabled(false);
+        swipeView.setColorSchemeColors(context.getThemeUtil().res.colorPrimary);
+        swipeView.setOnRefreshListener(() -> {
+            assertNotNull(context.getClient());
+            context.getClient().getBacklogManager().requestMoreBacklog(status.bufferId, 20);
+        });
+    }
 
+    private void setupHistory() {
         FastAdapter<IItem> fastAdapter = new FastAdapter<>();
         ItemAdapter<IItem> itemAdapter = new ItemAdapter<>();
         itemAdapter.wrap(fastAdapter);
@@ -261,43 +294,175 @@ public class ChatActivity extends AppCompatActivity {
         msgHistory.setAdapter(fastAdapter);
         msgHistory.setLayoutManager(new LinearLayoutManager(this));
         msgHistory.setItemAnimator(new DefaultItemAnimator());
+    }
 
-        swipeView.setColorSchemeColors(context.getThemeUtil().res.colorPrimary);
-        swipeView.setOnRefreshListener(() -> {
-            assertNotNull(context.getClient());
-            context.getClient().getBacklogManager().requestMoreBacklog(status.bufferId, 20);
-        });
+    private void setupContent() {
+        messages.setItemAnimator(new DefaultItemAnimator());
+        messages.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true));
+        messageAdapter = new MessageAdapter(this, context, new AutoScroller(messages));
+        messages.setAdapter(messageAdapter);
+    }
 
+    private void setupEditor() {
+        getMenuInflater().inflate(R.menu.formatting, formattingMenu.getMenu());
+        formattingMenu.setOnMenuItemClickListener(item -> {
+            switch (item.getItemId()) {
+                case R.id.format_bold:
+                    editor.toggleBold();
+                    return true;
+                case R.id.format_italic:
+                    editor.toggleItalic();
+                    return true;
+                case R.id.format_underline:
+                    editor.toggleUnderline();
+                    return true;
+                case R.id.action_history:
+                    slidingLayoutHistory.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED);
+                    return true;
+                default:
+                    return false;
+            }
+        });
+        editor = new AdvancedEditor(context, chatline);
         send.setOnClickListener(view -> sendInput());
 
-        slidingLayout.setAntiDragView(R.id.card_panel);
-        slidingLayout.setPanelSlideListener(new SlidingUpPanelLayout.PanelSlideListener() {
-            @Override
-            public void onPanelSlide(View panel, float slideOffset) {
+        setupEditorLayout();
+    }
 
+    private void setupDrawer(@Nullable Bundle savedInstanceState) {
+        drawerLeft = new DrawerBuilder()
+                .withActivity(this)
+                .withToolbar(toolbar)
+                .withAccountHeader(accountHeader)
+                .withSavedInstance(savedInstanceState)
+                .withTranslucentStatusBar(true)
+                .build();
+        drawerLeft.addStickyFooterItem(new PrimaryDrawerItem().withIcon(R.drawable.ic_server_light).withName("(Re-)Connect").withIdentifier(-1));
+        drawerLeft.addStickyFooterItem(new SecondaryDrawerItem().withName("Settings").withIdentifier(-2));
+        drawerLeft.setOnDrawerItemClickListener((view, position, drawerItem) -> {
+            long identifier = drawerItem.getIdentifier();
+            if (identifier == -1) {
+                showConnectDialog();
+                return false;
+            } else if (identifier == -2) {
+                showThemeDialog();
+                return false;
+            } else {
+                if (((IExpandable) drawerItem).getSubItems() != null) {
+                    drawerLeft.getAdapter().toggleExpandable(position);
+                    return true;
+                } else {
+                    selectBuffer((int) drawerItem.getIdentifier());
+                    return false;
+                }
             }
+        });
+    }
 
-            @Override
-            public void onPanelCollapsed(View panel) {
-                setChatlineExpanded(false);
-            }
+    private void setupHeader(@Nullable Bundle savedInstanceState) {
+        accountHeader = new AccountHeaderBuilder()
+                .withActivity(this)
+                .withCompactStyle(true)
+                .withHeaderBackground(R.drawable.bg)
+                .withSavedInstance(savedInstanceState)
+                .withProfileImagesVisible(false)
+                .withOnAccountHeaderListener((view, profile, current) -> {
+                    if (!current) {
+                        selectBufferViewConfig((int) profile.getIdentifier());
+                    }
+                    return true;
+                })
+                .build();
+    }
 
-            @Override
-            public void onPanelExpanded(View panel) {
-                setChatlineExpanded(true);
-            }
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.chat, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
 
-            @Override
-            public void onPanelAnchored(View panel) {
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        // Checks whether a hardware keyboard is available
+        if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
 
-            }
+        } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
 
-            @Override
-            public void onPanelHidden(View panel) {
+        }
+    }
 
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        List<Integer> filterSettings = Arrays.asList(
+                Message.Type.Join.value,
+                Message.Type.Part.value,
+                Message.Type.Quit.value,
+                Message.Type.Nick.value,
+                Message.Type.Mode.value,
+                Message.Type.Topic.value
+        );
+        int[] filterSettingsInts = new int[filterSettings.size()];
+        for (int i = 0; i < filterSettingsInts.length; i++) { filterSettingsInts[i] = filterSettings.get(i); }
+
+        switch (item.getItemId()) {
+            case R.id.action_hide_events: {
+                if (context.getClient() != null) {
+                    BacklogFilter backlogFilter = context.getClient().getBacklogManager().getFilter(status.bufferId);
+                    if (backlogFilter != null) {
+                        int oldFilters = backlogFilter.getFilters();
+                        List<Integer> oldFiltersList = new ArrayList<>();
+                        for (int type : filterSettings) {
+                            if ((type & oldFilters) != 0)
+                                oldFiltersList.add(filterSettings.indexOf(type));
+                        }
+                        Integer[] selectedIndices = oldFiltersList.toArray(new Integer[oldFiltersList.size()]);
+                        new MaterialDialog.Builder(this)
+                                .items(
+                                        "Joins",
+                                        "Parts",
+                                        "Quits",
+                                        "Nick Changes",
+                                        "Mode Changes",
+                                        "Topic Changes"
+                                )
+                                .itemsIds(filterSettingsInts)
+                                .itemsCallbackMultiChoice(
+                                        selectedIndices,
+                                        (dialog, which, text) -> false
+                                )
+                                .positiveText("Select")
+                                .negativeText("Cancel")
+                                .onPositive((dialog, which) -> {
+                                    int filters = 0x00000000;
+                                    if (dialog.getSelectedIndices() != null)
+                                    for (int i : dialog.getSelectedIndices()) {
+                                        filters |= filterSettings.get(i);
+                                    }
+                                    backlogFilter.setFilters(filters);
+                                })
+                                .buttonRippleColorAttr(R.attr.colorAccentFocus)
+                                .build()
+                                .show();
+                    }
+                }
             }
-        });
-        setChatlineExpanded(slidingLayout.getPanelState() == SlidingUpPanelLayout.PanelState.EXPANDED);
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        serviceInterface.disconnect();
+        unbindService(serviceConnection);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Intent intent = new Intent(this, QuasselService.class);
+        bindService(intent, serviceConnection, Context.BIND_IMPORTANT);
     }
 
     public void setChatlineExpanded(boolean expanded) {
@@ -354,20 +519,10 @@ public class ChatActivity extends AppCompatActivity {
         status.onSaveInstanceState(outState);
     }
 
-    private void connectToThread(@NonNull ClientBackgroundThread backgroundThread) {
-        assertNotNull(backgroundThread);
-
-        serviceInterface.disconnect();
-
-        backgroundThread.provider.event.register(this);
-        context.setClient(backgroundThread.handler.client);
-        context.setProvider(backgroundThread.provider);
-        selectBuffer(status.bufferId);
-        selectBufferViewConfig(status.bufferViewConfigId);
-        updateSubTitle();
-    }
-
     private void selectBufferViewConfig(@IntRange(from = -1) int bufferViewConfigId) {
+        status.bufferViewConfigId = bufferViewConfigId;
+        accountHeader.setActiveProfile(bufferViewConfigId, false);
+
         if (wrapper != null) wrapper.setDrawer(null);
         drawerLeft.removeAllItems();
         if (bufferViewConfigId == -1) {
@@ -381,14 +536,19 @@ public class ChatActivity extends AppCompatActivity {
 
             wrapper = new BufferViewConfigWrapper(context, viewConfig, drawerLeft);
             wrapper.updateDrawerItems();
+            String name = viewConfig.getBufferViewName();
         }
     }
 
     private void selectBuffer(@IntRange(from = -1) int bufferId) {
         if (bufferId == -1) {
+            swipeView.setEnabled(false);
+
             messageAdapter.setMessageList(MessageAdapter.emptyList());
             toolbar.setTitle(getResources().getString(R.string.app_name));
         } else {
+            swipeView.setEnabled(true);
+
             status.bufferId = bufferId;
             // Make sure we are actually connected
             ObservableSortedList<Message> list = context.getClient().getBacklogManager().getFiltered(status.bufferId);
@@ -399,12 +559,25 @@ public class ChatActivity extends AppCompatActivity {
 
             messageAdapter.setMessageList(list);
             toolbar.setTitle(buffer.getName());
+            updateNoColor(buffer, formattingMenu.getMenu());
         }
     }
 
-    private void connectToService() {
-        Intent intent = new Intent(this, QuasselService.class);
-        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+    private static void updateNoColor(Buffer buffer, Menu menu) {
+        boolean isNoColor = isNoColor(buffer);
+        menu.findItem(R.id.format_bold).setEnabled(!isNoColor);
+        menu.findItem(R.id.format_italic).setEnabled(!isNoColor);
+        menu.findItem(R.id.format_underline).setEnabled(!isNoColor);
+        menu.findItem(R.id.format_paint).setEnabled(!isNoColor);
+        menu.findItem(R.id.format_fill).setEnabled(!isNoColor);
+    }
+
+    public static boolean isNoColor(Buffer buffer) {
+        if (buffer instanceof ChannelBuffer && ((ChannelBuffer) buffer).getChannel() != null) {
+            return ((ChannelBuffer) buffer).getChannel().getD_ChanModes().contains("c");
+        } else {
+            return false;
+        }
     }
 
     private void onConnectionEstablished() {
@@ -419,8 +592,8 @@ public class ChatActivity extends AppCompatActivity {
         Buffer buffer = context.getClient().getBuffer(status.bufferId);
         assertNotNull(buffer);
 
-        CharSequence text = chatline.getText();
-        context.getClient().sendInput(buffer.getInfo(), text.toString());
+        String text = editor.toFormatString();
+        context.getClient().sendInput(buffer.getInfo(), text);
         chatline.setText("");
     }
 
@@ -447,6 +620,7 @@ public class ChatActivity extends AppCompatActivity {
     }
 
     private void updateBufferViewConfigs() {
+        assertNotNull(context.getClient().getBufferViewManager());
         Map<Integer, BufferViewConfig> bufferViews = context.getClient().getBufferViewManager().BufferViews;
         accountHeader.clear();
         for (Map.Entry<Integer, BufferViewConfig> entry : bufferViews.entrySet()) {
@@ -494,6 +668,8 @@ public class ChatActivity extends AppCompatActivity {
                 .title("Address")
                 .customView(R.layout.dialog_address, false)
                 .onPositive((dialog1, which) -> {
+                    if (binder != null && binder.getBackgroundThread() != null) binder.stopBackgroundThread();
+
                     View parent = dialog1.getCustomView();
                     AppCompatEditText hostField = (AppCompatEditText) parent.findViewById(R.id.host);
                     AppCompatEditText portField = (AppCompatEditText) parent.findViewById(R.id.port);
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java
index d8a929f9c..cb07ea5ad 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java
@@ -16,6 +16,7 @@ import de.kuschku.util.annotationbind.AutoBinder;
 import de.kuschku.util.annotationbind.AutoString;
 import de.kuschku.util.irc.IrcFormatHelper;
 import de.kuschku.util.irc.IrcUserUtils;
+import de.kuschku.util.ui.MessageUtil;
 import de.kuschku.util.ui.SpanFormatter;
 import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil;
 
@@ -23,8 +24,6 @@ import static de.kuschku.util.AndroidAssert.assertNotNull;
 
 @UiThread
 public class ChatMessageRenderer {
-    @NonNull
-    private final FormatStrings strings;
 
     private IrcFormatHelper helper;
     private MessageStyleContainer highlightStyle;
@@ -36,37 +35,36 @@ public class ChatMessageRenderer {
     private AppContext context;
 
     public ChatMessageRenderer(@NonNull Context ctx, @NonNull AppContext context) {
-        this.strings = new FormatStrings(ctx);
         this.context = context;
-        setTheme(context.getThemeUtil());
+        setTheme(context);
     }
 
-    public void setTheme(ThemeUtil themeUtil) {
-        this.helper = new IrcFormatHelper(themeUtil.res);
+    public void setTheme(AppContext context) {
+        this.helper = new IrcFormatHelper(context);
 
         this.highlightStyle = new MessageStyleContainer(
-                themeUtil.res.colorForegroundHighlight,
+                context.getThemeUtil().res.colorForegroundHighlight,
                 Typeface.NORMAL,
-                themeUtil.res.colorForegroundHighlight,
-                themeUtil.res.colorBackgroundHighlight
+                context.getThemeUtil().res.colorForegroundHighlight,
+                context.getThemeUtil().res.colorBackgroundHighlight
         );
         this.serverStyle = new MessageStyleContainer(
-                themeUtil.res.colorForegroundSecondary,
+                context.getThemeUtil().res.colorForegroundSecondary,
                 Typeface.ITALIC,
-                themeUtil.res.colorForegroundSecondary,
-                themeUtil.res.colorBackgroundSecondary
+                context.getThemeUtil().res.colorForegroundSecondary,
+                context.getThemeUtil().res.colorBackgroundSecondary
         );
         this.plainStyle = new MessageStyleContainer(
-                themeUtil.res.colorForeground,
+                context.getThemeUtil().res.colorForeground,
                 Typeface.NORMAL,
-                themeUtil.res.colorForegroundSecondary,
-                themeUtil.res.transparent
+                context.getThemeUtil().res.colorForegroundSecondary,
+                context.getThemeUtil().res.transparent
         );
         this.actionStyle = new MessageStyleContainer(
-                themeUtil.res.colorForegroundAction,
+                context.getThemeUtil().res.colorForegroundAction,
                 Typeface.ITALIC,
-                themeUtil.res.colorForegroundSecondary,
-                themeUtil.res.transparent
+                context.getThemeUtil().res.colorForegroundSecondary,
+                context.getThemeUtil().res.transparent
         );
     }
 
@@ -82,7 +80,7 @@ public class ChatMessageRenderer {
     private CharSequence formatNick(@NonNull String hostmask, boolean full) {
         CharSequence formattedNick = helper.formatUserNick(IrcUserUtils.getNick(hostmask));
         if (full) {
-            return strings.formatUsername(formattedNick, IrcUserUtils.getMask(hostmask));
+            return context.getThemeUtil().translations.formatUsername(formattedNick, IrcUserUtils.getMask(hostmask));
         } else {
             return formattedNick;
         }
@@ -106,7 +104,7 @@ public class ChatMessageRenderer {
     private void onBindPlain(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, plainStyle, highlightStyle, message.flags.Highlight);
         holder.content.setText(
-                strings.formatPlain(
+                context.getThemeUtil().translations.formatPlain(
                         formatNick(message.sender, false),
                         helper.formatIrcMessage(message.content)
                 )
@@ -115,7 +113,7 @@ public class ChatMessageRenderer {
 
     private void onBindNotice(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, plainStyle, highlightStyle, message.flags.Highlight);
-        holder.content.setText(strings.formatAction(
+        holder.content.setText(context.getThemeUtil().translations.formatAction(
                 formatNick(message.sender, false),
                 helper.formatIrcMessage(message.content)
         ));
@@ -124,7 +122,7 @@ public class ChatMessageRenderer {
     private void onBindAction(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, actionStyle, highlightStyle, message.flags.Highlight);
         holder.content.setText(
-                strings.formatAction(
+                context.getThemeUtil().translations.formatAction(
                         formatNick(message.sender, false),
                         helper.formatIrcMessage(message.content)
                 )
@@ -134,11 +132,11 @@ public class ChatMessageRenderer {
     private void onBindNick(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, serverStyle, highlightStyle, message.flags.Highlight);
         if (message.flags.Self)
-            holder.content.setText(strings.formatNick(
+            holder.content.setText(context.getThemeUtil().translations.formatNick(
                     formatNick(message.sender, false)
             ));
         else
-            holder.content.setText(strings.formatNick(
+            holder.content.setText(context.getThemeUtil().translations.formatNick(
                     formatNick(message.sender, false),
                     helper.formatUserNick(message.content)
             ));
@@ -151,7 +149,7 @@ public class ChatMessageRenderer {
 
     private void onBindJoin(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, serverStyle, highlightStyle, message.flags.Highlight);
-        holder.content.setText(strings.formatJoin(
+        holder.content.setText(context.getThemeUtil().translations.formatJoin(
                 formatNick(message.sender),
                 getBufferName(message)
         ));
@@ -159,7 +157,7 @@ public class ChatMessageRenderer {
 
     private void onBindPart(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, serverStyle, highlightStyle, message.flags.Highlight);
-        holder.content.setText(strings.formatPart(
+        holder.content.setText(context.getThemeUtil().translations.formatPart(
                 formatNick(message.sender),
                 getBufferName(message),
                 message.content
@@ -168,7 +166,7 @@ public class ChatMessageRenderer {
 
     private void onBindQuit(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, serverStyle, highlightStyle, message.flags.Highlight);
-        holder.content.setText(strings.formatQuit(
+        holder.content.setText(context.getThemeUtil().translations.formatQuit(
                 formatNick(message.sender),
                 message.content
         ));
@@ -201,7 +199,7 @@ public class ChatMessageRenderer {
 
     private void onBindDayChange(@NonNull MessageViewHolder holder, @NonNull Message message) {
         applyStyle(holder, serverStyle, highlightStyle, message.flags.Highlight);
-        holder.content.setText(strings.formatDayChange(
+        holder.content.setText(context.getThemeUtil().translations.formatDayChange(
                 context.getThemeUtil().formatter.getLongDateFormatter().print(message.time)
         ));
     }
@@ -305,143 +303,4 @@ public class ChatMessageRenderer {
             this.bgColor = bgColor;
         }
     }
-
-    public static class FormatStrings {
-        @AutoString(R.string.username_hostmask)
-        public String username_hostmask;
-
-        @AutoString(R.string.message_plain)
-        public String message_plain;
-
-        @AutoString(R.string.message_join)
-        public String message_join;
-
-        @AutoString(R.string.message_part)
-        public String message_part;
-
-        @AutoString(R.string.message_part_extra)
-        public String message_part_extra;
-
-        @AutoString(R.string.message_quit)
-        public String message_quit;
-
-        @AutoString(R.string.message_quit_extra)
-        public String message_quit_extra;
-
-        @AutoString(R.string.message_kill)
-        public String message_kill;
-
-        @AutoString(R.string.message_kick)
-        public String message_kick;
-
-        @AutoString(R.string.message_kick_extra)
-        public String message_kick_extra;
-
-        @AutoString(R.string.message_mode)
-        public String message_mode;
-
-        @AutoString(R.string.message_nick_self)
-        public String message_nick_self;
-
-        @AutoString(R.string.message_nick_other)
-        public String message_nick_other;
-
-        @AutoString(R.string.message_daychange)
-        public String message_daychange;
-
-        @AutoString(R.string.message_action)
-        public String message_action;
-
-        public FormatStrings(@NonNull Context ctx) {
-            try {
-                AutoBinder.bind(this, ctx);
-            } catch (IllegalAccessException e) {
-                Log.e("ERROR", e.toString());
-                e.printStackTrace();
-            }
-        }
-
-        @NonNull
-        public CharSequence formatUsername(@NonNull CharSequence nick, @NonNull CharSequence hostmask) {
-            return SpanFormatter.format(username_hostmask, nick, hostmask);
-        }
-
-        @NonNull
-        public CharSequence formatJoin(@NonNull CharSequence user, @NonNull CharSequence channel) {
-            return SpanFormatter.format(message_join, user, channel);
-        }
-
-        @NonNull
-        public CharSequence formatPart(@NonNull CharSequence user, @NonNull CharSequence channel) {
-            return SpanFormatter.format(message_part, user, channel);
-        }
-
-        @NonNull
-        public CharSequence formatPart(@NonNull CharSequence user, @NonNull CharSequence channel, @Nullable CharSequence reason) {
-            if (reason == null || reason.length() == 0) return formatPart(user, channel);
-
-            return SpanFormatter.format(message_part_extra, user, channel, reason);
-        }
-
-        @NonNull
-        public CharSequence formatQuit(@NonNull CharSequence user) {
-            return SpanFormatter.format(message_quit, user);
-        }
-
-        @NonNull
-        public CharSequence formatQuit(@NonNull CharSequence user, @Nullable CharSequence reason) {
-            if (reason == null || reason.length() == 0) return formatQuit(user);
-
-            return SpanFormatter.format(message_quit_extra, user, reason);
-        }
-
-        @NonNull
-        public CharSequence formatKill(@NonNull CharSequence user, @NonNull CharSequence channel) {
-            return SpanFormatter.format(message_kill, user, channel);
-        }
-
-        @NonNull
-        public CharSequence formatKick(@NonNull CharSequence user, @NonNull CharSequence kicked) {
-            return SpanFormatter.format(message_kick, user, kicked);
-        }
-
-        @NonNull
-        public CharSequence formatKick(@NonNull CharSequence user, @NonNull CharSequence kicked, @Nullable CharSequence reason) {
-            if (reason == null || reason.length() == 0) return formatKick(user, kicked);
-
-            return SpanFormatter.format(message_kick_extra, user, kicked, reason);
-        }
-
-        @NonNull
-        public CharSequence formatMode(@NonNull CharSequence mode, @NonNull CharSequence user) {
-            return SpanFormatter.format(message_mode, mode, user);
-        }
-
-        @NonNull
-        public CharSequence formatNick(@NonNull CharSequence newNick) {
-            return SpanFormatter.format(message_nick_self, newNick);
-        }
-
-        @NonNull
-        public CharSequence formatNick(@NonNull CharSequence oldNick, @Nullable CharSequence newNick) {
-            if (newNick == null || newNick.length() == 0) return formatNick(oldNick);
-
-            return SpanFormatter.format(message_nick_other, oldNick, newNick);
-        }
-
-        @NonNull
-        public CharSequence formatDayChange(@NonNull CharSequence day) {
-            return SpanFormatter.format(message_daychange, day);
-        }
-
-        @NonNull
-        public CharSequence formatAction(@NonNull CharSequence user, @NonNull CharSequence channel) {
-            return SpanFormatter.format(message_action, user, channel);
-        }
-
-        @NonNull
-        public CharSequence formatPlain(@NonNull CharSequence nick, @NonNull CharSequence message) {
-            return SpanFormatter.format(message_plain, nick, message);
-        }
-    }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java
index 9ac11f026..ea259ca33 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java
@@ -16,6 +16,7 @@ import de.kuschku.libquassel.localtypes.ChannelBuffer;
 import de.kuschku.libquassel.localtypes.QueryBuffer;
 import de.kuschku.libquassel.localtypes.StatusBuffer;
 import de.kuschku.libquassel.message.Message;
+import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.quasseldroid_ng.R;
 import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
 import de.kuschku.util.observables.IObservable;
@@ -80,25 +81,28 @@ public class BufferItem extends SecondaryDrawerItem implements IObservable<Gener
 
     @Override
     public StringHolder getName() {
-        return new StringHolder(buffer.getName());
+        if (buffer instanceof StatusBuffer)
+            return new StringHolder(context.getThemeUtil().translations.title_status_buffer);
+        else
+            return new StringHolder(buffer.getName());
     }
 
     @Override
     public ImageHolder getIcon() {
         if (buffer instanceof ChannelBuffer) {
-            if (buffer.isActive()) {
+            if (buffer.getStatus() != BufferInfo.BufferStatus.OFFLINE) {
                 return new ImageHolder(R.drawable.ic_status_channel);
             } else {
                 return new ImageHolder(R.drawable.ic_status_channel_offline);
             }
         } else if (buffer instanceof StatusBuffer) {
-            if (buffer.isActive()) {
+            if (buffer.getStatus() != BufferInfo.BufferStatus.OFFLINE) {
                 return new ImageHolder(R.drawable.ic_status);
             } else {
                 return new ImageHolder(R.drawable.ic_status_offline);
             }
         } else {
-            if (buffer.isActive()) {
+            if (buffer.getStatus() != BufferInfo.BufferStatus.OFFLINE) {
                 return new ImageHolder(R.drawable.ic_status);
             } else {
                 return new ImageHolder(R.drawable.ic_status_offline);
@@ -106,9 +110,21 @@ public class BufferItem extends SecondaryDrawerItem implements IObservable<Gener
         }
     }
 
+    @Override
+    public boolean isIconTinted() {
+        return buffer.getStatus() == BufferInfo.BufferStatus.ONLINE;
+    }
+
     @Override
     public ColorHolder getIconColor() {
-        return super.getIconColor();
+        return buffer.getStatus() == BufferInfo.BufferStatus.ONLINE  ?
+                ColorHolder.fromColor(context.getThemeUtil().res.colorAccent) :
+                new ColorHolder();
+    }
+
+    @Override
+    public ColorHolder getDescriptionTextColor() {
+        return ColorHolder.fromColor(context.getThemeUtil().res.colorForegroundSecondary);
     }
 
     @Override
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java
similarity index 88%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java
index f6512fed1..903e76bb3 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java
@@ -1,14 +1,22 @@
-package de.kuschku.quasseldroid_ng.ui.chat;
+package de.kuschku.quasseldroid_ng.ui.chat.drawer;
 
+import android.util.Log;
+
+import com.mikepenz.fastadapter.IIdentifyable;
 import com.mikepenz.materialdrawer.Drawer;
 import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import de.kuschku.libquassel.syncables.types.BufferViewConfig;
 import de.kuschku.libquassel.syncables.types.Network;
 import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
 import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem;
+import de.kuschku.util.backports.Stream;
 import de.kuschku.util.observables.callbacks.ElementCallback;
 import de.kuschku.util.observables.lists.ObservableSortedList;
 
@@ -24,13 +32,13 @@ public class BufferViewConfigWrapper {
         }
 
         @Override
-        public boolean areContentsTheSame(NetworkItem oldItem, NetworkItem newItem) {
-            return oldItem.getNetwork().getNetworkId() == newItem.getNetwork().getNetworkId();
+        public boolean areContentsTheSame(NetworkItem item1, NetworkItem item2) {
+            return item1 == item2;
         }
 
         @Override
         public boolean areItemsTheSame(NetworkItem item1, NetworkItem item2) {
-            return item1 == item2;
+            return item1.getNetwork().getNetworkId() == item2.getNetwork().getNetworkId();
         }
     });
 
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java
index 0ed01812c..36706986d 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java
@@ -12,6 +12,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import de.kuschku.libquassel.localtypes.Buffer;
+import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.syncables.types.BufferViewConfig;
 import de.kuschku.libquassel.syncables.types.Network;
 import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
@@ -21,6 +22,11 @@ import de.kuschku.util.observables.callbacks.GeneralCallback;
 import de.kuschku.util.observables.callbacks.wrappers.GeneralCallbackWrapper;
 import de.kuschku.util.observables.lists.ObservableSortedList;
 
+import static de.kuschku.libquassel.primitives.types.BufferInfo.Type.CHANNEL;
+import static de.kuschku.libquassel.primitives.types.BufferInfo.Type.GROUP;
+import static de.kuschku.libquassel.primitives.types.BufferInfo.Type.QUERY;
+import static de.kuschku.libquassel.primitives.types.BufferInfo.Type.STATUS;
+
 public class NetworkItem extends PrimaryDrawerItem implements IObservable<GeneralCallback>, GeneralCallback {
     private final AppContext context;
     private final Network network;
@@ -35,14 +41,13 @@ public class NetworkItem extends PrimaryDrawerItem implements IObservable<Genera
         this.network = network;
         this.config = config;
 
-        for (Integer bufferId : this.config.getBufferList()) {
+        for (Integer bufferId : this.config.getBuffers()) {
             Buffer buffer = context.getClient().getBuffer(bufferId);
             if (buffer != null && buffer.getInfo().networkId == network.getNetworkId()) {
                 this.buffers.add(new BufferItem(buffer, context));
-                Log.e("Drawer", "Buffer can not be null! BufferId: "+ bufferId);
             }
         }
-        this.config.getBufferList().addCallback(new ElementCallback<Integer>() {
+        this.config.getBuffers().addCallback(new ElementCallback<Integer>() {
             @Override
             public void notifyItemInserted(Integer element) {
                 if (network.getBuffers().contains(element)) {
@@ -131,17 +136,40 @@ public class NetworkItem extends PrimaryDrawerItem implements IObservable<Genera
     class AlphabeticalComparator implements ObservableSortedList.ItemComparator<BufferItem> {
         @Override
         public int compare(BufferItem o1, BufferItem o2) {
-            return o1.getName().getText().compareTo(o2.getName().getText());
+            BufferInfo.Type type1 = o1.getBuffer().getInfo().type;
+            BufferInfo.Type type2 = o2.getBuffer().getInfo().type;
+            if (type1 == type2) {
+                return o1.getBuffer().getName().compareTo(o2.getBuffer().getName());
+            } else {
+                // Type1 is status, Type2 isn’t
+                if (type1 == STATUS) return -1;
+                // Type2 is status, Type1 isn’t
+                if (type2 == STATUS) return 1;
+                // Type1 is channel, Type2 isn’t
+                if (type1 == CHANNEL) return -1;
+                // Type2 is channel, Type1 isn’t
+                if (type2 == CHANNEL) return 1;
+                // Type1 is group, Type2 isn’t
+                if (type1 == GROUP) return -1;
+                // Type2 is group, Type1 isn’t
+                if (type2 == GROUP) return 1;
+                // Type1 is query, Type2 isn’t
+                if (type1 == QUERY) return -1;
+                // Type2 is query, Type1 isn’t
+                if (type2 == QUERY) return 1;
+                // Per default, keep order
+                return -1;
+            }
         }
 
         @Override
-        public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) {
-            return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id;
+        public boolean areContentsTheSame(BufferItem item1, BufferItem item2) {
+            return item1 == item2;
         }
 
         @Override
         public boolean areItemsTheSame(BufferItem item1, BufferItem item2) {
-            return item1 == item2;
+            return item1.getBuffer().getInfo().id == item2.getBuffer().getInfo().id;
         }
     }
     
@@ -152,13 +180,13 @@ public class NetworkItem extends PrimaryDrawerItem implements IObservable<Genera
         }
 
         @Override
-        public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) {
-            return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id;
+        public boolean areContentsTheSame(BufferItem item1, BufferItem item2) {
+            return item1 == item2;
         }
 
         @Override
         public boolean areItemsTheSame(BufferItem item1, BufferItem item2) {
-            return item1 == item2;
+            return item1.getBuffer().getInfo().id == item2.getBuffer().getInfo().id;
         }
     }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java
new file mode 100644
index 000000000..8b1ed6384
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java
@@ -0,0 +1,116 @@
+package de.kuschku.quasseldroid_ng.ui.editor;
+
+import android.support.annotation.ColorInt;
+import android.support.annotation.IntRange;
+import android.text.Spanned;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.UnderlineSpan;
+import android.widget.EditText;
+
+import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
+
+public class AdvancedEditor {
+    private EditText editText;
+    private FormattingHelper helper;
+
+    public AdvancedEditor(AppContext context, EditText editText) {
+        this.helper = new FormattingHelper(context);
+        this.editText = editText;
+    }
+
+    public void toggleUnderline() {
+        toggleUnderline(editText.getSelectionStart(), editText.getSelectionEnd());
+    }
+    public void toggleUnderline(int start, int end) {
+        boolean isUnderline = false;
+        for (UnderlineSpan span : editText.getText().getSpans(start, end, UnderlineSpan.class)) {
+            if ((editText.getText().getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) continue;
+
+            isUnderline = (editText.getText().getSpanStart(span) == start && editText.getText().getSpanEnd(span) == end);
+            editText.getText().removeSpan(span);
+
+            if (isUnderline) break;
+        }
+        if (!isUnderline) {
+            editText.getText().setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+
+    public void toggleBold() {
+        toggleBold(editText.getSelectionStart(), editText.getSelectionEnd());
+    }
+    public void toggleBold(int start, int end) {
+        boolean isBold = false;
+        for (BoldSpan span : editText.getText().getSpans(start, end, BoldSpan.class)) {
+            if ((editText.getText().getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) continue;
+
+            isBold = (editText.getText().getSpanStart(span) == start && editText.getText().getSpanEnd(span) == end);
+            editText.getText().removeSpan(span);
+
+            if (isBold) break;
+        }
+        if (!isBold) {
+            editText.getText().setSpan(new BoldSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    public void toggleItalic() {
+        toggleItalic(editText.getSelectionStart(), editText.getSelectionEnd());
+    }
+    public void toggleItalic(int start, int end) {
+        boolean isItalic = false;
+        for (ItalicSpan span : editText.getText().getSpans(start, end, ItalicSpan.class)) {
+            if ((editText.getText().getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) continue;
+
+            isItalic = (editText.getText().getSpanStart(span) == start && editText.getText().getSpanEnd(span) == end);
+            editText.getText().removeSpan(span);
+
+            if (isItalic) break;
+        }
+        if (!isItalic) {
+            editText.getText().setSpan(new ItalicSpan(), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    public void toggleForeground(@IntRange(from = 0, to = 15) int color) {
+        toggleForeground(editText.getSelectionStart(), editText.getSelectionEnd(), color);
+    }
+    public void toggleForeground(int start, int end, @ColorInt int color) {
+        boolean isColored = false;
+        for (ForegroundColorSpan span : editText.getText().getSpans(start, end, ForegroundColorSpan.class)) {
+            if ((editText.getText().getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) continue;
+
+            isColored = span.getForegroundColor() == color && (editText.getText().getSpanStart(span) == start && editText.getText().getSpanEnd(span) == end);
+            editText.getText().removeSpan(span);
+
+            if (isColored) break;
+        }
+        if (!isColored) {
+            editText.getText().setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    public void toggleBackground(@IntRange(from = 0, to = 15) int color) {
+        toggleBackground(editText.getSelectionStart(), editText.getSelectionEnd(), color);
+    }
+    public void toggleBackground(int start, int end, @ColorInt int color) {
+        boolean isColored = false;
+        for (BackgroundColorSpan span : editText.getText().getSpans(start, end, BackgroundColorSpan.class)) {
+            if ((editText.getText().getSpanFlags(span) & Spanned.SPAN_COMPOSING) != 0) continue;
+
+            isColored = span.getBackgroundColor() == color && (editText.getText().getSpanStart(span) == start && editText.getText().getSpanEnd(span) == end);
+            editText.getText().removeSpan(span);
+
+            if (isColored) break;
+        }
+        if (!isColored) {
+            editText.getText().setSpan(new BackgroundColorSpan(color), start, end, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+    }
+
+    public String toFormatString() {
+        return helper.toEscapeCodes(editText.getText());
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/BoldSpan.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/BoldSpan.java
new file mode 100644
index 000000000..6786a1393
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/BoldSpan.java
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid_ng.ui.editor;
+
+import android.graphics.Typeface;
+import android.text.style.StyleSpan;
+
+public class BoldSpan extends StyleSpan {
+    public BoldSpan() {
+        super(Typeface.BOLD);
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java
new file mode 100644
index 000000000..1e6be5d2b
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java
@@ -0,0 +1,114 @@
+package de.kuschku.quasseldroid_ng.ui.editor;
+
+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 de.kuschku.quasseldroid_ng.ui.theme.AppContext;
+
+public class FormattingHelper {
+    private AppContext context;
+
+    public FormattingHelper(AppContext context) {
+        this.context = context;
+    }
+
+    public String toEscapeCodes(Spanned text) {
+        StringBuilder out = new StringBuilder();
+        withinParagraph(out, text, 0, text.length());
+        return out.toString();
+    }
+
+    public int colorToId(int color) {
+        int[] colors = context.getThemeUtil().res.mircColors;
+        for (int i = 0; i < colors.length; i++) {
+            if (colors[i] == color)
+                return i;
+        }
+        return 0;
+    }
+
+    private void withinParagraph(StringBuilder out, Spanned text,
+                                        int start, int end) {
+        int next;
+        for (int i = start; i < end; i = next) {
+            next = text.nextSpanTransition(i, end, CharacterStyle.class);
+            CharacterStyle[] style = text.getSpans(i, next,
+                    CharacterStyle.class);
+
+            boolean jump = false;
+
+            for (int j = 0; j < style.length; j++) {
+                if (jump) {
+                    jump = false;
+                } else {
+                    if ((text.getSpanFlags(style[j]) & Spanned.SPAN_COMPOSING) != 0)
+                        continue;
+
+                    if (style[j] instanceof BoldSpan) {
+                        out.append((char) 0x02);
+                    } else if (style[j] instanceof ItalicSpan) {
+                        out.append((char) 0x1D);
+                    } else if (style[j] instanceof UnderlineSpan) {
+                        out.append((char) 0x1F);
+                    } else if (style[j] instanceof ForegroundColorSpan) {
+                        int fg;
+                        int bg;
+                        fg = colorToId(((ForegroundColorSpan) style[j]).getForegroundColor());
+
+                        if ((j + 1 < style.length) && (style[j + 1] instanceof BackgroundColorSpan)) {
+                            bg = colorToId(((BackgroundColorSpan) style[j + 1]).getBackgroundColor());
+                        } else {
+                            bg = 99;
+                        }
+
+                        out.append((char) 0x03);
+                        out.append(String.format("%02d,%02d", fg, bg));
+
+                        jump = true;
+                    } else if (style[j] instanceof BackgroundColorSpan) {
+                        int fg;
+                        int bg;
+                        if ((j + 1 < style.length) && (style[j + 1] instanceof ForegroundColorSpan)) {
+                            fg = colorToId(((ForegroundColorSpan) style[j + 1]).getForegroundColor());
+                        } else {
+                            fg = 99;
+                        }
+
+                        bg = colorToId(((BackgroundColorSpan) style[j]).getBackgroundColor());
+
+                        out.append((char) 0x03);
+                        out.append(String.format("%02d,%02d", fg, bg));
+
+                        jump = true;
+                    }
+                }
+            }
+
+            out.append(text.subSequence(i,next));
+
+            for (int j = style.length - 1; j >= 0; j--) {
+                if ((text.getSpanFlags(style[j]) & Spanned.SPAN_COMPOSING) != 0)
+                    continue;
+
+                if (style[j] instanceof ForegroundColorSpan) {
+                    out.append((char) 0x03);
+                }
+                if (style[j] instanceof BackgroundColorSpan) {
+                    out.append((char) 0x03);
+                }
+                if (style[j] instanceof UnderlineSpan) {
+                    out.append((char) 0x1F);
+                }
+                if (style[j] instanceof BoldSpan) {
+                    out.append((char) 0x02);
+                }
+                if (style[j] instanceof ItalicSpan) {
+                    out.append((char) 0x1D);
+                }
+            }
+        }
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/ItalicSpan.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/ItalicSpan.java
new file mode 100644
index 000000000..93fed4cb0
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/ItalicSpan.java
@@ -0,0 +1,10 @@
+package de.kuschku.quasseldroid_ng.ui.editor;
+
+import android.graphics.Typeface;
+import android.text.style.StyleSpan;
+
+public class ItalicSpan extends StyleSpan {
+    public ItalicSpan() {
+        super(Typeface.ITALIC);
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java
index a4295233e..fa55f28ff 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java
@@ -3,18 +3,23 @@ package de.kuschku.quasseldroid_ng.ui.theme;
 import android.content.Context;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.UiThread;
 import android.support.v7.view.ContextThemeWrapper;
+import android.util.Log;
 
 import de.kuschku.quasseldroid_ng.R;
 import de.kuschku.util.annotationbind.AutoBinder;
 import de.kuschku.util.annotationbind.AutoColor;
 import de.kuschku.util.annotationbind.AutoDimen;
+import de.kuschku.util.annotationbind.AutoString;
 import de.kuschku.util.ui.DateTimeFormatHelper;
+import de.kuschku.util.ui.SpanFormatter;
 
 public class ThemeUtil {
     @NonNull
     public final Colors res = new Colors();
+    public final FormatStrings translations = new FormatStrings();
     public DateTimeFormatHelper formatter;
 
     public ThemeUtil(@NonNull Context ctx) {
@@ -31,11 +36,145 @@ public class ThemeUtil {
     public void initColors(@NonNull ContextThemeWrapper wrapper) {
         try {
             AutoBinder.bind(res, wrapper);
+            AutoBinder.bind(translations, wrapper);
         } catch (IllegalAccessException e) {
             e.printStackTrace();
         }
     }
 
+    public static class FormatStrings {
+        @AutoString(R.string.username_hostmask)
+        public String username_hostmask;
+
+        @AutoString(R.string.message_plain)
+        public String message_plain;
+
+        @AutoString(R.string.message_join)
+        public String message_join;
+
+        @AutoString(R.string.message_part)
+        public String message_part;
+
+        @AutoString(R.string.message_part_extra)
+        public String message_part_extra;
+
+        @AutoString(R.string.message_quit)
+        public String message_quit;
+
+        @AutoString(R.string.message_quit_extra)
+        public String message_quit_extra;
+
+        @AutoString(R.string.message_kill)
+        public String message_kill;
+
+        @AutoString(R.string.message_kick)
+        public String message_kick;
+
+        @AutoString(R.string.message_kick_extra)
+        public String message_kick_extra;
+
+        @AutoString(R.string.message_mode)
+        public String message_mode;
+
+        @AutoString(R.string.message_nick_self)
+        public String message_nick_self;
+
+        @AutoString(R.string.message_nick_other)
+        public String message_nick_other;
+
+        @AutoString(R.string.message_daychange)
+        public String message_daychange;
+
+        @AutoString(R.string.message_action)
+        public String message_action;
+
+        @AutoString(R.string.title_status_buffer)
+        public String title_status_buffer;
+
+        @NonNull
+        public CharSequence formatUsername(@NonNull CharSequence nick, @NonNull CharSequence hostmask) {
+            return SpanFormatter.format(username_hostmask, nick, hostmask);
+        }
+
+        @NonNull
+        public CharSequence formatJoin(@NonNull CharSequence user, @NonNull CharSequence channel) {
+            return SpanFormatter.format(message_join, user, channel);
+        }
+
+        @NonNull
+        public CharSequence formatPart(@NonNull CharSequence user, @NonNull CharSequence channel) {
+            return SpanFormatter.format(message_part, user, channel);
+        }
+
+        @NonNull
+        public CharSequence formatPart(@NonNull CharSequence user, @NonNull CharSequence channel, @Nullable CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatPart(user, channel);
+
+            return SpanFormatter.format(message_part_extra, user, channel, reason);
+        }
+
+        @NonNull
+        public CharSequence formatQuit(@NonNull CharSequence user) {
+            return SpanFormatter.format(message_quit, user);
+        }
+
+        @NonNull
+        public CharSequence formatQuit(@NonNull CharSequence user, @Nullable CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatQuit(user);
+
+            return SpanFormatter.format(message_quit_extra, user, reason);
+        }
+
+        @NonNull
+        public CharSequence formatKill(@NonNull CharSequence user, @NonNull CharSequence channel) {
+            return SpanFormatter.format(message_kill, user, channel);
+        }
+
+        @NonNull
+        public CharSequence formatKick(@NonNull CharSequence user, @NonNull CharSequence kicked) {
+            return SpanFormatter.format(message_kick, user, kicked);
+        }
+
+        @NonNull
+        public CharSequence formatKick(@NonNull CharSequence user, @NonNull CharSequence kicked, @Nullable CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatKick(user, kicked);
+
+            return SpanFormatter.format(message_kick_extra, user, kicked, reason);
+        }
+
+        @NonNull
+        public CharSequence formatMode(@NonNull CharSequence mode, @NonNull CharSequence user) {
+            return SpanFormatter.format(message_mode, mode, user);
+        }
+
+        @NonNull
+        public CharSequence formatNick(@NonNull CharSequence newNick) {
+            return SpanFormatter.format(message_nick_self, newNick);
+        }
+
+        @NonNull
+        public CharSequence formatNick(@NonNull CharSequence oldNick, @Nullable CharSequence newNick) {
+            if (newNick == null || newNick.length() == 0) return formatNick(oldNick);
+
+            return SpanFormatter.format(message_nick_other, oldNick, newNick);
+        }
+
+        @NonNull
+        public CharSequence formatDayChange(@NonNull CharSequence day) {
+            return SpanFormatter.format(message_daychange, day);
+        }
+
+        @NonNull
+        public CharSequence formatAction(@NonNull CharSequence user, @NonNull CharSequence channel) {
+            return SpanFormatter.format(message_action, user, channel);
+        }
+
+        @NonNull
+        public CharSequence formatPlain(@NonNull CharSequence nick, @NonNull CharSequence message) {
+            return SpanFormatter.format(message_plain, nick, message);
+        }
+    }
+
     public static class Colors {
         @AutoColor(android.R.color.transparent)
         @ColorInt
diff --git a/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java b/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java
index 077562e98..fc05b3521 100644
--- a/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java
+++ b/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java
@@ -24,7 +24,9 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.theme.AppContext;
 import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil;
+import de.kuschku.util.ui.MessageUtil;
 
 public class IrcFormatHelper {
     @NonNull
@@ -41,16 +43,16 @@ public class IrcFormatHelper {
     private static final Pattern channelPattern = Pattern.compile("((?:#|![A-Z0-9]{5})[^,:\\s]+(?::[^,:\\s]+)?)\\b", Pattern.CASE_INSENSITIVE);
 
     @NonNull
-    private final ThemeUtil.Colors colors;
+    private final AppContext context;
 
-    public IrcFormatHelper(@NonNull ThemeUtil.Colors colors) {
-        this.colors = colors;
+    public IrcFormatHelper(@NonNull AppContext context) {
+        this.context = context;
     }
 
     @NonNull
     public CharSequence formatUserNick(@NonNull String nick) {
         int colorIndex = IrcUserUtils.getSenderColor(nick);
-        int color = colors.senderColors[colorIndex];
+        int color = context.getThemeUtil().res.senderColors[colorIndex];
 
         SpannableString str = new SpannableString(nick);
         str.setSpan(new ForegroundColorSpan(color), 0, nick.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
@@ -62,7 +64,7 @@ public class IrcFormatHelper {
     public CharSequence formatIrcMessage(@NonNull String message) {
         List<FutureClickableSpan> spans = new LinkedList<>();
 
-        SpannableString str = new SpannableString(message);
+        SpannableString str = new SpannableString(MessageUtil.parseStyleCodes(context.getThemeUtil(), message, context.getSettings().mircColors.get()));
         Matcher urlMatcher = urlPattern.matcher(str);
         while (urlMatcher.find()) {
             spans.add(new FutureClickableSpan(new CustomURLSpan(urlMatcher.toString()), urlMatcher.start(), urlMatcher.end()));
diff --git a/app/src/main/java/de/kuschku/util/observables/ContentComparable.java b/app/src/main/java/de/kuschku/util/observables/ContentComparable.java
index 6295bc245..952ca7fea 100644
--- a/app/src/main/java/de/kuschku/util/observables/ContentComparable.java
+++ b/app/src/main/java/de/kuschku/util/observables/ContentComparable.java
@@ -1,5 +1,6 @@
 package de.kuschku.util.observables;
 
 public interface ContentComparable<T extends ContentComparable<T>> extends Comparable<T> {
-    boolean equalsContent(T other);
+    boolean areItemsTheSame(T other);
+    boolean areContentsTheSame(T other);
 }
diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ObservableComparableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ObservableComparableSortedList.java
index bf571f8ad..7c66e49b9 100644
--- a/app/src/main/java/de/kuschku/util/observables/lists/ObservableComparableSortedList.java
+++ b/app/src/main/java/de/kuschku/util/observables/lists/ObservableComparableSortedList.java
@@ -24,12 +24,12 @@ public class ObservableComparableSortedList<T extends ContentComparable<T>> exte
 
         @Override
         public boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem) {
-            return oldItem.equalsContent(newItem);
+            return oldItem.areContentsTheSame(newItem);
         }
 
         @Override
         public boolean areItemsTheSame(@NonNull T item1, @NonNull T item2) {
-            return item1.equals(item2);
+            return item1.areItemsTheSame(item2);
         }
     }
 }
diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java
index b6bab5cd4..33a0de54b 100644
--- a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java
+++ b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java
@@ -7,6 +7,7 @@ import android.support.v7.util.SortedList;
 import com.afollestad.materialdialogs.MaterialDialog;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
@@ -195,7 +196,11 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
     @NonNull
     @Override
     public Object[] toArray() {
-        throw new MaterialDialog.NotImplementedException("Not implemented");
+        Object[] array = new Object[list.size()];
+        for (int i = 0; i < list.size(); i++) {
+            array[i] = list.get(i);
+        }
+        return array;
     }
 
     @NonNull
@@ -216,6 +221,11 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
         boolean areItemsTheSame(T item1, T item2);
     }
 
+    @Override
+    public String toString() {
+        return Arrays.toString(toArray());
+    }
+
     class Callback extends SortedList.Callback<T> {
         @Override
         public int compare(T o1, T o2) {
@@ -284,12 +294,12 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
 
         @Override
         public boolean hasNext() {
-            return list.size() > position + 1;
+            return position < list.size();
         }
 
         @Override
         public boolean hasPrevious() {
-            return false;
+            return position >= 0;
         }
 
         @Override
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
index 0879d3b13..8237f7271 100644
--- a/app/src/main/res/layout/activity_chat.xml
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -13,7 +13,7 @@
         android:layout_height="match_parent"
         android:gravity="bottom"
         android:orientation="vertical"
-        app:umanoScrollableView="@+id/chatlineScroller"
+        app:umanoScrollableView="@+id/chatline_scroller"
         app:umanoPanelHeight="?attr/actionBarSize"
         app:umanoShadowHeight="4dp">
 
diff --git a/app/src/main/res/layout/slider_main.xml b/app/src/main/res/layout/slider_main.xml
index c9acedcfe..f3c374a74 100644
--- a/app/src/main/res/layout/slider_main.xml
+++ b/app/src/main/res/layout/slider_main.xml
@@ -1,79 +1,21 @@
 <?xml version="1.0" encoding="utf-8"?>
 <com.sothree.slidinguppanel.SlidingUpPanelLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sothree="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/sliding_layout_history"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
-    xmlns:app="http://schemas.android.com/tools"
     android:background="?attr/colorBackgroundCard"
     android:clickable="false"
     android:focusable="true"
     android:gravity="bottom"
-    sothree:umanoFadeColor="#00000000"
+    sothree:umanoFadeColor="?attr/colorBackgroundCard"
     sothree:umanoOverlay="true"
-    sothree:umanoPanelHeight="72dip"
+    sothree:umanoPanelHeight="@dimen/message_history_panel_height"
     sothree:umanoScrollableView="@+id/msg_history"
     sothree:umanoShadowHeight="0.0dip">
 
 
-    <RelativeLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:paddingBottom="72dp">
-
-        <ScrollView
-            android:id="@+id/chatlineScroller"
-            android:layout_width="0dip"
-            android:layout_height="match_parent"
-            android:layout_alignParentLeft="true"
-            android:layout_alignParentStart="true"
-            android:layout_toLeftOf="@+id/send"
-            android:layout_toStartOf="@+id/send"
-            android:layout_alignParentTop="true"
-            android:layout_above="@+id/formatting_toolbar">
-
-            <android.support.v7.widget.AppCompatEditText
-                android:id="@+id/chatline"
-                android:layout_width="match_parent"
-                android:layout_height="?attr/actionBarSize"
-                android:background="@android:color/transparent"
-                android:gravity="top"
-                android:hint="@string/message_placeholder"
-                android:inputType="textCapSentences|textShortMessage|textAutoCorrect"
-                android:paddingBottom="17dp"
-                android:paddingLeft="20dp"
-                android:paddingRight="20dp"
-                android:paddingTop="17dp"
-                android:textSize="16sp" />
-
-        </ScrollView>
-
-        <android.support.v7.widget.AppCompatImageButton
-            android:id="@+id/send"
-            style="?attr/buttonStyleSmall"
-            android:layout_width="56dp"
-            android:layout_height="?attr/actionBarSize"
-            android:layout_alignParentEnd="true"
-            android:layout_alignParentRight="true"
-            android:layout_alignParentTop="true"
-            android:layout_gravity="top"
-            android:background="?attr/selectableItemBackgroundBorderless"
-            android:padding="12dp"
-            android:src="@drawable/ic_send"
-            android:tint="?attr/colorAccent" />
-
-        <android.support.v7.widget.Toolbar
-            android:id="@+id/formatting_toolbar"
-            android:layout_width="match_parent"
-            android:layout_height="?attr/actionBarSize"
-            android:layout_alignParentBottom="true"
-            android:layout_alignParentLeft="true"
-            android:layout_alignParentStart="true">
-            <android.support.v7.widget.ActionMenuView
-                android:id="@+id/amvMenu"
-                android:layout_width="wrap_content"
-                android:layout_height="?attr/actionBarSize"/>
-        </android.support.v7.widget.Toolbar>
-    </RelativeLayout>
+    <include layout="@layout/widget_editor" />
 
     <FrameLayout
         android:id="@+id/card_panel"
@@ -100,7 +42,7 @@
                     android:gravity="center_vertical"
                     android:paddingLeft="16.0dip"
                     android:paddingRight="16.0dip"
-                    android:text="Message History"
+                    android:text="@string/message_history"
                     android:textAppearance="@style/TextAppearance.AppCompat.Body2"
                     android:textColor="?attr/colorForegroundSecondary" />
 
diff --git a/app/src/main/res/layout/widget_editor.xml b/app/src/main/res/layout/widget_editor.xml
new file mode 100644
index 000000000..58e98cb79
--- /dev/null
+++ b/app/src/main/res/layout/widget_editor.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingBottom="@dimen/message_history_panel_height">
+
+    <ScrollView
+        android:id="@+id/chatline_scroller"
+        android:layout_width="0dip"
+        android:layout_height="match_parent"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:layout_toLeftOf="@+id/send"
+        android:layout_toStartOf="@+id/send"
+        android:layout_alignParentTop="true"
+        android:layout_above="@+id/formatting_toolbar_container">
+
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/chatline"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="@android:color/transparent"
+            android:gravity="top"
+            android:hint="@string/message_placeholder"
+            android:inputType="textCapSentences|textShortMessage|textAutoCorrect"
+            android:paddingBottom="17dp"
+            android:paddingLeft="20dp"
+            android:paddingRight="20dp"
+            android:paddingTop="17dp"
+            android:textSize="16sp" />
+
+    </ScrollView>
+
+    <android.support.v7.widget.AppCompatImageButton
+        android:id="@+id/send"
+        style="?attr/buttonStyleSmall"
+        android:layout_width="56dp"
+        android:layout_height="?attr/actionBarSize"
+        android:layout_alignParentEnd="true"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_gravity="top"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        android:padding="12dp"
+        android:src="@drawable/ic_send"
+        android:tint="?attr/colorAccent" />
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/formatting_toolbar_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentStart="true"
+        android:background="?attr/colorBackgroundCard">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/formatting_toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize">
+            <android.support.v7.widget.ActionMenuView
+                android:id="@+id/formatting_menu"
+                android:layout_width="wrap_content"
+                android:layout_height="?attr/actionBarSize"/>
+        </android.support.v7.widget.Toolbar>
+    </android.support.design.widget.AppBarLayout>
+</RelativeLayout>
diff --git a/app/src/main/res/menu/chat.xml b/app/src/main/res/menu/chat.xml
new file mode 100644
index 000000000..9f5ae5a47
--- /dev/null
+++ b/app/src/main/res/menu/chat.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+    <item
+        android:id="@+id/action_hide_events"
+        android:icon="@android:drawable/ic_menu_sort_by_size"
+        android:orderInCategory="100"
+        android:title="Hide events"
+        app:showAsAction="ifRoom" />
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
+</menu>
diff --git a/app/src/main/res/menu/formatting.xml b/app/src/main/res/menu/formatting.xml
index 1e05f1471..d2319d915 100644
--- a/app/src/main/res/menu/formatting.xml
+++ b/app/src/main/res/menu/formatting.xml
@@ -26,4 +26,9 @@
         android:icon="?attr/ic_format_fill"
         app:showAsAction="always"
         android:title="@string/format_fill" />
+    <item
+        android:id="@+id/action_history"
+        android:icon="?attr/ic_history"
+        app:showAsAction="always"
+        android:title="@string/message_history" />
 </menu>
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 55dbf1cf9..c782848f4 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -65,6 +65,7 @@
     <attr name="ic_format_underline" format="reference" />
     <attr name="ic_format_paint" format="reference" />
     <attr name="ic_format_fill" format="reference" />
+    <attr name="ic_history" format="reference" />
 
     <attr name="cardStyle" format="reference" />
 </resources>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index a90926a73..879ca0f5a 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -11,4 +11,5 @@
     <!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:
          https://developer.android.com/design/patterns/navigation-drawer.html -->
     <dimen name="navigation_drawer_width">240dp</dimen>
+    <dimen name="message_history_panel_height">0dip</dimen>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 840c11d75..a6a06bdd2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,9 +1,6 @@
 <resources>
     <string name="app_name">QuasselDroid</string>
     <string name="action_settings">Settings</string>
-    <string name="default_port">4242</string>
-
-    <string name="username_hostmask">%1$s (%2$s)</string>
 
     <!-- Message format strings -->
     <string name="message_plain">%1$s: %2$s</string>
@@ -30,109 +27,18 @@
 
     <string name="message_action">* %1$s %2$s</string>
 
-
-    <string name="super_long_string">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.     Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.     Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.     Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat.     Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis.     At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, At accusam aliquyam diam diam dolore dolores duo eirmod eos erat, et nonumy sed tempor et et invidunt justo labore Stet clita ea et gubergren, kasd magna no rebum. sanctus sea sed takimata ut vero voluptua. est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.     Consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus.     Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.     Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.     Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.     Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo</string>
-    <string name="open_drawer">open</string>
-    <string name="close_drawer">close</string>
+    <!-- Editor UI -->
     <string name="message_placeholder">Write a message…</string>
-
-    <!-- Preferences -->
-    <string name="preference_theme">preference_theme</string>
-    <string name="preference_full_hostmask">preference_full_hostmask</string>
-    <string name="title_activity_scrolling">ScrollingActivity</string>
-    <string name="large_text">
-        "Material is the metaphor.\n\n"
-
-        "A material metaphor is the unifying theory of a rationalized space and a system of motion."
-        "The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
-        "technologically advanced and open to imagination and magic.\n"
-        "Surfaces and edges of the material provide visual cues that are grounded in reality. The "
-        "use of familiar tactile attributes helps users quickly understand affordances. Yet the "
-        "flexibility of the material creates new affordances that supercede those in the physical "
-        "world, without breaking the rules of physics.\n"
-        "The fundamentals of light, surface, and movement are key to conveying how objects move, "
-        "interact, and exist in space and in relation to each other. Realistic lighting shows "
-        "seams, divides space, and indicates moving parts.\n\n"
-
-        "Bold, graphic, intentional.\n\n"
-
-        "The foundational elements of print based design typography, grids, space, scale, color, "
-        "and use of imagery guide visual treatments. These elements do far more than please the "
-        "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
-        "imagery, large scale typography, and intentional white space create a bold and graphic "
-        "interface that immerse the user in the experience.\n"
-        "An emphasis on user actions makes core functionality immediately apparent and provides "
-        "waypoints for the user.\n\n"
-
-        "Motion provides meaning.\n\n"
-
-        "Motion respects and reinforces the user as the prime mover. Primary user actions are "
-        "inflection points that initiate motion, transforming the whole design.\n"
-        "All action takes place in a single environment. Objects are presented to the user without "
-        "breaking the continuity of experience even as they transform and reorganize.\n"
-        "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
-        "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
-
-        "3D world.\n\n"
-
-        "The material environment is a 3D space, which means all objects have x, y, and z "
-        "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
-        "positive z-axis extending towards the viewer. Every sheet of material occupies a single "
-        "position along the z-axis and has a standard 1dp thickness.\n"
-        "On the web, the z-axis is used for layering and not for perspective. The 3D world is "
-        "emulated by manipulating the y-axis.\n\n"
-
-        "Light and shadow.\n\n"
-
-        "Within the material environment, virtual lights illuminate the scene. Key lights create "
-        "directional shadows, while ambient light creates soft shadows from all angles.\n"
-        "Shadows in the material environment are cast by these two light sources. In Android "
-        "development, shadows occur when light sources are blocked by sheets of material at "
-        "various positions along the z-axis. On the web, shadows are depicted by manipulating the "
-        "y-axis only. The following example shows the card with a height of 6dp.\n\n"
-
-        "Resting elevation.\n\n"
-
-        "All material objects, regardless of size, have a resting elevation, or default elevation "
-        "that does not change. If an object changes elevation, it should return to its resting "
-        "elevation as soon as possible.\n\n"
-
-        "Component elevations.\n\n"
-
-        "The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
-        "does not vary from 6dp in one app to 16dp in another app).\n"
-        "Components may have different resting elevations across platforms, depending on the depth "
-        "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
-
-        "Responsive elevation and dynamic elevation offsets.\n\n"
-
-        "Some component types have responsive elevation, meaning they change elevation in response "
-        "to user input (e.g., normal, focused, and pressed) or system events. These elevation "
-        "changes are consistently implemented using dynamic elevation offsets.\n"
-        "Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
-        "to the component’s resting state. They ensure that elevation changes are consistent "
-        "across actions and component types. For example, all components that lift on press have "
-        "the same elevation change relative to their resting elevation.\n"
-        "Once the input event is completed or cancelled, the component will return to its resting "
-        "elevation.\n\n"
-
-        "Avoiding elevation interference.\n\n"
-
-        "Components with responsive elevations may encounter other components as they move between "
-        "their resting elevations and dynamic elevation offsets. Because material cannot pass "
-        "through other material, components avoid interfering with one another any number of ways, "
-        "whether on a per component basis or using the entire app layout.\n"
-        "On a component level, components can move or be removed before they cause interference. "
-        "For example, a floating action button (FAB) can disappear or move off screen before a "
-        "user picks up a card, or it can move if a snackbar appears.\n"
-        "On the layout level, design your app layout to minimize opportunities for interference. "
-        "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere "
-        "when a user tries to pick up one of cards.\n\n"
-    </string>
-    <string name="title_activity_demo">DemoActivity</string>
+    <string name="message_history">Message History</string>
     <string name="format_bold">Bold</string>
     <string name="format_italic">Italic</string>
     <string name="format_underline">Underline</string>
-    <string name="format_color">Text color</string>
-    <string name="format_fill">Background color</string>
+    <string name="format_color">Text Color</string>
+    <string name="format_fill">Background Color</string>
+
+    <!-- Misc -->
+    <string name="username_hostmask">%1$s (%2$s)</string>
+    <string name="title_status_buffer">Status Buffer</string>
+    <string name="open_drawer">open</string>
+    <string name="close_drawer">close</string>
 </resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 537c5f943..4e8d94402 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -16,6 +16,7 @@
         <item name="ic_format_underline">@drawable/ic_format_underline_dark</item>
         <item name="ic_format_paint">@drawable/ic_format_paint_dark</item>
         <item name="ic_format_fill">@drawable/ic_format_fill_dark</item>
+        <item name="ic_history">@drawable/ic_history_dark</item>
 
         <item name="cardStyle">@style/CardView.Dark</item>
     </style>
@@ -35,6 +36,7 @@
         <item name="ic_format_underline">@drawable/ic_format_underline_light</item>
         <item name="ic_format_paint">@drawable/ic_format_paint_light</item>
         <item name="ic_format_fill">@drawable/ic_format_fill_light</item>
+        <item name="ic_history">@drawable/ic_history_light</item>
 
         <item name="cardStyle">@style/CardView.Light</item>
     </style>
-- 
GitLab