From 2e0623a19fa4dbc79f02de562466d79944ca7bad Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Thu, 28 Jan 2016 21:18:56 +0100
Subject: [PATCH] Added proper backlog filtering, refactored some stuff

---
 app/build.gradle                              |   6 +-
 app/src/main/AndroidManifest.xml              |   2 +-
 .../java/de/kuschku/libquassel/Client.java    |  80 +++-
 .../de/kuschku/libquassel/CoreConnection.java |  64 ++-
 .../kuschku/libquassel/IProtocolHandler.java  |  10 +-
 .../kuschku/libquassel/ProtocolHandler.java   |  42 +-
 .../events/BacklogReceivedEvent.java          |   2 +-
 .../libquassel/events/GeneralErrorEvent.java  |   9 +-
 .../libquassel/events/LagChangedEvent.java    |  16 +
 .../exceptions/UnknownTypeException.java      |   8 +-
 .../serializers/HeartbeatReplySerializer.java |  46 +++
 .../serializers/HeartbeatSerializer.java      |  44 +++
 .../libquassel/functions/types/Heartbeat.java |  18 +
 .../functions/types/HeartbeatReply.java       |  18 +
 .../kuschku/libquassel/localtypes/Buffer.java |   2 +
 .../libquassel/localtypes/ChannelBuffer.java  |  11 +-
 .../localtypes/NotificationManager.java       |  18 +
 .../libquassel/localtypes/QueryBuffer.java    |   5 +
 .../libquassel/localtypes/StatusBuffer.java   |   5 +
 .../backlogmanagers/BacklogFilter.java        |  72 ++++
 .../backlogmanagers/BacklogManager.java       |  14 +-
 .../backlogmanagers/SimpleBacklogManager.java |  68 +++-
 .../serializers/CharSerializer.java           |  14 +-
 .../serializers/MessageSerializer.java        |  17 +-
 .../libquassel/protocols/DatastreamPeer.java  |  89 ++++-
 .../libquassel/protocols/LegacyPeer.java      | 104 +++--
 .../syncables/SyncableRegistry.java           |   8 +-
 .../kuschku/libquassel/syncables/Synced.java  |   8 +
 .../serializers/AliasManagerSerializer.java   |  60 +++
 .../serializers/BufferSyncerSerializer.java   |  13 +-
 .../BufferViewManagerSerializer.java          |   3 +-
 .../IgnoreListManagerSerializer.java          |  63 +++
 .../serializers/IrcChannelSerializer.java     |  12 +-
 .../serializers/IrcUserSerializer.java        |  32 +-
 .../serializers/NetworkConfigSerializer.java  |  63 +++
 .../serializers/NetworkSerializer.java        |  13 +-
 .../syncables/types/AliasManager.java         |  38 ++
 .../syncables/types/BufferSyncer.java         |  25 +-
 .../syncables/types/BufferViewConfig.java     |  58 ++-
 .../syncables/types/BufferViewManager.java    |  22 +-
 .../libquassel/syncables/types/Identity.java  |  16 +-
 .../syncables/types/IgnoreListManager.java    | 150 +++++++
 .../syncables/types/IrcChannel.java           | 205 +++++++++-
 .../libquassel/syncables/types/IrcUser.java   | 128 +++++-
 .../libquassel/syncables/types/Network.java   | 111 +++++-
 .../syncables/types/NetworkConfig.java        | 120 ++++++
 .../syncables/types/SyncableObject.java       |  10 +-
 .../quasseldroid_ng/ui/AppContext.java        |  65 +++
 .../kuschku/quasseldroid_ng/ui/AppTheme.java  |   7 +
 .../ui/chat/BufferViewConfigWrapper.java      | 104 +++++
 .../quasseldroid_ng/ui/chat/ChatActivity.java | 373 ++++++++++++++----
 .../ui/chat/chatview/ChatMessageRenderer.java |  33 +-
 .../ui/chat/chatview/MessageAdapter.java      |  16 +-
 .../ui/chat/drawer/BufferItem.java            | 144 +++++++
 .../ui/chat/drawer/BufferWrapper.java         |  78 ----
 .../ui/chat/drawer/NetworkItem.java           | 173 ++++++++
 .../ui/chat/drawer/NetworkWrapper.java        | 275 -------------
 .../java/de/kuschku/util/AndroidAssert.java   | 121 +++++-
 .../java/de/kuschku/util/DrawerUtils.java     |  26 --
 .../java/de/kuschku/util/ReflectionUtils.java |   9 +-
 .../keyboardutils/DialogKeyboardUtil.java     |  32 ++
 .../keyboardutils/EditTextKeyboardUtil.java   |  62 +++
 .../de/kuschku/util/niohelpers/Helper.java    |  32 ++
 .../callbacks/GeneralCallback.java            |   5 +
 .../wrappers/GeneralCallbackWrapper.java      |  28 ++
 .../wrappers/GeneralUICallbackWrapper.java    |  42 ++
 .../ChildParentObservableSortedList.java      |  83 ++++
 .../lists/ObservableSortedList.java           |  27 +-
 .../java/de/kuschku/util/ui/MessageUtil.java  | 167 ++++++++
 app/src/main/res/layout/activity_chat.xml     |  26 ++
 app/src/main/res/layout/activity_main.xml     |  37 --
 app/src/main/res/layout/core_dialog.xml       |  19 -
 app/src/main/res/layout/dialog_address.xml    |  33 ++
 app/src/main/res/layout/dialog_login.xml      |  32 ++
 app/src/main/res/layout/drawer.xml            |  47 ---
 app/src/main/res/layout/login_dialog.xml      |  19 -
 app/src/main/res/layout/toolbar.xml           |   3 +-
 .../main/res/layout/widget_chatmessage.xml    |   4 +-
 app/src/main/res/values/styles.xml            |   4 +-
 app/src/main/res/values/themes.xml            |  17 +
 80 files changed, 3256 insertions(+), 829 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java
 rename app/src/main/java/de/kuschku/libquassel/{ => localtypes}/backlogmanagers/BacklogManager.java (57%)
 rename app/src/main/java/de/kuschku/libquassel/{ => localtypes}/backlogmanagers/SimpleBacklogManager.java (56%)
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/Synced.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java
 create mode 100644 app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java
 delete mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java
 delete mode 100644 app/src/main/java/de/kuschku/util/DrawerUtils.java
 create mode 100644 app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java
 create mode 100644 app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java
 create mode 100644 app/src/main/java/de/kuschku/util/niohelpers/Helper.java
 create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java
 create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java
 create mode 100644 app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java
 create mode 100644 app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java
 create mode 100644 app/src/main/java/de/kuschku/util/ui/MessageUtil.java
 create mode 100644 app/src/main/res/layout/activity_chat.xml
 delete mode 100644 app/src/main/res/layout/activity_main.xml
 delete mode 100644 app/src/main/res/layout/core_dialog.xml
 create mode 100644 app/src/main/res/layout/dialog_address.xml
 create mode 100644 app/src/main/res/layout/dialog_login.xml
 delete mode 100644 app/src/main/res/layout/drawer.xml
 delete mode 100644 app/src/main/res/layout/login_dialog.xml

diff --git a/app/build.gradle b/app/build.gradle
index 398dbe519..44e183dac 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -46,14 +46,10 @@ android {
 }
 
 
