From 2f2465a4a61ab2271c0c04ef3f7da1bdfe559552 Mon Sep 17 00:00:00 2001
From: Janne Koschinski <janne@kuschku.de>
Date: Sun, 11 Sep 2016 18:10:50 +0200
Subject: [PATCH] Implemented Alias editing

---
 app/src/main/AndroidManifest.xml              |  14 ++
 .../types/abstracts/AAliasManager.java        |   8 +
 .../syncables/types/impl/AliasManager.java    | 114 +++++++-----
 .../types/interfaces/QAliasManager.java       |  19 +-
 .../quasseldroid_ng/ui/chat/MainActivity.java |   4 +
 .../aliases/AliasEditActivity.java            | 165 +++++++++++++++++
 .../aliases/AliasListActivity.java            | 168 ++++++++++++++++++
 .../aliases/helper/AliasSerializerHelper.java |  45 +++++
 .../util/irc/format/IrcFormatSerializer.java  |  23 ---
 .../main/res/layout/activity_alias_edit.xml   |  85 +++++++++
 .../res/layout/activity_chatlist_edit.xml     |   5 +-
 .../res/layout/activity_identity_edit.xml     |   5 +-
 .../res/layout/activity_ignorerule_edit.xml   |   5 +-
 .../main/res/layout/activity_network_edit.xml |   5 +-
 .../layout/activity_networkserver_edit.xml    |   5 +-
 .../main/res/layout/widget_settings_alias.xml |  58 ++++++
 app/src/main/res/menu/chat.xml                |   4 +
 app/src/main/res/values/strings_actions.xml   |   4 +-
 .../main/res/values/strings_coresettings.xml  |   3 +
 app/src/main/res/values/strings_titles.xml    |   2 +
 20 files changed, 663 insertions(+), 78 deletions(-)
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasEditActivity.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasListActivity.java
 create mode 100644 app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/helper/AliasSerializerHelper.java
 create mode 100644 app/src/main/res/layout/activity_alias_edit.xml
 create mode 100644 app/src/main/res/layout/widget_settings_alias.xml

diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8ab43ed4b..88188d21c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -158,6 +158,20 @@
             android:parentActivityName=".ui.coresettings.ignore.IgnoreListActivity"
             android:theme="@style/AppTheme.Light"/>
 
+        <activity
+            android:name=".ui.coresettings.aliases.AliasListActivity"
+            android:label="@string/titleEditAliases"
+            android:launchMode="singleTask"
+            android:parentActivityName=".ui.chat.MainActivity"
+            android:theme="@style/AppTheme.Light"/>
+
+        <activity
+            android:name=".ui.coresettings.aliases.AliasEditActivity"
+            android:label="@string/titleEditAlias"
+            android:launchMode="singleTask"
+            android:parentActivityName=".ui.coresettings.aliases.AliasListActivity"
+            android:theme="@style/AppTheme.Light"/>
+
         <activity
             android:name=".ui.setup.AccountSetupActivity"
             android:label="@string/titleAccountSetup"
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/AAliasManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/AAliasManager.java
index 17f4f03a5..06fb6a67b 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/AAliasManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/abstracts/AAliasManager.java
@@ -21,6 +21,9 @@
 
 package de.kuschku.libquassel.syncables.types.abstracts;
 
+import java.util.Map;
+
+import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.libquassel.syncables.types.SyncableObject;
 import de.kuschku.libquassel.syncables.types.interfaces.QAliasManager;
 
@@ -30,4 +33,9 @@ public abstract class AAliasManager extends SyncableObject<QAliasManager> implem
         _addAlias(name, expansion);
         syncVar("addAlias", name, expansion);
     }
