From f8c63977be9fc4bd08e0db5dec2ac4a637069864 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Tue, 12 Jan 2016 03:09:18 +0100
Subject: [PATCH] Added a lot of chat view related code

---
 app/src/main/AndroidManifest.xml              |   2 +-
 .../de/kuschku/libquassel/BusProvider.java    |   3 -
 .../java/de/kuschku/libquassel/Client.java    |   4 +-
 .../de/kuschku/libquassel/CoreConnection.java |   7 +-
 .../backlogmanagers/BacklogManager.java       |   4 +-
 .../backlogmanagers/SimpleBacklogManager.java |  16 +-
 .../events/BacklogReceivedEvent.java          |   9 +
 .../libquassel/localtypes/ChannelBuffer.java  |   2 +-
 .../libquassel/localtypes/QueryBuffer.java    |   2 +-
 .../libquassel/localtypes/StatusBuffer.java   |   2 +-
 .../types => message}/Message.java            |   3 +-
 .../primitives/QMetaTypeRegistry.java         |   2 +-
 .../serializers/MessageSerializer.java        |   2 +-
 .../ClientBackgroundThread.java               |   5 +-
 .../quasseldroid_ng/QuasselService.java       |   2 +-
 .../{ => ui}/BufferDrawerItem.java            |   3 +-
 .../ui/ChatMessageRenderer.java               | 322 +++++++++++++++++
 .../{ => ui}/MainActivity.java                | 329 ++++++++++--------
 .../quasseldroid_ng/ui/MessageAdapter.java    |  50 +++
 .../quasseldroid_ng/ui/MessageViewHolder.java |  22 ++
 .../{ => ui}/NetworkDrawerItem.java           |   2 +-
 .../util/CompatibilityUtils.java              |  13 +
 .../util/DateFormatHelper.java                |  18 +
 .../quasseldroid_ng/util/IrcFormatHelper.java |  31 ++
 .../util/IrcUserUtils.java                    |  11 +-
 .../{utils => util}/ServerAddress.java        |   2 +-
 .../quasseldroid_ng/util/SpanFormatter.java   | 113 ++++++
 .../quasseldroid_ng/util/ThemeUtil.java       | 120 +++++++
 .../java/de/kuschku/util/ObservableList.java  |   8 +
 app/src/main/res/layout/activity_main.xml     |   2 +-
 app/src/main/res/layout/content_main.xml      |  12 +-
 app/src/main/res/layout/core_dialog.xml       |   6 +-
 app/src/main/res/layout/login_dialog.xml      |   6 +-
 .../main/res/layout/widget_chatmessage.xml    |  26 ++
 app/src/main/res/values/attrs.xml             |  55 ++-
 app/src/main/res/values/strings.xml           |  27 ++
 app/src/main/res/values/styles.xml            |   7 +-
 app/src/main/res/values/themes.xml            |  95 +++++
 build.gradle                                  |   2 +-
 39 files changed, 1166 insertions(+), 181 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
 rename app/src/main/java/de/kuschku/libquassel/{primitives/types => message}/Message.java (98%)
 rename app/src/main/java/de/kuschku/quasseldroid_ng/{ => ui}/BufferDrawerItem.java (92%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatMessageRenderer.java
 rename app/src/main/java/de/kuschku/quasseldroid_ng/{ => ui}/MainActivity.java (53%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageAdapter.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageViewHolder.java
 rename app/src/main/java/de/kuschku/quasseldroid_ng/{ => ui}/NetworkDrawerItem.java (96%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/CompatibilityUtils.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/DateFormatHelper.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcFormatHelper.java
 rename app/src/main/java/de/kuschku/{ => quasseldroid_ng}/util/IrcUserUtils.java (89%)
 rename app/src/main/java/de/kuschku/quasseldroid_ng/{utils => util}/ServerAddress.java (82%)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/SpanFormatter.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/util/ThemeUtil.java
 create mode 100644 app/src/main/res/layout/widget_chatmessage.xml
 create mode 100644 app/src/main/res/values/themes.xml

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0417563f7..cff36a31b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,7 @@
         <service android:name=".QuasselService" />
 
         <activity
-            android:name=".MainActivity"
+            android:name=".ui.MainActivity"
             android:label="@string/app_name"
             android:launchMode="singleTop">
             <intent-filter>
diff --git a/app/src/main/java/de/kuschku/libquassel/BusProvider.java b/app/src/main/java/de/kuschku/libquassel/BusProvider.java
index f4848ec5a..b351a039d 100644
--- a/app/src/main/java/de/kuschku/libquassel/BusProvider.java
+++ b/app/src/main/java/de/kuschku/libquassel/BusProvider.java
@@ -18,17 +18,14 @@ public class BusProvider {
     }
 
     public void handle(Object o) {
-        //Log.d("HANDLE", o.getClass().getSimpleName());
         this.handle.post(o);
     }
 
     public void dispatch(Object o) {
-        //Log.d("DISPATCH", o.getClass().getSimpleName());
         this.dispatch.post(o);
     }
 
     public void sendEvent(Object o) {
-        //Log.d("EVENT", o.getClass().getSimpleName());
         this.event.post(o);
     }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/Client.java b/app/src/main/java/de/kuschku/libquassel/Client.java
index 199c761f0..5d01acbcf 100644
--- a/app/src/main/java/de/kuschku/libquassel/Client.java
+++ b/app/src/main/java/de/kuschku/libquassel/Client.java
@@ -17,7 +17,7 @@ import de.kuschku.libquassel.localtypes.Buffers;
 import de.kuschku.libquassel.objects.types.ClientInitAck;
 import de.kuschku.libquassel.objects.types.SessionState;
 import de.kuschku.libquassel.primitives.types.BufferInfo;
-import de.kuschku.libquassel.primitives.types.Message;
+import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.libquassel.syncables.types.BufferSyncer;
 import de.kuschku.libquassel.syncables.types.BufferViewManager;
@@ -33,6 +33,8 @@ public class Client {
     private final BacklogManager backlogManager;
     private final BusProvider busProvider;
     public int lag;
+    public int openBuffer;
+    public int openBufferView;
     private ConnectionChangeEvent.Status connectionStatus;
     private ClientInitAck core;
     private SessionState state;
diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
index 1755f261e..0a4970bf0 100644
--- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
+++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java
@@ -24,7 +24,7 @@ import de.kuschku.libquassel.primitives.types.Protocol;
 import de.kuschku.libquassel.protocols.DatastreamPeer;
 import de.kuschku.libquassel.protocols.LegacyPeer;
 import de.kuschku.libquassel.protocols.RemotePeer;
-import de.kuschku.quasseldroid_ng.utils.ServerAddress;
+import de.kuschku.quasseldroid_ng.util.ServerAddress;
 import de.kuschku.util.niohelpers.WrappedChannel;
 
 import static de.kuschku.libquassel.primitives.QMetaType.Type.UInt;
@@ -62,11 +62,12 @@ public class CoreConnection {
      * This method opens a socket to the specified address and starts the connection process.
      *
      * @throws IOException
+     * @param supportsKeepAlive
      */
-    public void open() throws IOException {
+    public void open(boolean supportsKeepAlive) throws IOException {
         // Intialize socket
         socket = new Socket();
-        socket.setKeepAlive(true);
+        if (supportsKeepAlive) socket.setKeepAlive(true);
         socket.connect(new InetSocketAddress(address.host, address.port), 10000);
 
         // Wrap socket in channel for nio functions
diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java b/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java
index a3a742338..7cd7ad5b1 100644
--- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/backlogmanagers/BacklogManager.java
@@ -5,7 +5,7 @@ import android.support.v7.widget.RecyclerView;
 
 import java.util.List;
 
-import de.kuschku.libquassel.primitives.types.Message;
+import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
 import de.kuschku.util.ObservableList;
 
@@ -19,4 +19,6 @@ public abstract class BacklogManager extends SyncableObject {
     public abstract ObservableList<Message> get(int bufferId);
 
     public abstract void bind(int bufferId, @Nullable RecyclerView.Adapter adapter);
+
+    public abstract void requestMoreBacklog(int bufferId, int count);
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java b/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java
index 1b2ca5f85..df96be405 100644
--- a/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/backlogmanagers/SimpleBacklogManager.java
@@ -10,9 +10,10 @@ import java.util.List;
 
 import de.kuschku.libquassel.BusProvider;
 import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.events.BacklogReceivedEvent;
 import de.kuschku.libquassel.functions.types.InitDataFunction;
 import de.kuschku.libquassel.functions.types.SyncFunction;
-import de.kuschku.libquassel.primitives.types.Message;
+import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.util.ObservableList;
 
@@ -36,6 +37,8 @@ public class SimpleBacklogManager extends BacklogManager {
 
     public void receiveBacklog(int bufferId, int from, int to, int count, int extra, List<Message> messages) {
         get(bufferId).list.addAll(messages);
+
+        busProvider.sendEvent(new BacklogReceivedEvent(bufferId));
     }
 
     @Override
@@ -50,6 +53,17 @@ public class SimpleBacklogManager extends BacklogManager {
             get(bufferId).setCallback(new ObservableList.RecyclerViewAdapterCallback(adapter));
     }
 
+    @Override
+    public void requestMoreBacklog(int bufferId, int count) {
+        ObservableList<Message> backlog = backlogs.get(bufferId);
+        int messageId =
+                (backlog == null) ? -1 :
+                (backlog.first() == null) ? -1 :
+                backlog.first().messageId;
+
+        requestBacklog(bufferId, -1, -1, count, 0);
+    }
+
     public ObservableList<Message> get(int bufferId) {
         if (backlogs.get(bufferId) == null)
             backlogs.put(bufferId, new ObservableList<>(Message.class));
diff --git a/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
new file mode 100644
index 000000000..b28fa6cd3
--- /dev/null
+++ b/app/src/main/java/de/kuschku/libquassel/events/BacklogReceivedEvent.java
@@ -0,0 +1,9 @@
+package de.kuschku.libquassel.events;
+
+public class BacklogReceivedEvent {
+    public final int bufferId;
+
+    public BacklogReceivedEvent(int bufferId) {
+        this.bufferId = bufferId;
+    }
+}
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 0f50962d3..96d986a21 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/ChannelBuffer.java
@@ -4,7 +4,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.syncables.types.IrcChannel;
-import de.kuschku.quasseldroid_ng.BufferDrawerItem;
+import de.kuschku.quasseldroid_ng.ui.BufferDrawerItem;
 
 public class ChannelBuffer implements Buffer {
     private final BufferInfo info;
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 df5ad5268..676aa546a 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java
@@ -4,7 +4,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.syncables.types.IrcUser;
-import de.kuschku.quasseldroid_ng.BufferDrawerItem;
+import de.kuschku.quasseldroid_ng.ui.BufferDrawerItem;
 
 public class QueryBuffer implements Buffer {
     private final BufferInfo info;
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 51a08e1e8..807402540 100644
--- a/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
+++ b/app/src/main/java/de/kuschku/libquassel/localtypes/StatusBuffer.java
@@ -4,7 +4,7 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.syncables.types.Network;
-import de.kuschku.quasseldroid_ng.BufferDrawerItem;
+import de.kuschku.quasseldroid_ng.ui.BufferDrawerItem;
 
 public class StatusBuffer implements Buffer {
     private final BufferInfo info;
diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/types/Message.java b/app/src/main/java/de/kuschku/libquassel/message/Message.java
similarity index 98%
rename from app/src/main/java/de/kuschku/libquassel/primitives/types/Message.java
rename to app/src/main/java/de/kuschku/libquassel/message/Message.java
index cb68f51a8..b3a80ef6c 100644
--- a/app/src/main/java/de/kuschku/libquassel/primitives/types/Message.java
+++ b/app/src/main/java/de/kuschku/libquassel/message/Message.java
@@ -1,4 +1,4 @@
-package de.kuschku.libquassel.primitives.types;
+package de.kuschku.libquassel.message;
 
 import android.support.annotation.NonNull;
 
@@ -7,6 +7,7 @@ import org.joda.time.DateTime;
 import java.io.Serializable;
 import java.util.Comparator;
 
+import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.util.ContentComparable;
 
 public class Message implements ContentComparable<Message> {
diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/QMetaTypeRegistry.java b/app/src/main/java/de/kuschku/libquassel/primitives/QMetaTypeRegistry.java
index 7219a30b5..27c87e26d 100644
--- a/app/src/main/java/de/kuschku/libquassel/primitives/QMetaTypeRegistry.java
+++ b/app/src/main/java/de/kuschku/libquassel/primitives/QMetaTypeRegistry.java
@@ -33,7 +33,7 @@ import de.kuschku.libquassel.primitives.serializers.VariantSerializer;
 import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer;
 import de.kuschku.libquassel.primitives.serializers.VoidSerializer;
 import de.kuschku.libquassel.primitives.types.BufferInfo;
-import de.kuschku.libquassel.primitives.types.Message;
+import de.kuschku.libquassel.message.Message;
 import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.libquassel.syncables.serializers.IdentitySerializer;
 import de.kuschku.libquassel.syncables.types.Identity;
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 eb0e07d29..6dffba067 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
@@ -6,7 +6,7 @@ import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ByteChannel;
 
-import de.kuschku.libquassel.primitives.types.Message;
+import de.kuschku.libquassel.message.Message;
 
 public class MessageSerializer implements PrimitiveSerializer<Message> {
     @Override
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ClientBackgroundThread.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ClientBackgroundThread.java
index 1cd423884..981a3e435 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ClientBackgroundThread.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ClientBackgroundThread.java
@@ -8,7 +8,8 @@ import de.kuschku.libquassel.CoreConnection;
 import de.kuschku.libquassel.ProtocolHandler;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.protocols.RemotePeer;
-import de.kuschku.quasseldroid_ng.utils.ServerAddress;
+import de.kuschku.quasseldroid_ng.util.CompatibilityUtils;
+import de.kuschku.quasseldroid_ng.util.ServerAddress;
 
 public class ClientBackgroundThread implements Runnable {
     public static final ClientData CLIENT_DATA = new ClientData(
@@ -33,7 +34,7 @@ public class ClientBackgroundThread implements Runnable {
     @Override
     public void run() {
         try {
-            connection.open();
+            connection.open(!CompatibilityUtils.isChromiumDevice());
         } catch (IOException e) {
             provider.sendEvent(new GeneralErrorEvent(e));
         }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasselService.java b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasselService.java
index 49006e9c8..22dda9dda 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/QuasselService.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/QuasselService.java
@@ -6,7 +6,7 @@ import android.os.Binder;
 import android.os.IBinder;
 
 import de.kuschku.libquassel.BusProvider;
-import de.kuschku.quasseldroid_ng.utils.ServerAddress;
+import de.kuschku.quasseldroid_ng.util.ServerAddress;
 
 public class QuasselService extends Service {
     private final IBinder binder = new LocalBinder();
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/BufferDrawerItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/BufferDrawerItem.java
similarity index 92%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/BufferDrawerItem.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/BufferDrawerItem.java
index c891932d9..2df5843fe 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/BufferDrawerItem.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/BufferDrawerItem.java
@@ -1,4 +1,4 @@
-package de.kuschku.quasseldroid_ng;
+package de.kuschku.quasseldroid_ng.ui;
 
 import com.mikepenz.materialdrawer.holder.ImageHolder;
 import com.mikepenz.materialdrawer.holder.StringHolder;
@@ -6,6 +6,7 @@ import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
 
 import de.kuschku.libquassel.localtypes.Buffer;
 import de.kuschku.libquassel.localtypes.ChannelBuffer;
+import de.kuschku.quasseldroid_ng.R;
 
 public class BufferDrawerItem extends SecondaryDrawerItem {
     final Buffer buffer;
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatMessageRenderer.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatMessageRenderer.java
new file mode 100644
index 000000000..272b26960
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/ChatMessageRenderer.java
@@ -0,0 +1,322 @@
+package de.kuschku.quasseldroid_ng.ui;
+
+import android.content.Context;
+import android.text.SpannableString;
+import android.text.TextUtils;
+
+import org.joda.time.format.DateTimeFormatter;
+
+import java.util.Locale;
+
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.util.DateFormatHelper;
+import de.kuschku.quasseldroid_ng.util.IrcFormatHelper;
+import de.kuschku.quasseldroid_ng.util.IrcUserUtils;
+import de.kuschku.quasseldroid_ng.util.SpanFormatter;
+import de.kuschku.quasseldroid_ng.util.ThemeUtil;
+
+public class ChatMessageRenderer {
+    private final ThemeUtil themeUtil;
+    private final DateTimeFormatter format;
+    private final FormatStrings strings;
+    private final IrcFormatHelper helper;
+
+    private Client client;
+    private boolean fullHostmask = false;
+
+    public ChatMessageRenderer(Context ctx) {
+        this.themeUtil = new ThemeUtil(ctx);
+        this.format = DateFormatHelper.getTimeFormatter(ctx);
+        this.strings = new FormatStrings(ctx);
+        this.helper = new IrcFormatHelper(themeUtil.colors);
+    }
+
+    public void setClient(Client client) {
+        this.client = client;
+    }
+
+    private void setColors(MessageViewHolder holder, boolean highlight) {
+        if (highlight) {
+            holder.content.setTextColor(themeUtil.colors.colorForegroundHighlight);
+            holder.time.setTextColor(themeUtil.colors.colorForegroundHighlight);
+            holder.itemView.setBackgroundColor(themeUtil.colors.colorBackgroundHighlight);
+        } else {
+            holder.content.setTextColor(themeUtil.colors.colorForeground);
+            holder.time.setTextColor(themeUtil.colors.colorForegroundSecondary);
+            holder.itemView.setBackgroundColor(themeUtil.colors.transparent);
+        }
+    }
+
+    private CharSequence formatNick(String hostmask, boolean full) {
+        CharSequence formattedNick = helper.formatUserNick(IrcUserUtils.getNick(hostmask));
+        if (full) {
+            return strings.formatUsername(formattedNick, IrcUserUtils.getMask(hostmask));
+        } else {
+            return formattedNick;
+        }
+    }
+
+    private CharSequence formatNick(String hostmask) {
+        return formatNick(hostmask, fullHostmask);
+    }
+
+    public void onBindPlain(MessageViewHolder holder, Message message) {
+        holder.content.setText(
+                strings.formatPlain(
+                        formatNick(message.sender, false),
+                        helper.formatIrcMessage(message.content)
+                )
+        );
+    }
+
+    public void onBindNotice(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindAction(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindNick(MessageViewHolder holder, Message message) {
+        if (message.flags.Self)
+            holder.content.setText(strings.formatNick(
+                    formatNick(message.sender, false)
+            ));
+        else
+            holder.content.setText(strings.formatNick(
+                    formatNick(message.sender, false),
+                    helper.formatUserNick(message.content)
+            ));
+    }
+
+    public void onBindMode(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindJoin(MessageViewHolder holder, Message message) {
+        holder.content.setText(strings.formatJoin(
+                formatNick(message.sender),
+                client.getBuffer(message.bufferInfo.id).getName()
+        ));
+    }
+
+    public void onBindPart(MessageViewHolder holder, Message message) {
+        holder.content.setText(strings.formatPart(
+                formatNick(message.sender),
+                client.getBuffer(message.bufferInfo.id).getName(),
+                message.content
+        ));
+    }
+
+    public void onBindQuit(MessageViewHolder holder, Message message) {
+        holder.content.setText(strings.formatQuit(
+                formatNick(message.sender),
+                message.content
+        ));
+    }
+
+    public void onBindKick(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindKill(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindServer(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindInfo(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindError(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindDayChange(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindTopic(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindNetsplitJoin(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindNetsplitQuit(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+
+    public void onBindInvite(MessageViewHolder holder, Message message) {
+        holder.content.setText(message.toString());
+    }
+    
+    public void onBind(MessageViewHolder holder, Message message) {
+        setColors(holder, message.flags.Highlight);
+        holder.time.setText(format.print(message.time));
+        switch (message.type) {
+            case Plain:
+                onBindPlain(holder, message);
+                break;
+            case Notice:
+                onBindNotice(holder, message);
+                break;
+            case Action:
+                onBindAction(holder, message);
+                break;
+            case Nick:
+                onBindNick(holder, message);
+                break;
+            case Mode:
+                onBindMode(holder, message);
+                break;
+            case Join:
+                onBindJoin(holder, message);
+                break;
+            case Part:
+                onBindPart(holder, message);
+                break;
+            case Quit:
+                onBindQuit(holder, message);
+                break;
+            case Kick:
+                onBindKick(holder, message);
+                break;
+            case Kill:
+                onBindKill(holder, message);
+                break;
+            case Server:
+                onBindServer(holder, message);
+                break;
+            case Info:
+                onBindInfo(holder, message);
+                break;
+            case Error:
+                onBindError(holder, message);
+                break;
+            case DayChange:
+                onBindDayChange(holder, message);
+                break;
+            case Topic:
+                onBindTopic(holder, message);
+                break;
+            case NetsplitJoin:
+                onBindNetsplitJoin(holder, message);
+                break;
+            case NetsplitQuit:
+                onBindNetsplitQuit(holder, message);
+                break;
+            case Invite:
+                onBindInvite(holder, message);
+                break;
+        }
+    }
+
+    private static class FormatStrings {
+        private final String username_hostmask;
+
+        private final String message_plain;
+        private final String message_join;
+        private final String message_part;
+        private final String message_part_extra;
+        private final String message_quit;
+        private final String message_quit_extra;
+        private final String message_kill;
+        private final String message_kick;
+        private final String message_kick_extra;
+        private final String message_mode;
+        private final String message_nick_self;
+        private final String message_nick_other;
+        private final String message_daychange;
+        private final String message_action;
+
+        public FormatStrings(Context ctx) {
+            username_hostmask = ctx.getString(R.string.username_hostmask);
+
+            message_plain = ctx.getString(R.string.message_plain);
+            message_join = ctx.getString(R.string.message_join);
+            message_part = ctx.getString(R.string.message_part);
+            message_part_extra = ctx.getString(R.string.message_part_extra);
+            message_quit = ctx.getString(R.string.message_quit);
+            message_quit_extra = ctx.getString(R.string.message_quit_extra);
+            message_kill = ctx.getString(R.string.message_kill);
+            message_kick = ctx.getString(R.string.message_kick);
+            message_kick_extra = ctx.getString(R.string.message_kick_extra);
+            message_mode = ctx.getString(R.string.message_mode);
+            message_nick_self = ctx.getString(R.string.message_nick_self);
+            message_nick_other = ctx.getString(R.string.message_nick_other);
+            message_daychange = ctx.getString(R.string.message_daychange);
+            message_action = ctx.getString(R.string.message_action);
+        }
+
+        public CharSequence formatUsername(CharSequence nick, CharSequence hostmask) {
+            return SpanFormatter.format(username_hostmask, nick, hostmask);
+        }
+
+        public CharSequence formatJoin(CharSequence user, CharSequence channel) {
+            return SpanFormatter.format(message_join, user, channel);
+        }
+
+        public CharSequence formatPart(CharSequence user, CharSequence channel) {
+            return SpanFormatter.format(message_part, user, channel);
+        }
+        public CharSequence formatPart(CharSequence user, CharSequence channel, CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatPart(user, channel);
+
+            return SpanFormatter.format(message_part_extra, user, channel, reason);
+        }
+
+        public CharSequence formatQuit(CharSequence user) {
+            return SpanFormatter.format(message_quit, user);
+        }
+        public CharSequence formatQuit(CharSequence user, CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatQuit(user);
+
+            return SpanFormatter.format(message_quit_extra, user, reason);
+        }
+
+        public CharSequence formatKill(CharSequence user, CharSequence channel) {
+            return SpanFormatter.format(message_kill, user, channel);
+        }
+
+        public CharSequence formatKick(CharSequence user, CharSequence kicked) {
+            return SpanFormatter.format(message_kick, user, kicked);
+        }
+        public CharSequence formatKick(CharSequence user, CharSequence kicked, CharSequence reason) {
+            if (reason == null || reason.length() == 0) return formatKick(user, kicked);
+
+            return SpanFormatter.format(message_kick_extra, user, kicked, reason);
+        }
+
+        public CharSequence formatMode(CharSequence mode, CharSequence user) {
+            return SpanFormatter.format(message_mode, mode, user);
+        }
+
+        public CharSequence formatNick(CharSequence newNick) {
+            return SpanFormatter.format(message_nick_self, newNick);
+        }
+        public CharSequence formatNick(CharSequence oldNick, CharSequence newNick) {
+            if (newNick == null || newNick.length() == 0) return formatNick(oldNick);
+
+            return SpanFormatter.format(message_nick_other, oldNick, newNick);
+        }
+
+        public CharSequence formatDayChange(CharSequence day) {
+            return SpanFormatter.format(message_daychange, day);
+        }
+
+        public CharSequence formatAction(CharSequence user, CharSequence channel) {
+            return SpanFormatter.format(message_action, user, channel);
+        }
+
+        public CharSequence formatPlain(CharSequence nick, CharSequence message) {
+            return SpanFormatter.format(message_plain, nick, message);
+        }
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/MainActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.java
similarity index 53%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/MainActivity.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.java
index b0d5681f5..eb4bd70b8 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/MainActivity.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MainActivity.java
@@ -1,30 +1,26 @@
-package de.kuschku.quasseldroid_ng;
+package de.kuschku.quasseldroid_ng.ui;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.support.design.widget.Snackbar;
-import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.app.AlertDialog;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.widget.AppCompatImageButton;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.support.v7.widget.Toolbar;
-import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.style.ForegroundColorSpan;
 import android.util.Log;
-import android.view.LayoutInflater;
+import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.EditText;
-import android.widget.TextView;
 import android.widget.Toast;
 
 import com.google.common.collect.Sets;
@@ -39,17 +35,17 @@ import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
 import com.mikepenz.materialdrawer.model.interfaces.IProfile;
 import com.mikepenz.materialdrawer.util.KeyboardUtil;
 
-import org.joda.time.format.DateTimeFormat;
-
-import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 
 import butterknife.Bind;
 import butterknife.ButterKnife;
 import de.kuschku.libquassel.BusProvider;
+import de.kuschku.libquassel.Client;
 import de.kuschku.libquassel.IProtocolHandler;
+import de.kuschku.libquassel.events.BacklogReceivedEvent;
 import de.kuschku.libquassel.events.ConnectionChangeEvent;
 import de.kuschku.libquassel.events.GeneralErrorEvent;
 import de.kuschku.libquassel.events.StatusMessageEvent;
@@ -57,15 +53,27 @@ import de.kuschku.libquassel.exceptions.UnknownTypeException;
 import de.kuschku.libquassel.functions.types.HandshakeFunction;
 import de.kuschku.libquassel.localtypes.Buffer;
 import de.kuschku.libquassel.objects.types.ClientLogin;
-import de.kuschku.libquassel.primitives.types.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.utils.ServerAddress;
-import de.kuschku.util.IrcUserUtils;
-import de.kuschku.util.ObservableList;
+import de.kuschku.quasseldroid_ng.BufferViewManagerChangedEvent;
+import de.kuschku.quasseldroid_ng.BuildConfig;
+import de.kuschku.quasseldroid_ng.QuasselService;
+import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.util.CompatibilityUtils;
+import de.kuschku.quasseldroid_ng.util.ServerAddress;
 import de.kuschku.util.backports.Stream;
 
 public class MainActivity extends AppCompatActivity {
+    private static final String BUFFER_ID = "BUFFER_ID";
+    private static final String BUFFER_VIEW_ID = "BUFFER_VIEW_ID";
+
+    private static final String KEY_HOST = "beta_hostname";
+    private static final String KEY_PORT = "beta_port";
+    private static final String KEY_USER = "beta_username";
+    private static final String KEY_PASS = "beta_password";
+
+    SharedPreferences pref;
 
     @Bind(R.id.toolbar)
     Toolbar toolbar;
@@ -79,9 +87,12 @@ public class MainActivity extends AppCompatActivity {
     @Bind(R.id.send)
     AppCompatImageButton send;
 
+    @Bind(R.id.swipeview)
+    SwipeRefreshLayout swipeView;
+
     Drawer drawer;
     AccountHeader header;
-    MessageAdapter adapter = new MessageAdapter();
+    MessageAdapter adapter;
 
     QuasselService.LocalBinder binder;
 
@@ -90,7 +101,20 @@ public class MainActivity extends AppCompatActivity {
             if (service instanceof QuasselService.LocalBinder) {
                 MainActivity.this.binder = (QuasselService.LocalBinder) service;
                 if (binder.getBackgroundThread() != null) {
+                    handler = binder.getBackgroundThread().handler;
+
                     toolbar.setSubtitle(binder.getBackgroundThread().connection.getStatus().name());
+                    if (bufferId != -1) switchBuffer(bufferId);
+                    if (bufferViewId != -1) switchBufferView(bufferViewId);
+
+                    // Horrible hack to load bufferviews back, should use ObservableList
+                    Client client = handler == null ? null : handler.getClient();
+                    BufferViewManager bufferViewManager = client == null ? null : client.getBufferViewManager();
+                    Map<Integer, BufferViewConfig> bufferViews = bufferViewManager == null ? null : bufferViewManager.BufferViews;
+                    if (bufferViews != null)
+                    for (int id : bufferViews.keySet()) {
+                        onEventMainThread(new BufferViewManagerChangedEvent(id, BufferViewManagerChangedEvent.Action.ADD));
+                    }
                 }
             }
         }
@@ -101,11 +125,14 @@ public class MainActivity extends AppCompatActivity {
 
     private IProtocolHandler handler;
     private int bufferId;
+    private int bufferViewId;
+    private BusProvider provider;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        //setTheme(R.style.AppTheme);
-        setTheme(R.style.AppTheme_Light);
+
+        // TODO: ADD THEME SELECTION
+        setTheme(R.style.Quassel);
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
@@ -113,6 +140,9 @@ public class MainActivity extends AppCompatActivity {
 
         setSupportActionBar(toolbar);
 
+        pref = getSharedPreferences(BuildConfig.APPLICATION_ID, Context.MODE_PRIVATE);
+        adapter = new MessageAdapter(this);
+
         // This fixes a horrible bug android has where opening the keyboard doesn’t resize the layout
         KeyboardUtil keyboardUtil = new KeyboardUtil(this, findViewById(R.id.layout));
         keyboardUtil.enable();
@@ -126,34 +156,9 @@ public class MainActivity extends AppCompatActivity {
                 .withSavedInstance(savedInstanceState)
                 .withCompactStyle(true)
                 .withProfileImagesVisible(false)
+                // TODO: REWRITE THIS
                 .withOnAccountHeaderListener((view, profile, current) -> {
-                    BufferViewConfig config = handler.getClient().getBufferViewManager().BufferViews.get(profile.getIdentifier());
-                    ArrayList<IDrawerItem> items = new ArrayList<>();
-                    if (config.getNetworkId() == 0) {
-                        items.addAll(
-                                new Stream<>(handler.getClient().getNetworks())
-                                        .map(network -> new NetworkDrawerItem(network,
-                                                Sets.intersection(network.getBuffers(), new HashSet<>(
-                                                        new Stream<>(config.getBufferList())
-                                                                .map(handler.getClient()::getBuffer)
-                                                                .list()
-                                                ))))
-                                        .list()
-                        );
-                    } else {
-                        Network network = handler.getClient().getNetwork(config.getNetworkId());
-                        items.add(new NetworkDrawerItem(network,
-                                Sets.intersection(network.getBuffers(), new HashSet<>(
-                                        new Stream<>(config.getBufferList())
-                                                .map(handler.getClient()::getBuffer)
-                                                .list()
-                                ))
-                        ));
-                    }
-                    drawer.setItems(items);
-                    for (int i = 0; i < drawer.getAdapter().getItemCount(); i++) {
-                        drawer.getAdapter().open(i);
-                    }
+                    switchBufferView(profile.getIdentifier());
                     return true;
                 })
                 .build();
@@ -162,22 +167,34 @@ public class MainActivity extends AppCompatActivity {
                 .withActivity(this)
                 .withToolbar(toolbar)
                 .withAccountHeader(header)
+                // TODO: REWRITE THIS
                 .withOnDrawerItemClickListener((view, position, drawerItem) -> {
                     if (drawerItem != null) {
                         if (position == -1) {
                             binder.stopBackgroundThread();
                             View coreview = View.inflate(this, R.layout.core_dialog, null);
+                            EditText hostname = ((EditText) coreview.findViewById(R.id.server));
+                            EditText port = ((EditText) coreview.findViewById(R.id.port));
+
+                            hostname.setText(pref.getString(KEY_HOST, ""));
+                            port.setText(String.valueOf(pref.getInt(KEY_PORT, 4242)));
                             new AlertDialog.Builder(this)
                                     .setView(coreview)
                                     .setPositiveButton("Connect", (dialog, which) -> {
-                                        EditText hostname = ((EditText) coreview.findViewById(R.id.server));
-                                        EditText port = ((EditText) coreview.findViewById(R.id.port));
-                                        if (binder.getBackgroundThread() != null)
-                                            binder.getBackgroundThread().provider.event.unregister(this);
+                                        if (provider != null) provider.event.unregister(this);
                                         binder.stopBackgroundThread();
-                                        BusProvider provider = new BusProvider();
+                                        provider = new BusProvider();
                                         provider.event.register(this);
-                                        binder.startBackgroundThread(provider, new ServerAddress(hostname.getText().toString().trim(), Integer.valueOf(port.getText().toString().trim())));
+
+                                        String value_hostname = hostname.getText().toString().trim();
+                                        Integer value_port = Integer.valueOf(port.getText().toString().trim());
+
+                                        SharedPreferences.Editor edit = pref.edit();
+                                        edit.putString(KEY_HOST, value_hostname);
+                                        edit.putInt(KEY_PORT, value_port);
+                                        edit.commit();
+
+                                        binder.startBackgroundThread(provider, new ServerAddress(value_hostname, value_port));
                                         handler = binder.getBackgroundThread().handler;
                                     })
                                     .setNegativeButton("Cancel", (dialog, which) -> {
@@ -200,23 +217,89 @@ public class MainActivity extends AppCompatActivity {
                 .withShowDrawerOnFirstLaunch(true)
                 .build();
 
+        // TODO: REWRITE THIS
+        if (CompatibilityUtils.isChromiumDevice()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                getWindow().setStatusBarColor(getResources().getColor(R.color.colorPrimary, getTheme()));
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                getWindow().setStatusBarColor(getResources().getColor(R.color.colorPrimary));
+            }
+        }
+
         drawer.addStickyFooterItem(new PrimaryDrawerItem().withName("(Re-)Connect").withIcon(R.drawable.ic_server_light));
 
         messages.setAdapter(adapter);
         messages.setLayoutManager(new LinearLayoutManager(this));
+        swipeView.setOnRefreshListener(() -> {
+            if (handler != null) handler.getClient().getBacklogManager().requestMoreBacklog(bufferId, 20);
+            else swipeView.setRefreshing(false);
+        });
+
+        send.setOnClickListener(view -> {
+            sendInput();
+        });
+        chatline.setOnKeyListener((v, keyCode, event) -> {
+            if (event.getAction() == KeyEvent.ACTION_DOWN && (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER))
+                sendInput();
 
-        send.setOnClickListener((view) -> {
-            Buffer buffer = handler.getClient().getBuffer(bufferId);
-            handler.getClient().sendInput(buffer.getInfo(), chatline.getText().toString());
-            chatline.setText("");
+            return false;
         });
     }
 
     @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(BUFFER_ID, bufferId);
+        outState.putInt(BUFFER_VIEW_ID, bufferViewId);
+        drawer.saveInstanceState(outState);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        if (savedInstanceState != null) {
+            bufferId = savedInstanceState.getInt(BUFFER_ID, -1);
+            bufferViewId = savedInstanceState.getInt(BUFFER_VIEW_ID, -1);
+        }
     }
 
+    // TODO: USE OBSERVABLELIST FOR THIS
+    private void switchBufferView(int bufferviewId) {
+        this.bufferViewId = bufferviewId;
+        adapter.setClient(handler.getClient());
+        BufferViewConfig config = handler.getClient().getBufferViewManager().BufferViews.get(bufferviewId);
+        ArrayList<IDrawerItem> items = new ArrayList<>();
+        if (config != null) {
+            if (config.getNetworkId() == 0) {
+                items.addAll(
+                        new Stream<>(handler.getClient().getNetworks())
+                                .map(network -> new NetworkDrawerItem(network,
+                                        Sets.intersection(network.getBuffers(), new HashSet<>(
+                                                new Stream<>(config.getBufferList())
+                                                        .map(handler.getClient()::getBuffer)
+                                                        .list()
+                                        ))))
+                                .list()
+                );
+            } else {
+                Network network = handler.getClient().getNetwork(config.getNetworkId());
+                items.add(new NetworkDrawerItem(network,
+                        Sets.intersection(network.getBuffers(), new HashSet<>(
+                                new Stream<>(config.getBufferList())
+                                        .map(handler.getClient()::getBuffer)
+                                        .list()
+                        ))
+                ));
+            }
+        }
+        drawer.setItems(items);
+        for (int i = 0; i < drawer.getAdapter().getItemCount(); i++) {
+            drawer.getAdapter().open(i);
+        }
+    }
+
+
+    // TODO: REWRITE THIS
     private void switchBuffer(int bufferId) {
         this.bufferId = bufferId;
 
@@ -232,7 +315,27 @@ public class MainActivity extends AppCompatActivity {
         drawer.closeDrawer();
     }
 
-    ;
+    // TODO: REWRITE THIS
+    private void sendInput() {
+        Buffer buffer = null;
+        Client client = null;
+        if (handler != null) client = handler.getClient();
+        if (client != null) buffer = client.getBuffer(bufferId);
+
+        String str = chatline.getText().toString();
+        if (buffer != null && !str.isEmpty())  handler.getClient().sendInput(buffer.getInfo(), str);
+        chatline.setText("");
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
 
     @Override
     protected void onDestroy() {
@@ -240,21 +343,38 @@ public class MainActivity extends AppCompatActivity {
         unbindService(serviceConnection);
     }
 
+    // TODO: REWRITE THIS
     public void onEventMainThread(ConnectionChangeEvent event) {
         switch (event.status) {
             case DISCONNECTED:
                 binder.stopBackgroundThread();
                 break;
             case CONNECTED:
+                // TODO: COMMENT THIS
+                System.gc();
+                if (bufferViewId == -1 && header.getProfiles().size() > 0)
+                    switchBufferView(header.getProfiles().get(0).getIdentifier());
                 break;
             case LOGIN_REQUIRED:
                 View loginview = View.inflate(this, R.layout.login_dialog, null);
+                EditText username = ((EditText) loginview.findViewById(R.id.username));
+                EditText password = ((EditText) loginview.findViewById(R.id.password));
+                username.setText(pref.getString(KEY_USER, ""));
+                password.setText(pref.getString(KEY_PASS, ""));
                 new AlertDialog.Builder(this)
                         .setView(loginview)
                         .setPositiveButton("Login", (dialog, which) -> {
+                            String value_user = username.getText().toString();
+                            String value_pass = password.getText().toString();
+
+                            SharedPreferences.Editor edit = pref.edit();
+                            edit.putString(KEY_USER, value_user);
+                            edit.putString(KEY_PASS, value_pass);
+                            edit.commit();
+
                             binder.getBackgroundThread().provider.dispatch(new HandshakeFunction(new ClientLogin(
-                                    ((EditText) loginview.findViewById(R.id.username)).getText().toString(),
-                                    ((EditText) loginview.findViewById(R.id.password)).getText().toString()
+                                    value_user,
+                                    value_pass
                             )));
                         })
                         .setNegativeButton("Cancel", (dialog, which) -> {
@@ -268,9 +388,9 @@ public class MainActivity extends AppCompatActivity {
         toolbar.setSubtitle(event.status.name());
     }
 
+    // TODO: USE OBSERVABLE LIST FOR THIS SHIT
     public void onEventMainThread(BufferViewManagerChangedEvent event) {
         IProfile activeProfile = header.getActiveProfile();
-        int selectedProfile = activeProfile == null ? -1 : activeProfile.getIdentifier();
         switch (event.action) {
             case ADD:
                 BufferViewConfig add = handler.getClient().getBufferViewManager().BufferViews.get(event.id);
@@ -292,92 +412,29 @@ public class MainActivity extends AppCompatActivity {
                 break;
         }
         Collections.sort(header.getProfiles(), (x, y) -> x.getIdentifier() - y.getIdentifier());
-        if (event.action == BufferViewManagerChangedEvent.Action.REMOVE && event.id == selectedProfile) {
+        if (event.action == BufferViewManagerChangedEvent.Action.REMOVE && event.id == bufferViewId) {
             ArrayList<IProfile> profiles = header.getProfiles();
             if (!profiles.isEmpty())
                 header.setActiveProfile(profiles.get(0), true);
-        } else if (event.action == BufferViewManagerChangedEvent.Action.MODIFY && event.id == selectedProfile) {
-            header.setActiveProfile(selectedProfile, true);
+        } else if (event.action == BufferViewManagerChangedEvent.Action.MODIFY && event.id == bufferViewId) {
+            header.setActiveProfile(bufferViewId, true);
         }
     }
 
+    public void onEventMainThread(BacklogReceivedEvent event) {
+        if (event.bufferId == bufferId) swipeView.setRefreshing(false);
+    }
+
+    // TODO: REWRITE THIS
     public void onEventMainThread(StatusMessageEvent event) {
         Toast.makeText(this, String.format("%s: %s", event.scope, event.message), Toast.LENGTH_LONG).show();
     }
 
+    // TODO: REWRITE THIS
     public void onEventMainThread(GeneralErrorEvent event) {
         if (event.exception != null && !(event.exception instanceof UnknownTypeException)) {
-            Log.e("libquassel", event.toString());
+            event.exception.printStackTrace();
             Snackbar.make(messages, event.toString(), Snackbar.LENGTH_LONG).show();
         }
     }
-
-    class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> {
-        ObservableList<Message> messageList = new ObservableList<>(Message.class);
-
-        public void setMessageList(ObservableList<Message> messageList) {
-            this.messageList.setCallback(null);
-            this.messageList = messageList;
-            this.messageList.setCallback(new ObservableList.RecyclerViewAdapterCallback(this));
-            notifyDataSetChanged();
-        }
-
-        @Override
-        public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-            return new MessageViewHolder(LayoutInflater.from(MainActivity.this).inflate(android.R.layout.simple_list_item_1, parent, false));
-        }
-
-        @Override
-        public void onBindViewHolder(MessageViewHolder holder, int position) {
-            int[] colors = {
-                    R.color.md_pink_500,
-                    R.color.md_purple_500,
-                    R.color.md_red_500,
-                    R.color.md_green_500,
-                    R.color.md_cyan_500,
-                    R.color.md_deep_purple_500,
-                    R.color.md_amber_500,
-                    R.color.md_blue_500,
-                    R.color.md_pink_700,
-                    R.color.md_purple_700,
-                    R.color.md_red_700,
-                    R.color.md_green_700,
-                    R.color.md_cyan_700,
-                    R.color.md_deep_purple_700,
-                    R.color.md_amber_700,
-                    R.color.md_blue_700
-            };
-
-            Message msg = messageList.list.get(position);
-            SpannableString timeSpan = new SpannableString(DateTimeFormat.forPattern("[hh:mm]").print(msg.time));
-            timeSpan.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.md_light_secondary)), 0, timeSpan.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-
-            String nick = IrcUserUtils.getNick(msg.sender);
-            SpannableString nickSpan = new SpannableString(nick);
-            nickSpan.setSpan(new ForegroundColorSpan(getResources().getColor(colors[IrcUserUtils.getSenderColor(nick)])), 0, nickSpan.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-
-            holder.text1.setText(TextUtils.concat(
-                    timeSpan,
-                    " ",
-                    nickSpan,
-                    " ",
-                    msg.content
-            ));
-        }
-
-        @Override
-        public int getItemCount() {
-            return messageList.list.size();
-        }
-    }
-
-    class MessageViewHolder extends RecyclerView.ViewHolder {
-        @Bind(android.R.id.text1)
-        TextView text1;
-
-        public MessageViewHolder(View itemView) {
-            super(itemView);
-            ButterKnife.bind(this, itemView);
-        }
-    }
 }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageAdapter.java
new file mode 100644
index 000000000..c95fac8ab
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageAdapter.java
@@ -0,0 +1,50 @@
+package de.kuschku.quasseldroid_ng.ui;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import de.kuschku.libquassel.Client;
+import de.kuschku.libquassel.message.Message;
+import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.util.ObservableList;
+
+public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> {
+    private ObservableList<Message> messageList = new ObservableList<>(Message.class);
+    private ChatMessageRenderer renderer;
+    private LayoutInflater inflater;
+
+    public MessageAdapter(Context ctx) {
+        this.inflater = LayoutInflater.from(ctx);
+        this.renderer = new ChatMessageRenderer(ctx);
+    }
+
+    public void setClient(Client client) {
+        renderer.setClient(client);
+    }
+
+    public void setMessageList(ObservableList<Message> messageList) {
+        this.messageList.setCallback(null);
+        this.messageList = messageList;
+        this.messageList.setCallback(new ObservableList.RecyclerViewAdapterCallback(this));
+        notifyDataSetChanged();
+    }
+
+    @Override
+    public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        return new MessageViewHolder(inflater.inflate(R.layout.widget_chatmessage, parent, false));
+    }
+
+    // TODO: REWRITE THIS
+    @Override
+    public void onBindViewHolder(MessageViewHolder holder, int position) {
+        Message msg = messageList.list.get(position);
+        renderer.onBind(holder, msg);
+    }
+
+    @Override
+    public int getItemCount() {
+        return messageList.list.size();
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageViewHolder.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageViewHolder.java
new file mode 100644
index 000000000..bd7131ad9
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/MessageViewHolder.java
@@ -0,0 +1,22 @@
+package de.kuschku.quasseldroid_ng.ui;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.TextView;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import de.kuschku.quasseldroid_ng.R;
+
+class MessageViewHolder extends RecyclerView.ViewHolder {
+    @Bind(R.id.time)
+    TextView time;
+
+    @Bind(R.id.content)
+    TextView content;
+
+    public MessageViewHolder(View itemView) {
+        super(itemView);
+        ButterKnife.bind(this, itemView);
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/NetworkDrawerItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/NetworkDrawerItem.java
similarity index 96%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/NetworkDrawerItem.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/ui/NetworkDrawerItem.java
index 5def317fb..4368fc4df 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/NetworkDrawerItem.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/NetworkDrawerItem.java
@@ -1,4 +1,4 @@
-package de.kuschku.quasseldroid_ng;
+package de.kuschku.quasseldroid_ng.ui;
 
 import com.mikepenz.materialdrawer.holder.StringHolder;
 import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/CompatibilityUtils.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/CompatibilityUtils.java
new file mode 100644
index 000000000..8a3c9ea7c
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/CompatibilityUtils.java
@@ -0,0 +1,13 @@
+package de.kuschku.quasseldroid_ng.util;
+
+import android.os.Build;
+
+public class CompatibilityUtils {
+    private CompatibilityUtils() {
+
+    }
+
+    public static boolean isChromiumDevice() {
+        return (Build.MANUFACTURER.toLowerCase().contains("chromium") && Build.BRAND.toLowerCase().contains("chromium"));
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/DateFormatHelper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/DateFormatHelper.java
new file mode 100644
index 000000000..f5620c835
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/DateFormatHelper.java
@@ -0,0 +1,18 @@
+package de.kuschku.quasseldroid_ng.util;
+
+import android.content.Context;
+
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+
+import java.text.SimpleDateFormat;
+
+public class DateFormatHelper {
+    private DateFormatHelper() {
+
+    }
+
+    public static DateTimeFormatter getTimeFormatter(Context ctx) {
+        return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getTimeFormat(ctx)).toLocalizedPattern());
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcFormatHelper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcFormatHelper.java
new file mode 100644
index 000000000..51e506ee7
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcFormatHelper.java
@@ -0,0 +1,31 @@
+package de.kuschku.quasseldroid_ng.util;
+
+
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+
+public class IrcFormatHelper {
+    private final ThemeUtil.Colors colors;
+
+    public IrcFormatHelper(ThemeUtil.Colors colors) {
+        this.colors = colors;
+    }
+
+    public CharSequence formatUserNick(String nick) {
+        int colorIndex = IrcUserUtils.getSenderColor(nick);
+        int color = colors.senderColors[colorIndex];
+
+        SpannableString str = new SpannableString(nick);
+        str.setSpan(new ForegroundColorSpan(color), 0, nick.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        str.setSpan(new StyleSpan(Typeface.BOLD), 0, nick.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        return str;
+    }
+
+    public CharSequence formatIrcMessage(String message) {
+        SpannableString str = new SpannableString(message);
+        return str;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/IrcUserUtils.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcUserUtils.java
similarity index 89%
rename from app/src/main/java/de/kuschku/util/IrcUserUtils.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcUserUtils.java
index 03953b10a..608a35c84 100644
--- a/app/src/main/java/de/kuschku/util/IrcUserUtils.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/IrcUserUtils.java
@@ -1,4 +1,4 @@
-package de.kuschku.util;
+package de.kuschku.quasseldroid_ng.util;
 
 import java.nio.charset.Charset;
 import java.util.Locale;
@@ -70,10 +70,15 @@ public class IrcUserUtils {
     }
 
     public static String getUser(String hostmask) {
-        return hostmask.split("!")[1].split("@")[0];
+        return getMask(hostmask).split("@")[0];
     }
 
     public static String getHost(String hostmask) {
-        return hostmask.split("@")[1];
+        return getMask(hostmask).split("@")[1];
+    }
+
+
+    public static String getMask(String hostmask) {
+        return hostmask.split("!")[1];
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/utils/ServerAddress.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ServerAddress.java
similarity index 82%
rename from app/src/main/java/de/kuschku/quasseldroid_ng/utils/ServerAddress.java
rename to app/src/main/java/de/kuschku/quasseldroid_ng/util/ServerAddress.java
index 74dc99443..20b2059ea 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/utils/ServerAddress.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ServerAddress.java
@@ -1,4 +1,4 @@
-package de.kuschku.quasseldroid_ng.utils;
+package de.kuschku.quasseldroid_ng.util;
 
 public class ServerAddress {
     public final String host;
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/SpanFormatter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/SpanFormatter.java
new file mode 100644
index 000000000..71c1ba9cb
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/SpanFormatter.java
@@ -0,0 +1,113 @@
+/*
+* Copyright © 2014 George T. Steel
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+package de.kuschku.quasseldroid_ng.util;
+
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.SpannedString;
+import android.text.Spannable;
+
+/**
+ * Provides {@link String#format} style functions that work with {@link Spanned} strings and preserve formatting.
+ *
+ * @author George T. Steel
+ *
+ */
+public class SpanFormatter {
+    public static final Pattern FORMAT_SEQUENCE	= Pattern.compile("%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])");
+
+    private SpanFormatter(){}
+
+    /**
+     * Version of {@link String#format(String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting.
+     * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved.
+     * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result.
+     * Any duplicates will appear as text only.
+     *
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter. If there are
+     *            more arguments than required by {@code format},
+     *            additional arguments are ignored.
+     * @return the formatted string (with spans).
+     */
+    public static SpannedString format(CharSequence format, Object... args) {
+        return format(Locale.getDefault(), format, args);
+    }
+
+    /**
+     * Version of {@link String#format(Locale, String, Object...)} that works on {@link Spanned} strings to preserve rich text formatting.
+     * Both the {@code format} as well as any {@code %s args} can be Spanned and will have their formatting preserved.
+     * Due to the way {@link Spannable}s work, any argument's spans will can only be included <b>once</b> in the result.
+     * Any duplicates will appear as text only.
+     *
+     * @param locale
+     *            the locale to apply; {@code null} value means no localization.
+     * @param format the format string (see {@link java.util.Formatter#format})
+     * @param args
+     *            the list of arguments passed to the formatter.
+     * @return the formatted string (with spans).
+     * @see String#format(Locale, String, Object...)
+     */
+    public static SpannedString format(Locale locale, CharSequence format, Object... args){
+        SpannableStringBuilder out = new SpannableStringBuilder(format);
+
+        int i = 0;
+        int argAt = -1;
+
+        while (i < out.length()){
+            Matcher m = FORMAT_SEQUENCE.matcher(out);
+            if (!m.find(i)) break;
+            i=m.start();
+            int exprEnd = m.end();
+
+            String argTerm = m.group(1);
+            String modTerm = m.group(2);
+            String typeTerm = m.group(3);
+
+            CharSequence cookedArg;
+
+            if (typeTerm.equals("%")){
+                cookedArg = "%";
+            }else if (typeTerm.equals("%")){
+                cookedArg = "\n";
+            }else{
+                int argIdx = 0;
+                if (argTerm.equals("")) argIdx = ++argAt;
+                else if (argTerm.equals("<")) argIdx = argAt;
+                else argIdx = Integer.parseInt(argTerm.substring(0, argTerm.length() - 1)) -1;
+
+                Object argItem = args[argIdx];
+
+                if (typeTerm.equals("s") && argItem instanceof Spanned){
+                    cookedArg = (Spanned) argItem;
+                }else{
+                    cookedArg = String.format(locale, "%"+modTerm+typeTerm, argItem);
+                }
+            }
+
+            out.replace(i, exprEnd, cookedArg);
+            i += cookedArg.length();
+        }
+
+        return new SpannedString(out);
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/util/ThemeUtil.java b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ThemeUtil.java
new file mode 100644
index 000000000..10927d499
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/util/ThemeUtil.java
@@ -0,0 +1,120 @@
+package de.kuschku.quasseldroid_ng.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.support.annotation.ColorInt;
+
+import de.kuschku.quasseldroid_ng.R;
+
+public class ThemeUtil {
+    public final Colors colors = new Colors();
+
+    private static final int[] ATTRS_SENDER = {
+            // sender colors
+            R.attr.senderColor0,
+            R.attr.senderColor1,
+            R.attr.senderColor2,
+            R.attr.senderColor3,
+            R.attr.senderColor4,
+            R.attr.senderColor5,
+            R.attr.senderColor6,
+            R.attr.senderColor7,
+            R.attr.senderColor8,
+            R.attr.senderColor9,
+            R.attr.senderColorA,
+            R.attr.senderColorB,
+            R.attr.senderColorC,
+            R.attr.senderColorD,
+            R.attr.senderColorE,
+            R.attr.senderColorF,
+    };
+
+    private static final int[] ATTRS_MIRC = {
+            // mirc colors
+            R.attr.mircColor0,
+            R.attr.mircColor1,
+            R.attr.mircColor2,
+            R.attr.mircColor3,
+            R.attr.mircColor4,
+            R.attr.mircColor5,
+            R.attr.mircColor6,
+            R.attr.mircColor7,
+            R.attr.mircColor8,
+            R.attr.mircColor9,
+            R.attr.mircColorA,
+            R.attr.mircColorB,
+            R.attr.mircColorC,
+            R.attr.mircColorD,
+            R.attr.mircColorE,
+            R.attr.mircColorF,
+    };
+
+    private static final int[] ATTRS_GENERAL = {
+            // General UI colors
+            R.attr.colorForeground,
+            R.attr.colorForegroundHighlight,
+            R.attr.colorForegroundSecondary,
+
+            R.attr.colorBackground,
+            R.attr.colorBackgroundHighlight,
+            R.attr.colorBackgroundCard,
+    };
+
+    private static final int[] ATTRS_TINT = {
+            // Tint colors
+            R.attr.colorTintActivity,
+            R.attr.colorTintMessage,
+            R.attr.colorTintHighlight
+    };
+
+    public ThemeUtil(Context ctx) {
+        initColors(ctx.getTheme());
+    }
+
+    public void initColors(Resources.Theme theme) {
+        TypedArray arr;
+
+        arr = theme.obtainStyledAttributes(ATTRS_SENDER);
+        for (int i = 0; i < colors.senderColors.length;i++) {
+            colors.senderColors[i] = arr.getColor(i, colors.transparent);
+        }
+        arr.recycle();
+
+        arr = theme.obtainStyledAttributes(ATTRS_MIRC);
+        for (int i = 0; i < colors.senderColors.length;i++) {
+            colors.mircColors[i] = arr.getColor(i, colors.transparent);
+        }
+        arr.recycle();
+
+        arr = theme.obtainStyledAttributes(ATTRS_GENERAL);
+        colors.colorForeground = arr.getColor(0, colors.transparent);
+        colors.colorForegroundHighlight = arr.getColor(1, colors.transparent);
+        colors.colorForegroundSecondary = arr.getColor(2, colors.transparent);
+        colors.colorBackground = arr.getColor(3, colors.transparent);
+        colors.colorBackgroundHighlight = arr.getColor(4, colors.transparent);
+        colors.colorBackgroundCard = arr.getColor(5, colors.transparent);
+        arr.recycle();
+
+        arr = theme.obtainStyledAttributes(ATTRS_TINT);
+        colors.colorTintActivity = arr.getColor(0, colors.transparent);
+        colors.colorTintMessage = arr.getColor(1, colors.transparent);
+        colors.colorTintHighlight = arr.getColor(2, colors.transparent);
+        arr.recycle();
+    }
+
+    public static class Colors {
+        @ColorInt public int transparent = 0x00000000;
+        @ColorInt public int[] senderColors = new int[16];
+        @ColorInt public int[] mircColors = new int[16];
+        @ColorInt public int colorForeground = 0x00000000;
+        @ColorInt public int colorForegroundHighlight = 0x00000000;
+        @ColorInt public int colorForegroundSecondary = 0x00000000;
+        @ColorInt public int colorBackground = 0x00000000;
+        @ColorInt public int colorBackgroundHighlight = 0x00000000;
+        @ColorInt public int colorBackgroundCard = 0x00000000;
+        @ColorInt public int colorTintActivity = 0x00000000;
+        @ColorInt public int colorTintMessage = 0x00000000;
+        @ColorInt public int colorTintHighlight = 0x00000000;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/ObservableList.java b/app/src/main/java/de/kuschku/util/ObservableList.java
index d28aa0d86..b3c37813f 100644
--- a/app/src/main/java/de/kuschku/util/ObservableList.java
+++ b/app/src/main/java/de/kuschku/util/ObservableList.java
@@ -22,9 +22,17 @@ public class ObservableList<T extends ContentComparable<T>> {
     }
 
     public T last() {
+        if (list.size() == 0) return null;
+
         return list.get(list.size() - 1);
     }
 
+    public T first() {
+        if (list.size() == 0) return null;
+
+        return list.get(0);
+    }
+
     public interface UICallback {
         void notifyItemInserted(int position);
 
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 003b52b4d..b117d1bdf 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,7 +5,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/layout"
-    tools:context="de.kuschku.quasseldroid_ng.MainActivity"
+    tools:context=".ui.MainActivity"
     android:orientation="vertical" >
 
     <android.support.design.widget.AppBarLayout
diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml
index 9a1f5707a..db9f019c4 100644
--- a/app/src/main/res/layout/content_main.xml
+++ b/app/src/main/res/layout/content_main.xml
@@ -5,13 +5,17 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:showIn="@layout/activity_main">
-
-    <android.support.v7.widget.RecyclerView
-        android:id="@+id/messages"
+    <android.support.v4.widget.SwipeRefreshLayout
+        android:id="@+id/swipeview"
         android:layout_width="match_parent"
         android:layout_height="0dip"
         android:layout_weight="1"
-        android:background="?attr/messagesBackground" />
+        android:background="?attr/colorBackground" >
+        <android.support.v7.widget.RecyclerView
+            android:id="@+id/messages"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </android.support.v4.widget.SwipeRefreshLayout>
 
     <View
         android:layout_width="fill_parent"
diff --git a/app/src/main/res/layout/core_dialog.xml b/app/src/main/res/layout/core_dialog.xml
index a91c3a2e7..36cdf9966 100644
--- a/app/src/main/res/layout/core_dialog.xml
+++ b/app/src/main/res/layout/core_dialog.xml
@@ -7,14 +7,12 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/server"
-        android:inputType="textUri"
-        android:text="192.168.178.241"/>
+        android:inputType="textUri" />
 
     <EditText
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/port"
-        android:inputType="number"
-        android:text="@string/default_port" />
+        android:inputType="number" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/login_dialog.xml b/app/src/main/res/layout/login_dialog.xml
index 000b67993..de8c9ad13 100644
--- a/app/src/main/res/layout/login_dialog.xml
+++ b/app/src/main/res/layout/login_dialog.xml
@@ -6,14 +6,12 @@
     <EditText
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:id="@+id/username"
-        android:text="test"/>
+        android:id="@+id/username" />
 
     <EditText
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:id="@+id/password"
-        android:inputType="textPassword"
-        android:text="password"/>
+        android:inputType="textPassword" />
 
 </LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/widget_chatmessage.xml b/app/src/main/res/layout/widget_chatmessage.xml
new file mode 100644
index 000000000..4b17f7deb
--- /dev/null
+++ b/app/src/main/res/layout/widget_chatmessage.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall" >
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/time"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:typeface="monospace"
+        android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+        android:paddingRight="?android:attr/listPreferredItemPaddingRight" />
+    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/content"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 3868f1a86..2e9ab05cb 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -1,7 +1,60 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
-    <attr name="iconServer" format="reference" />
     <attr name="messagesBackground" format="reference" />
     <attr name="chatlineBackground" format="reference" />
     <attr name="dividerColor" format="reference"/>
+
+    <!-- sender colors -->
+
+    <attr name="senderColor0" format="color" />
+    <attr name="senderColor1" format="color" />
+    <attr name="senderColor2" format="color" />
+    <attr name="senderColor3" format="color" />
+    <attr name="senderColor4" format="color" />
+    <attr name="senderColor5" format="color" />
+    <attr name="senderColor6" format="color" />
+    <attr name="senderColor7" format="color" />
+    <attr name="senderColor8" format="color" />
+    <attr name="senderColor9" format="color" />
+    <attr name="senderColorA" format="color" />
+    <attr name="senderColorB" format="color" />
+    <attr name="senderColorC" format="color" />
+    <attr name="senderColorD" format="color" />
+    <attr name="senderColorE" format="color" />
+    <attr name="senderColorF" format="color" />
+
+    <!-- mirc colors -->
+
+    <attr name="mircColor0" format="color" />
+    <attr name="mircColor1" format="color" />
+    <attr name="mircColor2" format="color" />
+    <attr name="mircColor3" format="color" />
+    <attr name="mircColor4" format="color" />
+    <attr name="mircColor5" format="color" />
+    <attr name="mircColor6" format="color" />
+    <attr name="mircColor7" format="color" />
+    <attr name="mircColor8" format="color" />
+    <attr name="mircColor9" format="color" />
+    <attr name="mircColorA" format="color" />
+    <attr name="mircColorB" format="color" />
+    <attr name="mircColorC" format="color" />
+    <attr name="mircColorD" format="color" />
+    <attr name="mircColorE" format="color" />
+    <attr name="mircColorF" format="color" />
+
+    <!-- Background and foreground colors for UI -->
+
+    <attr name="colorForeground" format="color" />
+    <attr name="colorForegroundHighlight" format="color" />
+    <attr name="colorForegroundSecondary" format="color" />
+
+    <attr name="colorBackground" format="color" />
+    <attr name="colorBackgroundHighlight" format="color" />
+    <attr name="colorBackgroundCard" format="color" />
+
+    <!-- Tint colors for drawer -->
+
+    <attr name="colorTintActivity" format="color" />
+    <attr name="colorTintMessage" format="color" />
+    <attr name="colorTintHighlight" format="color" />
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d999eaf81..dfce6f5d4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,4 +2,31 @@
     <string name="app_name">QuasselDroid</string>
     <string name="action_settings">Settings</string>
     <string name="default_port">4242</string>
+
+    <string name="username_hostmask">%1$s (%2$s)</string>
+
+    <!-- Message format strings -->
+    <string name="message_plain">%1$s: %2$s</string>
+
+    <string name="message_join">%1$s has joined %2$s</string>
+
+    <string name="message_part">%1$s has left %2$s</string>
+    <string name="message_part_extra">"%1$s has left %2$s (%3$s)</string>
+
+    <string name="message_quit">%1$s has quit</string>
+    <string name="message_quit_extra">%1$s has quit (%2$s)</string>
+
+    <string name="message_kill">%1$s was killed: %2$s</string>
+
+    <string name="message_kick">%1$s has kicked %2$s from %3$s</string>
+    <string name="message_kick_extra">%1$s has kicked %2$s from %3$s: %4$s</string>
+
+    <string name="message_mode">Mode %1$s by %2$s</string>
+
+    <string name="message_nick_self">You are now known as %1$s</string>
+    <string name="message_nick_other">%1$s is now known as %2$s</string>
+
+    <string name="message_daychange">{ Day changed to %1$s }</string>
+
+    <string name="message_action">* %1$s %2$s</string>
 </resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 47854a96c..d0b683875 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,25 +1,23 @@
 <resources>
 
     <!-- Base application theme. -->
-    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
+    <style name="AppTheme" parent="MaterialDrawerTheme">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
         <item name="colorAccent">@color/colorAccent</item>
 
-        <item name="iconServer">@drawable/ic_server_light</item>
         <item name="dividerColor">@color/material_drawer_dark_divider</item>
         <item name="messagesBackground">@color/messagesBackgroundDark</item>
         <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">
         <!-- Customize your theme here. -->
         <item name="colorPrimary">@color/colorPrimary</item>
         <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
         <item name="colorAccent">@color/colorAccent</item>
 
-        <item name="iconServer">@drawable/ic_server_dark</item>
         <item name="dividerColor">@color/material_drawer_divider</item>
         <item name="messagesBackground">@color/messagesBackgroundLight</item>
         <item name="chatlineBackground">@color/md_light_cards</item>
@@ -28,5 +26,4 @@
     <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
 
     <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Dark" />
-
 </resources>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..af5d1ca06
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="Material_Dark" parent="AppTheme">
+        <item name="senderColor0">@color/md_pink_500</item>
+        <item name="senderColor1">@color/md_purple_500</item>
+        <item name="senderColor2">@color/md_red_500</item>
+        <item name="senderColor3">@color/md_green_500</item>
+        <item name="senderColor4">@color/md_cyan_500</item>
+        <item name="senderColor5">@color/md_deep_purple_500</item>
+        <item name="senderColor6">@color/md_amber_500</item>
+        <item name="senderColor7">@color/md_blue_500</item>
+        <item name="senderColor8">@color/md_pink_500</item>
+        <item name="senderColor9">@color/md_purple_500</item>
+        <item name="senderColorA">@color/md_red_500</item>
+        <item name="senderColorB">@color/md_green_500</item>
+        <item name="senderColorC">@color/md_cyan_500</item>
+        <item name="senderColorD">@color/md_deep_purple_500</item>
+        <item name="senderColorE">@color/md_amber_500</item>
+        <item name="senderColorF">@color/md_blue_500</item>
+
+        <item name="colorForeground">@color/md_dark_primary_text</item>
+        <item name="colorForegroundHighlight">@color/md_dark_primary_text</item>
+        <item name="colorForegroundSecondary">@color/md_dark_secondary</item>
+
+        <item name="colorBackground">@color/md_dark_background</item>
+        <item name="colorBackgroundHighlight">#ff8811</item>
+        <item name="colorBackgroundCard">@color/md_dark_cards</item>
+
+        <item name="colorTintActivity">#88cc33</item>
+        <item name="colorTintMessage">#2277dd</item>
+        <item name="colorTintHighlight">#ff8811</item>
+    </style>
+
+    <style name="Material_Light" parent="AppTheme">
+        <item name="senderColor0">@color/md_pink_500</item>
+        <item name="senderColor1">@color/md_purple_500</item>
+        <item name="senderColor2">@color/md_red_500</item>
+        <item name="senderColor3">@color/md_green_500</item>
+        <item name="senderColor4">@color/md_cyan_500</item>
+        <item name="senderColor5">@color/md_deep_purple_500</item>
+        <item name="senderColor6">@color/md_amber_500</item>
+        <item name="senderColor7">@color/md_blue_500</item>
+        <item name="senderColor8">@color/md_pink_500</item>
+        <item name="senderColor9">@color/md_purple_500</item>
+        <item name="senderColorA">@color/md_red_500</item>
+        <item name="senderColorB">@color/md_green_500</item>
+        <item name="senderColorC">@color/md_cyan_500</item>
+        <item name="senderColorD">@color/md_deep_purple_500</item>
+        <item name="senderColorE">@color/md_amber_500</item>
+        <item name="senderColorF">@color/md_blue_500</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>
+
+        <item name="colorBackground">@color/md_light_background</item>
+        <item name="colorBackgroundHighlight">#ff8811</item>
+        <item name="colorBackgroundCard">@color/md_light_cards</item>
+
+        <item name="colorTintActivity">#88cc33</item>
+        <item name="colorTintMessage">#2277dd</item>
+        <item name="colorTintHighlight">#ff8811</item>
+    </style>
+
+    <style name="Quassel" parent="AppTheme.Light">
+        <item name="senderColor0">#e90d7f</item>
+        <item name="senderColor1">#8e55e9</item>
+        <item name="senderColor2">#b30e0e</item>
+        <item name="senderColor3">#17b339</item>
+        <item name="senderColor4">#58afb3</item>
+        <item name="senderColor5">#9d54b3</item>
+        <item name="senderColor6">#b39775</item>
+        <item name="senderColor7">#3176b3</item>
+        <item name="senderColor8">#e90d7f</item>
+        <item name="senderColor9">#8e55e9</item>
+        <item name="senderColorA">#b30e0e</item>
+        <item name="senderColorB">#17b339</item>
+        <item name="senderColorC">#58afb3</item>
+        <item name="senderColorD">#9d54b3</item>
+        <item name="senderColorE">#b39775</item>
+        <item name="senderColorF">#3176b3</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>
+
+        <item name="colorBackground">@color/md_light_background</item>
+        <item name="colorBackgroundHighlight">#ff8811</item>
+        <item name="colorBackgroundCard">@color/md_light_cards</item>
+
+        <item name="colorTintActivity">#88cc33</item>
+        <item name="colorTintMessage">#2277dd</item>
+        <item name="colorTintHighlight">#ff8811</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index fe48ba81f..26a7c8629 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.0.0-alpha2'
+        classpath 'com.android.tools.build:gradle:2.0.0-alpha3'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
-- 
GitLab