-
 dependencies {
     testCompile 'junit:junit:4.12'
 
-    compile('com.mikepenz:materialdrawer:5.0.0.fastAdapter.b5-SNAPSHOT@aar') {
-        transitive = true
-    }
-    compile('com.mikepenz:fastadapter:0.4.2-SNAPSHOT@aar') {
+    compile('com.mikepenz:materialdrawer:5.0.0.b21-SNAPSHOT@aar') {
         transitive = true
     }
     compile('com.github.afollestad.material-dialogs:core:0.8.5.3@aar') {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fe1d777f2..2be27dbc7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,7 +17,7 @@
             android:name=".ui.chat.ChatActivity"
             android:label="@string/app_name"
             android:launchMode="singleTask"
-            android:theme="@style/AppTheme.Light">
+            android:theme="@style/Quassel">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/app/src/main/java/de/kuschku/libquassel/Client.java b/app/src/main/java/de/kuschku/libquassel/Client.java
index 1024106b1..47831854c 100644
--- a/app/src/main/java/de/kuschku/libquassel/Client.java
+++ b/app/src/main/java/de/kuschku/libquassel/Client.java
@@ -2,6 +2,7 @@ package de.kuschku.libquassel;
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -9,28 +10,33 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
-import de.kuschku.libquassel.backlogmanagers.BacklogManager;
-import de.kuschku.libquassel.backlogmanagers.SimpleBacklogManager;
+import de.kuschku.libquassel.events.LagChangedEvent;
+import de.kuschku.libquassel.localtypes.NotificationManager;
+import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogManager;
+import de.kuschku.libquassel.localtypes.backlogmanagers.SimpleBacklogManager;
 import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.StatusMessageEvent;
+import de.kuschku.libquassel.functions.types.HandshakeFunction;
 import de.kuschku.libquassel.functions.types.InitRequestFunction;
 import de.kuschku.libquassel.functions.types.RpcCallFunction;
 import de.kuschku.libquassel.localtypes.Buffer;
 import de.kuschku.libquassel.localtypes.Buffers;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.objects.types.ClientInitAck;
+import de.kuschku.libquassel.objects.types.ClientLogin;
 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.BufferSyncer;
+import de.kuschku.libquassel.syncables.types.BufferViewConfig;
 import de.kuschku.libquassel.syncables.types.BufferViewManager;
+import de.kuschku.libquassel.syncables.types.IgnoreListManager;
+import de.kuschku.libquassel.syncables.types.IrcChannel;
+import de.kuschku.libquassel.syncables.types.IrcUser;
 import de.kuschku.libquassel.syncables.types.Network;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
-import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkWrapper;
 import de.kuschku.util.backports.Stream;
-import de.kuschku.util.observables.callbacks.UICallback;
-import de.kuschku.util.observables.lists.IObservableList;
-import de.kuschku.util.observables.lists.ObservableComparableSortedList;
+import de.kuschku.util.observables.lists.ObservableElementList;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
@@ -39,7 +45,7 @@ public class Client {
     @NonNull
     private final Map<Integer, Network> networks = new HashMap<>();
     @NonNull
-    private final IObservableList<UICallback, NetworkWrapper> networkList = new ObservableComparableSortedList<>(NetworkWrapper.class);
+    private final ObservableElementList<Integer> networkList = new ObservableElementList<>();
     @NonNull
     private final Map<Integer, Buffer> buffers = new HashMap<>();
     @NonNull
@@ -47,8 +53,10 @@ public class Client {
     @NonNull
     private final BacklogManager backlogManager;
     @NonNull
+    private final NotificationManager notificationManager = new NotificationManager();
+    @NonNull
     private final BusProvider busProvider;
-    public int lag;
+    private long lag;
     private ConnectionChangeEvent.Status connectionStatus;
     private ClientInitAck core;
     @Nullable
@@ -56,6 +64,7 @@ public class Client {
     private BufferViewManager bufferViewManager;
     private BufferSyncer bufferSyncer;
     private ClientData clientData;
+    private IgnoreListManager ignoreListManager;
 
     public Client(@NonNull final BusProvider busProvider) {
         this(new SimpleBacklogManager(busProvider), busProvider);
@@ -64,6 +73,7 @@ public class Client {
     public Client(@NonNull final BacklogManager backlogManager, @NonNull final BusProvider busProvider) {
         this.backlogManager = backlogManager;
         this.busProvider = busProvider;
+        this.backlogManager.setClient(this);
     }
 
     public void sendInput(@NonNull final BufferInfo info, @NonNull final String input) {
@@ -86,6 +96,7 @@ public class Client {
         assertNotNull(state);
 
         networks.put(network.getNetworkId(), network);
+        networkList.add(network.getNetworkId());
 
         for (BufferInfo info : state.BufferInfos) {
             if (info.networkId == network.getNetworkId()) {
@@ -104,6 +115,7 @@ public class Client {
 
     public void putBuffer(@NonNull final Buffer buffer) {
         this.buffers.put(buffer.getInfo().id, buffer);
+        this.notificationManager.init(buffer.getInfo().id);
     }
 
     @Nullable
@@ -111,11 +123,11 @@ public class Client {
         return this.buffers.get(bufferId);
     }
 
-    void sendInitRequest(@NonNull final String className, @Nullable final String objectName) {
+    public void sendInitRequest(@NonNull final String className, @Nullable final String objectName) {
         sendInitRequest(className, objectName, false);
     }
 
-    void sendInitRequest(@NonNull final String className, @Nullable final String objectName, boolean addToList) {
+    public void sendInitRequest(@NonNull final String className, @Nullable final String objectName, boolean addToList) {
         busProvider.dispatch(new InitRequestFunction(className, objectName));
 
         if (addToList)
@@ -135,33 +147,40 @@ public class Client {
     }
 
     @Nullable
-    public SyncableObject getObjectByIdentifier(@NonNull final String className, @NonNull final String objectName) {
+    public SyncableObject getObjectByIdentifier(@NonNull final String className, @Nullable final String objectName) {
         switch (className) {
             case "BacklogManager":
                 return getBacklogManager();
             case "IrcChannel": {
+                assertNotNull(objectName);
                 final int networkId = Integer.parseInt(objectName.split("/")[0]);
                 final String channelname = objectName.split("/")[1];
 
                 // Assert that networkId is valid
                 Network network = getNetwork(networkId);
                 assertNotNull(network);
-                return network.getChannels().get(channelname);
+                IrcChannel channel = network.getChannels().get(channelname);
+                assertNotNull("Channel " + channelname + " not found in " + network.getChannels().keySet(), channel);
+                return channel;
             }
             case "BufferSyncer":
                 return bufferSyncer;
             case "BufferViewConfig":
                 assertNotNull(getBufferViewManager());
-
+                assertNotNull(objectName);
                 return getBufferViewManager().BufferViews.get(Integer.valueOf(objectName));
             case "IrcUser": {
+                assertNotNull(objectName);
                 final int networkId = Integer.parseInt(objectName.split("/")[0]);
                 final String username = objectName.split("/")[1];
                 Network network = getNetwork(networkId);
                 assertNotNull(network);
-                return network.getUser(username);
+                IrcUser networkUser = network.getUser(username);
+                assertNotNull("User " + username + " not found in " + network.getUsers().keySet(), networkUser);
+                return networkUser;
             }
             case "Network": {
+                assertNotNull(objectName);
                 return getNetwork(Integer.parseInt(objectName));
             }
             default:
@@ -184,7 +203,7 @@ public class Client {
     }
 
     @NonNull
-    public BacklogManager getBacklogManager() {
+    public BacklogManager<?> getBacklogManager() {
         return backlogManager;
     }
 
@@ -226,8 +245,8 @@ public class Client {
     }
 
     @NonNull
-    public Collection<Network> getNetworks() {
-        return networks.values();
+    public ObservableElementList<Integer> getNetworks() {
+        return networkList;
     }
 
     @NonNull
@@ -240,8 +259,31 @@ public class Client {
         busProvider.sendEvent(new ConnectionChangeEvent(connectionStatus));
     }
 
+    public void login(String username, String password) {
+        busProvider.dispatch(new HandshakeFunction(new ClientLogin(
+                username, password
+        )));
+    }
+
     @NonNull
-    public IObservableList<UICallback, NetworkWrapper> getNetworkList() {
-        return networkList;
+    public NotificationManager getNotificationManager() {
+        return notificationManager;
+    }
+
+    public void setLag(long l) {
+        lag = l;
+        busProvider.sendEvent(new LagChangedEvent(lag));
+    }
+
+    public long getLag() {
+        return lag;
+    }
+
+    public IgnoreListManager getIgnoreListManager() {
+        return ignoreListManager;
+    }
+
+    public void setIgnoreListManager(IgnoreListManager ignoreListManager) {
+        this.ignoreListManager = ignoreListManager;
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
index 003d968aa..23387e781 100644
--- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
+++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
@@ -5,6 +5,8 @@ import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.Log;
 
+import org.joda.time.DateTime;
+
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.Socket;
@@ -20,6 +22,7 @@ import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.events.HandshakeFailedEvent;
 import de.kuschku.libquassel.functions.types.HandshakeFunction;
+import de.kuschku.libquassel.functions.types.Heartbeat;
 import de.kuschku.libquassel.objects.types.ClientInit;
 import de.kuschku.libquassel.primitives.QMetaTypeRegistry;
 import de.kuschku.libquassel.primitives.serializers.ProtocolSerializer;
@@ -50,7 +53,9 @@ public class CoreConnection {
     @Nullable
     private ExecutorService outputExecutor;
     @Nullable
-    private ExecutorService inputExecutor;
+    private EndableThread inputThread;
+    @Nullable
+    private EndableThread heartbeatThread;
     @Nullable
     private RemotePeer remotePeer;
     @Nullable
@@ -95,7 +100,6 @@ public class CoreConnection {
 
         // Create executor for write events
         outputExecutor = Executors.newSingleThreadExecutor();
-        inputExecutor = Executors.newSingleThreadExecutor();
 
         // Execute handshake
         handshake();
@@ -112,7 +116,8 @@ public class CoreConnection {
         client.setConnectionStatus(ConnectionChangeEvent.Status.DISCONNECTED);
 
         // We can do this because we clean up the file handles ourselves
-        if (inputExecutor != null) inputExecutor.shutdownNow();
+        if (inputThread != null) inputThread.end();
+        if (heartbeatThread != null) heartbeatThread.end();
         if (outputExecutor != null) outputExecutor.shutdownNow();
 
         // Which we do exactly here
@@ -154,7 +159,6 @@ public class CoreConnection {
      */
     private void handshake() throws IOException {
         assertNotNull(channel);
-        assertNotNull(inputExecutor);
 
         // Start protocol handshake with magic version and feature flags
         QMetaTypeRegistry.serialize(UInt, channel, 0x42b33f00 | clientData.flags.flags);
@@ -166,7 +170,10 @@ public class CoreConnection {
         QMetaTypeRegistry.serialize(UInt, channel, 0x01 << 31);
 
         // Spawn and start a new read thread
-        inputExecutor.submit(new ReadRunnable());
+        inputThread = new ReadThread();
+        heartbeatThread = new HeartbeatThread();
+        inputThread.start();
+        heartbeatThread.start();
     }
 
     public void onEventAsync(HandshakeFailedEvent event) {
@@ -192,14 +199,20 @@ public class CoreConnection {
     /**
      * A runnable that reads from the channel and calls the functions responsible for processing the read data.
      */
-    private class ReadRunnable implements Runnable {
+    private class ReadThread extends EndableThread {
+        private boolean running = true;
+
+        public ReadThread() {
+            setName(getClass().getSimpleName());
+        }
+
         @Override
         public void run() {
             assertNotNull(client);
 
             try {
                 boolean hasReadPreHandshake = false;
-                while (true) {
+                while (running) {
                     if (!hasReadPreHandshake) {
                         final ByteBuffer buffer = ByteBuffer.allocate(4);
                         assertNotNull(buffer);
@@ -248,5 +261,42 @@ public class CoreConnection {
                 busProvider.sendEvent(new GeneralErrorEvent(e));
             }
         }
+
+        @Override
+        public void end() {
+            running = false;
+        }
+    }
+
+    private class HeartbeatThread extends EndableThread {
+        private boolean running = true;
+
+        public HeartbeatThread() {
+            setName(getClass().getSimpleName());
+        }
+
+        @Override
+        public void run() {
+            try {
+                assertNotNull(client);
+
+                while (running) {
+                    busProvider.dispatch(new Heartbeat(DateTime.now()));
+
+                    Thread.sleep(30 * 1000);
+                }
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void end() {
+            running = false;
+        }
+    }
+
+    private abstract class EndableThread extends Thread {
+        public abstract void end();
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
index 53353c2e7..ef0fdb7e7 100644
--- a/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
+++ b/app/src/main/java/de/kuschku/libquassel/IProtocolHandler.java
@@ -21,15 +21,15 @@ public interface IProtocolHandler {
 
     void onEventMainThread(SyncFunction packedFunc);
 
-    void onEventMainThread(ClientInitReject message);
+    void onEvent(ClientInitReject message);
 
-    void onEventMainThread(ClientInitAck message);
+    void onEvent(ClientInitAck message);
 
-    void onEventMainThread(ClientLoginAck message);
+    void onEvent(ClientLoginAck message);
 
-    void onEventMainThread(ClientLoginReject message);
+    void onEvent(ClientLoginReject message);
 
-    void onEventMainThread(SessionInit message);
+    void onEvent(SessionInit 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 ef9f947eb..6cafde2d2 100644
--- a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java
+++ b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java
@@ -3,12 +3,16 @@ package de.kuschku.libquassel;
 import android.support.annotation.NonNull;
 import android.util.Log;
 
+import org.joda.time.DateTime;
+
 import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.events.HandshakeFailedEvent;
 import de.kuschku.libquassel.events.LoginFailedEvent;
 import de.kuschku.libquassel.events.LoginSuccessfulEvent;
 import de.kuschku.libquassel.exceptions.UnknownTypeException;
+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;
@@ -20,10 +24,13 @@ import de.kuschku.libquassel.objects.types.ClientLoginReject;
 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.SyncableObject;
 import de.kuschku.util.AndroidAssert;
 import de.kuschku.util.ReflectionUtils;
 
+import static de.kuschku.util.AndroidAssert.assertNotNull;
+
 public class ProtocolHandler implements IProtocolHandler {
     @NonNull
     public final Client client;
@@ -51,7 +58,7 @@ public class ProtocolHandler implements IProtocolHandler {
                 }
             }
             SyncableObject object = SyncableRegistry.from(packedFunc);
-            AndroidAssert.assertNotNull(object);
+            assertNotNull(object);
 
             object.init(packedFunc, busProvider, client);
         } catch (Exception e) {
@@ -79,21 +86,21 @@ public class ProtocolHandler implements IProtocolHandler {
     public void onEventMainThread(@NonNull SyncFunction packedFunc) {
         try {
             final Object syncable = client.getObjectByIdentifier(packedFunc.className, packedFunc.objectName);
-            if (syncable != null) {
-                ReflectionUtils.invokeMethod(syncable, packedFunc.methodName, packedFunc.params);
-            } else {
-                busProvider.sendEvent(new GeneralErrorEvent(new UnknownTypeException(packedFunc.className)));
-            }
+            AndroidAssert.assertNotNull("Object not found: " + packedFunc.className+":"+packedFunc.objectName, syncable);
+            ReflectionUtils.invokeMethod(syncable, packedFunc.methodName, packedFunc.params);
         } catch (Exception e) {
-            busProvider.sendEvent(new GeneralErrorEvent(e));
+            busProvider.sendEvent(new GeneralErrorEvent(e, packedFunc.toString()));
+        } catch (Error e) {
+            e.printStackTrace();
+            Log.e("EVENT", packedFunc.toString());
         }
     }
 
-    public void onEventMainThread(@NonNull ClientInitReject message) {
+    public void onEvent(@NonNull ClientInitReject message) {
         busProvider.sendEvent(new HandshakeFailedEvent(message.Error));
     }
 
-    public void onEventMainThread(ClientInitAck message) {
+    public void onEvent(ClientInitAck message) {
         client.setCore(message);
 
         if (client.getCore().Configured) {
@@ -105,16 +112,16 @@ public class ProtocolHandler implements IProtocolHandler {
         }
     }
 
-    public void onEventMainThread(ClientLoginAck message) {
+    public void onEvent(ClientLoginAck message) {
         busProvider.sendEvent(new LoginSuccessfulEvent());
         client.setConnectionStatus(ConnectionChangeEvent.Status.CONNECTING);
     }
 
-    public void onEventMainThread(@NonNull ClientLoginReject message) {
+    public void onEvent(@NonNull ClientLoginReject message) {
         busProvider.sendEvent(new LoginFailedEvent(message.Error));
     }
 
-    public void onEventMainThread(@NonNull SessionInit message) {
+    public void onEvent(@NonNull SessionInit message) {
         client.setState(message.SessionState);
 
         client.setConnectionStatus(ConnectionChangeEvent.Status.INITIALIZING_DATA);
@@ -134,6 +141,17 @@ public class ProtocolHandler implements IProtocolHandler {
         }
     }
 
+    public void onEvent(@NonNull Heartbeat heartbeat) {
+        busProvider.dispatch(new HeartbeatReply(heartbeat.dateTime));
+    }
+
+    public void onEventMainThread(@NonNull HeartbeatReply heartbeat) {
+        long roundtrip = DateTime.now().getMillis() - heartbeat.dateTime.getMillis();
+        long lag = (long) (roundtrip * 0.5);
+
+        client.setLag(lag);
+    }
+
     @NonNull
     @Override
     public Client getClient() {
diff --git a/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
index 1a9c1f93a..e16606c29 100644
--- a/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
+++ b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
@@ -4,7 +4,7 @@ import android.support.annotation.IntRange;
 
 @SuppressWarnings("WeakerAccess")
 public class BacklogReceivedEvent {
-    @IntRange(from = 0)
+    @IntRange(from = -1)
     public final int bufferId;
 
     public BacklogReceivedEvent(int bufferId) {
diff --git a/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java b/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java
index 4f3b5db6c..9a06e6d02 100644
--- a/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java
+++ b/app/src/main/java/de/kuschku/libquassel/events/GeneralErrorEvent.java
@@ -13,11 +13,18 @@ public class GeneralErrorEvent {
         this.exception = exception;
     }
 
+    public GeneralErrorEvent(Exception exception, String debugInfo) {
+        this.debugInfo = debugInfo;
+        this.exception = exception;
+    }
+
     @Override
     public String toString() {
         if (debugInfo == null)
             return String.format("%s: %s", exception.getClass().getSimpleName(), exception.getLocalizedMessage());
-        else
+        else if (exception == null)
             return debugInfo;
+        else
+            return String.format("%s: %s\n%s", exception.getClass().getSimpleName(), exception.getLocalizedMessage(), debugInfo);
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java
new file mode 100644
index 000000000..4b5cdcf56
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/events/LagChangedEvent.java
@@ -0,0 +1,16 @@
+package de.kuschku.libquassel.events;
+
+public class LagChangedEvent {
+    public final long lag;
+
+    public LagChangedEvent(long lag) {
+        this.lag = lag;
+    }
+
+    @Override
+    public String toString() {
+        return "LagChangedEvent{" +
+                "lag=" + lag +
+                '}';
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java b/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java
index 97b2d2a81..f7ac88da4 100644
--- a/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java
+++ b/app/src/main/java/de/kuschku/libquassel/exceptions/UnknownTypeException.java
@@ -4,9 +4,15 @@ import android.support.annotation.Nullable;
 
 public class UnknownTypeException extends IllegalArgumentException {
     public final String typeName;
+    public final String additionalData;
 
     public UnknownTypeException(String typeName) {
+        this(typeName, null);
+    }
+
+    public UnknownTypeException(String typeName, Object additionalData) {
         this.typeName = typeName;
+        this.additionalData = String.valueOf(additionalData);
     }
 
     @Nullable
@@ -17,6 +23,6 @@ public class UnknownTypeException extends IllegalArgumentException {
 
     @Override
     public String getMessage() {
-        return String.format("Unknown type: %s", typeName);
+        return String.format("Unknown type: %s; %s", typeName, additionalData);
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java
new file mode 100644
index 000000000..ffc10823f
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java
@@ -0,0 +1,46 @@
+package de.kuschku.libquassel.functions.serializers;
+
+import android.support.annotation.NonNull;
+
+import org.joda.time.DateTime;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import de.kuschku.libquassel.functions.FunctionType;
+import de.kuschku.libquassel.functions.types.Heartbeat;
+import de.kuschku.libquassel.functions.types.HeartbeatReply;
+import de.kuschku.libquassel.primitives.types.QVariant;
+
+import static de.kuschku.util.AndroidAssert.assertTrue;
+
+public class HeartbeatReplySerializer implements FunctionSerializer<HeartbeatReply> {
+    @NonNull
+    private static final HeartbeatReplySerializer serializer = new HeartbeatReplySerializer();
+
+    private HeartbeatReplySerializer() {
+    }
+
+    @NonNull
+    public static HeartbeatReplySerializer get() {
+        return serializer;
+    }
+
+    @NonNull
+    @Override
+    public List serialize(@NonNull HeartbeatReply data) {
+        return Arrays.asList(
+                FunctionType.HEARTBEATREPLY.id,
+                new QVariant<>(data.dateTime)
+        );
+    }
+
+    @NonNull
+    @Override
+    public HeartbeatReply deserialize(@NonNull List packedFunc) {
+        assertTrue(packedFunc.size() == 1);
+
+        return new HeartbeatReply((DateTime) ((QVariant) packedFunc.remove(0)).data);
+    }
+}
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
new file mode 100644
index 000000000..ec2e4c59d
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatSerializer.java
@@ -0,0 +1,44 @@
+package de.kuschku.libquassel.functions.serializers;
+
+import android.support.annotation.NonNull;
+
+import org.joda.time.DateTime;
+
+import java.util.Arrays;
+import java.util.List;
+
+import de.kuschku.libquassel.functions.FunctionType;
+import de.kuschku.libquassel.functions.types.Heartbeat;
+import de.kuschku.libquassel.primitives.types.QVariant;
+
+import static de.kuschku.util.AndroidAssert.assertTrue;
+
+public class HeartbeatSerializer implements FunctionSerializer<Heartbeat> {
+    @NonNull
+    private static final HeartbeatSerializer serializer = new HeartbeatSerializer();
+
+    private HeartbeatSerializer() {
+    }
+
+    @NonNull
+    public static HeartbeatSerializer get() {
+        return serializer;
+    }
+
+    @NonNull
+    @Override
+    public List serialize(@NonNull Heartbeat data) {
+        return Arrays.asList(
+                FunctionType.HEARTBEAT.id,
+                new QVariant<>(data.dateTime)
+        );
+    }
+
+    @NonNull
+    @Override
+    public Heartbeat deserialize(@NonNull List packedFunc) {
+        assertTrue(packedFunc.size() == 1);
+
+        return new Heartbeat((DateTime) ((QVariant) packedFunc.remove(0)).data);
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java b/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java
new file mode 100644
index 000000000..cf10f69f7
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/functions/types/Heartbeat.java
@@ -0,0 +1,18 @@
+package de.kuschku.libquassel.functions.types;
+
+import org.joda.time.DateTime;
+
+public class Heartbeat {
+    public final DateTime dateTime;
+
+    public Heartbeat(DateTime dateTime) {
+        this.dateTime = dateTime;
+    }
+
+    @Override
+    public String toString() {
+        return "Heartbeat{" +
+                "dateTime=" + dateTime +
+                '}';
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java b/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java
new file mode 100644
index 000000000..ecfe819d5
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/functions/types/HeartbeatReply.java
@@ -0,0 +1,18 @@
+package de.kuschku.libquassel.functions.types;
+
+import org.joda.time.DateTime;
+
+public class HeartbeatReply {
+    public final DateTime dateTime;
+
+    public HeartbeatReply(DateTime dateTime) {
+        this.dateTime = dateTime;
+    }
+
+    @Override
+    public String toString() {
+        return "HeartbeatReply{" +
+                "dateTime=" + 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 3a29426f9..40b7c73c8 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/Buffer.java
@@ -11,4 +11,6 @@ public interface Buffer {
 
     @Nullable
     String getName();
+
+    boolean isActive();
 }
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 4e0f977bd..2865b1a63 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
@@ -9,10 +9,10 @@ import de.kuschku.libquassel.syncables.types.IrcChannel;
 public class ChannelBuffer implements Buffer {
     @NonNull
     private final BufferInfo info;
-    @NonNull
+    @Nullable
     private final IrcChannel channel;
 
-    public ChannelBuffer(@NonNull BufferInfo info, @NonNull IrcChannel channel) {
+    public ChannelBuffer(@NonNull BufferInfo info, @Nullable IrcChannel channel) {
         this.info = info;
         this.channel = channel;
     }
@@ -29,7 +29,12 @@ public class ChannelBuffer implements Buffer {
         return getInfo().name;
     }
 
-    @NonNull
+    @Override
+    public boolean isActive() {
+        return channel != null;
+    }
+
+    @Nullable
     public IrcChannel getChannel() {
         return channel;
     }
diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
new file mode 100644
index 000000000..5dd652996
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/NotificationManager.java
@@ -0,0 +1,18 @@
+package de.kuschku.libquassel.localtypes;
+
+import android.util.SparseArray;
+
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.util.observables.lists.ObservableComparableSortedList;
+
+public class NotificationManager {
+    private SparseArray<ObservableComparableSortedList<Message>> notifications = new SparseArray<>();
+
+    public ObservableComparableSortedList<Message> getNotifications(int bufferid) {
+        return notifications.get(bufferid);
+    }
+
+    public void init(int id) {
+        notifications.put(id, new ObservableComparableSortedList<>(Message.class));
+    }
+}
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 9aea13918..b64c6be50 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
@@ -29,6 +29,11 @@ public class QueryBuffer implements Buffer {
         return getInfo().name;
     }
 
+    @Override
+    public boolean isActive() {
+        return user != null;
+    }
+
     @Nullable
     public IrcUser getUser() {
         return user;
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 8218fbbc5..bc25d633c 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
@@ -29,6 +29,11 @@ public class StatusBuffer implements Buffer {
         return network.getNetworkName();
     }
 
+    @Override
+    public boolean isActive() {
+        return network.isConnected();
+    }
+
     @NonNull
     @Override
     public String toString() {
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
new file mode 100644
index 000000000..ec15e0623
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java
@@ -0,0 +1,72 @@
+package de.kuschku.libquassel.localtypes.backlogmanagers;
+
+import android.support.annotation.NonNull;
+
+import com.android.internal.util.Predicate;
+
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
+import de.kuschku.util.observables.callbacks.UICallback;
+import de.kuschku.util.observables.lists.ObservableSortedList;
+
+public class BacklogFilter implements UICallback {
+    @NonNull
+    private final Client client;
+    @NonNull
+    private final ObservableSortedList<Message> unfiltered;
+    @NonNull
+    private final ObservableSortedList<Message> filtered;
+
+    public BacklogFilter(@NonNull Client client, @NonNull ObservableSortedList<Message> unfiltered, @NonNull ObservableSortedList<Message> filtered) {
+        this.client = client;
+        this.unfiltered = unfiltered;
+        this.filtered = filtered;
+    }
+
+    @Override
+    public void notifyItemInserted(int position) {
+        Message message = unfiltered.get(position);
+        if (filterItem(message)) filtered.add(message);
+    }
+
+    private boolean filterItem(Message message) {
+        return !client.getIgnoreListManager().matches(message);
+    }
+
+    @Override
+    public void notifyItemChanged(int position) {
+        filtered.notifyItemChanged(position);
+    }
+
+    @Override
+    public void notifyItemRemoved(int position) {
+        filtered.remove(position);
+    }
+
+    @Override
+    public void notifyItemMoved(int from, int to) {
+        // Can’t occur: Sorted List
+    }
+
+    @Override
+    public void notifyItemRangeInserted(int position, int count) {
+        for (int i = position; i < position + count; i++) {
+            notifyItemInserted(i);
+        }
+    }
+
+    @Override
+    public void notifyItemRangeChanged(int position, int count) {
+        for (int i = position; i < position + count; i++) {
+            notifyItemChanged(i);
+        }
+    }
+
+    @Override
+    public void notifyItemRangeRemoved(int position, int count) {
+        for (int i = position; i < position + count; i++) {
+            notifyItemRemoved(i);
+        }
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java
similarity index 57%
rename from app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java
rename to app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java
index 3c0b700c7..df71178de 100644
--- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java
@@ -1,26 +1,32 @@
-package de.kuschku.libquassel.backlogmanagers;
+package de.kuschku.libquassel.localtypes.backlogmanagers;
 
+import android.support.annotation.IntRange;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 
 import java.util.List;
 
+import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
 import de.kuschku.util.observables.AutoScroller;
 import de.kuschku.util.observables.lists.ObservableSortedList;
 
-public abstract class BacklogManager extends SyncableObject {
+public abstract class BacklogManager<T extends BacklogManager<T>> extends SyncableObject<T> {
     public abstract void requestBacklog(int bufferId, int from, int to, int count, int extra);
 
     public abstract void receiveBacklog(int bufferId, int from, int to, int count, int extra, @NonNull List<Message> messages);
 
     public abstract void displayMessage(int bufferId, @NonNull Message message);
 
-    public abstract ObservableSortedList<Message> get(int bufferId);
+    public abstract ObservableSortedList<Message> get(@IntRange(from = -1) int bufferId);
 
-    public abstract void bind(int bufferId, @NonNull RecyclerView.Adapter adapter, @Nullable AutoScroller scroller);
+    public abstract ObservableSortedList<Message> getFiltered(@IntRange(from = -1) int bufferId);
+
+    public abstract BacklogFilter getFilter(@IntRange(from = -1) int bufferId);
 
     public abstract void requestMoreBacklog(int bufferId, int count);
+
+    public abstract void setClient(Client client);
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
similarity index 56%
rename from app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java
rename to app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
index 118408905..edfee61b3 100644
--- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java
@@ -1,14 +1,16 @@
-package de.kuschku.libquassel.backlogmanagers;
+package de.kuschku.libquassel.localtypes.backlogmanagers;
 
 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;
 
 import java.util.List;
+import java.util.Map;
 
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
@@ -24,18 +26,28 @@ import de.kuschku.util.observables.lists.ObservableSortedList;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
-public class SimpleBacklogManager extends BacklogManager {
+public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> {
     @NonNull
     private final SparseArray<ObservableSortedList<Message>> backlogs = new SparseArray<>();
     @NonNull
+    private final SparseArray<ObservableSortedList<Message>> filteredBacklogs = new SparseArray<>();
+    @NonNull
+    private final SparseArray<BacklogFilter> filters = new SparseArray<>();
+    @NonNull
     private final BusProvider busProvider;
+    @Nullable
+    private Client client;
 
     public SimpleBacklogManager(@NonNull BusProvider busProvider) {
         this.busProvider = busProvider;
     }
 
+    public void setClient(@Nullable Client client) {
+        this.client = client;
+    }
+
     public void requestBacklog(int bufferId, int from, int to, int count, int extra) {
-        busProvider.dispatch(new SyncFunction("BacklogManager", "", "requestBacklog", Lists.newArrayList(
+        busProvider.dispatch(new SyncFunction<>("BacklogManager", "", "requestBacklog", Lists.newArrayList(
                 new QVariant<>("BufferId", bufferId),
                 new QVariant<>("MsgId", from),
                 new QVariant<>("MsgId", to),
@@ -45,7 +57,7 @@ public class SimpleBacklogManager extends BacklogManager {
     }
 
     public void receiveBacklog(@IntRange(from = 0) int bufferId, int from, int to, int count, int extra, @NonNull List<Message> messages) {
-        get(bufferId).list.addAll(messages);
+        get(bufferId).addAll(messages);
 
         busProvider.sendEvent(new BacklogReceivedEvent(bufferId));
     }
@@ -55,7 +67,7 @@ public class SimpleBacklogManager extends BacklogManager {
         ObservableSortedList<Message> messages = get(bufferId);
         assertNotNull(messages);
 
-        messages.list.add(message);
+        messages.add(message);
     }
 
     public void bind(@IntRange(from = 0) int bufferId, @NonNull RecyclerView.Adapter adapter, @Nullable AutoScroller scroller) {
@@ -70,24 +82,54 @@ public class SimpleBacklogManager extends BacklogManager {
         ObservableSortedList<Message> backlog = backlogs.get(bufferId);
         int messageId =
                 (backlog == null) ? -1 :
-                        (backlog.last() == null) ? -1 :
-                                backlog.last().messageId;
+                (backlog.last() == null) ? -1 :
+                backlog.last().messageId;
 
         requestBacklog(bufferId, -1, messageId, count, 0);
     }
 
-    public ObservableSortedList<Message> get(@IntRange(from = 0) int bufferId) {
-        if (backlogs.get(bufferId) == null)
-            backlogs.put(bufferId, new ObservableComparableSortedList<>(Message.class, true));
+    public ObservableSortedList<Message> get(@IntRange(from = -1) int bufferId) {
+        ensureExisting(bufferId);
 
-        ObservableSortedList<Message> messages = backlogs.get(bufferId);
-        assertNotNull(messages);
+        return backlogs.get(bufferId);
+    }
+
+    public ObservableSortedList<Message> getFiltered(@IntRange(from = -1) int bufferId) {
+        ensureExisting(bufferId);
+
+        return filteredBacklogs.get(bufferId);
+    }
+
+    public BacklogFilter getFilter(@IntRange(from = -1) int bufferId) {
+        ensureExisting(bufferId);
 
-        return messages;
+        return filters.get(bufferId);
+    }
+
+    private void ensureExisting(@IntRange(from = -1) int bufferId) {
+        if (backlogs.get(bufferId) == null) {
+            ObservableComparableSortedList<Message> messages = new ObservableComparableSortedList<>(Message.class, true);
+            ObservableComparableSortedList<Message> filteredMessages = new ObservableComparableSortedList<>(Message.class, true);
+            BacklogFilter backlogFilter = new BacklogFilter(client, messages, filteredMessages);
+            messages.addCallback(backlogFilter);
+            backlogs.put(bufferId, messages);
+            filteredBacklogs.put(bufferId, filteredMessages);
+            filters.put(bufferId, backlogFilter);
+        }
     }
 
     @Override
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
 
     }
+
+    @Override
+    public void update(SimpleBacklogManager from) {
+
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java
index 1f55e8cff..d993ba5e4 100644
--- a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/CharSerializer.java
@@ -21,16 +21,18 @@ public class CharSerializer implements PrimitiveSerializer<Character> {
 
     @Override
     public void serialize(@NonNull final ByteChannel channel, @NonNull final Character data) throws IOException {
-        final ByteBuffer buffer = Charset.forName("UTF-16BE").encode(String.valueOf(data.charValue()));
-        channel.write(buffer);
+        final ByteBuffer contentBuffer = Charset.forName("UTF-16BE").encode(String.copyValueOf(new char[]{data}));
+        channel.write(contentBuffer);
     }
 
     @NonNull
     @Override
     public Character deserialize(@NonNull final ByteBuffer buffer) throws IOException {
-        final ByteBuffer contentBuffer = ByteBuffer.allocate(2);
-        contentBuffer.put(buffer.array(), buffer.position(), 2);
-        buffer.position(buffer.position() + 2);
-        return Charset.forName("UTF-16BE").decode(buffer).get();
+        int len = 2;
+        final ByteBuffer contentBuffer = ByteBuffer.allocate(len);
+        contentBuffer.put(buffer.array(), buffer.position(), len);
+        contentBuffer.position(0);
+        buffer.position(buffer.position() + len);
+        return Charset.forName("UTF-16BE").decode(contentBuffer).toString().charAt(0);
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java
index 2b7690fdc..abe83b399 100644
--- a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java
@@ -3,6 +3,7 @@ package de.kuschku.libquassel.primitives.serializers;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
+import de.kuschku.libquassel.primitives.types.BufferInfo;
 import org.joda.time.DateTime;
 
 import java.io.IOException;
@@ -39,18 +40,22 @@ public class MessageSerializer implements PrimitiveSerializer<Message> {
     @Nullable
     @Override
     public Message deserialize(@NonNull final ByteBuffer buffer) throws IOException {
+        Integer messageId = IntSerializer.get().deserialize(buffer);
+        DateTime time = new DateTime(((long) IntSerializer.get().deserialize(buffer)) * 1000);
+        Message.Type type = Message.Type.fromId(IntSerializer.get().deserialize(buffer));
+        Message.Flags flags = new Message.Flags(ByteSerializer.get().deserialize(buffer));
+        BufferInfo bufferInfo = BufferInfoSerializer.get().deserialize(buffer);
         String sender = ByteArraySerializer.get().deserialize(buffer);
         String message = ByteArraySerializer.get().deserialize(buffer);
 
         assertNotNull(sender);
         assertNotNull(message);
-
         return new Message(
-                IntSerializer.get().deserialize(buffer),
-                new DateTime(((long) IntSerializer.get().deserialize(buffer)) * 1000),
-                Message.Type.fromId(IntSerializer.get().deserialize(buffer)),
-                new Message.Flags(ByteSerializer.get().deserialize(buffer)),
-                BufferInfoSerializer.get().deserialize(buffer),
+                messageId,
+                time,
+                type,
+                flags,
+                bufferInfo,
                 sender,
                 message
         );
diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java
index 4938a2693..ed1322d3f 100644
--- a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java
+++ b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java
@@ -2,6 +2,7 @@ package de.kuschku.libquassel.protocols;
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Lists;
@@ -9,17 +10,25 @@ import com.google.common.collect.Lists;
 import java.io.ByteArrayOutputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.CoreConnection;
 import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.functions.FunctionType;
+import de.kuschku.libquassel.functions.serializers.HeartbeatReplySerializer;
+import de.kuschku.libquassel.functions.serializers.HeartbeatSerializer;
 import de.kuschku.libquassel.functions.serializers.InitDataFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.InitRequestFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.PackedInitDataFunctionSerializer;
@@ -28,6 +37,8 @@ import de.kuschku.libquassel.functions.serializers.PackedSyncFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.UnpackedRpcCallFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.UnpackedSyncFunctionSerializer;
 import de.kuschku.libquassel.functions.types.HandshakeFunction;
+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;
@@ -38,6 +49,7 @@ import de.kuschku.libquassel.primitives.serializers.IntSerializer;
 import de.kuschku.libquassel.primitives.serializers.PrimitiveSerializer;
 import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer;
 import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.util.niohelpers.Helper;
 import de.kuschku.util.niohelpers.WrappedChannel;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
@@ -48,19 +60,22 @@ import static de.kuschku.util.AndroidAssert.assertNotNull;
  *
  * @author Janne Koschinski
  */
-@SuppressWarnings({"unchecked"})
+@SuppressWarnings({"unchecked", "unused"})
 public class DatastreamPeer implements RemotePeer {
     @NonNull
     private final CoreConnection connection;
     @NonNull
     private final BusProvider busProvider;
     @NonNull
+    private final ExecutorService parseExecutor;
+    @NonNull
     private ByteBuffer buffer = ByteBuffer.allocate(0);
 
     public DatastreamPeer(@NonNull CoreConnection connection, @NonNull BusProvider busProvider) {
         this.connection = connection;
         this.busProvider = busProvider;
         this.busProvider.dispatch.register(this);
+        this.parseExecutor = Executors.newCachedThreadPool();
     }
 
     @NonNull
@@ -104,6 +119,7 @@ public class DatastreamPeer implements RemotePeer {
     }
 
     public void onEventBackgroundThread(@NonNull SyncFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantVariantListSerializer.<SyncFunction>get(),
                 UnpackedSyncFunctionSerializer.get().serialize(func)
@@ -111,6 +127,7 @@ public class DatastreamPeer implements RemotePeer {
     }
 
     public void onEventBackgroundThread(@NonNull RpcCallFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantVariantListSerializer.<RpcCallFunction>get(),
                 UnpackedRpcCallFunctionSerializer.get().serialize(func)
@@ -118,6 +135,7 @@ public class DatastreamPeer implements RemotePeer {
     }
 
     public void onEventBackgroundThread(@NonNull InitRequestFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantVariantListSerializer.<InitRequestFunction>get(),
                 InitRequestFunctionSerializer.get().serializePacked(func)
@@ -125,16 +143,36 @@ public class DatastreamPeer implements RemotePeer {
     }
 
     public void onEventBackgroundThread(@NonNull InitDataFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantVariantListSerializer.<InitDataFunction>get(),
                 InitDataFunctionSerializer.get().serialize(func)
         ));
     }
 
+    public void onEventBackgroundThread(@NonNull Heartbeat func) {
+        assertNotNull(connection.getOutputExecutor());
+        connection.getOutputExecutor().submit(new OutputRunnable<>(
+                VariantVariantListSerializer.<InitDataFunction>get(),
+                HeartbeatSerializer.get().serialize(func)
+        ));
+    }
+
+    public void onEventBackgroundThread(@NonNull HeartbeatReply func) {
+        assertNotNull(connection.getOutputExecutor());
+        connection.getOutputExecutor().submit(new OutputRunnable<>(
+                VariantVariantListSerializer.<InitDataFunction>get(),
+                HeartbeatReplySerializer.get().serialize(func)
+        ));
+    }
+
     public void onEventBackgroundThread(@NonNull HandshakeFunction func) {
+        assertNotNull(connection.getOutputExecutor());
+        Map<String, QVariant> variantMap = MessageTypeRegistry.toVariantMap(func.data).data;
+        assertNotNull(variantMap);
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantVariantListSerializer.get(),
-                DatastreamPeer.mapToList(MessageTypeRegistry.toVariantMap(func.data).data)
+                DatastreamPeer.mapToList(variantMap)
         ));
     }
 
@@ -143,7 +181,7 @@ public class DatastreamPeer implements RemotePeer {
     }
 
     private void handlePackedFunc(@NonNull List<QVariant> data) {
-        final FunctionType type = FunctionType.fromId((Integer) data.remove(0).data);
+        final FunctionType type = FunctionType.fromId((int) data.remove(0).data);
         switch (type) {
             case SYNC:
                 busProvider.handle(PackedSyncFunctionSerializer.get().deserialize(data));
@@ -158,7 +196,11 @@ public class DatastreamPeer implements RemotePeer {
                 busProvider.handle(PackedInitDataFunctionSerializer.get().deserialize(data));
                 break;
             case HEARTBEAT:
+                busProvider.handle(HeartbeatSerializer.get().deserialize(data));
+                break;
             case HEARTBEATREPLY:
+                busProvider.handle(HeartbeatReplySerializer.get().deserialize(data));
+                break;
             default:
                 busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data));
                 break;
@@ -176,17 +218,7 @@ public class DatastreamPeer implements RemotePeer {
         buffer = ByteBuffer.allocate(size);
         connection.getChannel().read(buffer);
 
-        // TODO: Put this into a future with a time limit, and parallelize it.
-        final List data = VariantVariantListSerializer.get().deserialize(buffer);
-        if (connection.getStatus() == ConnectionChangeEvent.Status.CONNECTING
-                || connection.getStatus() == ConnectionChangeEvent.Status.HANDSHAKE
-                || connection.getStatus() == ConnectionChangeEvent.Status.CORE_SETUP_REQUIRED
-                || connection.getStatus() == ConnectionChangeEvent.Status.USER_SETUP_REQUIRED
-                || connection.getStatus() == ConnectionChangeEvent.Status.LOGIN_REQUIRED) {
-            handleHandshakeMessage(data);
-        } else {
-            handlePackedFunc(data);
-        }
+        parseExecutor.submit(new ParseRunnable(buffer));
     }
 
     @NonNull
@@ -238,4 +270,33 @@ public class DatastreamPeer implements RemotePeer {
             }
         }
     }
+
+    private class ParseRunnable implements Runnable {
+        ByteBuffer buffer;
+        public ParseRunnable(ByteBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // TODO: Put this into a future with a time limit, and parallelize it.
+                final List data = VariantVariantListSerializer.get().deserialize(buffer);
+                if (connection.getStatus() == ConnectionChangeEvent.Status.CONNECTING
+                        || connection.getStatus() == ConnectionChangeEvent.Status.HANDSHAKE
+                        || connection.getStatus() == ConnectionChangeEvent.Status.CORE_SETUP_REQUIRED
+                        || connection.getStatus() == ConnectionChangeEvent.Status.USER_SETUP_REQUIRED
+                        || connection.getStatus() == ConnectionChangeEvent.Status.LOGIN_REQUIRED) {
+                    handleHandshakeMessage(data);
+                } else {
+                    handlePackedFunc(data);
+                }
+            } catch (BufferUnderflowException|BufferOverflowException e) {
+                Helper.printHexDump(buffer.array());
+                busProvider.sendEvent(new GeneralErrorEvent(e));
+            } catch (Exception e) {
+                busProvider.sendEvent(new GeneralErrorEvent(e));
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java
index 53582f5ad..603fdd0c1 100644
--- a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java
+++ b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java
@@ -8,17 +8,24 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.CoreConnection;
+import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.functions.FunctionType;
+import de.kuschku.libquassel.functions.serializers.HeartbeatReplySerializer;
+import de.kuschku.libquassel.functions.serializers.HeartbeatSerializer;
 import de.kuschku.libquassel.functions.serializers.InitDataFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.InitRequestFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.UnpackedInitDataFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.UnpackedRpcCallFunctionSerializer;
 import de.kuschku.libquassel.functions.serializers.UnpackedSyncFunctionSerializer;
 import de.kuschku.libquassel.functions.types.HandshakeFunction;
+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;
@@ -30,10 +37,12 @@ import de.kuschku.libquassel.primitives.serializers.PrimitiveSerializer;
 import de.kuschku.libquassel.primitives.serializers.VariantSerializer;
 import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer;
 import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.util.AndroidAssert;
 import de.kuschku.util.niohelpers.WrappedChannel;
 
 import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantList;
 import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantMap;
+import static de.kuschku.util.AndroidAssert.*;
 
 /**
  * A helper class processing incoming and outgoing messages.
@@ -41,22 +50,26 @@ import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantMap;
  *
  * @author Janne Koschinski
  */
-@SuppressWarnings({"unchecked"})
+@SuppressWarnings({"unchecked", "unused"})
 public class LegacyPeer implements RemotePeer {
     @NonNull
     private final CoreConnection connection;
     @NonNull
     private final BusProvider busProvider;
     @NonNull
+    private final ExecutorService parseExecutor;
+    @NonNull
     private ByteBuffer buffer = ByteBuffer.allocate(0);
 
     public LegacyPeer(@NonNull CoreConnection connection, @NonNull BusProvider busProvider) {
         this.connection = connection;
         this.busProvider = busProvider;
         this.busProvider.dispatch.register(this);
+        this.parseExecutor = Executors.newCachedThreadPool();
     }
 
     public final void onEventBackgroundThread(@NonNull SyncFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         final List serialize = UnpackedSyncFunctionSerializer.get().serialize(func);
         connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
                 new QVariant(new QMetaType(List.class, QMetaType.Type.QVariantList, VariantVariantListSerializer.get()),
@@ -64,21 +77,37 @@ public class LegacyPeer implements RemotePeer {
     }
 
     public void onEventBackgroundThread(@NonNull RpcCallFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
                 new QVariant<>(UnpackedRpcCallFunctionSerializer.get().serialize(func))));
     }
 
     public void onEventBackgroundThread(@NonNull InitRequestFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
                 new QVariant<>(InitRequestFunctionSerializer.get().serialize(func))));
     }
 
     public void onEventBackgroundThread(@NonNull InitDataFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
                 new QVariant<>(InitDataFunctionSerializer.get().serialize(func))));
     }
 
+    public void onEventBackgroundThread(@NonNull Heartbeat func) {
+        assertNotNull(connection.getOutputExecutor());
+        connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
+                new QVariant<>(HeartbeatSerializer.get().serialize(func))));
+    }
+
+    public void onEventBackgroundThread(@NonNull HeartbeatReply func) {
+        assertNotNull(connection.getOutputExecutor());
+        connection.getOutputExecutor().submit(new OutputRunnable<>(VariantSerializer.get(),
+                new QVariant<>(HeartbeatReplySerializer.get().serialize(func))));
+    }
+
     public void onEventBackgroundThread(@NonNull HandshakeFunction func) {
+        assertNotNull(connection.getOutputExecutor());
         connection.getOutputExecutor().submit(new OutputRunnable<>(
                 VariantSerializer.get(), MessageTypeRegistry.toVariantMap(func.data)));
     }
@@ -93,32 +122,7 @@ public class LegacyPeer implements RemotePeer {
         buffer = ByteBuffer.allocate(size);
         connection.getChannel().read(buffer);
 
-        // TODO: Put this into a future with a time limit, and parallelize it.
-        final QVariant data = VariantSerializer.get().deserialize(buffer);
-        if (data.type.type == QVariantMap) {
-            busProvider.handle(MessageTypeRegistry.from((Map<String, QVariant>) data.data));
-        } else if (data.type.type == QVariantList) {
-            final FunctionType type = FunctionType.fromId((Integer) ((List<Object>) data.data).remove(0));
-            switch (type) {
-                case SYNC:
-                    busProvider.handle(UnpackedSyncFunctionSerializer.get().deserialize((List<QVariant>) data.data));
-                    break;
-                case RPCCALL:
-                    busProvider.handle(UnpackedRpcCallFunctionSerializer.get().deserialize((List<QVariant>) data.data));
-                    break;
-                case INITREQUEST:
-                    busProvider.handle(InitRequestFunctionSerializer.get().deserialize((List<QVariant>) data.data));
-                    break;
-                case INITDATA:
-                    busProvider.handle(UnpackedInitDataFunctionSerializer.get().deserialize((List<QVariant>) data.data));
-                    break;
-                case HEARTBEAT:
-                case HEARTBEATREPLY:
-                default:
-                    busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data));
-                    break;
-            }
-        }
+        parseExecutor.submit(new ParseRunnable(buffer));
     }
 
     @NonNull
@@ -168,4 +172,50 @@ public class LegacyPeer implements RemotePeer {
             }
         }
     }
+
+    private class ParseRunnable implements Runnable {
+        ByteBuffer buffer;
+        public ParseRunnable(ByteBuffer buffer) {
+            this.buffer = buffer;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // TODO: Put this into a future with a time limit, and parallelize it.
+                final QVariant data = VariantSerializer.get().deserialize(buffer);
+                assertNotNull(data.data);
+                if (data.type.type == QVariantMap) {
+                    busProvider.handle(MessageTypeRegistry.from((Map<String, QVariant>) data.data));
+                } else if (data.type.type == QVariantList) {
+                    final FunctionType type = FunctionType.fromId((Integer) ((List<Object>) data.data).remove(0));
+                    switch (type) {
+                        case SYNC:
+                            busProvider.handle(UnpackedSyncFunctionSerializer.get().deserialize((List<QVariant>) data.data));
+                            break;
+                        case RPCCALL:
+                            busProvider.handle(UnpackedRpcCallFunctionSerializer.get().deserialize((List<QVariant>) data.data));
+                            break;
+                        case INITREQUEST:
+                            busProvider.handle(InitRequestFunctionSerializer.get().deserialize((List<QVariant>) data.data));
+                            break;
+                        case INITDATA:
+                            busProvider.handle(UnpackedInitDataFunctionSerializer.get().deserialize((List<QVariant>) data.data));
+                            break;
+                        case HEARTBEAT:
+                            busProvider.handle(HeartbeatSerializer.get().deserialize((List<QVariant>) data));
+                            break;
+                        case HEARTBEATREPLY:
+                            busProvider.handle(HeartbeatReplySerializer.get().deserialize((List<QVariant>) data));
+                            break;
+                        default:
+                            busProvider.sendEvent(new GeneralErrorEvent("Unknown package received: " + data));
+                            break;
+                    }
+                }
+            } catch (Exception e) {
+                busProvider.sendEvent(new GeneralErrorEvent(e));
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java b/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java
index 64928ad78..4d2d1f508 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/SyncableRegistry.java
@@ -9,12 +9,15 @@ import java.util.Map;
 import de.kuschku.libquassel.exceptions.UnknownTypeException;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.objects.serializers.ObjectSerializer;
+import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer;
 import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer;
 import de.kuschku.libquassel.syncables.serializers.BufferViewConfigSerializer;
 import de.kuschku.libquassel.syncables.serializers.BufferViewManagerSerializer;
 import de.kuschku.libquassel.syncables.serializers.IdentitySerializer;
+import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer;
 import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer;
 import de.kuschku.libquassel.syncables.serializers.IrcUserSerializer;
+import de.kuschku.libquassel.syncables.serializers.NetworkConfigSerializer;
 import de.kuschku.libquassel.syncables.serializers.NetworkSerializer;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
 
@@ -23,6 +26,7 @@ public class SyncableRegistry {
     private static final Map<String, ObjectSerializer<? extends SyncableObject>> map = new HashMap<>();
 
     static {
+        map.put("IgnoreListManager", IgnoreListManagerSerializer.get());
         map.put("BufferSyncer", BufferSyncerSerializer.get());
         map.put("BufferViewConfig", BufferViewConfigSerializer.get());
         map.put("BufferViewManager", BufferViewManagerSerializer.get());
@@ -30,6 +34,8 @@ public class SyncableRegistry {
         map.put("IrcChannel", IrcChannelSerializer.get());
         map.put("IrcUser", IrcUserSerializer.get());
         map.put("Network", NetworkSerializer.get());
+        map.put("NetworkConfig", NetworkConfigSerializer.get());
+        map.put("AliasManager", AliasManagerSerializer.get());
     }
 
     private SyncableRegistry() {
@@ -39,7 +45,7 @@ public class SyncableRegistry {
     @Nullable
     public static SyncableObject from(@NonNull InitDataFunction function) throws UnknownTypeException {
         ObjectSerializer<? extends SyncableObject> serializer = map.get(function.className);
-        if (serializer == null) throw new UnknownTypeException(function.className);
+        if (serializer == null) throw new UnknownTypeException(function.className, function);
         return serializer.from(function);
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java b/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java
new file mode 100644
index 000000000..bd4e49d59
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/Synced.java
@@ -0,0 +1,8 @@
+package de.kuschku.libquassel.syncables;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+public @interface Synced {
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java
new file mode 100644
index 000000000..2e089880b
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java
@@ -0,0 +1,60 @@
+package de.kuschku.libquassel.syncables.serializers;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+import de.kuschku.libquassel.functions.types.PackedFunction;
+import de.kuschku.libquassel.functions.types.SerializedFunction;
+import de.kuschku.libquassel.functions.types.UnpackedFunction;
+import de.kuschku.libquassel.objects.serializers.ObjectSerializer;
+import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.types.AliasManager;
+
+public class AliasManagerSerializer implements ObjectSerializer<AliasManager> {
+    @NonNull
+    private static final AliasManagerSerializer serializer = new AliasManagerSerializer();
+
+    @NonNull
+    public static AliasManagerSerializer get() {
+        return serializer;
+    }
+
+    private AliasManagerSerializer() {
+
+    }
+
+    @Nullable
+    @Override
+    public QVariant<Map<String, QVariant>> toVariantMap(@NonNull AliasManager data) {
+        throw new IllegalArgumentException();
+    }
+
+    @NonNull
+    @Override
+    public AliasManager fromDatastream(@NonNull Map<String, QVariant> map) {
+        return fromLegacy(map);
+    }
+
+    @NonNull
+    @Override
+    public AliasManager fromLegacy(@NonNull Map<String, QVariant> map) {
+        Map<String, QVariant<List<String>>> aliases = (Map<String, QVariant<List<String>>>) map.get("Aliases").data;
+        return new AliasManager(
+                aliases.get("names").data,
+                aliases.get("expansions").data
+        );
+    }
+
+    @Nullable
+    @Override
+    public AliasManager from(@NonNull SerializedFunction function) {
+        if (function instanceof PackedFunction)
+            return fromLegacy(((PackedFunction) function).getData());
+        else if (function instanceof UnpackedFunction)
+            return fromDatastream(((UnpackedFunction) function).getData());
+        else throw new IllegalArgumentException();
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java
index 76866b898..786cf2644 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferSyncerSerializer.java
@@ -30,25 +30,22 @@ public class BufferSyncerSerializer implements ObjectSerializer<BufferSyncer> {
     @Nullable
     @Override
     public QVariant<Map<String, QVariant>> toVariantMap(@NonNull BufferSyncer data) {
-        // TODO: Implement this
-        return null;
+        // FIXME: IMPLEMENT
+        throw new IllegalArgumentException();
     }
 
     @NonNull
     @Override
     public BufferSyncer fromDatastream(@NonNull Map<String, QVariant> map) {
-        return new BufferSyncer(
-                DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data),
-                DatastreamPeer.unboxedListToMap((List<Integer>) map.get("MarkerLines").data)
-        );
+        return fromLegacy(map);
     }
 
     @NonNull
     @Override
     public BufferSyncer fromLegacy(@NonNull Map<String, QVariant> map) {
         return new BufferSyncer(
-                (Map<Integer, Integer>) map.get("LastSeenMsg").data,
-                (Map<Integer, Integer>) map.get("MarkerLines").data
+                DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data),
+                DatastreamPeer.unboxedListToMap((List<Integer>) map.get("LastSeenMsg").data)
         );
     }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java
index c73a286b0..e58dbb8b0 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/BufferViewManagerSerializer.java
@@ -29,7 +29,8 @@ public class BufferViewManagerSerializer implements ObjectSerializer<BufferViewM
     @Nullable
     @Override
     public QVariant<Map<String, QVariant>> toVariantMap(@NonNull BufferViewManager data) {
-        return null;
+        // FIXME: IMPLEMENT
+        throw new IllegalArgumentException();
     }
 
     @NonNull
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java
new file mode 100644
index 000000000..2a114c537
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java
@@ -0,0 +1,63 @@
+package de.kuschku.libquassel.syncables.serializers;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.List;
+import java.util.Map;
+
+import de.kuschku.libquassel.functions.types.PackedFunction;
+import de.kuschku.libquassel.functions.types.SerializedFunction;
+import de.kuschku.libquassel.functions.types.UnpackedFunction;
+import de.kuschku.libquassel.objects.serializers.ObjectSerializer;
+import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.types.IgnoreListManager;
+
+public class IgnoreListManagerSerializer implements ObjectSerializer<IgnoreListManager> {
+    private static IgnoreListManagerSerializer serializer = new IgnoreListManagerSerializer();
+    public static IgnoreListManagerSerializer get() {
+        return serializer;
+    }
+
+    private IgnoreListManagerSerializer() {
+
+    }
+
+    @Nullable
+    @Override
+    public QVariant<Map<String, QVariant>> toVariantMap(@NonNull IgnoreListManager data) {
+        // FIXME: IMPLEMENT
+        throw new IllegalArgumentException();
+    }
+
+    @NonNull
+    @Override
+    public IgnoreListManager fromDatastream(@NonNull Map<String, QVariant> map) {
+        return fromLegacy(map);
+    }
+
+    @NonNull
+    @Override
+    public IgnoreListManager fromLegacy(@NonNull Map<String, QVariant> map) {
+        Map<String, QVariant> internalMap = (Map<String, QVariant>) map.get("IgnoreList").data;
+        return new IgnoreListManager(
+                (List<Integer>) internalMap.get("scope").data,
+                (List<Integer>) internalMap.get("ignoreType").data,
+                (List<Boolean>) internalMap.get("isActive").data,
+                (List<String>) internalMap.get("scopeRule").data,
+                (List<Boolean>) internalMap.get("isRegEx").data,
+                (List<Integer>) internalMap.get("strictness").data,
+                (List<String>) internalMap.get("ignoreRule").data
+        );
+    }
+
+    @Nullable
+    @Override
+    public IgnoreListManager from(@NonNull SerializedFunction function) {
+        if (function instanceof PackedFunction)
+            return fromLegacy(((PackedFunction) function).getData());
+        else if (function instanceof UnpackedFunction)
+            return fromDatastream(((UnpackedFunction) function).getData());
+        else throw new IllegalArgumentException();
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java
index 7bfcb5527..7505909f3 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcChannelSerializer.java
@@ -30,12 +30,12 @@ public class IrcChannelSerializer implements ObjectSerializer<IrcChannel> {
     @Override
     public QVariant<Map<String, QVariant>> toVariantMap(@NonNull IrcChannel data) {
         final QVariant<Map<String, QVariant>> map = new QVariant<>(new HashMap<>());
-        map.data.put("name", new QVariant<>(data.name));
-        map.data.put("topic", new QVariant<>(data.topic));
-        map.data.put("password", new QVariant<>(data.password));
-        map.data.put("UserModes", StringObjectMapSerializer.<String>get().toVariantMap(data.UserModes));
-        map.data.put("ChanModes", new QVariant<>(data.ChanModes));
-        map.data.put("encrypted", new QVariant<>(data.encrypted));
+        map.data.put("name", new QVariant<>(data.getName()));
+        map.data.put("topic", new QVariant<>(data.getTopic()));
+        map.data.put("password", new QVariant<>(data.getPassword()));
+        map.data.put("UserModes", StringObjectMapSerializer.<String>get().toVariantMap(data.getUserModes()));
+        map.data.put("ChanModes", new QVariant<>(data.getChanModes()));
+        map.data.put("encrypted", new QVariant<>(data.isEncrypted()));
         return map;
     }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java
index 5004534e1..d5fd53e02 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IrcUserSerializer.java
@@ -36,22 +36,22 @@ public class IrcUserSerializer implements ObjectSerializer<IrcUser> {
         final QVariant<Map<String, QVariant>> map = new QVariant<>(new HashMap<>());
         assertNotNull(map.data);
 
-        map.data.put("server", new QVariant<>(data.server));
-        map.data.put("ircOperator", new QVariant<>(data.ircOperator));
-        map.data.put("away", new QVariant<>(data.away));
-        map.data.put("lastAwayMessage", new QVariant<>(data.lastAwayMessage));
-        map.data.put("idleTime", new QVariant<>(data.idleTime));
-        map.data.put("whoisServiceReply", new QVariant<>(data.whoisServiceReply));
-        map.data.put("suserHost", new QVariant<>(data.suserHost));
-        map.data.put("nick", new QVariant<>(data.nick));
-        map.data.put("realName", new QVariant<>(data.realName));
-        map.data.put("awayMessage", new QVariant<>(data.awayMessage));
-        map.data.put("loginTime", new QVariant<>(data.loginTime));
-        map.data.put("encrypted", new QVariant<>(data.encrypted));
-        map.data.put("channels", new QVariant<>(data.channels));
-        map.data.put("host", new QVariant<>(data.host));
-        map.data.put("userModes", new QVariant<>(data.userModes));
-        map.data.put("user", new QVariant<>(data.user));
+        map.data.put("server", new QVariant<>(data.getServer()));
+        map.data.put("ircOperator", new QVariant<>(data.getIrcOperator()));
+        map.data.put("away", new QVariant<>(data.isAway()));
+        map.data.put("lastAwayMessage", new QVariant<>(data.getLastAwayMessage()));
+        map.data.put("idleTime", new QVariant<>(data.getIdleTime()));
+        map.data.put("whoisServiceReply", new QVariant<>(data.getWhoisServiceReply()));
+        map.data.put("suserHost", new QVariant<>(data.getSuserHost()));
+        map.data.put("nick", new QVariant<>(data.getNick()));
+        map.data.put("realName", new QVariant<>(data.getRealName()));
+        map.data.put("awayMessage", new QVariant<>(data.getAwayMessage()));
+        map.data.put("loginTime", new QVariant<>(data.getLoginTime()));
+        map.data.put("encrypted", new QVariant<>(data.isEncrypted()));
+        map.data.put("channels", new QVariant<>(data.getChannels()));
+        map.data.put("host", new QVariant<>(data.getHost()));
+        map.data.put("userModes", new QVariant<>(data.getUserModes()));
+        map.data.put("user", new QVariant<>(data.getUser()));
         return map;
     }
 
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java
new file mode 100644
index 000000000..277b08eaf
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java
@@ -0,0 +1,63 @@
+package de.kuschku.libquassel.syncables.serializers;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Map;
+
+import de.kuschku.libquassel.functions.types.PackedFunction;
+import de.kuschku.libquassel.functions.types.SerializedFunction;
+import de.kuschku.libquassel.functions.types.UnpackedFunction;
+import de.kuschku.libquassel.objects.serializers.ObjectSerializer;
+import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.types.NetworkConfig;
+import de.kuschku.libquassel.syncables.types.SyncableObject;
+
+public class NetworkConfigSerializer implements ObjectSerializer<NetworkConfig> {
+    private static NetworkConfigSerializer serializer = new NetworkConfigSerializer();
+    public static NetworkConfigSerializer get() {
+        return serializer;
+    }
+
+    private NetworkConfigSerializer() {
+
+    }
+
+    @Nullable
+    @Override
+    public QVariant<Map<String, QVariant>> toVariantMap(@NonNull NetworkConfig data) {
+        // FIXME: IMPLEMENT
+        throw new IllegalArgumentException();
+    }
+
+    @NonNull
+    @Override
+    public NetworkConfig fromDatastream(@NonNull Map<String, QVariant> map) {
+        return fromLegacy(map);
+    }
+
+    @NonNull
+    @Override
+    public NetworkConfig fromLegacy(@NonNull Map<String, QVariant> map) {
+        return new NetworkConfig(
+                (int) map.get("autoWhoNickLimit").data,
+                (int) map.get("autoWhoDelay").data,
+                (boolean) map.get("autoWhoEnabled").data,
+                (boolean) map.get("standardCtcp").data,
+                (int) map.get("pingInterval").data,
+                (int) map.get("autoWhoInterval").data,
+                (int) map.get("maxPingCount").data,
+                (boolean) map.get("pingTimeoutEnabled").data
+        );
+    }
+
+    @Nullable
+    @Override
+    public NetworkConfig from(@NonNull SerializedFunction function) {
+        if (function instanceof PackedFunction)
+            return fromLegacy(((PackedFunction) function).getData());
+        else if (function instanceof UnpackedFunction)
+            return fromDatastream(((UnpackedFunction) function).getData());
+        else throw new IllegalArgumentException();
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java
index 2c395dc26..4d19448ba 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkSerializer.java
@@ -37,8 +37,8 @@ public class NetworkSerializer implements ObjectSerializer<Network> {
     @Nullable
     @Override
     public QVariant<Map<String, QVariant>> toVariantMap(@NonNull Network data) {
-        // FIXME: Implement this
-        return null;
+        // FIXME: IMPLEMENT
+        throw new IllegalArgumentException();
     }
 
     @NonNull
@@ -51,7 +51,7 @@ public class NetworkSerializer implements ObjectSerializer<Network> {
 
         final Map<String, IrcChannel> channelMap = new HashMap<>(channels.size());
         for (IrcChannel channel : channels) {
-            channelMap.put(channel.name, channel);
+            channelMap.put(channel.getName(), channel);
         }
 
         final Map<String, IrcUser> userMap = new HashMap<>(users.size());
@@ -87,6 +87,9 @@ public class NetworkSerializer implements ObjectSerializer<Network> {
         for (IrcUser user : users) {
             user.setNetwork(network);
         }
+        for (IrcChannel channel : channels) {
+            channel.setNetwork(network);
+        }
 
         return network;
     }
@@ -154,12 +157,12 @@ public class NetworkSerializer implements ObjectSerializer<Network> {
         final Map<String, IrcChannel> channels = new HashMap<>(wrappedChannels.size());
         for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedChannels.entrySet()) {
             final IrcChannel ircChannel = IrcChannelSerializer.get().fromLegacy(entry.getValue().data);
-            channels.put(ircChannel.name, ircChannel);
+            channels.put(ircChannel.getName(), ircChannel);
         }
         final Map<String, IrcUser> users = new HashMap<>(wrappedUsers.size());
         for (Map.Entry<String, QVariant<Map<String, QVariant>>> entry : wrappedUsers.entrySet()) {
             final IrcUser ircUser = IrcUserSerializer.get().fromLegacy(entry.getValue().data);
-            users.put(ircUser.nick, ircUser);
+            users.put(ircUser.getNick(), ircUser);
         }
         final Map<String, String> supports = StringObjectMapSerializer.<String>get().fromLegacy((Map<String, QVariant>) map.get("Supports").data);
         Network network = new Network(
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java
new file mode 100644
index 000000000..4236fc778
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/AliasManager.java
@@ -0,0 +1,38 @@
+package de.kuschku.libquassel.syncables.types;
+
+import android.support.annotation.NonNull;
+
+import java.util.List;
+import java.util.Map;
+
+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.AliasManagerSerializer;
+
+public class AliasManager extends SyncableObject<AliasManager> {
+    public List<String> names;
+    public List<String> expansions;
+
+    public AliasManager(List<String> names, List<String> expansions) {
+        this.names = names;
+        this.expansions = expansions;
+    }
+
+    @Override
+    public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+
+    }
+
+    @Override
+    public void update(AliasManager from) {
+        names = from.names;
+        expansions = from.expansions;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(AliasManagerSerializer.get().fromDatastream(from));
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java
index 20d24a33f..40cae47dc 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java
@@ -10,15 +10,18 @@ import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.message.Message;
+import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer;
+import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer;
 import de.kuschku.util.observables.lists.ObservableSortedList;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
-public class BufferSyncer extends SyncableObject {
+public class BufferSyncer extends SyncableObject<BufferSyncer> {
     @NonNull
-    private final SparseIntArray LastSeenMsg = new SparseIntArray();
+    private SparseIntArray LastSeenMsg = new SparseIntArray();
     @NonNull
-    private final SparseIntArray MarkerLines = new SparseIntArray();
+    private SparseIntArray MarkerLines = new SparseIntArray();
 
     private Client client;
 
@@ -41,6 +44,17 @@ public class BufferSyncer extends SyncableObject {
         setClient(client);
     }
 
+    @Override
+    public void update(BufferSyncer from) {
+        LastSeenMsg = from.LastSeenMsg;
+        MarkerLines = from.MarkerLines;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(BufferSyncerSerializer.get().fromDatastream(from));
+    }
+
     public void markBufferAsRead(@IntRange(from = 0) int bufferId) {
         ObservableSortedList<Message> buffer = client.getBacklogManager().get(bufferId);
         assertNotNull(buffer);
@@ -69,4 +83,9 @@ public class BufferSyncer extends SyncableObject {
     public int getMarkerLine(int bufferId) {
         return MarkerLines.get(bufferId, -1);
     }
+
+    public void removeBuffer(int bufferId) {
+        LastSeenMsg.put(bufferId, -1);
+        MarkerLines.put(bufferId, -1);
+    }
 }
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 90844af33..b0fa90823 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,21 +1,31 @@
 package de.kuschku.libquassel.syncables.types;
 
+import android.net.*;
 import android.support.annotation.NonNull;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 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;
 
-public class BufferViewConfig extends SyncableObject {
+import static de.kuschku.util.AndroidAssert.*;
+
+public class BufferViewConfig extends SyncableObject<BufferViewConfig> {
     private String bufferViewName;
     private List<Integer> TemporarilyRemovedBuffers;
     private boolean hideInactiveNetworks;
     private IObservableList<ElementCallback<Integer>, Integer> BufferList;
+    private IObservableList<ElementCallback<Integer>, Integer> NetworkList = new ObservableElementList<>();
     private int allowedBufferTypes;
     private boolean sortAlphabetically;
     private boolean disableDecoration;
@@ -24,12 +34,13 @@ public class BufferViewConfig extends SyncableObject {
     private int minimumActivity;
     private boolean hideInactiveBuffers;
     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;
-        TemporarilyRemovedBuffers = temporarilyRemovedBuffers;
+        this.TemporarilyRemovedBuffers = temporarilyRemovedBuffers;
         this.hideInactiveNetworks = hideInactiveNetworks;
-        BufferList = new ObservableElementList<>(bufferList);
+        this.BufferList = new ObservableElementList<>(bufferList);
         this.allowedBufferTypes = allowedBufferTypes;
         this.sortAlphabetically = sortAlphabetically;
         this.disableDecoration = disableDecoration;
@@ -37,7 +48,7 @@ public class BufferViewConfig extends SyncableObject {
         this.networkId = networkId;
         this.minimumActivity = minimumActivity;
         this.hideInactiveBuffers = hideInactiveBuffers;
-        RemovedBuffers = removedBuffers;
+        this.RemovedBuffers = removedBuffers;
     }
 
     public String getBufferViewName() {
@@ -114,6 +125,11 @@ public class BufferViewConfig extends SyncableObject {
 
     public void setNetworkId(int networkId) {
         this.networkId = networkId;
+        if (this.networkId != -1) {
+            this.NetworkList.addAll(client.getNetworks());
+        } else {
+            this.NetworkList.retainAll(Collections.singletonList(networkId));
+        }
     }
 
     public int getMinimumActivity() {
@@ -161,10 +177,33 @@ public class BufferViewConfig extends SyncableObject {
 
     @Override
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+        this.client = client;
         setObjectName(function.objectName);
+        assertNotNull(client.getBufferViewManager());
         client.getBufferViewManager().BufferViews.put(Integer.valueOf(function.objectName), this);
     }
 
+    @Override
+    public void update(BufferViewConfig from) {
+        this.bufferViewName = from.bufferViewName;
+        this.TemporarilyRemovedBuffers = from.TemporarilyRemovedBuffers;
+        this.hideInactiveNetworks = from.hideInactiveNetworks;
+        this.BufferList = from.BufferList;
+        this.allowedBufferTypes = from.allowedBufferTypes;
+        this.sortAlphabetically = from.sortAlphabetically;
+        this.disableDecoration = from.disableDecoration;
+        this.addNewBuffersAutomatically = from.addNewBuffersAutomatically;
+        this.networkId = from.networkId;
+        this.minimumActivity = from.minimumActivity;
+        this.hideInactiveBuffers = from.hideInactiveBuffers;
+        this.RemovedBuffers = from.RemovedBuffers;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(BufferViewConfigSerializer.get().fromDatastream(from));
+    }
+
     public void addBuffer(int bufferId, int position) {
         BufferList.add(position, bufferId);
     }
@@ -182,4 +221,15 @@ public class BufferViewConfig extends SyncableObject {
         removeBuffer(bufferId);
         sync("removeBuffer", new Object[]{bufferId});
     }
+
+    @NonNull
+    public IObservableList<ElementCallback<Integer>, Integer> getNetworkList() {
+        return NetworkList;
+    }
+
+    public void doLateInit() {
+        NetworkList.clear();
+        // This should initialize the network list
+        setNetworkId(getNetworkId());
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java
index 863443d9c..c6e927881 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java
@@ -9,10 +9,14 @@ import java.util.Map;
 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.BufferViewManagerSerializer;
 
-public class BufferViewManager extends SyncableObject {
+public class BufferViewManager extends SyncableObject<BufferViewManager> {
     @NonNull
-    public final Map<Integer, BufferViewConfig> BufferViews = new HashMap<>();
+    public Map<Integer, BufferViewConfig> BufferViews = new HashMap<>();
+    private Client client;
 
     public BufferViewManager(@NonNull List<Integer> BufferViewIds) {
         for (int i : BufferViewIds) {
@@ -30,7 +34,21 @@ public class BufferViewManager extends SyncableObject {
 
     @Override
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+        this.client = client;
         setObjectName(function.objectName);
         client.setBufferViewManager(this);
     }
+
+    @Override
+    public void update(BufferViewManager from) {
+        this.BufferViews = from.BufferViews;
+        for (int id : BufferViews.keySet()) {
+            client.sendInitRequest("BufferViewConfig", String.valueOf(id));
+        }
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(BufferViewManagerSerializer.get().fromDatastream(from));
+    }
 }
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 cfb0048a0..dc065d339 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
@@ -3,13 +3,17 @@ package de.kuschku.libquassel.syncables.types;
 import android.support.annotation.NonNull;
 
 import java.util.List;
+import java.util.Map;
 
 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.Syncable;
+import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer;
+import de.kuschku.libquassel.syncables.serializers.IdentitySerializer;
 
-public class Identity extends SyncableObject {
+public class Identity extends SyncableObject<Identity> {
     @Syncable
     private String identityName;
     @Syncable
@@ -257,4 +261,14 @@ public class Identity extends SyncableObject {
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
 
     }
+
+    @Override
+    public void update(Identity from) {
+
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(IdentitySerializer.get().fromDatastream(from));
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java
new file mode 100644
index 000000000..8904263a1
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java
@@ -0,0 +1,150 @@
+package de.kuschku.libquassel.syncables.types;
+
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import de.kuschku.libquassel.BusProvider;
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.functions.types.InitDataFunction;
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.libquassel.primitives.types.QVariant;
+import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer;
+import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer;
+
+import static de.kuschku.util.AndroidAssert.assertEquals;
+
+public class IgnoreListManager extends SyncableObject<IgnoreListManager> {
+    List<IgnoreRule> ignoreRules = new ArrayList<>();
+
+    public IgnoreListManager(List<Integer> scope, List<Integer> ignoreType,
+                             List<Boolean> isActive, List<String> scopeRule, List<Boolean> isRegEx,
+                             List<Integer> strictness, List<String> ignoreRule) {
+        assertEquals(scope.size(),ignoreType.size(), isActive.size(),scopeRule.size(), isRegEx.size(), strictness.size(), ignoreRule.size());
+
+        for (int i = 0; i < scope.size(); i++) {
+            ignoreRules.add(new IgnoreRule(
+                    scope.get(i),
+                    ignoreType.get(i),
+                    isActive.get(i),
+                    scopeRule.get(i),
+                    isRegEx.get(i),
+                    strictness.get(i),
+                    ignoreRule.get(i)
+            ));
+        }
+    }
+
+    @Override
+    public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+        client.setIgnoreListManager(this);
+    }
+
+    @Override
+    public void update(IgnoreListManager from) {
+
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(IgnoreListManagerSerializer.get().fromDatastream(from));
+    }
+
+    public boolean matches(Message message) {
+        return false;
+    }
+
+    public static class IgnoreRule {
+        private Scope scope;
+        private Type ignoreType;
+        private boolean isActive;
+        private String scopeRule;
+        private boolean isRegEx;
+        private Strictness strictness;
+        private String ignoreRule;
+
+        public IgnoreRule(Integer scope, Integer ignoreType, boolean isActive, String scopeRule, boolean isRegEx, Integer strictness, String ignoreRule) {
+            this(
+                    Scope.of(scope),
+                    Type.of(ignoreType),
+                    isActive,
+                    scopeRule,
+                    isRegEx,
+                    Strictness.of(strictness),
+                    ignoreRule
+            );
+        }
+
+        public IgnoreRule(Scope scope, Type ignoreType, boolean isActive, String scopeRule, boolean isRegEx, Strictness strictness, String ignoreRule) {
+            this.scope = scope;
+            this.ignoreType = ignoreType;
+            this.isActive = isActive;
+            this.scopeRule = scopeRule;
+            this.isRegEx = isRegEx;
+            this.strictness = strictness;
+            this.ignoreRule = ignoreRule;
+        }
+
+        public enum Strictness {
+            INVALID(-1),
+            UNMATCHED(0),
+            SOFT(1),
+            HARD(2);
+
+            public final int id;
+            Strictness(int id) {
+                this.id = id;
+            }
+            public static Strictness of(int id) {
+                switch (id) {
+                    case 0: return UNMATCHED;
+                    case 1: return SOFT;
+                    case 2: return HARD;
+                    default: return INVALID;
+                }
+            }
+        }
+
+        public enum Type {
+            INVALID(-1),
+            SENDER_IGNORE(0),
+            MESSAGE_IGNORE(1),
+            CTCP_IGNORE(2);
+
+            public final int id;
+            Type(int id) {
+                this.id = id;
+            }
+            public static Type of(int id) {
+                switch (id) {
+                    case 0: return SENDER_IGNORE;
+                    case 1: return MESSAGE_IGNORE;
+                    case 2: return CTCP_IGNORE;
+                    default: return INVALID;
+                }
+            }
+        }
+
+        public enum Scope {
+            INVALID(-1),
+            GLOBAL_SCOPE(0),
+            NETWORK_SCOPE(1),
+            CHANNEL_SCOPE(2);
+
+            public final int id;
+            Scope(int id) {
+                this.id = id;
+            }
+            public static Scope of(int id) {
+                switch (id) {
+                    case 0: return GLOBAL_SCOPE;
+                    case 1: return NETWORK_SCOPE;
+                    case 2: return CHANNEL_SCOPE;
+                    default: return INVALID;
+                }
+            }
+        }
+    }
+}
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 88ce98da9..853a559cb 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
@@ -1,21 +1,38 @@
 package de.kuschku.libquassel.syncables.types;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 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.Synced;
+import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer;
+import de.kuschku.util.AndroidAssert;
 
-public class IrcChannel extends SyncableObject {
-    public final String name;
-    public final String topic;
-    public final String password;
-    public final Map<String, String> UserModes;
-    public final Map<String, Object> ChanModes;
-    public final boolean encrypted;
+import static de.kuschku.util.AndroidAssert.*;
+
+public class IrcChannel extends SyncableObject<IrcChannel> {
+    @Synced private String name;
+    @Synced private String topic;
+    @Synced private String password;
+    @Synced private Map<String, String> UserModes;
+    @Synced private Map<String, Object> ChanModes;
+    @Synced private boolean encrypted;
+
+    @Nullable
+    private Network network;
 
     public IrcChannel(String name, String topic, String password, Map<String, String> userModes,
                       Map<String, Object> chanModes, boolean encrypted) {
@@ -27,6 +44,26 @@ public class IrcChannel extends SyncableObject {
         this.encrypted = encrypted;
     }
 
+    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");
+    }
+
+    public Map<String, String> getB_ChanModes() {
+        if (ChanModes.get("B") == null) ChanModes.put("B", new HashMap<>());
+        return (Map<String, String>) ChanModes.get("B");
+    }
+
+    public Map<String, String> getC_ChanModes() {
+        if (ChanModes.get("C") == null) ChanModes.put("C", new HashMap<>());
+        return (Map<String, String>) ChanModes.get("C");
+    }
+
+    public Set<String> getD_ChanModes() {
+        if (ChanModes.get("D") == null) ChanModes.put("D", new HashSet<>());
+        return (Set<String>) ChanModes.get("D");
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -40,6 +77,15 @@ public class IrcChannel extends SyncableObject {
                 '}';
     }
 
+    @Nullable
+    public Network getNetwork() {
+        return network;
+    }
+
+    public void setNetwork(@Nullable Network network) {
+        this.network = network;
+    }
+
     public void joinIrcUsers(@NonNull List<String> users, @NonNull List<String> modes) {
         for (int i = 0; i < users.size(); i++) {
             joinIrcUser(users.get(i), modes.get(i));
@@ -70,13 +116,158 @@ public class IrcChannel extends SyncableObject {
             UserModes.put(nick, UserModes.get(nick).replace(mode, ""));
     }
 
+    public void addChannelMode(Character mode, String params) {
+        addChannelMode(String.copyValueOf(new char[]{mode}), params);
+    }
+    public void addChannelMode(char mode, String params) {
+        addChannelMode(String.copyValueOf(new char[]{mode}), params);
+    }
+    public void addChannelMode(String mode, String params) {
+        assertNotNull(network);
+
+        Network.ChannelModeType type = network.channelModeType(mode);
+        switch (type) {
+            case NOT_A_CHANMODE:
+                return;
+            case A_CHANMODE:
+                if (!getA_ChanModes().containsKey(mode)) {
+                    getA_ChanModes().put(mode, new ArrayList<>(Collections.singleton(params)));
+                } else {
+                    getA_ChanModes().get(mode).add(params);
+                }
+                break;
+            case B_CHANMODE:
+                getB_ChanModes().put(mode, params);
+                break;
+            case C_CHANMODE:
+                getB_ChanModes().put(mode, params);
+                break;
+            case D_CHANMODE:
+
+                getD_ChanModes().add(mode);
+                break;
+        }
+    }
+    public void removeChannelMode(Character mode, String params) {
+        removeChannelMode(String.copyValueOf(new char[]{mode}), params);
+    }
+    public void removeChannelMode(char mode, String params) {
+        removeChannelMode(String.copyValueOf(new char[]{mode}), params);
+    }
+    public void removeChannelMode(String mode, String params) {
+        assertNotNull(network);
+
+        Network.ChannelModeType type = network.channelModeType(mode);
+        switch (type) {
+            case NOT_A_CHANMODE:
+                return;
+            case A_CHANMODE:
+                if (getA_ChanModes().containsKey(mode))
+                    getA_ChanModes().get(mode).removeAll(Collections.singleton(params));
+                break;
+            case B_CHANMODE:
+                getB_ChanModes().remove(mode);
+                break;
+            case C_CHANMODE:
+                getB_ChanModes().remove(mode);
+                break;
+            case D_CHANMODE:
+                getB_ChanModes().remove(mode);
+                break;
+        }
+    }
+
+    public boolean hasMode(Character mode) {
+        return hasMode(String.copyValueOf(new char[]{mode}));
+    }
+    public boolean hasMode(char mode) {
+        return hasMode(String.copyValueOf(new char[]{mode}));
+    }
+    public boolean hasMode(String mode) {
+        assertNotNull(network);
+
+        Network.ChannelModeType type = network.channelModeType(mode);
+        switch (type) {
+            case A_CHANMODE:
+                return getA_ChanModes().containsKey(mode);
+            case B_CHANMODE:
+                return getA_ChanModes().containsKey(mode);
+            case C_CHANMODE:
+                return getA_ChanModes().containsKey(mode);
+            case D_CHANMODE:
+                return getA_ChanModes().containsKey(mode);
+
+            default:
+                return false;
+        }
+    }
+
     public void renameUser(String oldNick, String newNick) {
         UserModes.put(newNick, UserModes.get(oldNick));
         UserModes.remove(oldNick);
     }
 
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getTopic() {
+        return topic;
+    }
+
+    public void setTopic(String topic) {
+        this.topic = topic;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+    public Map<String, String> getUserModes() {
+        return UserModes;
+    }
+
+    public void setUserModes(Map<String, String> userModes) {
+        UserModes = userModes;
+    }
+
+    public boolean isEncrypted() {
+        return encrypted;
+    }
+
+    public void setEncrypted(boolean encrypted) {
+        this.encrypted = encrypted;
+    }
+
     @Override
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
 
     }
+
+    @Override
+    public void update(IrcChannel from) {
+        this.name = from.name;
+        this.topic = from.topic;
+        this.password = from.password;
+        this.UserModes = from.UserModes;
+        this.ChanModes = from.ChanModes;
+        this.encrypted = from.encrypted;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(IrcChannelSerializer.get().fromDatastream(from));
+    }
+
+    public Map<String, Object> getChanModes() {
+        return ChanModes;
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java
index b13a94d86..2aa1d62ab 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java
@@ -6,31 +6,35 @@ import android.support.annotation.Nullable;
 import org.joda.time.DateTime;
 
 import java.util.List;
+import java.util.Map;
 
 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.IrcUserSerializer;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
-public class IrcUser extends SyncableObject {
-    public String server;
-    public String ircOperator;
-    public boolean away;
-    public int lastAwayMessage;
-    public DateTime idleTime;
-    public String whoisServiceReply;
-    public String suserHost;
-    public String nick;
-    public String realName;
-    public String awayMessage;
-    public DateTime loginTime;
-    public boolean encrypted;
+public class IrcUser extends SyncableObject<IrcUser> {
+    private String server;
+    private String ircOperator;
+    private boolean away;
+    private int lastAwayMessage;
+    private DateTime idleTime;
+    private String whoisServiceReply;
+    private String suserHost;
+    private String nick;
+    private String realName;
+    private String awayMessage;
+    private DateTime loginTime;
+    private boolean encrypted;
     @NonNull
-    public List<String> channels;
-    public String host;
-    public String userModes;
-    public String user;
+    private List<String> channels;
+    private String host;
+    private String userModes;
+    private String user;
 
     private Network network;
 
@@ -56,6 +60,71 @@ public class IrcUser extends SyncableObject {
         this.user = user;
     }
 
+    public String getServer() {
+        return server;
+    }
+
+    public String getIrcOperator() {
+        return ircOperator;
+    }
+
+    public boolean isAway() {
+        return away;
+    }
+
+    public int getLastAwayMessage() {
+        return lastAwayMessage;
+    }
+
+    public DateTime getIdleTime() {
+        return idleTime;
+    }
+
+    public String getWhoisServiceReply() {
+        return whoisServiceReply;
+    }
+
+    public String getSuserHost() {
+        return suserHost;
+    }
+
+    public String getNick() {
+        return nick;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public String getAwayMessage() {
+        return awayMessage;
+    }
+
+    public DateTime getLoginTime() {
+        return loginTime;
+    }
+
+    public boolean isEncrypted() {
+        return encrypted;
+    }
+
+    @NonNull
+    public List<String> getChannels() {
+        return channels;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public String getUserModes() {
+        return userModes;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
     /* BEGIN SYNC */
 
     public void setServer(String server) {
@@ -155,6 +224,31 @@ public class IrcUser extends SyncableObject {
         setNetwork(network);
     }
 
+    @Override
+    public void update(IrcUser from) {
+        this.server = from.server;
+        this.ircOperator = from.ircOperator;
+        this.away = from.away;
+        this.lastAwayMessage = from.lastAwayMessage;
+        this.idleTime = from.idleTime;
+        this.whoisServiceReply = from.whoisServiceReply;
+        this.suserHost = from.suserHost;
+        this.nick = from.nick;
+        this.realName = from.realName;
+        this.awayMessage = from.awayMessage;
+        this.loginTime = from.loginTime;
+        this.encrypted = from.encrypted;
+        this.channels = from.channels;
+        this.host = from.host;
+        this.userModes = from.userModes;
+        this.user = from.user;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(IrcUserSerializer.get().fromDatastream(from));
+    }
+
     public void quit() {
         network.quit(this.nick);
     }
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 375b6b93c..834955523 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
@@ -14,14 +14,15 @@ import java.util.Set;
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
-import de.kuschku.libquassel.functions.types.InitRequestFunction;
 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.observables.ContentComparable;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
 
-public class Network extends SyncableObject implements ContentComparable<Network> {
+public class Network extends SyncableObject<Network> implements ContentComparable<Network> {
     @NonNull
     private final Set<Buffer> buffers = new HashSet<>();
     @NonNull
@@ -76,6 +77,7 @@ public class Network extends SyncableObject implements ContentComparable<Network
     @Nullable
     private Map<String, IrcMode> supportedModes;
     private int networkId;
+    private Client client;
 
     public Network(@NonNull Map<String, IrcChannel> channels, @NonNull Map<String, IrcUser> users,
                    @NonNull List<NetworkServer> serverList, @NonNull Map<String, String> supports,
@@ -124,7 +126,7 @@ public class Network extends SyncableObject implements ContentComparable<Network
         assertNotNull(provider);
 
         for (IrcUser user : getUsers().values()) {
-            provider.dispatch(new InitRequestFunction("IrcUser", getNetworkId() + "/" + user.nick));
+            client.sendInitRequest("IrcUser", getNetworkId() + "/" + user.getNick());
         }
     }
 
@@ -154,7 +156,7 @@ public class Network extends SyncableObject implements ContentComparable<Network
     }
 
     public void addIrcUser(String sender) {
-        provider.dispatch(new InitRequestFunction("IrcUser", getObjectName() + "/" + sender));
+        client.sendInitRequest("IrcUser", getObjectName() + "/" + sender);
     }
 
     @Nullable
@@ -194,6 +196,19 @@ public class Network extends SyncableObject implements ContentComparable<Network
         return channels;
     }
 
+    public void addIrcChannel(String channelName) {
+        IrcChannel ircChannel = new IrcChannel(
+                channelName,
+                null,
+                null,
+                new HashMap<>(),
+                new HashMap<>(),
+                false
+        );
+        ircChannel.setNetwork(this);
+        channels.put(channelName, ircChannel);
+    }
+
     public void setChannels(@NonNull Map<String, IrcChannel> channels) {
         this.channels = channels;
     }
@@ -476,13 +491,57 @@ public class Network extends SyncableObject implements ContentComparable<Network
     @Override
     public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
         setObjectName(function.objectName);
-        setBusProvider(provider);
         setNetworkId(Integer.parseInt(function.objectName));
+        setBusProvider(provider);
+        setClient(client);
+        doInit();
+    }
+
+    @Override
+    public void doInit() {
         getBuffers().addAll(client.getBuffers(getNetworkId()));
         initUsers();
         client.putNetwork(this);
     }
 
+    @Override
+    public void update(Network from) {
+        this.channels = from.channels;
+        this.users = from.users;
+        this.ServerList = from.ServerList;
+        this.Supports = from.Supports;
+        this.autoIdentifyPassword = from.autoIdentifyPassword;
+        this.autoIdentifyService = from.autoIdentifyService;
+        this.autoReconnectInterval = from.autoReconnectInterval;
+        this.autoReconnectRetries = from.autoReconnectRetries;
+        this.codecForDecoding = from.codecForDecoding;
+        this.codecForEncoding = from.codecForEncoding;
+        this.codecForServer = from.codecForServer;
+        this.connectionState = from.connectionState;
+        this.currentServer = from.currentServer;
+        this.identityId = from.identityId;
+        this.isConnected = from.isConnected;
+        this.latency = from.latency;
+        this.myNick = from.myNick;
+        this.networkName = from.networkName;
+        this.perform = from.perform;
+        this.rejoinChannels = from.rejoinChannels;
+        this.saslAccount = from.saslAccount;
+        this.saslPassword = from.saslPassword;
+        this.unlimitedReconnectRetries = from.unlimitedReconnectRetries;
+        this.useAutoIdentify = from.useAutoIdentify;
+        this.useAutoReconnect = from.useAutoReconnect;
+        this.useRandomServer = from.useRandomServer;
+        this.useSasl = from.useSasl;
+        parsePrefix();
+        assertNotNull(supportedModes);
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(NetworkSerializer.get().fromDatastream(from));
+    }
+
     @Override
     public boolean equalsContent(@NonNull Network other) {
         return networkId == other.networkId;
@@ -493,6 +552,10 @@ public class Network extends SyncableObject implements ContentComparable<Network
         return networkId - another.networkId;
     }
 
+    public void setClient(Client client) {
+        this.client = client;
+    }
+
     public static class IrcMode {
         public final int rank;
         public final String prefix;
@@ -511,4 +574,42 @@ public class Network extends SyncableObject implements ContentComparable<Network
                     '}';
         }
     }
+
+    public ChannelModeType channelModeType(char mode) {
+        return channelModeType(String.copyValueOf(new char[]{mode}));
+    }
+
+    public ChannelModeType channelModeType(String mode) {
+        if (mode.isEmpty())
+            return ChannelModeType.NOT_A_CHANMODE;
+
+        String rawChanModes = getSupports().get("CHANMODES");
+        if (rawChanModes == null || rawChanModes.isEmpty())
+            return ChannelModeType.NOT_A_CHANMODE;
+
+        String[] chanModes = rawChanModes.split(",");
+        for (int i = 0; i < chanModes.length; i++) {
+            if (chanModes[i].contains(mode)) {
+                switch (i) {
+                    case 0: return ChannelModeType.A_CHANMODE;
+                    case 1: return ChannelModeType.B_CHANMODE;
+                    case 2: return ChannelModeType.C_CHANMODE;
+                    case 3: return ChannelModeType.D_CHANMODE;
+                    default: return ChannelModeType.NOT_A_CHANMODE;
+                }
+            }
+        }
+        return ChannelModeType.NOT_A_CHANMODE;
+    }
+
+    // see:
+    //  http://www.irc.org/tech_docs/005.html
+    //  http://www.irc.org/tech_docs/draft-brocklesby-irc-isupport-03.txt
+    public enum ChannelModeType {
+        NOT_A_CHANMODE,
+        A_CHANMODE,
+        B_CHANMODE,
+        C_CHANMODE,
+        D_CHANMODE
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java
new file mode 100644
index 000000000..1540f2994
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java
@@ -0,0 +1,120 @@
+package de.kuschku.libquassel.syncables.types;
+
+import android.support.annotation.NonNull;
+
+import java.util.Map;
+
+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.NetworkConfigSerializer;
+
+public class NetworkConfig extends SyncableObject<NetworkConfig> {
+    private int autoWhoNickLimit;
+    private int autoWhoDelay;
+    private boolean autoWhoEnabled;
+    private boolean standardCtcp;
+    private int pingInterval;
+    private int autoWhoInterval;
+    private int maxPingCount;
+    private boolean pingTimeoutEnabled;
+
+    public NetworkConfig(int autoWhoNickLimit, int autoWhoDelay, boolean autoWhoEnabled, boolean standardCtcp, int pingInterval, int autoWhoInterval, int maxPingCount, boolean pingTimeoutEnabled) {
+        this.autoWhoNickLimit = autoWhoNickLimit;
+        this.autoWhoDelay = autoWhoDelay;
+        this.autoWhoEnabled = autoWhoEnabled;
+        this.standardCtcp = standardCtcp;
+        this.pingInterval = pingInterval;
+        this.autoWhoInterval = autoWhoInterval;
+        this.maxPingCount = maxPingCount;
+        this.pingTimeoutEnabled = pingTimeoutEnabled;
+    }
+
+    public int getAutoWhoNickLimit() {
+        return autoWhoNickLimit;
+    }
+
+    public void setAutoWhoNickLimit(int autoWhoNickLimit) {
+        this.autoWhoNickLimit = autoWhoNickLimit;
+    }
+
+    public int getAutoWhoDelay() {
+        return autoWhoDelay;
+    }
+
+    public void setAutoWhoDelay(int autoWhoDelay) {
+        this.autoWhoDelay = autoWhoDelay;
+    }
+
+    public boolean isAutoWhoEnabled() {
+        return autoWhoEnabled;
+    }
+
+    public void setAutoWhoEnabled(boolean autoWhoEnabled) {
+        this.autoWhoEnabled = autoWhoEnabled;
+    }
+
+    public boolean isStandardCtcp() {
+        return standardCtcp;
+    }
+
+    public void setStandardCtcp(boolean standardCtcp) {
+        this.standardCtcp = standardCtcp;
+    }
+
+    public int getPingInterval() {
+        return pingInterval;
+    }
+
+    public void setPingInterval(int pingInterval) {
+        this.pingInterval = pingInterval;
+    }
+
+    public int getAutoWhoInterval() {
+        return autoWhoInterval;
+    }
+
+    public void setAutoWhoInterval(int autoWhoInterval) {
+        this.autoWhoInterval = autoWhoInterval;
+    }
+
+    public int getMaxPingCount() {
+        return maxPingCount;
+    }
+
+    public void setMaxPingCount(int maxPingCount) {
+        this.maxPingCount = maxPingCount;
+    }
+
+    public boolean isPingTimeoutEnabled() {
+        return pingTimeoutEnabled;
+    }
+
+    public void setPingTimeoutEnabled(boolean pingTimeoutEnabled) {
+        this.pingTimeoutEnabled = pingTimeoutEnabled;
+    }
+
+    @Override
+    public void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client) {
+
+    }
+
+    @Override
+    public void update(NetworkConfig from) {
+        this.autoWhoNickLimit = from.autoWhoNickLimit;
+        this.autoWhoDelay = from.autoWhoDelay;
+        this.autoWhoEnabled = from.autoWhoEnabled;
+        this.standardCtcp = from.standardCtcp;
+        this.pingInterval = from.pingInterval;
+        this.autoWhoInterval = from.autoWhoInterval;
+        this.maxPingCount = from.maxPingCount;
+        this.pingTimeoutEnabled = from.pingTimeoutEnabled;
+    }
+
+    @Override
+    public void update(Map<String, QVariant> from) {
+        update(NetworkConfigSerializer.get().fromDatastream(from));
+    }
+}
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java
index 299480bc1..1b7830386 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java
@@ -4,15 +4,18 @@ import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 
 import java.util.Arrays;
+import java.util.Map;
 
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
 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 static de.kuschku.util.AndroidAssert.assertNotNull;
 
-public abstract class SyncableObject {
+public abstract class SyncableObject<T extends SyncableObject<T>> {
     @Nullable
     protected BusProvider provider;
     @Nullable
@@ -47,4 +50,9 @@ public abstract class SyncableObject {
     }
 
     public abstract void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client);
+
+    public void doInit() {}
+
+    public abstract void update(T from);
+    public abstract void update(Map<String, QVariant> from);
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java
new file mode 100644
index 000000000..2851cbb08
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppContext.java
@@ -0,0 +1,65 @@
+package de.kuschku.quasseldroid_ng.ui;
+
+import de.kuschku.libquassel.BusProvider;
+import de.kuschku.libquassel.Client;
+import de.kuschku.quasseldroid_ng.ui.chat.WrappedSettings;
+import de.kuschku.util.ui.ThemeUtil;
+
+public class AppContext {
+    private ThemeUtil themeUtil;
+    private WrappedSettings settings;
+    private Client client;
+    private BusProvider provider;
+
+    public ThemeUtil getThemeUtil() {
+        return themeUtil;
+    }
+
+    public void setThemeUtil(ThemeUtil themeUtil) {
+        this.themeUtil = themeUtil;
+    }
+
+    public AppContext withThemeUtil(ThemeUtil themeUtil) {
+        setThemeUtil(themeUtil);
+        return this;
+    }
+
+    public WrappedSettings getSettings() {
+        return settings;
+    }
+
+    public void setSettings(WrappedSettings settings) {
+        this.settings = settings;
+    }
+
+    public AppContext withSettings(WrappedSettings settings) {
+        setSettings(settings);
+        return this;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+
+    public void setClient(Client client) {
+        this.client = client;
+    }
+
+    public AppContext withClient(Client client) {
+        setClient(client);
+        return this;
+    }
+
+    public BusProvider getProvider() {
+        return provider;
+    }
+
+    public void setProvider(BusProvider provider) {
+        this.provider = provider;
+    }
+
+    public AppContext withProvider(BusProvider provider) {
+        setProvider(provider);
+        return this;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java
index 12b8d76aa..c24ebeff6 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/AppTheme.java
@@ -29,4 +29,11 @@ public enum AppTheme {
             case "QUASSEL": return QUASSEL;
         }
     }
+
+    @Override
+    public String toString() {
+        return name() + "{" +
+                "themeId=" + themeId +
+                '}';
+    }
 }
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/BufferViewConfigWrapper.java
new file mode 100644
index 000000000..474ba962c
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/BufferViewConfigWrapper.java
@@ -0,0 +1,104 @@
+package de.kuschku.quasseldroid_ng.ui.chat;
+
+import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.syncables.types.BufferViewConfig;
+import de.kuschku.libquassel.syncables.types.Network;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
+import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem;
+import de.kuschku.util.observables.callbacks.ElementCallback;
+import de.kuschku.util.observables.lists.ObservableComparableSortedList;
+import de.kuschku.util.observables.lists.ObservableSortedList;
+
+import static de.kuschku.util.AndroidAssert.assertNotNull;
+
+public class BufferViewConfigWrapper {
+    private Drawer drawer;
+    private BufferViewConfig config;
+    private ObservableSortedList<NetworkItem> networks = new ObservableSortedList<>(NetworkItem.class, new ObservableSortedList.ItemComparator<NetworkItem>() {
+        @Override
+        public int compare(NetworkItem o1, NetworkItem o2) {
+            return o1.getName().getText().compareTo(o2.getName().getText());
+        }
+
+        @Override
+        public boolean areContentsTheSame(NetworkItem oldItem, NetworkItem newItem) {
+            return oldItem.getNetwork().getNetworkId() == newItem.getNetwork().getNetworkId();
+        }
+
+        @Override
+        public boolean areItemsTheSame(NetworkItem item1, NetworkItem item2) {
+            return item1 == item2;
+        }
+    });
+
+    public BufferViewConfigWrapper(AppContext context, BufferViewConfig config, Drawer drawer) {
+        this.config = config;
+        this.drawer = drawer;
+        config.doLateInit();
+        networks.clear();
+        for (Integer networkId : config.getNetworkList()) {
+            Network network = context.getClient().getNetwork(networkId);
+            assertNotNull(network);
+            networks.add(new NetworkItem(context, network, config));
+        }
+        config.getNetworkList().addCallback(new ElementCallback<Integer>() {
+            @Override
+            public void notifyItemInserted(Integer element) {
+                networks.add(new NetworkItem(context, context.getClient().getNetwork(element), config));
+            }
+
+            @Override
+            public void notifyItemRemoved(Integer element) {
+                for (NetworkItem network : networks) {
+                    if (network.getNetwork().getNetworkId() == element) {
+                        networks.remove(network);
+                        break;
+                    }
+                }
+            }
+
+            @Override
+            public void notifyItemChanged(Integer element) {
+                for (NetworkItem network : networks) {
+                    if (network.getNetwork().getNetworkId() == element) {
+                        networks.notifyItemChanged(networks.indexOf(network));
+                        break;
+                    }
+                }
+            }
+        });
+    }
+
+    public void updateDrawerItems() {
+        drawer.removeAllItems();
+        for (IDrawerItem item : getItems()) {
+            drawer.addItem(item);
+        }
+        for (int i = 0; i < drawer.getAdapter().getItemCount(); i++) {
+            IDrawerItem item = drawer.getAdapter().getItem(i);
+            if (item instanceof NetworkItem) {
+                NetworkItem networkItem = (NetworkItem) item;
+                if (networkItem.getNetwork().isConnected())
+                    drawer.getAdapter().expand(i);
+            }
+        }
+    }
+
+    public void setDrawer(Drawer drawer) {
+        this.drawer = drawer;
+    }
+
+    public ArrayList<IDrawerItem> getItems() {
+        ArrayList<IDrawerItem> items = new ArrayList<>();
+        for (IDrawerItem item : networks) {
+            items.add(item);
+        }
+        return items;
+    }
+}
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 9e55b5786..f7fb9ed33 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
@@ -1,6 +1,8 @@
 package de.kuschku.quasseldroid_ng.ui.chat;
 
 import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -8,66 +10,78 @@ 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.NavigationView;
-import android.support.v4.widget.DrawerLayout;
+import android.support.design.widget.Snackbar;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.AppCompatEditText;
 import android.support.v7.widget.AppCompatImageButton;
-import android.support.v7.widget.AppCompatSpinner;
 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.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.RelativeLayout;
+import android.util.Log;
+import android.view.View;
 
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.google.common.base.Splitter;
 import com.mikepenz.fastadapter.FastAdapter;
-import com.mikepenz.materialize.util.UIUtils;
+import com.mikepenz.fastadapter.IExpandable;
+import com.mikepenz.fastadapter.IItem;
+import com.mikepenz.materialdrawer.AccountHeader;
+import com.mikepenz.materialdrawer.AccountHeaderBuilder;
+import com.mikepenz.materialdrawer.Drawer;
+import com.mikepenz.materialdrawer.DrawerBuilder;
+import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
+import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IProfile;
 import com.sothree.slidinguppanel.SlidingUpPanelLayout;
 
-import aspm.OnChangeListener;
-import aspm.PreferenceElement;
-import aspm.StringPreference;
-import aspm.annotations.Preference;
+import java.util.Map;
+
 import aspm.annotations.PreferenceWrapper;
 import butterknife.Bind;
 import butterknife.ButterKnife;
+import de.kuschku.libquassel.BusProvider;
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.events.BacklogReceivedEvent;
+import de.kuschku.libquassel.events.ConnectionChangeEvent;
+import de.kuschku.libquassel.events.GeneralErrorEvent;
+import de.kuschku.libquassel.events.LagChangedEvent;
+import de.kuschku.libquassel.exceptions.UnknownTypeException;
+import de.kuschku.libquassel.localtypes.Buffer;
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.libquassel.syncables.types.BufferViewConfig;
+import de.kuschku.libquassel.syncables.types.BufferViewManager;
+import de.kuschku.libquassel.syncables.types.Network;
 import de.kuschku.quasseldroid_ng.BuildConfig;
 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.AppContext;
 import de.kuschku.quasseldroid_ng.ui.AppTheme;
 import de.kuschku.quasseldroid_ng.ui.chat.chatview.MessageAdapter;
-import de.kuschku.util.DrawerUtils;
+import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem;
+import de.kuschku.util.keyboardutils.DialogKeyboardUtil;
+import de.kuschku.util.ServerAddress;
 import de.kuschku.util.instancestateutil.Storable;
 import de.kuschku.util.instancestateutil.Store;
 import de.kuschku.util.observables.AutoScroller;
+import de.kuschku.util.observables.lists.ObservableSortedList;
+import de.kuschku.util.ui.SpanFormatter;
+import de.kuschku.util.ui.ThemeUtil;
 
 import static de.kuschku.util.AndroidAssert.assertNotNull;
-import static de.kuschku.util.AndroidAssert.assertTrue;
 
 @UiThread
 public class ChatActivity extends AppCompatActivity {
-    @NonNull
-    private final Status status = new Status();
-
-    @Bind(R.id.drawer_left)
-    DrawerLayout drawerLeft;
-
-    @Bind(R.id.navigation_left)
-    NavigationView navigationLeft;
-
     @Bind(R.id.toolbar)
     Toolbar toolbar;
-
     @Bind(R.id.sliding_layout)
     SlidingUpPanelLayout slidingLayout;
 
     @Bind(R.id.chatline)
     AppCompatEditText chatline;
-
     @Bind(R.id.send)
     AppCompatImageButton send;
 
@@ -76,40 +90,66 @@ public class ChatActivity extends AppCompatActivity {
 
     @Bind(R.id.swipe_view)
     SwipeRefreshLayout swipeView;
-
     @Bind(R.id.messages)
     RecyclerView messages;
 
-    @Bind(R.id.navigation_header_container)
-    RelativeLayout navigationHeaderContainer;
 
-    @Bind(R.id.buffer_view_spinner)
-    AppCompatSpinner bufferViewSpinner;
-
-
-    WrappedSettings settings;
     @PreferenceWrapper(BuildConfig.APPLICATION_ID)
     public static abstract class Settings {
         String theme;
         boolean fullHostmask;
         int textSize;
+        boolean mircColors;
+
+        String lastHost;
+        int lastPort;
+        String lastUsername;
+        String lastPassword;
     }
 
-    @Nullable
-    private QuasselService.LocalBinder binder;
+    private AppContext context = new AppContext();
+
+    @NonNull
+    private final Status status = new Status();
+    private static class Status extends Storable {
+        @Store int bufferId = -1;
+        @Store int bufferViewConfigId = -1;
+    }
 
-    @Nullable
+    private ServiceInterface serviceInterface = new ServiceInterface();
+    private class ServiceInterface {
+        private void connect(@NonNull ServerAddress address) {
+            assertNotNull(binder);
+            disconnect();
+
+            BusProvider provider = new BusProvider();
+            provider.event.register(ChatActivity.this);
+            binder.startBackgroundThread(provider, address);
+            onConnectionEstablished();
+        }
+
+        private void disconnect() {
+            if (binder != null) binder.stopBackgroundThread();
+            if (backgroundThread != null) backgroundThread.provider.event.unregister(this);
+            backgroundThread = null;
+        }
+    }
+
+    private QuasselService.LocalBinder binder;
     private ClientBackgroundThread backgroundThread;
 
+    private MessageAdapter messageAdapter;
+
+    private AccountHeader accountHeader;
+    private Drawer drawerLeft;
+    private BufferViewConfigWrapper wrapper;
+    private CharSequence subtitle;
+
     private ServiceConnection serviceConnection = new ServiceConnection() {
         @UiThread
         public void onServiceConnected(@NonNull ComponentName cn, @NonNull IBinder service) {
-            assertNotNull(cn);
-            assertNotNull(service);
-
             if (service instanceof QuasselService.LocalBinder) {
                 ChatActivity.this.binder = (QuasselService.LocalBinder) service;
-                assertNotNull(binder);
                 if (binder.getBackgroundThread() != null) {
                     connectToThread(binder.getBackgroundThread());
                 }
@@ -118,41 +158,78 @@ public class ChatActivity extends AppCompatActivity {
 
         @UiThread
         public void onServiceDisconnected(@NonNull ComponentName cn) {
-            assertNotNull(cn);
-
-            backgroundThread = null;
+            serviceInterface.disconnect();
             binder = null;
         }
     };
 
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
-        settings = new WrappedSettings(this);
-        setTheme(AppTheme.resFromString(settings.theme.get()));
+        context.setSettings(new WrappedSettings(this));
+        AppTheme theme = AppTheme.QUASSEL;
+        setTheme(theme.themeId);
+        context.setThemeUtil(new ThemeUtil(this, theme));
 
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
+        setContentView(R.layout.activity_chat);
         ButterKnife.bind(this);
         setSupportActionBar(toolbar);
 
-        DrawerUtils.initDrawer(this, drawerLeft, toolbar, R.string.open_drawer, R.string.close_drawer);
-
-        ViewGroup.LayoutParams lp = navigationHeaderContainer.getLayoutParams();
-        assertNotNull(lp);
-        lp.height = lp.height + UIUtils.getStatusBarHeight(this);
-        navigationHeaderContainer.setLayoutParams(lp);
+        connectToService();
+
+        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();
+
+        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.setOnDrawerItemClickListener((view, position, drawerItem) -> {
+            long identifier = drawerItem.getIdentifier();
+            if (identifier == -1) {
+                showConnectDialog();
+                return false;
+            } else {
+                if (((IExpandable) drawerItem).getSubItems() != null) {
+                    drawerLeft.getAdapter().toggleExpandable(position);
+                    return true;
+                } else {
+                    selectBuffer((int) drawerItem.getIdentifier());
+                    return false;
+                }
+            }
+        });
 
-        messages.setLayoutManager(new LinearLayoutManager(this));
         messages.setItemAnimator(new DefaultItemAnimator());
-        messages.setAdapter(new MessageAdapter(this, new AutoScroller(messages)));
+        messages.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true));
+        messageAdapter = new MessageAdapter(this, context, new AutoScroller(messages));
+        messages.setAdapter(messageAdapter);
 
+        msgHistory.setAdapter(new FastAdapter<>());
         msgHistory.setLayoutManager(new LinearLayoutManager(this));
         msgHistory.setItemAnimator(new DefaultItemAnimator());
-        msgHistory.setAdapter(new FastAdapter<>());
 
-        bufferViewSpinner.setAdapter(new ArrayAdapter<>(this, R.layout.md_simplelist_item, android.R.id.title, new String[]{"All Chats", "Queries", "Highlights"}));
-        bufferViewSpinner.setPopupBackgroundResource(R.drawable.popup_background_material);
-        bufferViewSpinner.setSelection(0);
+        swipeView.setOnRefreshListener(() -> {
+            assertNotNull(context.getClient());
+            context.getClient().getBacklogManager().requestMoreBacklog(status.bufferId, 20);
+        });
+
+        send.setOnClickListener(view -> sendInput());
     }
 
     @Override
@@ -174,7 +251,7 @@ public class ChatActivity extends AppCompatActivity {
     private void connectToThread(@NonNull ClientBackgroundThread backgroundThread) {
         assertNotNull(backgroundThread);
 
-        if (this.backgroundThread != null) backgroundThread.provider.event.unregister(this);
+        serviceInterface.disconnect();
 
         this.backgroundThread = backgroundThread;
         backgroundThread.provider.event.register(this);
@@ -183,35 +260,181 @@ public class ChatActivity extends AppCompatActivity {
     }
 
     private void selectBufferViewConfig(@IntRange(from = -1) int bufferViewConfigId) {
+        if (wrapper != null) wrapper.setDrawer(null);
+        drawerLeft.removeAllItems();
         if (bufferViewConfigId == -1) {
-            // TODO: Implement this
-
+            drawerLeft.removeAllItems();
         } else {
-            assertNotNull(this.backgroundThread);
-            assertNotNull(this.backgroundThread.handler.client.getBufferViewManager());
-            assertNotNull(this.backgroundThread.handler.client.getBufferViewManager().BufferViews.get(bufferViewConfigId));
-
-            // TODO: Implement this
+            drawerLeft.removeAllItems();
+            BufferViewManager bufferViewManager = context.getClient().getBufferViewManager();
+            assertNotNull(bufferViewManager);
+            BufferViewConfig viewConfig = bufferViewManager.BufferViews.get(bufferViewConfigId);
+            assertNotNull(viewConfig);
+
+            wrapper = new BufferViewConfigWrapper(context, viewConfig, drawerLeft);
+            wrapper.updateDrawerItems();
         }
     }
 
     private void selectBuffer(@IntRange(from = -1) int bufferId) {
         if (bufferId == -1) {
-
+            messageAdapter.setMessageList(MessageAdapter.emptyList());
+            toolbar.setTitle(getResources().getString(R.string.app_name));
         } else {
-            assertNotNull(this.backgroundThread);
-            assertNotNull(this.backgroundThread.handler.client.getBuffer(bufferId));
+            status.bufferId = bufferId;
+            // Make sure we are actually connected
+            ObservableSortedList<Message> list = context.getClient().getBacklogManager().getFiltered(status.bufferId);
+            Buffer buffer = context.getClient().getBuffer(status.bufferId);
+            // Make sure everything is properly defined
+            assertNotNull("Buffer is null: " + bufferId, buffer);
+            assertNotNull(list);
+
+            messageAdapter.setMessageList(list);
+            toolbar.setTitle(buffer.getName());
+        }
+    }
+
+    private void connectToService() {
+        Intent intent = new Intent(this, QuasselService.class);
+        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private void onConnectionEstablished() {
+        assertNotNull(binder);
+        this.backgroundThread = binder.getBackgroundThread();
+        assertNotNull(this.backgroundThread);
+        context.setClient(this.backgroundThread.handler.client);
+        assertNotNull(context.getClient());
+    }
+
+    private void sendInput() {
+        if (context.getClient() == null) return;
+        Buffer buffer = context.getClient().getBuffer(status.bufferId);
+        assertNotNull(buffer);
 
+        CharSequence text = chatline.getText();
+        context.getClient().sendInput(buffer.getInfo(), text.toString());
+    }
+
+    public void onEventMainThread(ConnectionChangeEvent event) {
+        setSubtitle(event.status.name());
+
+        switch (event.status) {
+            case HANDSHAKE:
+                break;
+            case CORE_SETUP_REQUIRED:
+                break;
+            case LOGIN_REQUIRED:
+                assertNotNull(context.getClient());
+
+                showLoginDialog();
+                break;
+            case USER_SETUP_REQUIRED:
+                break;
+            case CONNECTED:
+                Log.e("TIME", String.valueOf(System.currentTimeMillis()));
+                updateBufferViewConfigs();
+                break;
         }
-        assertTrue(bufferId == -1 || null != this.backgroundThread.handler.client.getBuffer(bufferId));
+    }
 
-        // TODO: Implement this
+    private void updateBufferViewConfigs() {
+        Map<Integer, BufferViewConfig> bufferViews = context.getClient().getBufferViewManager().BufferViews;
+        accountHeader.clear();
+        for (Map.Entry<Integer, BufferViewConfig> entry : bufferViews.entrySet()) {
+            if (entry.getValue() != null) {
+                accountHeader.addProfiles(
+                        new ProfileDrawerItem()
+                                .withName(entry.getValue().getBufferViewName())
+                                .withIdentifier(entry.getKey())
+                );
+            }
+        }
+        selectBufferViewConfig(status.bufferViewConfigId);
+        selectBuffer(status.bufferId);
     }
 
-    private static class Status extends Storable {
-        @Store
-        public int bufferId = -1;
-        @Store
-        public int bufferViewConfigId = -1;
+    private void showLoginDialog() {
+        MaterialDialog dialog = new MaterialDialog.Builder(this)
+                .title("Address")
+                .customView(R.layout.dialog_login, false)
+                .onPositive((dialog1, which) -> {
+                    View parent = dialog1.getCustomView();
+                    AppCompatEditText usernameField = (AppCompatEditText) parent.findViewById(R.id.username);
+                    AppCompatEditText passwordField = (AppCompatEditText) parent.findViewById(R.id.password);
+                    String username = usernameField.getText().toString();
+                    String password = passwordField.getText().toString();
+                    context.getSettings().lastUsername.set(username);
+                    context.getSettings().lastPassword.set(password);
+                    context.getClient().login(username, password);
+
+                    Log.e("TIME", String.valueOf(System.currentTimeMillis()));
+                })
+                .positiveText("Login")
+                .neutralText("Cancel")
+                .build();
+        dialog.setOnKeyListener(new DialogKeyboardUtil(dialog));
+        ((AppCompatEditText) dialog.getView().findViewById(R.id.username)).setText(context.getSettings().lastUsername.or(""));
+        ((AppCompatEditText) dialog.getView().findViewById(R.id.password)).setText(context.getSettings().lastPassword.or(""));
+        dialog.show();
+    }
+
+    public void showConnectDialog() {
+        MaterialDialog dialog = new MaterialDialog.Builder(this)
+                .title("Address")
+                .customView(R.layout.dialog_address, false)
+                .onPositive((dialog1, which) -> {
+                    View parent = dialog1.getCustomView();
+                    AppCompatEditText hostField = (AppCompatEditText) parent.findViewById(R.id.host);
+                    AppCompatEditText portField = (AppCompatEditText) parent.findViewById(R.id.port);
+                    String host = hostField.getText().toString().trim();
+                    int port = Integer.valueOf(portField.getText().toString().trim());
+                    context.getSettings().lastHost.set(host);
+                    context.getSettings().lastPort.set(port);
+                    serviceInterface.connect(new ServerAddress(host, port));
+                })
+                .positiveText("Connect")
+                .neutralText("Cancel")
+                .build();
+        AppCompatEditText hostField = (AppCompatEditText) dialog.getView().findViewById(R.id.host);
+        AppCompatEditText portField = (AppCompatEditText) dialog.getView().findViewById(R.id.port);
+
+        dialog.setOnKeyListener(new DialogKeyboardUtil(dialog));
+        hostField.setText(context.getSettings().lastHost.or(""));
+        portField.setText(String.valueOf(context.getSettings().lastPort.or(4242)));
+
+        dialog.show();
+    }
+
+    public void onEventMainThread(BacklogReceivedEvent event) {
+        if (event.bufferId == status.bufferId) {
+            swipeView.setRefreshing(false);
+        }
+    }
+
+    public void onEventMainThread(GeneralErrorEvent event) {
+        Snackbar.make(messages, event.toString(), Snackbar.LENGTH_LONG).show();
+        for (String line : Splitter.fixedLength(2048).split(event.toString())) {
+            Log.e("ChatActivity", line);
+        }
+        if (event.exception != null)
+            event.exception.printStackTrace();
+    }
+
+    public void onEventMainThread(LagChangedEvent event) {
+        updateSubTitle();
+    }
+
+    protected void setSubtitle(CharSequence subtitle) {
+        this.subtitle = subtitle;
+        updateSubTitle();
+    }
+
+    private void updateSubTitle() {
+        if (context.getClient() != null) {
+            toolbar.setSubtitle(SpanFormatter.format("Lag: %.2f, %s", context.getClient().getLag() / 1000.0F, subtitle));
+        } else {
+            toolbar.setSubtitle(subtitle);
+        }
     }
 }
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 28b3d4c71..50205398e 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.libquassel.localtypes.Buffer;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.quasseldroid_ng.BuildConfig;
 import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
 import de.kuschku.quasseldroid_ng.ui.AppTheme;
 import de.kuschku.util.annotationbind.AutoBinder;
 import de.kuschku.util.annotationbind.AutoString;
@@ -40,26 +41,14 @@ public class ChatMessageRenderer {
     private MessageStyleContainer actionStyle;
     private MessageStyleContainer plainStyle;
 
-    @Nullable
-    private Client client;
-
-    //@NonNull
-    //private final SharedPreferences preferences;
-
-    public ChatMessageRenderer(@NonNull Context ctx) {
-        this(ctx, new ThemeUtil(ctx));
-    }
-
-    public ChatMessageRenderer(@NonNull Context ctx, @NonNull AppTheme theme) {
-        this(ctx, new ThemeUtil(ctx, theme));
-    }
-
-    public ChatMessageRenderer(@NonNull Context ctx, @NonNull ThemeUtil themeUtil) {
-        //this.preferences = ctx.getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
+    @NonNull
+    private AppContext context;
 
+    public ChatMessageRenderer(@NonNull Context ctx, @NonNull AppContext context) {
         this.format = DateFormatHelper.getTimeFormatter(ctx);
         this.strings = new FormatStrings(ctx);
-        setTheme(themeUtil);
+        this.context = context;
+        setTheme(context.getThemeUtil());
     }
 
     public void setTheme(ThemeUtil themeUtil) {
@@ -91,10 +80,6 @@ public class ChatMessageRenderer {
         );
     }
 
-    public void setClient(@NonNull Client client) {
-        this.client = client;
-    }
-
     private void applyStyle(@NonNull MessageViewHolder holder, @NonNull MessageStyleContainer style, @NonNull MessageStyleContainer highlightStyle, boolean highlight) {
         MessageStyleContainer container = highlight ? highlightStyle : style;
         holder.content.setTextColor(container.textColor);
@@ -115,13 +100,13 @@ public class ChatMessageRenderer {
 
     @NonNull
     private CharSequence formatNick(@NonNull String hostmask) {
-        return formatNick(hostmask, true);
+        return formatNick(hostmask, context.getSettings().fullHostmask.or(false));
     }
 
     @NonNull
     private CharSequence getBufferName(Message message) {
-        assertNotNull(client);
-        Buffer buffer = client.getBuffer(message.bufferInfo.id);
+        assertNotNull(context.getClient());
+        Buffer buffer = context.getClient().getBuffer(message.bufferInfo.id);
         assertNotNull(buffer);
         String name = buffer.getName();
         assertNotNull(name);
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java
index a2d0d3f5f..61e434651 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java
@@ -11,6 +11,7 @@ import android.view.ViewGroup;
 import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.message.Message;
 import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
 import de.kuschku.util.observables.AutoScroller;
 import de.kuschku.util.observables.callbacks.UICallback;
 import de.kuschku.util.observables.callbacks.wrappers.AdapterUICallbackWrapper;
@@ -29,18 +30,14 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> {
     @NonNull
     private final UICallback callback;
     @NonNull
-    private IObservableList<UICallback, Message> messageList = new ObservableComparableSortedList<>(Message.class);
+    private IObservableList<UICallback, Message> messageList = emptyList();
 
-    public MessageAdapter(@NonNull Context ctx, @Nullable AutoScroller scroller) {
+    public MessageAdapter(@NonNull Context ctx, @NonNull AppContext context, @Nullable AutoScroller scroller) {
         this.inflater = LayoutInflater.from(ctx);
-        this.renderer = new ChatMessageRenderer(ctx);
+        this.renderer = new ChatMessageRenderer(ctx, context);
         this.callback = new AdapterUICallbackWrapper(this, scroller);
     }
 
-    public void setClient(@NonNull Client client) {
-        renderer.setClient(client);
-    }
-
     public void setMessageList(@NonNull ObservableSortedList<Message> messageList) {
         this.messageList.removeCallback(callback);
         this.messageList = messageList;
@@ -66,4 +63,9 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> {
     public int getItemCount() {
         return messageList.size();
     }
+
+    private static ObservableSortedList<Message> emptyList = new ObservableComparableSortedList<Message>(Message.class);
+    public static ObservableSortedList<Message> emptyList() {
+        return emptyList;
+    }
 }
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
new file mode 100644
index 000000000..320a41bb2
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java
@@ -0,0 +1,144 @@
+package de.kuschku.quasseldroid_ng.ui.chat.drawer;
+
+import android.graphics.Color;
+import android.view.View;
+import android.widget.TextView;
+
+import com.mikepenz.materialdrawer.holder.BadgeStyle;
+import com.mikepenz.materialdrawer.holder.ColorHolder;
+import com.mikepenz.materialdrawer.holder.ImageHolder;
+import com.mikepenz.materialdrawer.holder.StringHolder;
+import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+
+import de.kuschku.libquassel.localtypes.Buffer;
+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.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
+import de.kuschku.util.observables.IObservable;
+import de.kuschku.util.observables.callbacks.GeneralCallback;
+import de.kuschku.util.observables.callbacks.wrappers.GeneralCallbackWrapper;
+import de.kuschku.util.observables.callbacks.wrappers.GeneralUICallbackWrapper;
+import de.kuschku.util.observables.lists.ObservableComparableSortedList;
+import de.kuschku.util.ui.MessageUtil;
+
+public class BufferItem extends SecondaryDrawerItem implements IObservable<GeneralCallback>, GeneralCallback {
+    private final Buffer buffer;
+    private final AppContext context;
+    private final ObservableComparableSortedList<Message> notifications;
+
+    private GeneralCallbackWrapper callback = new GeneralCallbackWrapper();
+
+    public BufferItem(Buffer buffer, AppContext context) {
+        this.buffer = buffer;
+        this.context = context;
+        notifications = context.getClient().getNotificationManager().getNotifications(buffer.getInfo().id);
+        notifications.addCallback(new GeneralUICallbackWrapper() {
+            @Override
+            public void notifyChanged() {
+                BufferItem.this.notifyChanged();
+            }
+        });
+    }
+
+    public void notifyChanged() {
+        callback.notifyChanged();
+    }
+
+    @Override
+    public StringHolder getBadge() {
+        return new StringHolder(String.valueOf(notifications.size()));
+    }
+
+    @Override
+    public BadgeStyle getBadgeStyle() {
+        if (notifications.isEmpty()) {
+            return new BadgeStyle();
+        } else {
+            return new BadgeStyle().withTextColor(Color.WHITE).withColorRes(R.color.md_red_700);
+        }
+    }
+
+    @Override
+    public StringHolder getDescription() {
+        if (buffer instanceof QueryBuffer) {
+            QueryBuffer queryBuffer = (QueryBuffer) buffer;
+            if (queryBuffer.getUser() != null)
+                return new StringHolder(queryBuffer.getUser().getRealName());
+        } else if (buffer instanceof StatusBuffer) {
+
+        } else if (buffer instanceof ChannelBuffer) {
+            ChannelBuffer channelBuffer = (ChannelBuffer) buffer;
+            if (channelBuffer.getChannel() != null)
+                return new StringHolder(channelBuffer.getChannel().getTopic());
+        }
+        return super.getDescription();
+    }
+
+    @Override
+    public StringHolder getName() {
+        return new StringHolder(buffer.getName());
+    }
+
+    @Override
+    public ImageHolder getIcon() {
+        if (buffer instanceof ChannelBuffer) {
+            if (buffer.isActive()) {
+                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()) {
+                return new ImageHolder(R.drawable.ic_status);
+            } else {
+                return new ImageHolder(R.drawable.ic_status_offline);
+            }
+        } else {
+            if (buffer.isActive()) {
+                return new ImageHolder(R.drawable.ic_status);
+            } else {
+                return new ImageHolder(R.drawable.ic_status_offline);
+            }
+        }
+    }
+
+    @Override
+    public ColorHolder getIconColor() {
+        return super.getIconColor();
+    }
+
+    @Override
+    public void addCallback(GeneralCallback callback) {
+        this.callback.addCallback(callback);
+    }
+
+    @Override
+    public void removeCallback(GeneralCallback callback) {
+        this.callback.removeCallback(callback);
+    }
+
+    public Buffer getBuffer() {
+        return buffer;
+    }
+
+    @Override
+    public long getIdentifier() {
+        return buffer.getInfo().id;
+    }
+
+    @Override
+    public void onPostBindView(IDrawerItem drawerItem, View view) {
+        super.onPostBindView(drawerItem, view);
+
+        if (getDescription() != null && getDescription().getText() != null)
+        ((TextView) view.findViewById(R.id.material_drawer_description)).setText(MessageUtil.parseStyleCodes(
+                context.getThemeUtil(),
+                getDescription().getText(),
+                context.getSettings().mircColors.or(true)
+        ));
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java
deleted file mode 100644
index 5b435df17..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferWrapper.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package de.kuschku.quasseldroid_ng.ui.chat.drawer;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.UiThread;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.bignerdranch.expandablerecyclerview.ViewHolder.ChildViewHolder;
-import com.mikepenz.materialdrawer.holder.StringHolder;
-import com.mikepenz.materialdrawer.model.interfaces.Nameable;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import de.kuschku.libquassel.localtypes.Buffer;
-import de.kuschku.quasseldroid_ng.R;
-import de.kuschku.util.ui.Bindable;
-
-@UiThread
-public class BufferWrapper implements Nameable<BufferWrapper> {
-    @NonNull
-    private final Buffer buffer;
-
-    public BufferWrapper(@NonNull Buffer buffer) {
-        this.buffer = buffer;
-    }
-
-    @NonNull
-    @Override
-    public BufferWrapper withName(String name) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public BufferWrapper withName(int nameRes) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public BufferWrapper withName(StringHolder name) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public StringHolder getName() {
-        return new StringHolder(buffer.getName());
-    }
-
-    @NonNull
-    public Buffer getBuffer() {
-        return buffer;
-    }
-
-    public static class ViewHolder extends ChildViewHolder implements Bindable<BufferWrapper> {
-        @Bind(R.id.material_drawer_icon)
-        ImageView materialDrawerIcon;
-        @Bind(R.id.material_drawer_name)
-        TextView materialDrawerName;
-        @Bind(R.id.material_drawer_description)
-        TextView materialDrawerDescription;
-        @Bind(R.id.material_drawer_badge_container)
-        View materialDrawerBadgeContainer;
-        @Bind(R.id.material_drawer_badge)
-        TextView materialDrawerBadge;
-
-        public ViewHolder(@NonNull View itemView) {
-            super(itemView);
-            ButterKnife.bind(this, itemView);
-        }
-
-        public void bind(@NonNull BufferWrapper wrapper) {
-            materialDrawerName.setText(wrapper.getName().getText());
-        }
-    }
-}
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
new file mode 100644
index 000000000..7c03161fb
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java
@@ -0,0 +1,173 @@
+package de.kuschku.quasseldroid_ng.ui.chat.drawer;
+
+import android.content.Context;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.widget.TextView;
+
+import com.mikepenz.fastadapter.utils.ViewHolderFactory;
+import com.mikepenz.materialdrawer.holder.ColorHolder;
+import com.mikepenz.materialdrawer.holder.StringHolder;
+import com.mikepenz.materialdrawer.model.BaseViewHolder;
+import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
+import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.localtypes.Buffer;
+import de.kuschku.libquassel.syncables.types.BufferViewConfig;
+import de.kuschku.libquassel.syncables.types.Network;
+import de.kuschku.quasseldroid_ng.ui.AppContext;
+import de.kuschku.util.AndroidAssert;
+import de.kuschku.util.observables.IObservable;
+import de.kuschku.util.observables.callbacks.ElementCallback;
+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.util.AndroidAssert.*;
+
+public class NetworkItem extends PrimaryDrawerItem implements IObservable<GeneralCallback>, GeneralCallback {
+    private final AppContext context;
+    private final Network network;
+    private final BufferViewConfig config;
+    private final ObservableSortedList<BufferItem> buffers = new ObservableSortedList<>(BufferItem.class, new AlphabeticalComparator());
+    private final SparseArray<BufferItem> bufferIds = new SparseArray<>();
+
+    private final GeneralCallbackWrapper callback = new GeneralCallbackWrapper();
+
+    public NetworkItem(AppContext context, Network network, BufferViewConfig config) {
+        this.context = context;
+        this.network = network;
+        this.config = config;
+
+        for (Integer bufferId : this.config.getBufferList()) {
+            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>() {
+            @Override
+            public void notifyItemInserted(Integer element) {
+                if (network.getBuffers().contains(element)) {
+                    if (bufferIds.get(element) == null) {
+                        Buffer buffer = context.getClient().getBuffer(element);
+
+                        BufferItem bufferItem = new BufferItem(buffer, context);
+                        buffers.add(bufferItem);
+                        bufferItem.addCallback(NetworkItem.this);
+                        bufferIds.put(element, bufferItem);
+                        notifyChanged();
+                    }
+                }
+            }
+
+            @Override
+            public void notifyItemRemoved(Integer element) {
+                if (bufferIds.get(element) != null) {
+                    bufferIds.remove(element);
+                    notifyChanged();
+                }
+            }
+
+            @Override
+            public void notifyItemChanged(Integer element) {
+                if (bufferIds.get(element) != null) {
+                    notifyChanged();
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean isIconTinted() {
+        return super.isIconTinted();
+    }
+
+    @Override
+    public ColorHolder getIconColor() {
+        return super.getIconColor();
+    }
+
+    @Override
+    public StringHolder getDescription() {
+        return new StringHolder(String.valueOf(network.getLatency()));
+    }
+
+    @Override
+    public StringHolder getName() {
+        return new StringHolder(network.getNetworkName());
+    }
+
+    @Override
+    public List<IDrawerItem> getSubItems() {
+        ArrayList<IDrawerItem> items = new ArrayList<>();
+        for (IDrawerItem item : buffers) {
+            items.add(item);
+        }
+        return items;
+    }
+
+    @Override
+    public void notifyChanged() {
+        this.callback.notifyChanged();
+    }
+
+    @Override
+    public void addCallback(GeneralCallback callback) {
+        this.callback.addCallback(callback);
+    }
+
+    @Override
+    public void removeCallback(GeneralCallback callback) {
+        this.callback.removeCallback(callback);
+    }
+
+    public Network getNetwork() {
+        return network;
+    }
+
+    @Override
+    public long getIdentifier() {
+        return network.getNetworkId();
+    }
+
+    class AlphabeticalComparator implements ObservableSortedList.ItemComparator<BufferItem> {
+        @Override
+        public int compare(BufferItem o1, BufferItem o2) {
+            return o1.getName().getText().compareTo(o2.getName().getText());
+        }
+
+        @Override
+        public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) {
+            return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id;
+        }
+
+        @Override
+        public boolean areItemsTheSame(BufferItem item1, BufferItem item2) {
+            return item1 == item2;
+        }
+    }
+    
+    class NoneComparator implements ObservableSortedList.ItemComparator<BufferItem> {
+        @Override
+        public int compare(BufferItem o1, BufferItem o2) {
+            return o1.getBuffer().getInfo().id - o2.getBuffer().getInfo().id;
+        }
+
+        @Override
+        public boolean areContentsTheSame(BufferItem oldItem, BufferItem newItem) {
+            return oldItem.getBuffer().getInfo().id == newItem.getBuffer().getInfo().id;
+        }
+
+        @Override
+        public boolean areItemsTheSame(BufferItem item1, BufferItem item2) {
+            return item1 == item2;
+        }
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java
deleted file mode 100644
index 3a9beab7b..000000000
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkWrapper.java
+++ /dev/null
@@ -1,275 +0,0 @@
-package de.kuschku.quasseldroid_ng.ui.chat.drawer;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.UiThread;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.bignerdranch.expandablerecyclerview.Model.ParentListItem;
-import com.bignerdranch.expandablerecyclerview.ViewHolder.ParentViewHolder;
-import com.mikepenz.materialdrawer.holder.StringHolder;
-import com.mikepenz.materialdrawer.model.interfaces.Nameable;
-
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import de.kuschku.libquassel.Client;
-import de.kuschku.libquassel.localtypes.Buffer;
-import de.kuschku.libquassel.message.Message;
-import de.kuschku.libquassel.syncables.types.BufferViewConfig;
-import de.kuschku.libquassel.syncables.types.Network;
-import de.kuschku.quasseldroid_ng.R;
-import de.kuschku.util.observables.ContentComparable;
-import de.kuschku.util.observables.callbacks.ElementCallback;
-import de.kuschku.util.observables.callbacks.UIChildCallback;
-import de.kuschku.util.observables.callbacks.wrappers.ChildUICallbackWrapper;
-import de.kuschku.util.observables.callbacks.wrappers.MultiUIChildCallback;
-import de.kuschku.util.observables.lists.ObservableSortedList;
-import de.kuschku.util.ui.Bindable;
-
-@UiThread
-public class NetworkWrapper implements ParentListItem, Nameable<NetworkWrapper>, ContentComparable<NetworkWrapper> {
-    @NonNull
-    private final MultiUIChildCallback callback = MultiUIChildCallback.of();
-
-    @NonNull
-    private final Network network;
-    @NonNull
-    private final Client client;
-    @NonNull
-    private final ChildUICallbackWrapper wrapper = new ChildUICallbackWrapper(callback);
-    @NonNull
-    private ObservableSortedList<BufferWrapper> buffers = new ObservableSortedList<>(BufferWrapper.class, new BufferWrapperSorterWrapper(getSorter(SortMode.NONE)));
-    private int groupId;
-
-    public NetworkWrapper(@NonNull Network network, @NonNull Client client, @NonNull BufferViewConfig bufferViewConfig, @NonNull SortMode sortMode, int groupId) {
-        this.network = network;
-        this.client = client;
-        setSortMode(sortMode);
-        bufferViewConfig.getBufferList().addCallback(new ElementCallback<Integer>() {
-            @Override
-            public void notifyItemInserted(Integer element) {
-                Buffer buffer = client.getBuffer(element);
-                if (buffer.getInfo().networkId != network.getNetworkId()) return;
-
-                buffers.add(new BufferWrapper(buffer));
-            }
-
-            @Override
-            public void notifyItemRemoved(Integer element) {
-                Buffer buffer = client.getBuffer(element);
-                if (buffer.getInfo().networkId != network.getNetworkId()) return;
-
-                buffers.remove(new BufferWrapper(buffer));
-            }
-
-            @Override
-            public void notifyItemChanged(Integer element) {
-                Buffer buffer = client.getBuffer(element);
-                if (buffer.getInfo().networkId != network.getNetworkId()) return;
-
-                callback.notifyChildItemChanged(groupId, buffers.indexOf(new BufferWrapper(buffer)));
-            }
-        });
-        buffers.addCallback(wrapper);
-        wrapper.setGroupPosition(groupId);
-    }
-
-    public void addCallback(@NonNull UIChildCallback callback) {
-        this.callback.addCallback(callback);
-    }
-
-    public void removeCallback(@NonNull UIChildCallback callback) {
-        this.callback.removeCallback(callback);
-    }
-
-    public void setGroupId(int groupId) {
-        this.groupId = groupId;
-        this.wrapper.setGroupPosition(groupId);
-    }
-
-    public void setSortMode(@NonNull SortMode sortMode) {
-        ObservableSortedList.ItemComparator<BufferWrapper> sorter = new BufferWrapperSorterWrapper(getSorter(sortMode));
-        ObservableSortedList<BufferWrapper> newBuffers = new ObservableSortedList<>(BufferWrapper.class, sorter);
-        if (buffers.size() > newBuffers.size()) {
-            for (int i = newBuffers.size(); i < buffers.size(); i++) {
-                callback.notifyChildItemRemoved(groupId, i);
-            }
-        } else if (newBuffers.size() > buffers.size()) {
-            for (int i = buffers.size(); i < newBuffers.size(); i++) {
-                callback.notifyChildItemInserted(groupId, i);
-            }
-        }
-
-        int commonElementCount = Math.min(buffers.size(), newBuffers.size());
-        for (int i = 0; i < commonElementCount; i++) {
-            callback.notifyChildItemChanged(groupId, i);
-        }
-        buffers = newBuffers;
-    }
-
-    @NonNull
-    private ObservableSortedList.ItemComparator<Buffer> getSorter(@NonNull SortMode sortMode) {
-        switch (sortMode) {
-            case ALPHABETICAL:
-                return new AlphabeticalBufferSorter();
-            case RECENT_ACTIVITY:
-                return new RecentActivityBufferSorter();
-            case LAST_SEEN:
-                return new LastSeenBufferSorter();
-            default:
-                return new IdBufferSorter();
-        }
-    }
-
-    @NonNull
-    @Override
-    public List<?> getChildItemList() {
-        return buffers;
-    }
-
-    @Override
-    public boolean isInitiallyExpanded() {
-        return network.isConnected();
-    }
-
-    @NonNull
-    @Override
-    public NetworkWrapper withName(String name) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public NetworkWrapper withName(int nameRes) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public NetworkWrapper withName(StringHolder name) {
-        return this;
-    }
-
-    @NonNull
-    @Override
-    public StringHolder getName() {
-        return new StringHolder(network.getNetworkName());
-    }
-
-    @Override
-    public boolean equalsContent(@NonNull NetworkWrapper other) {
-        return network.equalsContent(other.network);
-    }
-
-    @Override
-    public int compareTo(@NonNull NetworkWrapper another) {
-        return network.compareTo(another.network);
-    }
-
-    public enum SortMode {
-        NONE,
-        ALPHABETICAL,
-        RECENT_ACTIVITY,
-        LAST_SEEN
-    }
-
-    public static class ViewHolder extends ParentViewHolder implements Bindable<NetworkWrapper> {
-        @Bind(R.id.material_drawer_icon)
-        ImageView materialDrawerIcon;
-        @Bind(R.id.material_drawer_name)
-        TextView materialDrawerName;
-        @Bind(R.id.material_drawer_description)
-        TextView materialDrawerDescription;
-        @Bind(R.id.material_drawer_badge_container)
-        View materialDrawerBadgeContainer;
-        @Bind(R.id.material_drawer_badge)
-        TextView materialDrawerBadge;
-
-        public ViewHolder(@NonNull View itemView) {
-            super(itemView);
-            ButterKnife.bind(this, itemView);
-        }
-
-        public void bind(@NonNull NetworkWrapper wrapper) {
-            materialDrawerName.setText(wrapper.getName().getText());
-        }
-    }
-
-    private static class BufferWrapperSorterWrapper implements ObservableSortedList.ItemComparator<BufferWrapper> {
-        @NonNull
-        private final ObservableSortedList.ItemComparator<Buffer> wrapped;
-
-        public BufferWrapperSorterWrapper(@NonNull ObservableSortedList.ItemComparator<Buffer> wrapped) {
-            this.wrapped = wrapped;
-        }
-
-        @Override
-        public int compare(@NonNull BufferWrapper lhs, @NonNull BufferWrapper rhs) {
-            return wrapped.compare(lhs.getBuffer(), rhs.getBuffer());
-        }
-
-        @Override
-        public boolean areContentsTheSame(@NonNull BufferWrapper oldItem, @NonNull BufferWrapper newItem) {
-            return wrapped.areContentsTheSame(oldItem.getBuffer(), newItem.getBuffer());
-        }
-
-        @Override
-        public boolean areItemsTheSame(@NonNull BufferWrapper item1, @NonNull BufferWrapper item2) {
-            return areContentsTheSame(item1, item2);
-        }
-    }
-
-    private class IdBufferSorter extends BasicBufferSorter {
-        @Override
-        public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) {
-            return lhs.getInfo().id - rhs.getInfo().id;
-        }
-    }
-
-    private class AlphabeticalBufferSorter extends BasicBufferSorter {
-        @Override
-        public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) {
-            return lhs.getName().compareTo(rhs.getName());
-        }
-    }
-
-    private class RecentActivityBufferSorter extends BasicBufferSorter {
-        @Override
-        public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) {
-            return getLastMessageId(lhs) - getLastMessageId(rhs);
-        }
-
-        private int getLastMessageId(@NonNull Buffer buffer) {
-            int bufferId = buffer.getInfo().id;
-            Message message = client.getBacklogManager().get(bufferId).last();
-            return message == null ? -1 : message.messageId;
-        }
-    }
-
-    private class LastSeenBufferSorter extends BasicBufferSorter {
-        @Override
-        public int compare(@NonNull Buffer lhs, @NonNull Buffer rhs) {
-            return getLastSeenMessageId(lhs) - getLastSeenMessageId(rhs);
-        }
-
-        private int getLastSeenMessageId(@NonNull Buffer buffer) {
-            int bufferId = buffer.getInfo().id;
-            return client.getBufferSyncer().getLastSeenMsg(bufferId);
-        }
-    }
-
-    private abstract class BasicBufferSorter implements ObservableSortedList.ItemComparator<Buffer> {
-        @Override
-        public boolean areContentsTheSame(@NonNull Buffer oldItem, @NonNull Buffer newItem) {
-            return oldItem.getInfo().id == newItem.getInfo().id;
-        }
-
-        @Override
-        public boolean areItemsTheSame(@NonNull Buffer item1, @NonNull Buffer item2) {
-            return areContentsTheSame(item1, item2);
-        }
-    }
-}
diff --git a/app/src/main/java/de/kuschku/util/AndroidAssert.java b/app/src/main/java/de/kuschku/util/AndroidAssert.java
index 69937703a..f9d7bb0f8 100644
--- a/app/src/main/java/de/kuschku/util/AndroidAssert.java
+++ b/app/src/main/java/de/kuschku/util/AndroidAssert.java
@@ -1,10 +1,11 @@
 package de.kuschku.util;
 
 import android.support.annotation.Nullable;
-import android.support.design.BuildConfig;
 
 import junit.framework.Assert;
 
+import de.kuschku.quasseldroid_ng.BuildConfig;
+
 /**
  * Class to provide the Assert functionality of JUnit at runtime for debug builds
  */
@@ -13,6 +14,12 @@ public class AndroidAssert extends Assert {
     private AndroidAssert() {
     }
 
+    public static void assertTrue(Throwable message, boolean condition) {
+        if (BuildConfig.DEBUG) {
+            if (!condition) fail(message);
+        }
+    }
+
     public static void assertTrue(String message, boolean condition) {
         if (BuildConfig.DEBUG) {
             if (!condition) fail(message);
@@ -25,13 +32,9 @@ public class AndroidAssert extends Assert {
         }
     }
 
-    public static void assertTrueOrNotNull(boolean condition, @Nullable Object... nonNull) {
+    public static void assertFalse(Throwable message, boolean condition) {
         if (BuildConfig.DEBUG) {
-            if (condition) return;
-            if (nonNull == null) fail();
-            for (Object o : nonNull) {
-                if (o == null) fail();
-            }
+            if (condition) fail(message);
         }
     }
 
@@ -47,6 +50,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void fail(Throwable message) {
+        if (BuildConfig.DEBUG) {
+            throw new RuntimeException(message);
+        }
+    }
+
     public static void fail(String message) {
         if (BuildConfig.DEBUG) {
             throw new AssertionError(message);
@@ -59,6 +68,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, Object expected, Object actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, Object expected, Object actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -71,6 +86,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, String expected, String actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, String expected, String actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -83,6 +104,13 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, double expected, double actual, double delta) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
+
     public static void assertEquals(String message, double expected, double actual, double delta) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -95,6 +123,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, float expected, float actual, float delta) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, float expected, float actual, float delta) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -107,6 +141,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, long expected, long actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, long expected, long actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -119,6 +159,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, boolean expected, boolean actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, boolean expected, boolean actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -131,6 +177,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, byte expected, byte actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, byte expected, byte actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -143,6 +195,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, char expected, char actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, char expected, char actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -155,6 +213,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, short expected, short actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, short expected, short actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -167,6 +231,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertEquals(Throwable message, int expected, int actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, Objects.equals(expected, actual));
+        }
+    }
+
     public static void assertEquals(String message, int expected, int actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, Objects.equals(expected, actual));
@@ -191,6 +261,12 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertNotNull(Throwable message, @Nullable Object object) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, object != null);
+        }
+    }
+
     public static void assertNull(@Nullable Object object) {
         if (BuildConfig.DEBUG) {
             assertTrue(object == null);
@@ -203,18 +279,37 @@ public class AndroidAssert extends Assert {
         }
     }
 
+    public static void assertNull(Throwable message, @Nullable Object object) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, object == null);
+        }
+    }
+
+
     public static void assertSame(String message, Object expected, Object actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, expected == actual);
         }
     }
 
+    public static void assertSame(Throwable message, Object expected, Object actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, expected == actual);
+        }
+    }
+
     public static void assertSame(Object expected, Object actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(expected == actual);
         }
     }
 
+    public static void assertNotSame(Throwable message, Object expected, Object actual) {
+        if (BuildConfig.DEBUG) {
+            assertTrue(message, expected != actual);
+        }
+    }
+
     public static void assertNotSame(String message, Object expected, Object actual) {
         if (BuildConfig.DEBUG) {
             assertTrue(message, expected != actual);
@@ -244,4 +339,16 @@ public class AndroidAssert extends Assert {
             assertEquals(message, expected, actual);
         }
     }
+
+    public static void assertEquals(int... elements) {
+        if (BuildConfig.DEBUG) {
+            if (elements.length > 0) {
+                int first = elements[0];
+                for (int i = 1; i < elements.length; i++) {
+                    if (first != elements[i])
+                        fail();
+                }
+            }
+        }
+    }
 }
diff --git a/app/src/main/java/de/kuschku/util/DrawerUtils.java b/app/src/main/java/de/kuschku/util/DrawerUtils.java
deleted file mode 100644
index efed3bd9e..000000000
--- a/app/src/main/java/de/kuschku/util/DrawerUtils.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.kuschku.util;
-
-import android.app.Activity;
-import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.StringRes;
-import android.support.annotation.UiThread;
-import android.support.v4.widget.DrawerLayout;
-import android.support.v7.app.ActionBarDrawerToggle;
-import android.support.v7.widget.Toolbar;
-
-import de.kuschku.quasseldroid_ng.R;
-import de.kuschku.util.ui.MaterialActionBarDrawerToggle;
-
-public class DrawerUtils {
-    @UiThread
-    public static void initDrawer(@NonNull Activity actvity, @NonNull DrawerLayout layout, Toolbar toolbar, @StringRes int open_res, @StringRes int close_res) {
-        ActionBarDrawerToggle actionBarDrawerToggle = new MaterialActionBarDrawerToggle(actvity, layout, toolbar, open_res, close_res);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            actvity.getWindow().setStatusBarColor(actvity.getResources().getColor(android.R.color.transparent));
-            layout.setStatusBarBackground(R.color.colorPrimaryDark);
-        }
-        layout.setDrawerListener(actionBarDrawerToggle);
-        actionBarDrawerToggle.syncState();
-    }
-}
diff --git a/app/src/main/java/de/kuschku/util/ReflectionUtils.java b/app/src/main/java/de/kuschku/util/ReflectionUtils.java
index ff536e629..9dc4ed733 100644
--- a/app/src/main/java/de/kuschku/util/ReflectionUtils.java
+++ b/app/src/main/java/de/kuschku/util/ReflectionUtils.java
@@ -2,6 +2,7 @@ package de.kuschku.util;
 
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.util.Log;
 
 import com.google.common.primitives.Primitives;
 
@@ -32,7 +33,8 @@ public class ReflectionUtils {
 
         Class<?>[] classes = new Class<?>[argv.length];
         for (int i = 0; i < argv.length; i++) {
-            classes[i] = argv[i].getClass();
+            if (argv[i] == null) classes[i] = null;
+            else classes[i] = argv[i].getClass();
         }
         Method m = getMethodFromSignature(name, o.getClass(), classes);
         if (m == null)
@@ -41,6 +43,7 @@ public class ReflectionUtils {
         try {
             m.invoke(o, argv);
         } catch (Exception e) {
+            Log.e("DEBUG", m.toString());
             throw new SyncInvocationException(e, String.format("Error invoking %s::%s with arguments %s and classes %s", o.getClass().getSimpleName(), name, Arrays.toString(argv), Arrays.toString(classes)));
         }
     }
@@ -68,6 +71,10 @@ public class ReflectionUtils {
                 for (int i = 0; i < parameterTypes.length; i++) {
                     Class<?> mParam = m.getParameterTypes()[i];
                     Class<?> vParam = parameterTypes[i];
+
+                    // Can’t check type of null values, so we’ll assume it will work
+                    if (vParam == null) continue;
+
                     assertNotNull(vParam);
 
                     if (mParam.isPrimitive() && Primitives.isWrapperType(vParam))
diff --git a/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java b/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java
new file mode 100644
index 000000000..3e36cc221
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/keyboardutils/DialogKeyboardUtil.java
@@ -0,0 +1,32 @@
+package de.kuschku.util.keyboardutils;
+
+import android.content.DialogInterface;
+import android.view.KeyEvent;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+
+/**
+ * A util class that automatically handles <code>enter</code> and <code>esc</code> in dialogs
+ * properly: By calling onPositive or onNeutral
+ */
+public class DialogKeyboardUtil implements DialogInterface.OnKeyListener {
+    MaterialDialog dialog;
+
+    public DialogKeyboardUtil(MaterialDialog dialog) {
+        this.dialog = dialog;
+    }
+
+    @Override
+    public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
+            dialog.getActionButton(DialogAction.POSITIVE).callOnClick();
+            dialog.dismiss();
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_ESCAPE) {
+            dialog.dismiss();
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java b/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java
new file mode 100644
index 000000000..317a416c5
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/keyboardutils/EditTextKeyboardUtil.java
@@ -0,0 +1,62 @@
+package de.kuschku.util.keyboardutils;
+
+import android.support.v7.widget.AppCompatEditText;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+//TODO: FIND A WAY TO TEST THIS – THE EMULATOR DOESN'T WORK
+public class EditTextKeyboardUtil implements AppCompatEditText.OnKeyListener {
+    AppCompatEditText editText;
+    boolean reverse;
+
+    public EditTextKeyboardUtil(AppCompatEditText editText) {
+        this.editText = editText;
+    }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        Log.e("DEBUG", keyCode + " : " + event.toString());
+
+        if (event.isShiftPressed()) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    if (editText.getSelectionStart() == editText.getSelectionEnd())
+                        reverse = true;
+
+                    if (reverse) {
+                        int start = Math.max(0, editText.getSelectionStart() - 1);
+                        editText.setSelection(start, editText.getSelectionEnd());
+                    } else {
+                        int end = Math.min(editText.length(), editText.getSelectionEnd() - 1);
+                        editText.setSelection(end, end);
+                    }
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    if (editText.getSelectionStart() == editText.getSelectionEnd())
+                        reverse = false;
+
+                    if (reverse) {
+                        int start = Math.max(0, editText.getSelectionStart() + 1);
+                        editText.setSelection(start, editText.getSelectionEnd());
+                    } else {
+                        int end = Math.min(editText.length(), editText.getSelectionEnd() + 1);
+                        editText.setSelection(end, end);
+                    }
+                    break;
+            }
+        } else {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_LEFT:
+                    int start = Math.max(0, editText.getSelectionStart() - 1);
+                    editText.setSelection(start, start);
+                    break;
+                case KeyEvent.KEYCODE_DPAD_RIGHT:
+                    int end = Math.min(editText.length(), editText.getSelectionEnd() + 1);
+                    editText.setSelection(end, end);
+                    break;
+            }
+        }
+        return false;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/niohelpers/Helper.java b/app/src/main/java/de/kuschku/util/niohelpers/Helper.java
new file mode 100644
index 000000000..30a6250a8
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/niohelpers/Helper.java
@@ -0,0 +1,32 @@
+package de.kuschku.util.niohelpers;
+
+import android.util.Log;
+
+public class Helper {
+    // Making default constructor invisible
+    private Helper() {
+
+    }
+
+    public static void printHexDump(byte[] data) {
+        Log.e("HexDump", "Hexdump following: ");
+        String bytes = "";
+        String text = "";
+        int i;
+        for (i = 0; i < data.length; i++) {
+            bytes += String.format("%02x ", data[i]);
+            text += encodeChar(data[1]);
+            if (i > 0 && (i + 1) % 8 == 0) {
+                Log.e("HexDump", String.format("%08x ", i - 7) + bytes + text);
+                bytes = "";
+                text = "";
+            }
+        }
+        Log.e("HexDump", String.format("%08x ", i - 7) + bytes + text);
+    }
+
+    private static char encodeChar(byte data) {
+        if (data < 127 && data > 32) return (char) data;
+        else return '.';
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java b/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java
new file mode 100644
index 000000000..1cf2fa624
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/observables/callbacks/GeneralCallback.java
@@ -0,0 +1,5 @@
+package de.kuschku.util.observables.callbacks;
+
+public interface GeneralCallback {
+    void notifyChanged();
+}
diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java
new file mode 100644
index 000000000..1cb1f28d6
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralCallbackWrapper.java
@@ -0,0 +1,28 @@
+package de.kuschku.util.observables.callbacks.wrappers;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import de.kuschku.util.observables.IObservable;
+import de.kuschku.util.observables.callbacks.GeneralCallback;
+
+public class GeneralCallbackWrapper implements IObservable<GeneralCallback>, GeneralCallback {
+    Set<GeneralCallback> callbacks = new HashSet<>();
+
+    @Override
+    public void notifyChanged() {
+        for (GeneralCallback callback : callbacks) {
+            callback.notifyChanged();
+        }
+    }
+
+    @Override
+    public void addCallback(GeneralCallback callback) {
+        callbacks.add(callback);
+    }
+
+    @Override
+    public void removeCallback(GeneralCallback callback) {
+        callbacks.remove(callback);
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java
new file mode 100644
index 000000000..fd1982fe7
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/observables/callbacks/wrappers/GeneralUICallbackWrapper.java
@@ -0,0 +1,42 @@
+package de.kuschku.util.observables.callbacks.wrappers;
+
+import de.kuschku.util.observables.callbacks.UICallback;
+
+public abstract class GeneralUICallbackWrapper implements UICallback {
+    public abstract void notifyChanged();
+
+    @Override
+    public void notifyItemInserted(int position) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemChanged(int position) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemRemoved(int position) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemMoved(int from, int to) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemRangeInserted(int position, int count) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemRangeChanged(int position, int count) {
+        notifyChanged();
+    }
+
+    @Override
+    public void notifyItemRangeRemoved(int position, int count) {
+        notifyChanged();
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java
new file mode 100644
index 000000000..6485c2fd2
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java
@@ -0,0 +1,83 @@
+package de.kuschku.util.observables.lists;
+
+import android.support.annotation.NonNull;
+
+import de.kuschku.util.observables.IObservable;
+import de.kuschku.util.observables.callbacks.UICallback;
+import de.kuschku.util.observables.callbacks.UIChildCallback;
+import de.kuschku.util.observables.callbacks.UIChildParentCallback;
+import de.kuschku.util.observables.callbacks.UIParentCallback;
+import de.kuschku.util.observables.callbacks.wrappers.MultiUIChildParentCallback;
+import de.kuschku.util.observables.callbacks.wrappers.ParentUICallbackWrapper;
+
+public class ChildParentObservableSortedList<T extends IObservable<UIChildCallback>> extends ObservableSortedList<T> {
+
+    private MultiUIChildParentCallback callback = MultiUIChildParentCallback.of();
+
+    public ChildParentObservableSortedList(@NonNull Class<T> cl, @NonNull ItemComparator<T> comparator) {
+        super(cl, comparator);
+        registerCallbacks();
+    }
+
+    public ChildParentObservableSortedList(@NonNull Class<T> cl, @NonNull ItemComparator<T> comparator, boolean reverse) {
+        super(cl, comparator, reverse);
+        registerCallbacks();
+    }
+
+    private void registerCallbacks() {
+        super.addCallback(new MyWrapper(callback));
+    }
+
+    public void addChildParentCallback(@NonNull UIChildParentCallback callback) {
+        this.callback.addCallback(callback);
+    }
+
+    public void removeChildParentCallback(@NonNull UIChildParentCallback callback) {
+        this.callback.removeCallback(callback);
+    }
+
+    private class MyWrapper extends ParentUICallbackWrapper {
+        public MyWrapper(@NonNull UIParentCallback wrapped) {
+            super(wrapped);
+        }
+
+        @Override
+        public void notifyItemInserted(int position) {
+            super.notifyItemInserted(position);
+            get(position).addCallback(callback);
+        }
+
+        @Override
+        public void notifyItemChanged(int position) {
+            super.notifyItemChanged(position);
+        }
+
+        @Override
+        public void notifyItemRemoved(int position) {
+            super.notifyItemRemoved(position);
+        }
+
+        @Override
+        public void notifyItemMoved(int from, int to) {
+            super.notifyItemMoved(from, to);
+        }
+
+        @Override
+        public void notifyItemRangeInserted(int position, int count) {
+            super.notifyItemRangeInserted(position, count);
+            for (int i = position; i < position + count; i++) {
+                get(position).addCallback(callback);
+            }
+        }
+
+        @Override
+        public void notifyItemRangeChanged(int position, int count) {
+            super.notifyItemRangeChanged(position, count);
+        }
+
+        @Override
+        public void notifyItemRangeRemoved(int position, int count) {
+            super.notifyItemRangeRemoved(position, count);
+        }
+    }
+}
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 97901b4ed..b6bab5cd4 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
@@ -20,7 +20,7 @@ import static de.kuschku.util.AndroidAssert.assertTrue;
 
 public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
     @NonNull
-    public final SortedList<T> list;
+    private final SortedList<T> list;
     private final boolean reverse;
 
     @NonNull
@@ -120,7 +120,7 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
 
     @Override
     public int lastIndexOf(Object object) {
-        return 0;
+        return indexOf(object);
     }
 
     @NonNull
@@ -138,17 +138,28 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
     @Nullable
     @Override
     public T remove(int location) {
-        return null;
+        T item = list.get(location);
+        list.remove(item);
+        return item;
     }
 
     @Override
     public boolean remove(Object object) {
-        return false;
+        try {
+            list.remove((T) object);
+            return true;
+        } catch (ClassCastException e) {
+            return false;
+        }
     }
 
     @Override
     public boolean removeAll(@NonNull Collection<?> collection) {
-        return false;
+        boolean result = true;
+        for (Object o : collection) {
+            result &= remove(o);
+        }
+        return result;
     }
 
     @Override
@@ -164,7 +175,7 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
 
     @Override
     public int size() {
-        return 0;
+        return list.size();
     }
 
     @NonNull
@@ -193,6 +204,10 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> {
         throw new MaterialDialog.NotImplementedException("Not implemented");
     }
 
+    public void notifyItemChanged(int position) {
+        callback.notifyItemChanged(position);
+    }
+
     public interface ItemComparator<T> {
         int compare(T o1, T o2);
 
diff --git a/app/src/main/java/de/kuschku/util/ui/MessageUtil.java b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java
new file mode 100644
index 000000000..75a7e02cd
--- /dev/null
+++ b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java
@@ -0,0 +1,167 @@
+package de.kuschku.util.ui;
+
+import android.graphics.Typeface;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+
+public class MessageUtil {
+    // Transparent in ARGB
+    private static final int COLOR_TRANSPARENT = 0x00000000;
+
+    /**
+     * Parse mIRC style codes in IrcMessage
+     */
+    public static SpannableString parseStyleCodes(ThemeUtil themeUtil, String content, boolean parse) {
+        if (!parse) {
+            return new SpannableString(content
+                    .replaceAll("\\x02","")
+                    .replaceAll("\\x0F","")
+                    .replaceAll("\\x1D","")
+                    .replaceAll("\\x1F","")
+                    .replaceAll("\\x03[0-9]{1,2}(,[0-9]{1,2})?","")
+                    .replaceAll("\\x03",""));
+        }
+
+        final char boldIndicator = 2;
+        final char normalIndicator = 15;
+        final char italicIndicator = 29;
+        final char underlineIndicator = 31;
+        final char colorIndicator = 3;
+
+        if (content.indexOf(boldIndicator) == -1
+                && content.indexOf(italicIndicator) == -1
+                && content.indexOf(underlineIndicator) == -1
+                && content.indexOf(colorIndicator) == -1)
+            return new SpannableString(content);
+
+        SpannableStringBuilder newString = new SpannableStringBuilder(content);
+
+        int start, end, endSearchOffset, startIndicatorLength, style, fg, bg;
+        while (true) {
+            content = newString.toString();
+            start = -1;
+            end = -1;
+            startIndicatorLength = 1;
+            style = 0;
+            fg = -1;
+            bg = -1;
+
+            // Colors?
+            start = content.indexOf(colorIndicator);
+
+            if (start != -1) {
+                // Note that specifying colour codes here is optional, as the same indicator will cancel existing colours
+                endSearchOffset = start + 1;
+                if (endSearchOffset < content.length()) {
+                    if (Character.isDigit(content.charAt(endSearchOffset))) {
+                        if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) {
+                            fg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 2));
+                            endSearchOffset += 2;
+                        } else {
+                            fg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 1));
+                            endSearchOffset += 1;
+                        }
+
+                        if (endSearchOffset < content.length() && content.charAt(endSearchOffset) == ',') {
+                            if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) {
+                                endSearchOffset++;
+                                if (endSearchOffset + 1 < content.length() && Character.isDigit(content.charAt(endSearchOffset + 1))) {
+                                    bg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 2));
+                                    endSearchOffset += 2;
+                                } else {
+                                    bg = Integer.parseInt(content.substring(endSearchOffset, endSearchOffset + 1));
+                                    endSearchOffset += 1;
+                                }
+                            }
+                        }
+                    }
+                }
+                startIndicatorLength = endSearchOffset - start;
+
+                end = content.indexOf(colorIndicator, endSearchOffset);
+            }
+
+            if (start == -1) {
+                start = content.indexOf(boldIndicator);
+                if (start != -1) {
+                    end = content.indexOf(boldIndicator, start + 1);
+                    style = Typeface.BOLD;
+                }
+            }
+
+            if (start == -1) {
+                start = content.indexOf(italicIndicator);
+                if (start != -1) {
+                    end = content.indexOf(italicIndicator, start + 1);
+                    style = Typeface.ITALIC;
+                }
+            }
+
+            if (start == -1) {
+                start = content.indexOf(underlineIndicator);
+                if (start != -1) {
+                    end = content.indexOf(underlineIndicator, start + 1);
+                    style = -1;
+                }
+            }
+
+            if (start == -1)
+                break;
+
+            int norm = content.indexOf(normalIndicator, start + 1);
+            if (norm != -1 && (end == -1 || norm < end))
+                end = norm;
+
+            if (end == -1)
+                end = content.length();
+
+            if (end - (start + startIndicatorLength) > 0) {
+                // Only set spans if there's any text between start & end
+                if (style == -1) {
+                    newString.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+                } else {
+                    newString.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+
+                if (fg != -1 && themeUtil.colors.mircColors[fg] != COLOR_TRANSPARENT) {
+                    newString.setSpan(new ForegroundColorSpan(themeUtil.colors.mircColors[fg]), start, end,
+                            Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+                if (bg != -1 && themeUtil.colors.mircColors[fg] != COLOR_TRANSPARENT) {
+                    newString.setSpan(new BackgroundColorSpan(themeUtil.colors.mircColors[fg]), start, end,
+                            Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+                }
+            }
+
+            // Intentionally don't remove "normal" indicators or color here, as they are multi-purpose
+            if (end < content.length() && (content.charAt(end) == boldIndicator
+                    || content.charAt(end) == italicIndicator
+                    || content.charAt(end) == underlineIndicator))
+                newString.delete(end, end + 1);
+
+            newString.delete(start, start + startIndicatorLength);
+        }
+
+        // NOW we remove the "normal" and color indicator
+        while (true) {
+            content = newString.toString();
+            int normPos = content.indexOf(normalIndicator);
+            if (normPos != -1)
+                newString.delete(normPos, normPos + 1);
+
+            int colorPos = content.indexOf(colorIndicator);
+            if (colorPos != -1)
+                newString.delete(colorPos, colorPos + 1);
+
+            if (normPos == -1 && colorPos == -1)
+                break;
+        }
+
+        return new SpannableString(newString);
+    }
+}
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
new file mode 100644
index 000000000..a0a00ed3b
--- /dev/null
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.sothree.slidinguppanel.SlidingUpPanelLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/sliding_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:gravity="bottom"
+    android:orientation="vertical"
+    app:umanoPanelHeight="?attr/actionBarSize"
+    app:umanoShadowHeight="4dp">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <include layout="@layout/toolbar" />
+
+        <include layout="@layout/content_main" />
+
+    </LinearLayout>
+
+    <include layout="@layout/slider_main" />
+
+</com.sothree.slidinguppanel.SlidingUpPanelLayout>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index caa8472e0..000000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:id="@+id/drawer_left"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:fitsSystemWindows="true"
-    tools:context=".ui.chat.ChatActivity">
-
-    <com.sothree.slidinguppanel.SlidingUpPanelLayout
-        android:id="@+id/sliding_layout"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="bottom"
-        android:orientation="vertical"
-        app:umanoPanelHeight="?attr/actionBarSize"
-        app:umanoShadowHeight="4dp">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:orientation="vertical">
-
-            <include layout="@layout/toolbar" />
-
-            <include layout="@layout/content_main" />
-
-        </LinearLayout>
-
-        <include layout="@layout/slider_main" />
-
-    </com.sothree.slidinguppanel.SlidingUpPanelLayout>
-
-    <include layout="@layout/drawer" />
-
-</android.support.v4.widget.DrawerLayout>
diff --git a/app/src/main/res/layout/core_dialog.xml b/app/src/main/res/layout/core_dialog.xml
deleted file mode 100644
index a8c5541f0..000000000
--- a/app/src/main/res/layout/core_dialog.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <EditText
-        android:id="@+id/server"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:inputType="textUri" />
-
-    <EditText
-        android:id="@+id/port"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:inputType="number" />
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/dialog_address.xml b/app/src/main/res/layout/dialog_address.xml
new file mode 100644
index 000000000..0942bf2b9
--- /dev/null
+++ b/app/src/main/res/layout/dialog_address.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <android.support.design.widget.TextInputLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/host"
+            android:hint="Hostname"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textUri" />
+    </android.support.design.widget.TextInputLayout>
+
+    <android.support.design.widget.TextInputLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/port"
+            android:hint="Port"
+            android:text="4242"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="number" />
+    </android.support.design.widget.TextInputLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/dialog_login.xml b/app/src/main/res/layout/dialog_login.xml
new file mode 100644
index 000000000..fedef624f
--- /dev/null
+++ b/app/src/main/res/layout/dialog_login.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:paddingLeft="@dimen/activity_horizontal_margin"
+    android:paddingRight="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <android.support.design.widget.TextInputLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/username"
+            android:hint="Username"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="text" />
+    </android.support.design.widget.TextInputLayout>
+
+    <android.support.design.widget.TextInputLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/password"
+            android:hint="Password"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword" />
+    </android.support.design.widget.TextInputLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/drawer.xml b/app/src/main/res/layout/drawer.xml
deleted file mode 100644
index 9361ba5af..000000000
--- a/app/src/main/res/layout/drawer.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/navigation_left"
-    android:layout_width="wrap_content"
-    android:layout_height="match_parent"
-    android:layout_gravity="start">
-
-    <LinearLayout
-        android:layout_width="320dp"
-        android:layout_height="match_parent"
-        android:orientation="vertical">
-
-        <RelativeLayout
-            android:id="@+id/navigation_header_container"
-            android:layout_width="match_parent"
-            android:layout_height="72dp"
-            android:orientation="vertical">
-
-            <android.support.v7.widget.AppCompatImageView
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:src="@drawable/bg" />
-
-            <android.support.v7.widget.Toolbar
-                android:id="@+id/buffer_view_toolbar"
-                android:layout_width="match_parent"
-                android:layout_height="72dp"
-                android:layout_alignParentBottom="true"
-                android:theme="@style/AppTheme.Light">
-
-                <android.support.v7.widget.AppCompatSpinner
-                    android:id="@+id/buffer_view_spinner"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent" />
-
-            </android.support.v7.widget.Toolbar>
-
-        </RelativeLayout>
-
-        <android.support.v7.widget.RecyclerView
-            android:layout_width="wrap_content"
-            android:layout_height="0dip"
-            android:layout_weight="1" />
-    </LinearLayout>
-
-</android.support.design.widget.NavigationView>
diff --git a/app/src/main/res/layout/login_dialog.xml b/app/src/main/res/layout/login_dialog.xml
deleted file mode 100644
index b30ba636d..000000000
--- a/app/src/main/res/layout/login_dialog.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-    <EditText
-        android:id="@+id/username"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:inputType="text" />
-
-    <EditText
-        android:id="@+id/password"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:inputType="textPassword" />
-
-</LinearLayout>
diff --git a/app/src/main/res/layout/toolbar.xml b/app/src/main/res/layout/toolbar.xml
index 67aacd354..8cbf7e23c 100644
--- a/app/src/main/res/layout/toolbar.xml
+++ b/app/src/main/res/layout/toolbar.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.AppBarLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.design.widget.AppBarLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/app/src/main/res/layout/widget_chatmessage.xml b/app/src/main/res/layout/widget_chatmessage.xml
index f114000c7..b365486e8 100644
--- a/app/src/main/res/layout/widget_chatmessage.xml
+++ b/app/src/main/res/layout/widget_chatmessage.xml
@@ -19,7 +19,6 @@
         android:layout_height="wrap_content"
         android:layout_marginEnd="@dimen/message_horizontal"
         android:layout_marginRight="@dimen/message_horizontal"
-        android:text="10:08"
         android:typeface="monospace" />
 
     <TextView
@@ -27,6 +26,5 @@
         android:layout_width="0dip"
         android:layout_height="wrap_content"
         android:layout_weight="1"
-        android:clickable="true"
-        android:text="NK33 well, the issue is I need the main engines to tilt 90 degrees for vertical takeoff and landing" />
+        android:clickable="true" />
 </LinearLayout>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 3fc5c10a5..569a42b14 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,7 +1,7 @@
 <resources>
 
     <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
+    <style name="AppTheme" parent="MaterialDrawerTheme.TranslucentStatus">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
@@ -12,7 +12,7 @@
         <item name="chatlineBackground">@color/md_dark_cards</item>
     </style>
 
-    <style name="AppTheme.Light" parent="Theme.AppCompat.Light.NoActionBar">
+    <style name="AppTheme.Light" parent="MaterialDrawerTheme.Light.DarkToolbar.TranslucentStatus">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 81bf17eb7..3c39fb034 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -85,6 +85,23 @@
         <item name="senderColorE">#b39775</item>
         <item name="senderColorF">#3176b3</item>
 
+        <item name="mircColor0">#ffffff</item>
+        <item name="mircColor1">#000000</item>
+        <item name="mircColor2">#000080</item>
+        <item name="mircColor3">#008000</item>
+        <item name="mircColor4">#ff0000</item>
+        <item name="mircColor5">#800000</item>
+        <item name="mircColor6">#800080</item>
+        <item name="mircColor7">#ffa500</item>
+        <item name="mircColor8">#ffff00</item>
+        <item name="mircColor9">#00ff00</item>
+        <item name="mircColorA">#008080</item>
+        <item name="mircColorB">#00ffff</item>
+        <item name="mircColorC">#4169e1</item>
+        <item name="mircColorD">#ff00ff</item>
+        <item name="mircColorE">#808080</item>
+        <item name="mircColorF">#c0c0c0</item>
+
         <item name="colorForeground">@color/md_light_primary_text</item>
         <item name="colorForegroundHighlight">@color/md_light_primary_text</item>
         <item name="colorForegroundSecondary">@color/md_light_secondary</item>
-- 
GitLab