+
+    @Override
+    public void requestUpdate(Map<String, QVariant<Object>> variantMap) {
+        syncVar("requestUpdate", variantMap);
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/AliasManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/AliasManager.java
index 17a99fe9b..72c1b7997 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/AliasManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/impl/AliasManager.java
@@ -27,6 +27,7 @@ import com.google.common.base.Joiner;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
@@ -44,6 +45,8 @@ import de.kuschku.libquassel.syncables.types.abstracts.AAliasManager;
 import de.kuschku.libquassel.syncables.types.interfaces.QAliasManager;
 import de.kuschku.libquassel.syncables.types.interfaces.QIrcUser;
 import de.kuschku.libquassel.syncables.types.interfaces.QNetwork;
+import de.kuschku.util.backports.Objects;
+import de.kuschku.util.observables.lists.ObservableSortedList;
 
 public class AliasManager extends AAliasManager {
     @NonNull
@@ -59,8 +62,23 @@ public class AliasManager extends AAliasManager {
             new Alias("back", "/quote away")
     };
 
-    private List<String> names = new ArrayList<>();
-    private List<Alias> aliases = new ArrayList<>();
+    private Map<String, Alias> map = new HashMap<>();
+    private ObservableSortedList<Alias> aliases = new ObservableSortedList<>(Alias.class, new ObservableSortedList.ItemComparator<Alias>() {
+        @Override
+        public int compare(Alias o1, Alias o2) {
+            return o1.name.compareTo(o2.name);
+        }
+
+        @Override
+        public boolean areContentsTheSame(Alias oldItem, Alias newItem) {
+            return Objects.equals(oldItem.name, newItem.name);
+        }
+
+        @Override
+        public boolean areItemsTheSame(Alias item1, Alias item2) {
+            return Objects.equals(item1.expansion, item2.expansion);
+        }
+    });
 
     private Client client;
 
@@ -70,7 +88,6 @@ public class AliasManager extends AAliasManager {
         }
     }
 
-    //TODO: TEST
     @NonNull
     private static List<Command> expand(@NonNull String expansion, @NonNull BufferInfo info, @NonNull QNetwork network, @NonNull String args) {
         List<Command> results = new LinkedList<>();
@@ -109,12 +126,12 @@ public class AliasManager extends AAliasManager {
                 command = command.replaceAll(String.format(Locale.US, "$%d:hostname", j), host);
                 command = command.replaceAll(String.format(Locale.US, "$%d", j), params.get(j - 1));
             }
-            command = command.replaceAll("\\$0", args);
-            command = command.replaceAll("\\$channelname", info.name != null ? info.name : "");
-            command = command.replaceAll("\\$channel", info.name != null ? info.name : "");
-            command = command.replaceAll("\\$currentnick", network.myNick());
-            command = command.replaceAll("\\$nick", network.myNick());
-            command = command.replaceAll("\\$network", network.networkName());
+            command = command.replace("$0", args);
+            command = command.replace("$channelname", info.name != null ? info.name : "");
+            command = command.replace("$channel", info.name != null ? info.name : "");
+            command = command.replace("$currentnick", network.myNick());
+            command = command.replace("$nick", network.myNick());
+            command = command.replace("$network", network.networkName());
             expandedCommands.add(command);
         }
         while (!expandedCommands.isEmpty()) {
@@ -130,48 +147,30 @@ public class AliasManager extends AAliasManager {
         return results;
     }
 
-    @Override
-    public int indexOf(String name) {
-        return names.indexOf(name);
-    }
-
-    public int indexOfIgnoreCase(String name) {
-        for (int i = 0; i < names.size(); i++) {
-            if (names.get(i).equalsIgnoreCase(name))
-                return i;
-        }
-        return -1;
-    }
-
     @Override
     public boolean contains(String name) {
-        return names.contains(name);
+        return map.containsKey(name.toLowerCase(Locale.US));
     }
 
     @Override
     public boolean isEmpty() {
-        return names.isEmpty();
+        return aliases.isEmpty();
     }
 
     @Override
     public int count() {
-        return names.size();
+        return aliases.size();
     }
 
     @Override
-    public void removeAt(int index) {
-        names.remove(index);
-        aliases.remove(index);
-    }
-
-    @Override
-    public List<Alias> aliases() {
+    public ObservableSortedList<Alias> aliases() {
         return aliases;
     }
 
     @Override
-    public List<Alias> defaults() {
-        names.clear();
+    public ObservableSortedList<Alias> defaults() {
+        map.clear();
+        aliases.clear();
         for (Alias alias : DEFAULTS) {
             _addAlias(alias.name, alias.expansion);
         }
@@ -204,10 +203,9 @@ public class AliasManager extends AAliasManager {
                 command = message.substring(1, space);
                 args = message.substring(space + 1);
             }
-            int index = indexOfIgnoreCase(command);
+            Alias alias = map.get(command.toLowerCase(Locale.US));
             QNetwork network = client.networkManager().network(info.networkId);
-            if (index != -1 && network != null) {
-                Alias alias = aliases.get(index);
+            if (alias != null && network != null) {
                 list.addAll(expand(alias.expansion, info, network, args));
             } else {
                 list.add(new Command(info, message));
@@ -218,8 +216,8 @@ public class AliasManager extends AAliasManager {
 
     @Override
     public void _addAlias(String name, String expansion) {
-        names.add(name);
-        aliases.add(new Alias(name, expansion));
+        Alias alias = new Alias(name, expansion);
+        _addAlias(alias);
         _update();
     }
 
@@ -238,12 +236,42 @@ public class AliasManager extends AAliasManager {
 
     @Override
     public void _update(@NonNull QAliasManager from) {
-        List<String> names = new ArrayList<>(from.aliases().size());
+        List<Alias> toRemove = new ArrayList<>();
+        for (Alias alias : aliases) {
+            if (!from.aliases().contains(alias))
+                toRemove.add(alias);
+        }
+        for (Alias alias : toRemove) {
+            _removeAlias(alias);
+        }
         for (Alias alias : from.aliases()) {
-            names.add(alias.name);
+            if (!aliases.contains(alias)) {
+                _addAlias(alias);
+            }
         }
-        this.names = names;
-        aliases = from.aliases();
         _update();
     }
+
+    @Override
+    public void _removeAlias(Alias alias) {
+        aliases.remove(alias);
+        map.remove(alias.name);
+    }
+
+    @Override
+    public Alias alias(String name) {
+        return map.get(name);
+    }
+
+
+    @Override
+    public void requestUpdate() {
+        requestUpdate(AliasManagerSerializer.get().toVariantMap(this));
+    }
+
+    @Override
+    public void _addAlias(Alias alias) {
+        aliases.add(alias);
+        map.put(alias.name, alias);
+    }
 }
diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QAliasManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QAliasManager.java
index 4e05f12e6..89dce5455 100644
--- a/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QAliasManager.java
+++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/interfaces/QAliasManager.java
@@ -30,21 +30,18 @@ import de.kuschku.libquassel.objects.types.Command;
 import de.kuschku.libquassel.primitives.types.BufferInfo;
 import de.kuschku.libquassel.primitives.types.QVariant;
 import de.kuschku.libquassel.syncables.Synced;
+import de.kuschku.util.observables.lists.ObservableSortedList;
 
 public interface QAliasManager extends QObservable<QAliasManager> {
-    int indexOf(final String name);
-
     boolean contains(final String name);
 
     boolean isEmpty();
 
     int count();
 
-    void removeAt(int index);
-
-    List<Alias> aliases();
+    ObservableSortedList<Alias> aliases();
 
-    List<Alias> defaults();
+    ObservableSortedList<Alias> defaults();
 
     // TODO: specify later on
     @NonNull
@@ -59,6 +56,16 @@ public interface QAliasManager extends QObservable<QAliasManager> {
 
     void _addAlias(final String name, final String expansion);
 
+    void _addAlias(Alias alias);
+
+    void _removeAlias(Alias alias);
+
+    Alias alias(String name);
+
+    void requestUpdate(Map<String, QVariant<Object>> variantMap);
+
+    void requestUpdate();
+
     class Alias {
         public final String name;
         public final String expansion;
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java
index bac7bccdd..4b46c6976 100644
--- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/MainActivity.java
@@ -85,6 +85,7 @@ import de.kuschku.quasseldroid_ng.ui.chat.drawer.BufferViewConfigAdapter;
 import de.kuschku.quasseldroid_ng.ui.chat.fragment.ChatFragment;
 import de.kuschku.quasseldroid_ng.ui.chat.fragment.LoadingFragment;
 import de.kuschku.quasseldroid_ng.ui.chat.util.Status;
+import de.kuschku.quasseldroid_ng.ui.coresettings.aliases.AliasListActivity;
 import de.kuschku.quasseldroid_ng.ui.coresettings.chatlist.ChatListListActivity;
 import de.kuschku.quasseldroid_ng.ui.coresettings.identity.IdentityListActivity;
 import de.kuschku.quasseldroid_ng.ui.coresettings.ignore.IgnoreListActivity;
@@ -286,6 +287,9 @@ public class MainActivity extends BoundActivity {
             case R.id.action_ignorelist:
                 startActivity(new Intent(this, IgnoreListActivity.class));
                 return true;
+            case R.id.action_aliaslist:
+                startActivity(new Intent(this, AliasListActivity.class));
+                return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasEditActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasEditActivity.java
new file mode 100644
index 000000000..981c7db50
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasEditActivity.java
@@ -0,0 +1,165 @@
+/*
+ * QuasselDroid - Quassel client for Android
+ * Copyright (C) 2016 Janne Koschinski
+ * Copyright (C) 2016 Ken Børge Viktil
+ * Copyright (C) 2016 Magnus Fjell
+ * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid_ng.ui.coresettings.aliases;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.EditText;
+
+import com.afollestad.materialdialogs.MaterialDialog;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import de.kuschku.libquassel.syncables.types.impl.AliasManager;
+import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.coresettings.aliases.helper.AliasSerializerHelper;
+import de.kuschku.util.servicebound.BoundActivity;
+
+public class AliasEditActivity extends BoundActivity {
+    public static final int RESULT_DELETE = -2;
+
+    @Bind(R.id.toolbar)
+    Toolbar toolbar;
+
+    @Bind(R.id.name)
+    EditText name;
+
+    @Bind(R.id.expansion)
+    EditText expansion;
+
+    private AliasManager.Alias item;
+    private Bundle original;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_alias_edit);
+        ButterKnife.bind(this);
+
+        setSupportActionBar(toolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+        Intent intent = getIntent();
+        if (intent != null) {
+            original = intent.getBundleExtra("alias");
+            if (original != null) {
+                item = AliasSerializerHelper.deserialize(original);
+
+                name.setText(item.name);
+                expansion.setText(item.expansion.replaceAll("; ?", "\n"));
+            }
+        }
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        if (original != null) {
+            getMenuInflater().inflate(R.menu.confirm_delete, menu);
+        } else {
+            getMenuInflater().inflate(R.menu.confirm, menu);
+        }
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (hasChanged(build())) {
+            new MaterialDialog.Builder(this)
+                    .content(R.string.confirmationUnsavedChanges)
+                    .positiveText(R.string.actionYes)
+                    .negativeText(R.string.actionNo)
+                    .positiveColor(context.themeUtil().res.colorAccent)
+                    .negativeColor(context.themeUtil().res.colorForeground)
+                    .backgroundColorAttr(R.attr.colorBackgroundDialog)
+                    .onPositive((dialog, which) -> {
+                        save();
+                        super.onBackPressed();
+                    })
+                    .onNegative((dialog, which) -> super.onBackPressed())
+                    .show();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                onBackPressed();
+                return true;
+            case R.id.action_delete: {
+                new MaterialDialog.Builder(this)
+                        .content(getString(R.string.confirmationDelete, this.item.name))
+                        .positiveText(R.string.actionYes)
+                        .negativeText(R.string.actionNo)
+                        .positiveColor(context.themeUtil().res.colorAccent)
+                        .negativeColor(context.themeUtil().res.colorForeground)
+                        .backgroundColorAttr(R.attr.colorBackgroundDialog)
+                        .onPositive((dialog, which) -> {
+                            delete();
+                            finish();
+                        })
+                        .build()
+                        .show();
+            }
+            return true;
+            case R.id.action_confirm: {
+                save();
+                finish();
+            }
+            return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    private void delete() {
+        Intent intent = new Intent();
+        intent.putExtra("original", original.getString("name"));
+        setResult(RESULT_DELETE, intent);
+    }
+
+    private void save() {
+        Intent intent = new Intent();
+        if (original != null)
+            intent.putExtra("original", original.getString("name"));
+        intent.putExtra("alias", build());
+        setResult(RESULT_OK, intent);
+    }
+
+    private boolean hasChanged(Bundle bundle) {
+        return this.original != null && bundle != null && !this.original.equals(bundle);
+    }
+
+    private Bundle build() {
+        Bundle bundle = new Bundle();
+        bundle.putString("name", name.getText().toString());
+        bundle.putString("expansion", expansion.getText().toString().replace("\n", "; "));
+        return bundle;
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasListActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasListActivity.java
new file mode 100644
index 000000000..2c6b19144
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/AliasListActivity.java
@@ -0,0 +1,168 @@
+/*
+ * QuasselDroid - Quassel client for Android
+ * Copyright (C) 2016 Janne Koschinski
+ * Copyright (C) 2016 Ken Børge Viktil
+ * Copyright (C) 2016 Magnus Fjell
+ * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid_ng.ui.coresettings.aliases;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.FloatingActionButton;
+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.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import butterknife.Bind;
+import butterknife.ButterKnife;
+import de.kuschku.libquassel.syncables.types.impl.AliasManager;
+import de.kuschku.libquassel.syncables.types.interfaces.QAliasManager;
+import de.kuschku.quasseldroid_ng.R;
+import de.kuschku.quasseldroid_ng.ui.coresettings.aliases.helper.AliasSerializerHelper;
+import de.kuschku.util.observables.callbacks.wrappers.AdapterUICallbackWrapper;
+import de.kuschku.util.servicebound.BoundActivity;
+import de.kuschku.util.ui.DividerItemDecoration;
+
+public class AliasListActivity extends BoundActivity {
+    final OnAliasClickListener clickListener = alias -> {
+        if (alias != null) {
+            Intent intent = new Intent(this, AliasEditActivity.class);
+            intent.putExtra("alias", AliasSerializerHelper.serialize(alias));
+            startActivityForResult(intent, 0, null);
+        }
+    };
+
+    @Bind(R.id.list)
+    RecyclerView list;
+    @Bind(R.id.add)
+    FloatingActionButton add;
+    @Bind(R.id.toolbar)
+    Toolbar toolbar;
+    AliasAdapter adapter;
+    private QAliasManager manager;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_network_list);
+        ButterKnife.bind(this);
+
+        list.setLayoutManager(new LinearLayoutManager(this));
+        list.setItemAnimator(new DefaultItemAnimator());
+        list.addItemDecoration(new DividerItemDecoration(this));
+        adapter = new AliasAdapter();
+        list.setAdapter(adapter);
+
+        add.setOnClickListener(view -> startActivityForResult(new Intent(this, AliasEditActivity.class), 0, null));
+
+        setSupportActionBar(toolbar);
+        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+    }
+
+    @Override
+    protected void onConnected() {
+        manager = context.client().aliasManager();
+        adapter.setManager(manager);
+    }
+
+    @Override
+    protected void onDisconnected() {
+        manager = null;
+        adapter.setManager(null);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (data != null) {
+            if (resultCode == RESULT_OK) {
+                AliasManager.Alias item = AliasSerializerHelper.deserialize(data.getBundleExtra("alias"));
+                if (data.getStringExtra("original") != null)
+                    manager._removeAlias(manager.alias(data.getStringExtra("original")));
+                manager._addAlias(item);
+                manager.requestUpdate();
+            } else if (resultCode == AliasEditActivity.RESULT_DELETE) {
+                manager._removeAlias(manager.alias(data.getStringExtra("original")));
+                manager.requestUpdate();
+            }
+        }
+    }
+
+    interface OnAliasClickListener {
+        void onClick(AliasManager.Alias network);
+    }
+
+    private class AliasAdapter extends RecyclerView.Adapter<AliasViewHolder> {
+        final AdapterUICallbackWrapper wrapper = new AdapterUICallbackWrapper(this);
+        QAliasManager manager;
+
+        public void setManager(QAliasManager manager) {
+            if (this.manager != null)
+                this.manager.aliases().addCallback(wrapper);
+
+            this.manager = manager;
+
+            if (this.manager != null)
+                this.manager.aliases().addCallback(wrapper);
+        }
+
+        @Override
+        public AliasViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+            View view = inflater.inflate(R.layout.widget_settings_alias, parent, false);
+            return new AliasViewHolder(view);
+        }
+
+        @Override
+        public void onBindViewHolder(AliasViewHolder holder, int position) {
+            holder.bind(manager != null ? manager.aliases().get(position) : null);
+        }
+
+        @Override
+        public int getItemCount() {
+            return manager == null ? 0 : manager.aliases().size();
+        }
+    }
+
+    class AliasViewHolder extends RecyclerView.ViewHolder {
+        @Bind(R.id.alias_name)
+        TextView name;
+
+        @Bind(R.id.alias_description)
+        TextView description;
+
+        private AliasManager.Alias alias;
+
+        public AliasViewHolder(View itemView) {
+            super(itemView);
+            ButterKnife.bind(this, itemView);
+            itemView.setOnClickListener(view -> clickListener.onClick(alias));
+        }
+
+        public void bind(AliasManager.Alias alias) {
+            this.alias = alias;
+            name.setText(alias == null ? "" : alias.name);
+            description.setText(alias == null ? "" : alias.expansion);
+        }
+    }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/helper/AliasSerializerHelper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/helper/AliasSerializerHelper.java
new file mode 100644
index 000000000..e5ba9919a
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/coresettings/aliases/helper/AliasSerializerHelper.java
@@ -0,0 +1,45 @@
+/*
+ * QuasselDroid - Quassel client for Android
+ * Copyright (C) 2016 Janne Koschinski
+ * Copyright (C) 2016 Ken Børge Viktil
+ * Copyright (C) 2016 Magnus Fjell
+ * Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation, either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package de.kuschku.quasseldroid_ng.ui.coresettings.aliases.helper;
+
+import android.os.Bundle;
+
+import de.kuschku.libquassel.syncables.types.impl.AliasManager;
+
+public class AliasSerializerHelper {
+    private AliasSerializerHelper() {
+    }
+
+    public static Bundle serialize(AliasManager.Alias alias) {
+        Bundle bundle = new Bundle();
+        bundle.putString("name", alias.name);
+        bundle.putString("expansion", alias.expansion);
+        return bundle;
+    }
+
+    public static AliasManager.Alias deserialize(Bundle bundle) {
+        return new AliasManager.Alias(
+                bundle.getString("name"),
+                bundle.getString("expansion")
+        );
+    }
+}
diff --git a/app/src/main/java/de/kuschku/util/irc/format/IrcFormatSerializer.java b/app/src/main/java/de/kuschku/util/irc/format/IrcFormatSerializer.java
index 981ba6d88..f2de6ec06 100644
--- a/app/src/main/java/de/kuschku/util/irc/format/IrcFormatSerializer.java
+++ b/app/src/main/java/de/kuschku/util/irc/format/IrcFormatSerializer.java
@@ -27,7 +27,6 @@ import android.text.style.BackgroundColorSpan;
 import android.text.style.CharacterStyle;
 import android.text.style.ForegroundColorSpan;
 import android.text.style.UnderlineSpan;
-import android.util.Log;
 
 import java.util.Locale;
 
@@ -67,8 +66,6 @@ public class IrcFormatSerializer {
         boolean underline = false;
         boolean italic = false;
 
-        testLog(text);
-
         for (int i = start; i < end; i = next) {
             next = text.nextSpanTransition(i, end, CharacterStyle.class);
             CharacterStyle[] style = text.getSpans(i, next, CharacterStyle.class);
@@ -153,24 +150,4 @@ public class IrcFormatSerializer {
         if (bold || italic || underline || background != -1 || foreground != -1)
             out.append(CODE_RESET);
     }
-
-    private void testLog(Spanned text) {
-        StringBuilder out = new StringBuilder();
-        int next;
-        for (int i = 0; i < text.length(); i = next) {
-            next = text.nextSpanTransition(i, text.length(), CharacterStyle.class);
-            CharacterStyle[] styles = text.getSpans(i, next, CharacterStyle.class);
-
-            for (CharacterStyle style : styles) {
-                out.append("<").append(style.getClass().getSimpleName()).append(">");
-            }
-
-            out.append(text.subSequence(i, next));
-
-            for (int i1 = styles.length - 1; i1 >= 0; i1--) {
-                out.append("</").append(styles[i1].getClass().getSimpleName()).append(">");
-            }
-        }
-        Log.e("IrcFormat", String.valueOf(out));
-    }
 }
diff --git a/app/src/main/res/layout/activity_alias_edit.xml b/app/src/main/res/layout/activity_alias_edit.xml
new file mode 100644
index 000000000..df5b02c13
--- /dev/null
+++ b/app/src/main/res/layout/activity_alias_edit.xml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ QuasselDroid - Quassel client for Android
+  ~ Copyright (C) 2016 Janne Koschinski
+  ~ Copyright (C) 2016 Ken Børge Viktil
+  ~ Copyright (C) 2016 Magnus Fjell
+  ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify it
+  ~ under the terms of the GNU General Public License as published by the Free
+  ~ Software Foundation, either version 3 of the License, or (at your option)
+  ~ any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along
+  ~ with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/appBar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="?attr/actionBarTheme">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"/>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
+
+            <android.support.design.widget.TextInputLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <android.support.design.widget.TextInputEditText
+                    android:id="@+id/name"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/labelAliasName"/>
+
+            </android.support.design.widget.TextInputLayout>
+
+
+            <android.support.design.widget.TextInputLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <android.support.design.widget.TextInputEditText
+                    android:id="@+id/expansion"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:hint="@string/labelAliasExpansion"/>
+
+            </android.support.design.widget.TextInputLayout>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_chatlist_edit.xml b/app/src/main/res/layout/activity_chatlist_edit.xml
index ffc25b0bd..243e05c1a 100644
--- a/app/src/main/res/layout/activity_chatlist_edit.xml
+++ b/app/src/main/res/layout/activity_chatlist_edit.xml
@@ -49,7 +49,10 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:padding="16dp">
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
 
             <android.support.design.widget.TextInputLayout
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/activity_identity_edit.xml b/app/src/main/res/layout/activity_identity_edit.xml
index 5151fcf25..1edeb64e2 100644
--- a/app/src/main/res/layout/activity_identity_edit.xml
+++ b/app/src/main/res/layout/activity_identity_edit.xml
@@ -48,7 +48,10 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:padding="16dp">
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
 
             <android.support.design.widget.TextInputLayout
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/activity_ignorerule_edit.xml b/app/src/main/res/layout/activity_ignorerule_edit.xml
index 744d02d33..2a947c256 100644
--- a/app/src/main/res/layout/activity_ignorerule_edit.xml
+++ b/app/src/main/res/layout/activity_ignorerule_edit.xml
@@ -49,7 +49,10 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:padding="16dp">
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
 
             <LinearLayout
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/activity_network_edit.xml b/app/src/main/res/layout/activity_network_edit.xml
index 41a8a5bc2..936a509d1 100644
--- a/app/src/main/res/layout/activity_network_edit.xml
+++ b/app/src/main/res/layout/activity_network_edit.xml
@@ -49,7 +49,10 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:padding="16dp">
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
 
             <android.support.design.widget.TextInputLayout
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/activity_networkserver_edit.xml b/app/src/main/res/layout/activity_networkserver_edit.xml
index aa3a00bb3..947275caf 100644
--- a/app/src/main/res/layout/activity_networkserver_edit.xml
+++ b/app/src/main/res/layout/activity_networkserver_edit.xml
@@ -49,7 +49,10 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:orientation="vertical"
-            android:padding="16dp">
+            android:paddingBottom="16dp"
+            android:paddingLeft="?listPreferredItemPaddingLeft"
+            android:paddingRight="?listPreferredItemPaddingRight"
+            android:paddingTop="16dp">
 
             <android.support.design.widget.TextInputLayout
                 android:layout_width="match_parent"
diff --git a/app/src/main/res/layout/widget_settings_alias.xml b/app/src/main/res/layout/widget_settings_alias.xml
new file mode 100644
index 000000000..5a2706480
--- /dev/null
+++ b/app/src/main/res/layout/widget_settings_alias.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ QuasselDroid - Quassel client for Android
+  ~ Copyright (C) 2016 Janne Koschinski
+  ~ Copyright (C) 2016 Ken Børge Viktil
+  ~ Copyright (C) 2016 Magnus Fjell
+  ~ Copyright (C) 2016 Martin Sandsmark <martin.sandsmark@kde.org>
+  ~
+  ~ This program is free software: you can redistribute it and/or modify it
+  ~ under the terms of the GNU General Public License as published by the Free
+  ~ Software Foundation, either version 3 of the License, or (at your option)
+  ~ any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License along
+  ~ with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="56dp"
+              android:background="?selectableItemBackground"
+              android:clickable="true"
+              android:orientation="horizontal"
+              android:paddingLeft="?listPreferredItemPaddingLeft"
+              android:paddingRight="?listPreferredItemPaddingRight">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/alias_name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif-medium"
+            android:gravity="center_vertical|start"
+            android:lines="1"
+            android:singleLine="true"
+            android:textSize="@dimen/material_drawer_item_profile_text"/>
+
+        <TextView
+            android:id="@+id/alias_description"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:fontFamily="sans-serif"
+            android:gravity="center_vertical|start"
+            android:lines="1"
+            android:singleLine="true"
+            android:textSize="@dimen/material_drawer_item_profile_description"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/menu/chat.xml b/app/src/main/res/menu/chat.xml
index 2c9941432..3fd95e90d 100644
--- a/app/src/main/res/menu/chat.xml
+++ b/app/src/main/res/menu/chat.xml
@@ -47,6 +47,10 @@
         android:id="@+id/action_ignorelist"
         android:title="@string/actionIgnore"
         app:showAsAction="never"/>
+    <item
+        android:id="@+id/action_aliaslist"
+        android:title="@string/actionAlias"
+        app:showAsAction="never"/>
     <item
         android:id="@+id/action_reauth"
         android:title="@string/actionDisconnect"
diff --git a/app/src/main/res/values/strings_actions.xml b/app/src/main/res/values/strings_actions.xml
index b22f8452b..b00ff6fe2 100644
--- a/app/src/main/res/values/strings_actions.xml
+++ b/app/src/main/res/values/strings_actions.xml
@@ -21,7 +21,9 @@
   -->
 
 <resources>
+
     <string name="actionAddAccount">Add Account</string>
+    <string name="actionAlias">Aliases</string>
     <string name="actionCancel">Cancel</string>
     <string name="actionConnect">Connect</string>
     <string name="actionDelete">Delete</string>
@@ -34,7 +36,7 @@
     <string name="actionHideTemp">Hide Temporarily</string>
     <string name="actionHistory">Input History</string>
     <string name="actionIdentities">Identities</string>
-    <string name="actionIgnore">Ignore</string>
+    <string name="actionIgnore">Ignore List</string>
     <string name="actionJoin">Join</string>
     <string name="actionJoinChannel">Join Channel</string>
     <string name="actionLogin">Login</string>
diff --git a/app/src/main/res/values/strings_coresettings.xml b/app/src/main/res/values/strings_coresettings.xml
index a68570b6a..c61ad23b7 100644
--- a/app/src/main/res/values/strings_coresettings.xml
+++ b/app/src/main/res/values/strings_coresettings.xml
@@ -22,6 +22,9 @@
 
 <resources>
 
+    <string name="labelAliasExpansion">Expansion</string>
+    <string name="labelAliasName">Alias</string>
+
     <string name="labelChatlistAddNewChatsAutomatically">Add new chats automatically</string>
     <string name="labelChatlistHideInactiveChats">Hide inactive chats</string>
     <string name="labelChatlistHideInactiveNetworks">Hide inactive networks</string>
diff --git a/app/src/main/res/values/strings_titles.xml b/app/src/main/res/values/strings_titles.xml
index 18355311b..a6d2f907b 100644
--- a/app/src/main/res/values/strings_titles.xml
+++ b/app/src/main/res/values/strings_titles.xml
@@ -23,6 +23,8 @@
 <resources>
     <string name="titleAccountSelection">Account Selection</string>
     <string name="titleAccountSetup">Account Setup</string>
+    <string name="titleEditAlias">Edit Alias</string>
+    <string name="titleEditAliases">Edit Aliases</string>
     <string name="titleChannelDetails">Channel Details</string>
     <string name="titleChannelModes">Channel Modes</string>
     <string name="titleCoreSetup">Core Setup</string>
-- 
GitLab