diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 226aa378c09e8bd1fc9e89a9e60e669c392a1f3a..8299e64ebf21220f8de021b2d5c60d08d5725645 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -110,7 +110,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   // App Compat
   withVersion("27.1.1") {
@@ -166,7 +166,6 @@ dependencies {
 
   // UI
   implementation("me.zhanghai.android.materialprogressbar", "library", "1.4.2")
-  implementation("com.simplecityapps", "recyclerview-fastscroll", "1.0.18")
   withVersion("0.9.6.0") {
     implementation("com.afollestad.material-dialogs", "core", version)
     implementation("com.afollestad.material-dialogs", "commons", version)
diff --git a/app/sampledata/libraries.json b/app/sampledata/libraries.json
index 7fbe083e233112beb90777f9e89e29be1700f54c..22490ae5dcc76f6bd3e290cd1c7721f0d6241cf8 100644
--- a/app/sampledata/libraries.json
+++ b/app/sampledata/libraries.json
@@ -161,7 +161,7 @@
     },
     {
       "name": "Kotlin Standard Library",
-      "version": "1.2.50",
+      "version": "1.2.51",
       "license": {
         "short_name": "Apache-2.0",
         "full_name": "Apache License"
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
index ebcf54b35215f9c290ad6504e083280d0793d49d..f9d77a0ee1b1e634ad753bd5bb8df11ae48191d6 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/buffers/BufferListAdapter.kt
@@ -29,7 +29,6 @@ import android.widget.ImageView
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
 import de.kuschku.libquassel.protocol.BufferId
 import de.kuschku.libquassel.protocol.Buffer_Activity
 import de.kuschku.libquassel.protocol.NetworkId
@@ -39,6 +38,7 @@ import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.MessageSettings
 import de.kuschku.quasseldroid.util.helper.*
 import de.kuschku.quasseldroid.util.lists.ListAdapter
+import de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView
 import de.kuschku.quasseldroid.viewmodel.data.BufferListItem
 import de.kuschku.quasseldroid.viewmodel.data.BufferProps
 import de.kuschku.quasseldroid.viewmodel.data.BufferState
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
index ffb92f96aa7d9557e61bf2a262205769c20520b0..b009b83ae20e6fdd6c2f1ce053eb3913a5b30d04 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/chat/nicks/NickListAdapter.kt
@@ -29,7 +29,6 @@ import android.widget.ImageView
 import android.widget.TextView
 import butterknife.BindView
 import butterknife.ButterKnife
-import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
 import de.kuschku.libquassel.util.helpers.nullIf
 import de.kuschku.quasseldroid.R
 import de.kuschku.quasseldroid.settings.MessageSettings
@@ -37,6 +36,7 @@ import de.kuschku.quasseldroid.util.helper.letIf
 import de.kuschku.quasseldroid.util.helper.loadAvatars
 import de.kuschku.quasseldroid.util.helper.visibleIf
 import de.kuschku.quasseldroid.util.ui.SpanFormatter
+import de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView
 import de.kuschku.quasseldroid.viewmodel.data.IrcUserItem
 
 class NickListAdapter(
diff --git a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
index e0cc24042eeeefb134eafcad2630177bbc1f1f24..4611a1feba47b8103a92c5b641ead3c584476f9b 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/ui/clientsettings/about/AboutFragment.kt
@@ -227,7 +227,7 @@ class AboutFragment : DaggerFragment() {
       ),
       Library(
         name = "Kotlin Standard Library",
-        version = "1.2.50",
+        version = "1.2.51",
         license = apache2,
         url = "https://kotlinlang.org/"
       ),
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixApi.kt b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixApi.kt
index 842a5e0bfb6a408582c070459743bb83e7de8746..67319783bd7e0d3c971e0447d44a33f869352380 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixApi.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/avatars/MatrixApi.kt
@@ -29,7 +29,7 @@ interface MatrixApi {
   @GET("/_matrix/client/r0/profile/{name}/avatar_url")
   fun avatarUrl(@Path("name") name: String): Call<MatrixAvatarResponse>
 
-  @GET("/_matrix/media/r0/thumbnail/{server}/{id}/?width=32&height=32&method=crop")
+  @GET("/_matrix/media/r0/thumbnail/{server}/{id}/?width={width}&height={height}&method={method]")
   fun avatarThumbnail(
     @Path("server") server: String,
     @Path("id") id: String,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/DrawerRecyclerView.kt b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DrawerRecyclerView.kt
index e4cb9d8cc30ff18fd88f7f5586a22069f7dbcefc..dd5417b104a0ffa5ab69f220750ef4587f43b617 100644
--- a/app/src/main/java/de/kuschku/quasseldroid/util/ui/DrawerRecyclerView.kt
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/DrawerRecyclerView.kt
@@ -25,8 +25,8 @@ import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import android.support.v4.view.ViewCompat
 import android.util.AttributeSet
-import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
 import de.kuschku.quasseldroid.R
+import de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView
 
 class DrawerRecyclerView @JvmOverloads constructor(
   context: Context,
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/interfaces/OnFastScrollStateChangeListener.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/interfaces/OnFastScrollStateChangeListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..a44eb339387980a2d0d82775d656fb8ea97552ce
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/interfaces/OnFastScrollStateChangeListener.java
@@ -0,0 +1,14 @@
+package de.kuschku.quasseldroid.util.ui.fastscroll.interfaces;
+
+public interface OnFastScrollStateChangeListener {
+
+  /**
+   * Called when fast scrolling begins
+   */
+  void onFastScrollStart();
+
+  /**
+   * Called when fast scrolling ends
+   */
+  void onFastScrollStop();
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/utils/Utils.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/utils/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d1c548b419cba3eb7ada2bc644f8d837dc5d8bf8
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/utils/Utils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2016 Tim Malseed
+ *
+ *  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.util.ui.fastscroll.utils;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.os.Build;
+import android.util.TypedValue;
+import android.view.View;
+
+public class Utils {
+
+  /**
+   * Converts dp to px
+   *
+   * @param res Resources
+   * @param dp  the value in dp
+   * @return int
+   */
+  public static int toPixels(Resources res, float dp) {
+    return (int) (dp * res.getDisplayMetrics().density);
+  }
+
+  /**
+   * Converts sp to px
+   *
+   * @param res Resources
+   * @param sp  the value in sp
+   * @return int
+   */
+  public static int toScreenPixels(Resources res, float sp) {
+    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, res.getDisplayMetrics());
+  }
+
+  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+  public static boolean isRtl(Resources res) {
+    return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) &&
+      (res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollPopup.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollPopup.java
new file mode 100644
index 0000000000000000000000000000000000000000..acc9fa585a545ea2620bdd2d060178b835e1d208
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollPopup.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2016 Tim Malseed
+ *
+ *  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.util.ui.fastscroll.views;
+
+import android.animation.ObjectAnimator;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.support.annotation.Keep;
+import android.text.TextUtils;
+
+import de.kuschku.quasseldroid.util.ui.fastscroll.utils.Utils;
+
+public class FastScrollPopup {
+
+  private FastScrollRecyclerView mRecyclerView;
+
+  private Resources mRes;
+
+  private int mBackgroundSize;
+  private int mCornerRadius;
+
+  private Path mBackgroundPath = new Path();
+  private RectF mBackgroundRect = new RectF();
+  private Paint mBackgroundPaint;
+  private int mBackgroundColor = 0xff000000;
+
+  private Rect mInvalidateRect = new Rect();
+  private Rect mTmpRect = new Rect();
+
+  // The absolute bounds of the fast scroller bg
+  private Rect mBgBounds = new Rect();
+
+  private String mSectionName;
+
+  private Paint mTextPaint;
+  private Rect mTextBounds = new Rect();
+
+  private float mAlpha = 1;
+
+  private ObjectAnimator mAlphaAnimator;
+  private boolean mVisible;
+
+  @FastScroller.FastScrollerPopupPosition
+  private int mPosition;
+
+  FastScrollPopup(Resources resources, FastScrollRecyclerView recyclerView) {
+
+    mRes = resources;
+
+    mRecyclerView = recyclerView;
+
+    mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    mTextPaint.setAlpha(0);
+
+    setTextSize(Utils.toScreenPixels(mRes, 44));
+    setBackgroundSize(Utils.toPixels(mRes, 88));
+  }
+
+  public void setBgColor(int color) {
+    mBackgroundColor = color;
+    mBackgroundPaint.setColor(color);
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  public void setTextColor(int color) {
+    mTextPaint.setColor(color);
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  public void setTextSize(int size) {
+    mTextPaint.setTextSize(size);
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  public void setBackgroundSize(int size) {
+    mBackgroundSize = size;
+    mCornerRadius = mBackgroundSize / 2;
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  public void setTypeface(Typeface typeface) {
+    mTextPaint.setTypeface(typeface);
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  /**
+   * Animates the visibility of the fast scroller popup.
+   */
+  public void animateVisibility(boolean visible) {
+    if (mVisible != visible) {
+      mVisible = visible;
+      if (mAlphaAnimator != null) {
+        mAlphaAnimator.cancel();
+      }
+      mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", visible ? 1f : 0f);
+      mAlphaAnimator.setDuration(visible ? 200 : 150);
+      mAlphaAnimator.start();
+    }
+  }
+
+  @Keep
+  public float getAlpha() {
+    return mAlpha;
+  }
+
+  // Setter/getter for the popup alpha for animations
+  @Keep
+  public void setAlpha(float alpha) {
+    mAlpha = alpha;
+    mRecyclerView.invalidate(mBgBounds);
+  }
+
+  @FastScroller.FastScrollerPopupPosition
+  public int getPopupPosition() {
+    return mPosition;
+  }
+
+  public void setPopupPosition(@FastScroller.FastScrollerPopupPosition int position) {
+    mPosition = position;
+  }
+
+  private float[] createRadii() {
+    if (mPosition == FastScroller.FastScrollerPopupPosition.CENTER) {
+      return new float[]{mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius};
+    }
+
+    if (Utils.isRtl(mRes)) {
+      return new float[]{mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, 0, 0};
+    } else {
+      return new float[]{mCornerRadius, mCornerRadius, mCornerRadius, mCornerRadius, 0, 0, mCornerRadius, mCornerRadius};
+    }
+  }
+
+  public void draw(Canvas canvas) {
+    if (isVisible()) {
+      // Draw the fast scroller popup
+      int restoreCount = canvas.save();
+      canvas.translate(mBgBounds.left, mBgBounds.top);
+      mTmpRect.set(mBgBounds);
+      mTmpRect.offsetTo(0, 0);
+
+      mBackgroundPath.reset();
+      mBackgroundRect.set(mTmpRect);
+
+      float[] radii = createRadii();
+
+      mBackgroundPath.addRoundRect(mBackgroundRect, radii, Path.Direction.CW);
+
+      mBackgroundPaint.setAlpha((int) (Color.alpha(mBackgroundColor) * mAlpha));
+      mTextPaint.setAlpha((int) (mAlpha * 255));
+      canvas.drawPath(mBackgroundPath, mBackgroundPaint);
+      canvas.drawText(mSectionName, (mBgBounds.width() - mTextBounds.width()) / 2,
+        mBgBounds.height() - (mBgBounds.height() - mTextBounds.height()) / 2,
+        mTextPaint);
+      canvas.restoreToCount(restoreCount);
+    }
+  }
+
+  public void setSectionName(String sectionName) {
+    if (!sectionName.equals(mSectionName)) {
+      mSectionName = sectionName;
+      mTextPaint.getTextBounds(sectionName, 0, sectionName.length(), mTextBounds);
+      // Update the width to use measureText since that is more accurate
+      mTextBounds.right = (int) (mTextBounds.left + mTextPaint.measureText(sectionName));
+    }
+  }
+
+  /**
+   * Updates the bounds for the fast scroller.
+   *
+   * @return the invalidation rect for this update.
+   */
+  public Rect updateFastScrollerBounds(FastScrollRecyclerView recyclerView, int thumbOffsetY) {
+    mInvalidateRect.set(mBgBounds);
+
+    mBgBounds.top += recyclerView.getPaddingTop();
+    mBgBounds.left += recyclerView.getPaddingLeft();
+    mBgBounds.right -= recyclerView.getPaddingRight();
+    mBgBounds.bottom -= recyclerView.getPaddingBottom();
+
+    if (isVisible()) {
+      // Calculate the dimensions and position of the fast scroller popup
+      int edgePadding = recyclerView.getScrollBarWidth();
+      int bgPadding = Math.round((mBackgroundSize - mTextBounds.height()) / 10) * 5;
+      int bgHeight = mBackgroundSize;
+      int bgWidth = Math.max(mBackgroundSize, mTextBounds.width() + (2 * bgPadding));
+      if (mPosition == FastScroller.FastScrollerPopupPosition.CENTER) {
+        mBgBounds.left = (recyclerView.getWidth() - bgWidth) / 2;
+        mBgBounds.right = mBgBounds.left + bgWidth;
+        mBgBounds.top = (recyclerView.getHeight() - bgHeight) / 2;
+      } else {
+        if (Utils.isRtl(mRes)) {
+          mBgBounds.left = (2 * recyclerView.getScrollBarWidth());
+          mBgBounds.right = mBgBounds.left + bgWidth;
+        } else {
+          mBgBounds.right = recyclerView.getWidth() - (2 * recyclerView.getScrollBarWidth());
+          mBgBounds.left = mBgBounds.right - bgWidth;
+        }
+        mBgBounds.top = recyclerView.getPaddingTop() + thumbOffsetY - bgHeight + recyclerView.getScrollBarThumbHeight() / 2;
+        mBgBounds.top = Math.max(edgePadding + recyclerView.getPaddingTop(), Math.min(mBgBounds.top, recyclerView.getHeight() - edgePadding - bgHeight - recyclerView.getPaddingBottom()));
+      }
+      mBgBounds.bottom = mBgBounds.top + bgHeight;
+    } else {
+      mBgBounds.setEmpty();
+    }
+
+    // Combine the old and new fast scroller bounds to create the full invalidate rect
+    mInvalidateRect.union(mBgBounds);
+    return mInvalidateRect;
+  }
+
+  public boolean isVisible() {
+    return (mAlpha > 0f) && (!TextUtils.isEmpty(mSectionName));
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollRecyclerView.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollRecyclerView.java
new file mode 100644
index 0000000000000000000000000000000000000000..87ae6432071a54be3f47e52db265ccfd150228e8
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScrollRecyclerView.java
@@ -0,0 +1,561 @@
+/*
+ * Copyright (c) 2016 Tim Malseed
+ *
+ *  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.util.ui.fastscroll.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Typeface;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseIntArray;
+import android.view.MotionEvent;
+import android.view.View;
+
+import de.kuschku.quasseldroid.R;
+import de.kuschku.quasseldroid.util.ui.fastscroll.interfaces.OnFastScrollStateChangeListener;
+import de.kuschku.quasseldroid.util.ui.fastscroll.utils.Utils;
+
+public class FastScrollRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener {
+
+  private static final String TAG = "FastScrollRecyclerView";
+
+  private FastScroller mScrollbar;
+
+  private boolean mFastScrollEnabled = true;
+  private ScrollPositionState mScrollPosState = new ScrollPositionState();
+  private int mDownX;
+  private int mDownY;
+  private int mLastY;
+  private SparseIntArray mScrollOffsets;
+  private ScrollOffsetInvalidator mScrollOffsetInvalidator;
+  private OnFastScrollStateChangeListener mStateChangeListener;
+
+  public FastScrollRecyclerView(Context context) {
+    this(context, null);
+  }
+
+  public FastScrollRecyclerView(Context context, AttributeSet attrs) {
+    this(context, attrs, 0);
+  }
+
+  public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+
+    TypedArray typedArray = context.getTheme().obtainStyledAttributes(
+      attrs, R.styleable.FastScrollRecyclerView, 0, 0);
+    try {
+      mFastScrollEnabled = typedArray.getBoolean(R.styleable.FastScrollRecyclerView_fastScrollThumbEnabled, true);
+    } finally {
+      typedArray.recycle();
+    }
+
+    mScrollbar = new FastScroller(context, this, attrs);
+    mScrollOffsetInvalidator = new ScrollOffsetInvalidator();
+    mScrollOffsets = new SparseIntArray();
+  }
+
+  public int getScrollBarWidth() {
+    return mScrollbar.getWidth();
+  }
+
+  public int getScrollBarThumbHeight() {
+    return mScrollbar.getThumbHeight();
+  }
+
+  @Override
+  protected void onFinishInflate() {
+    super.onFinishInflate();
+    addOnItemTouchListener(this);
+  }
+
+  @Override
+  public void setAdapter(Adapter adapter) {
+    if (getAdapter() != null) {
+      getAdapter().unregisterAdapterDataObserver(mScrollOffsetInvalidator);
+    }
+
+    if (adapter != null) {
+      adapter.registerAdapterDataObserver(mScrollOffsetInvalidator);
+    }
+
+    super.setAdapter(adapter);
+  }
+
+  /**
+   * We intercept the touch handling only to support fast scrolling when initiated from the
+   * scroll bar.  Otherwise, we fall back to the default RecyclerView touch handling.
+   */
+  @Override
+  public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent ev) {
+    return handleTouchEvent(ev);
+  }
+
+  @Override
+  public void onTouchEvent(RecyclerView rv, MotionEvent ev) {
+    handleTouchEvent(ev);
+  }
+
+  /**
+   * Handles the touch event and determines whether to show the fast scroller (or updates it if
+   * it is already showing).
+   */
+  private boolean handleTouchEvent(MotionEvent ev) {
+    int action = ev.getAction();
+    int x = (int) ev.getX();
+    int y = (int) ev.getY();
+    switch (action) {
+      case MotionEvent.ACTION_DOWN:
+        // Keep track of the down positions
+        mDownX = x;
+        mDownY = mLastY = y;
+        mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
+        break;
+      case MotionEvent.ACTION_MOVE:
+        mLastY = y;
+        mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
+        break;
+      case MotionEvent.ACTION_UP:
+      case MotionEvent.ACTION_CANCEL:
+        mScrollbar.handleTouchEvent(ev, mDownX, mDownY, mLastY, mStateChangeListener);
+        break;
+    }
+    return mScrollbar.isDragging();
+  }
+
+  @Override
+  public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+
+  }
+
+  /**
+   * Returns the available scroll height:
+   * AvailableScrollHeight = Total height of the all items - last page height
+   *
+   * @param yOffset the offset from the top of the recycler view to start tracking.
+   */
+  protected int getAvailableScrollHeight(int adapterHeight, int yOffset) {
+    int visibleHeight = getHeight();
+    int scrollHeight = getPaddingTop() + yOffset + adapterHeight + getPaddingBottom();
+    int availableScrollHeight = scrollHeight - visibleHeight;
+    return availableScrollHeight;
+  }
+
+  /**
+   * Returns the available scroll bar height:
+   * AvailableScrollBarHeight = Total height of the visible view - thumb height
+   */
+  protected int getAvailableScrollBarHeight() {
+    int visibleHeight = getHeight();
+    int availableScrollBarHeight = visibleHeight - mScrollbar.getThumbHeight();
+    return availableScrollBarHeight;
+  }
+
+  @Override
+  public void draw(Canvas c) {
+    super.draw(c);
+    if (mFastScrollEnabled) {
+      onUpdateScrollbar();
+      mScrollbar.draw(c);
+    }
+  }
+
+  /**
+   * Updates the scrollbar thumb offset to match the visible scroll of the recycler view.  It does
+   * this by mapping the available scroll area of the recycler view to the available space for the
+   * scroll bar.
+   *
+   * @param scrollPosState the current scroll position
+   * @param rowCount       the number of rows, used to calculate the total scroll height (assumes that
+   */
+  protected void updateThumbPosition(ScrollPositionState scrollPosState, int rowCount) {
+
+    int availableScrollHeight;
+    int availableScrollBarHeight;
+    int scrolledPastHeight;
+
+    if (getAdapter() instanceof MeasurableAdapter) {
+      availableScrollHeight = getAvailableScrollHeight(calculateAdapterHeight(), 0);
+      scrolledPastHeight = calculateScrollDistanceToPosition(scrollPosState.rowIndex);
+    } else {
+      availableScrollHeight = getAvailableScrollHeight(rowCount * scrollPosState.rowHeight, 0);
+      scrolledPastHeight = scrollPosState.rowIndex * scrollPosState.rowHeight;
+    }
+
+    availableScrollBarHeight = getAvailableScrollBarHeight();
+
+    // Only show the scrollbar if there is height to be scrolled
+    if (availableScrollHeight <= 0) {
+      mScrollbar.setThumbPosition(-1, -1);
+      return;
+    }
+
+    // Calculate the current scroll position, the scrollY of the recycler view accounts for the
+    // view padding, while the scrollBarY is drawn right up to the background padding (ignoring
+    // padding)
+    int scrollY = getPaddingTop() + scrolledPastHeight - scrollPosState.rowTopOffset;
+    int scrollBarY = (int) (((float) scrollY / availableScrollHeight) * availableScrollBarHeight);
+
+    // Calculate the position and size of the scroll bar
+    int scrollBarX;
+    if (Utils.isRtl(getResources())) {
+      scrollBarX = 0;
+    } else {
+      scrollBarX = getWidth() - mScrollbar.getWidth();
+    }
+    mScrollbar.setThumbPosition(scrollBarX, scrollBarY);
+  }
+
+  /**
+   * Maps the touch (from 0..1) to the adapter position that should be visible.
+   */
+  public String scrollToPositionAtProgress(float touchFraction) {
+    int itemCount = getAdapter().getItemCount();
+    if (itemCount == 0) {
+      return "";
+    }
+    int spanCount = 1;
+    int rowCount = itemCount;
+    if (getLayoutManager() instanceof GridLayoutManager) {
+      spanCount = ((GridLayoutManager) getLayoutManager()).getSpanCount();
+      rowCount = (int) Math.ceil((double) rowCount / spanCount);
+    }
+
+    // Stop the scroller if it is scrolling
+    stopScroll();
+
+    getCurScrollState(mScrollPosState);
+
+    float itemPos;
+    int availableScrollHeight;
+
+    int scrollPosition;
+    int scrollOffset;
+
+    if (getAdapter() instanceof MeasurableAdapter) {
+      itemPos = findItemPosition(touchFraction);
+      availableScrollHeight = calculateAdapterHeight();
+      scrollPosition = (int) itemPos;
+      scrollOffset = calculateScrollDistanceToPosition(scrollPosition) - (int) (touchFraction * availableScrollHeight);
+    } else {
+      itemPos = findItemPosition(touchFraction);
+      availableScrollHeight = getAvailableScrollHeight(rowCount * mScrollPosState.rowHeight, 0);
+
+      //The exact position of our desired item
+      int exactItemPos = (int) (availableScrollHeight * touchFraction);
+
+      //The offset used here is kind of hard to explain.
+      //If the position we wish to scroll to is, say, position 10.5, we scroll to position 10,
+      //and then offset by 0.5 * rowHeight. This is how we achieve smooth scrolling.
+      scrollPosition = spanCount * exactItemPos / mScrollPosState.rowHeight;
+      scrollOffset = -(exactItemPos % mScrollPosState.rowHeight);
+    }
+
+    LinearLayoutManager layoutManager = ((LinearLayoutManager) getLayoutManager());
+    layoutManager.scrollToPositionWithOffset(scrollPosition, scrollOffset);
+
+    if (!(getAdapter() instanceof SectionedAdapter)) {
+      return "";
+    }
+
+    int posInt = (int) ((touchFraction == 1) ? itemPos - 1 : itemPos);
+
+    SectionedAdapter sectionedAdapter = (SectionedAdapter) getAdapter();
+    return sectionedAdapter.getSectionName(posInt);
+  }
+
+  @SuppressWarnings("unchecked")
+  private float findItemPosition(float touchFraction) {
+
+    if (getAdapter() instanceof MeasurableAdapter) {
+      MeasurableAdapter measurer = (MeasurableAdapter) getAdapter();
+      int viewTop = (int) (touchFraction * calculateAdapterHeight());
+
+      for (int i = 0; i < getAdapter().getItemCount(); i++) {
+        int top = calculateScrollDistanceToPosition(i);
+        int bottom = top + measurer.getViewTypeHeight(this, findViewHolderForAdapterPosition(i), getAdapter().getItemViewType(i));
+        if (viewTop >= top && viewTop <= bottom) {
+          return i;
+        }
+      }
+
+      // Should never happen
+      Log.w(TAG, "Failed to find a view at the provided scroll fraction (" + touchFraction + ")");
+      return touchFraction * getAdapter().getItemCount();
+    } else {
+      return getAdapter().getItemCount() * touchFraction;
+    }
+  }
+
+  /**
+   * Updates the bounds for the scrollbar.
+   */
+  public void onUpdateScrollbar() {
+
+    if (getAdapter() == null) {
+      return;
+    }
+
+    int rowCount = getAdapter().getItemCount();
+    if (getLayoutManager() instanceof GridLayoutManager) {
+      int spanCount = ((GridLayoutManager) getLayoutManager()).getSpanCount();
+      rowCount = (int) Math.ceil((double) rowCount / spanCount);
+    }
+    // Skip early if, there are no items.
+    if (rowCount == 0) {
+      mScrollbar.setThumbPosition(-1, -1);
+      return;
+    }
+
+    // Skip early if, there no child laid out in the container.
+    getCurScrollState(mScrollPosState);
+    if (mScrollPosState.rowIndex < 0) {
+      mScrollbar.setThumbPosition(-1, -1);
+      return;
+    }
+
+    updateThumbPosition(mScrollPosState, rowCount);
+  }
+
+  /**
+   * Returns the current scroll state of the apps rows.
+   */
+  private void getCurScrollState(ScrollPositionState stateOut) {
+    stateOut.rowIndex = -1;
+    stateOut.rowTopOffset = -1;
+    stateOut.rowHeight = -1;
+
+    int itemCount = getAdapter().getItemCount();
+
+    // Return early if there are no items, or no children.
+    if (itemCount == 0 || getChildCount() == 0) {
+      return;
+    }
+
+    View child = getChildAt(0);
+
+    stateOut.rowIndex = getChildAdapterPosition(child);
+    if (getLayoutManager() instanceof GridLayoutManager) {
+      stateOut.rowIndex = stateOut.rowIndex / ((GridLayoutManager) getLayoutManager()).getSpanCount();
+    }
+    stateOut.rowTopOffset = getLayoutManager().getDecoratedTop(child);
+    stateOut.rowHeight = child.getHeight() + getLayoutManager().getTopDecorationHeight(child)
+      + getLayoutManager().getBottomDecorationHeight(child);
+  }
+
+  /**
+   * Calculates the total height of all views above a position in the recycler view. This method
+   * should only be called when the attached adapter implements {@link MeasurableAdapter}.
+   *
+   * @param adapterIndex The index in the adapter to find the total height above the
+   *                     corresponding view
+   * @return The total height of all views above {@code adapterIndex} in pixels
+   */
+  @SuppressWarnings("unchecked")
+  private int calculateScrollDistanceToPosition(int adapterIndex) {
+    if (!(getAdapter() instanceof MeasurableAdapter)) {
+      throw new IllegalStateException("calculateScrollDistanceToPosition() should only be called where the RecyclerView.Adapter is an instance of MeasurableAdapter");
+    }
+
+    if (mScrollOffsets.indexOfKey(adapterIndex) >= 0) {
+      return mScrollOffsets.get(adapterIndex);
+    }
+
+    int totalHeight = 0;
+    MeasurableAdapter measurer = (MeasurableAdapter) getAdapter();
+
+    // TODO Take grid layouts into account
+
+    for (int i = 0; i < adapterIndex; i++) {
+      mScrollOffsets.put(i, totalHeight);
+      int viewType = getAdapter().getItemViewType(i);
+      totalHeight += measurer.getViewTypeHeight(this, findViewHolderForAdapterPosition(i), viewType);
+    }
+
+    mScrollOffsets.put(adapterIndex, totalHeight);
+    return totalHeight;
+  }
+
+  /**
+   * Calculates the total height of the recycler view. This method should only be called when the
+   * attached adapter implements {@link MeasurableAdapter}.
+   *
+   * @return The total height of all rows in the RecyclerView
+   */
+  private int calculateAdapterHeight() {
+    if (!(getAdapter() instanceof MeasurableAdapter)) {
+      throw new IllegalStateException("calculateAdapterHeight() should only be called where the RecyclerView.Adapter is an instance of MeasurableAdapter");
+    }
+    return calculateScrollDistanceToPosition(getAdapter().getItemCount());
+  }
+
+  public void showScrollbar() {
+    mScrollbar.show();
+  }
+
+  public void setThumbColor(@ColorInt int color) {
+    mScrollbar.setThumbColor(color);
+  }
+
+  public void setTrackColor(@ColorInt int color) {
+    mScrollbar.setTrackColor(color);
+  }
+
+  public void setPopupBgColor(@ColorInt int color) {
+    mScrollbar.setPopupBgColor(color);
+  }
+
+  public void setPopupTextColor(@ColorInt int color) {
+    mScrollbar.setPopupTextColor(color);
+  }
+
+  public void setPopupTextSize(int textSize) {
+    mScrollbar.setPopupTextSize(textSize);
+  }
+
+  public void setPopUpTypeface(Typeface typeface) {
+    mScrollbar.setPopupTypeface(typeface);
+  }
+
+  public void setAutoHideDelay(int hideDelay) {
+    mScrollbar.setAutoHideDelay(hideDelay);
+  }
+
+  public void setAutoHideEnabled(boolean autoHideEnabled) {
+    mScrollbar.setAutoHideEnabled(autoHideEnabled);
+  }
+
+  public void setOnFastScrollStateChangeListener(OnFastScrollStateChangeListener stateChangeListener) {
+    mStateChangeListener = stateChangeListener;
+  }
+
+  @Deprecated
+  public void setStateChangeListener(OnFastScrollStateChangeListener stateChangeListener) {
+    setOnFastScrollStateChangeListener(stateChangeListener);
+  }
+
+  public void setThumbInactiveColor(@ColorInt int color) {
+    mScrollbar.setThumbInactiveColor(color);
+  }
+
+  public void allowThumbInactiveColor(boolean allowInactiveColor) {
+    mScrollbar.enableThumbInactiveColor(allowInactiveColor);
+  }
+
+  @Deprecated
+  public void setThumbInactiveColor(boolean allowInactiveColor) {
+    allowThumbInactiveColor(allowInactiveColor);
+  }
+
+  public void setFastScrollEnabled(boolean fastScrollEnabled) {
+    mFastScrollEnabled = fastScrollEnabled;
+  }
+
+  @Deprecated
+  public void setThumbEnabled(boolean thumbEnabled) {
+    setFastScrollEnabled(thumbEnabled);
+  }
+
+  /**
+   * Set the FastScroll Popup position. This is either {@link FastScroller.FastScrollerPopupPosition#ADJACENT},
+   * meaning the popup moves adjacent to the FastScroll thumb, or {@link FastScroller.FastScrollerPopupPosition#CENTER},
+   * meaning the popup is static and centered within the RecyclerView.
+   */
+  public void setPopupPosition(@FastScroller.FastScrollerPopupPosition int popupPosition) {
+    mScrollbar.setPopupPosition(popupPosition);
+  }
+
+  public interface SectionedAdapter {
+    @NonNull
+    String getSectionName(int position);
+  }
+
+  /**
+   * FastScrollRecyclerView by default assumes that all items in a RecyclerView will have
+   * ItemViews with the same heights so that the total height of all views in the RecyclerView
+   * can be calculated. If your list uses different view heights, then make your adapter implement
+   * this interface.
+   */
+  public interface MeasurableAdapter<VH extends ViewHolder> {
+    /**
+     * Gets the height of a specific view type, including item decorations
+     *
+     * @param recyclerView The recyclerView that this item view will be placed in
+     * @param viewHolder   The viewHolder that corresponds to this item view
+     * @param viewType     The view type to get the height of
+     * @return The height of a single view for the given view type in pixels
+     */
+    int getViewTypeHeight(RecyclerView recyclerView, @Nullable VH viewHolder, int viewType);
+  }
+
+  /**
+   * The current scroll state of the recycler view.  We use this in onUpdateScrollbar()
+   * and scrollToPositionAtProgress() to determine the scroll position of the recycler view so
+   * that we can calculate what the scroll bar looks like, and where to jump to from the fast
+   * scroller.
+   */
+  public static class ScrollPositionState {
+    // The index of the first visible row
+    int rowIndex;
+    // The offset of the first visible row
+    int rowTopOffset;
+    // The height of a given row (they are currently all the same height)
+    int rowHeight;
+  }
+
+  private class ScrollOffsetInvalidator extends AdapterDataObserver {
+    private void invalidateAllScrollOffsets() {
+      mScrollOffsets.clear();
+    }
+
+    @Override
+    public void onChanged() {
+      invalidateAllScrollOffsets();
+    }
+
+    @Override
+    public void onItemRangeChanged(int positionStart, int itemCount) {
+      invalidateAllScrollOffsets();
+    }
+
+    @Override
+    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+      invalidateAllScrollOffsets();
+    }
+
+    @Override
+    public void onItemRangeInserted(int positionStart, int itemCount) {
+      invalidateAllScrollOffsets();
+    }
+
+    @Override
+    public void onItemRangeRemoved(int positionStart, int itemCount) {
+      invalidateAllScrollOffsets();
+    }
+
+    @Override
+    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+      invalidateAllScrollOffsets();
+    }
+  }
+}
diff --git a/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScroller.java b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScroller.java
new file mode 100644
index 0000000000000000000000000000000000000000..1cc7cee4fde78e199ff8fbd88ab355457e07259d
--- /dev/null
+++ b/app/src/main/java/de/kuschku/quasseldroid/util/ui/fastscroll/views/FastScroller.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2016 Tim Malseed
+ *
+ *  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.util.ui.fastscroll.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.support.annotation.ColorInt;
+import android.support.annotation.IntDef;
+import android.support.annotation.Keep;
+import android.support.v4.view.animation.FastOutLinearInInterpolator;
+import android.support.v4.view.animation.LinearOutSlowInInterpolator;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import java.lang.annotation.Retention;
+
+import de.kuschku.quasseldroid.R;
+import de.kuschku.quasseldroid.util.ui.fastscroll.interfaces.OnFastScrollStateChangeListener;
+import de.kuschku.quasseldroid.util.ui.fastscroll.utils.Utils;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+public class FastScroller {
+  private static final int DEFAULT_AUTO_HIDE_DELAY = 1500;
+  private final Runnable mHideRunnable;
+  private FastScrollRecyclerView mRecyclerView;
+  private FastScrollPopup mPopup;
+  private int mThumbHeight;
+  private int mWidth;
+  private Paint mThumb;
+  private Paint mTrack;
+  private Rect mTmpRect = new Rect();
+  private Rect mInvalidateRect = new Rect();
+  private Rect mInvalidateTmpRect = new Rect();
+  // The inset is the buffer around which a point will still register as a click on the scrollbar
+  private int mTouchInset;
+  // This is the offset from the top of the scrollbar when the user first starts touching.  To
+  // prevent jumping, this offset is applied as the user scrolls.
+  private int mTouchOffset;
+  private Point mThumbPosition = new Point(-1, -1);
+  private Point mOffset = new Point(0, 0);
+  private boolean mIsDragging;
+  private Animator mAutoHideAnimator;
+  private boolean mAnimatingShow;
+  private int mAutoHideDelay = DEFAULT_AUTO_HIDE_DELAY;
+  private boolean mAutoHideEnabled = true;
+  private int mThumbActiveColor;
+  private int mThumbInactiveColor = 0x79000000;
+  private boolean mThumbInactiveState;
+
+  public FastScroller(Context context, FastScrollRecyclerView recyclerView, AttributeSet attrs) {
+
+    Resources resources = context.getResources();
+
+    mRecyclerView = recyclerView;
+    mPopup = new FastScrollPopup(resources, recyclerView);
+
+    mThumbHeight = Utils.toPixels(resources, 48);
+    mWidth = Utils.toPixels(resources, 8);
+
+    mTouchInset = Utils.toPixels(resources, -24);
+
+    mThumb = new Paint(Paint.ANTI_ALIAS_FLAG);
+    mTrack = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+    TypedArray typedArray = context.getTheme().obtainStyledAttributes(
+      attrs, R.styleable.FastScrollRecyclerView, 0, 0);
+    try {
+      mAutoHideEnabled = typedArray.getBoolean(R.styleable.FastScrollRecyclerView_fastScrollAutoHide, true);
+      mAutoHideDelay = typedArray.getInteger(R.styleable.FastScrollRecyclerView_fastScrollAutoHideDelay, DEFAULT_AUTO_HIDE_DELAY);
+      mThumbInactiveState = typedArray.getBoolean(R.styleable.FastScrollRecyclerView_fastScrollEnableThumbInactiveColor, true);
+      mThumbActiveColor = typedArray.getColor(R.styleable.FastScrollRecyclerView_fastScrollThumbColor, 0x79000000);
+      mThumbInactiveColor = typedArray.getColor(R.styleable.FastScrollRecyclerView_fastScrollThumbInactiveColor, 0x79000000);
+
+      int trackColor = typedArray.getColor(R.styleable.FastScrollRecyclerView_fastScrollTrackColor, 0x28000000);
+      int popupBgColor = typedArray.getColor(R.styleable.FastScrollRecyclerView_fastScrollPopupBgColor, 0xff000000);
+      int popupTextColor = typedArray.getColor(R.styleable.FastScrollRecyclerView_fastScrollPopupTextColor, 0xffffffff);
+      int popupTextSize = typedArray.getDimensionPixelSize(R.styleable.FastScrollRecyclerView_fastScrollPopupTextSize, Utils.toScreenPixels(resources, 44));
+      int popupBackgroundSize = typedArray.getDimensionPixelSize(R.styleable.FastScrollRecyclerView_fastScrollPopupBackgroundSize, Utils.toPixels(resources, 88));
+      @FastScrollerPopupPosition int popupPosition = typedArray.getInteger(R.styleable.FastScrollRecyclerView_fastScrollPopupPosition, FastScrollerPopupPosition.ADJACENT);
+
+      mTrack.setColor(trackColor);
+      mThumb.setColor(mThumbInactiveState ? mThumbInactiveColor : mThumbActiveColor);
+      mPopup.setBgColor(popupBgColor);
+      mPopup.setTextColor(popupTextColor);
+      mPopup.setTextSize(popupTextSize);
+      mPopup.setBackgroundSize(popupBackgroundSize);
+      mPopup.setPopupPosition(popupPosition);
+    } finally {
+      typedArray.recycle();
+    }
+
+    mHideRunnable = new Runnable() {
+      @Override
+      public void run() {
+        if (!mIsDragging) {
+          if (mAutoHideAnimator != null) {
+            mAutoHideAnimator.cancel();
+          }
+          mAutoHideAnimator = ObjectAnimator.ofInt(FastScroller.this, "offsetX", (Utils.isRtl(mRecyclerView.getResources()) ? -1 : 1) * mWidth);
+          mAutoHideAnimator.setInterpolator(new FastOutLinearInInterpolator());
+          mAutoHideAnimator.setDuration(200);
+          mAutoHideAnimator.start();
+        }
+      }
+    };
+
+    mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+      @Override
+      public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+        super.onScrolled(recyclerView, dx, dy);
+
+        if (!mRecyclerView.isInEditMode()) {
+          show();
+        }
+      }
+    });
+
+    if (mAutoHideEnabled) {
+      postAutoHideDelayed();
+    }
+  }
+
+  public int getThumbHeight() {
+    return mThumbHeight;
+  }
+
+  public int getWidth() {
+    return mWidth;
+  }
+
+  public boolean isDragging() {
+    return mIsDragging;
+  }
+
+  /**
+   * Handles the touch event and determines whether to show the fast scroller (or updates it if
+   * it is already showing).
+   */
+  public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY,
+                               OnFastScrollStateChangeListener stateChangeListener) {
+    ViewConfiguration config = ViewConfiguration.get(mRecyclerView.getContext());
+
+    int action = ev.getAction();
+    int y = (int) ev.getY();
+    switch (action) {
+      case MotionEvent.ACTION_DOWN:
+        if (isNearPoint(downX, downY)) {
+          mTouchOffset = downY - mThumbPosition.y;
+        }
+        break;
+      case MotionEvent.ACTION_MOVE:
+        // Check if we should start scrolling
+        if (!mIsDragging && isNearPoint(downX, downY) &&
+          Math.abs(y - downY) > config.getScaledTouchSlop()) {
+          mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
+          mIsDragging = true;
+          mTouchOffset += (lastY - downY);
+          mPopup.animateVisibility(true);
+          if (stateChangeListener != null) {
+            stateChangeListener.onFastScrollStart();
+          }
+          if (mThumbInactiveState) {
+            mThumb.setColor(mThumbActiveColor);
+          }
+        }
+        if (mIsDragging) {
+          // Update the fastscroller section name at this touch position
+          int top = 0;
+          int bottom = mRecyclerView.getHeight() - mThumbHeight;
+          float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
+          String sectionName = mRecyclerView.scrollToPositionAtProgress((boundedY - top) / (bottom - top));
+          mPopup.setSectionName(sectionName);
+          mPopup.animateVisibility(!sectionName.isEmpty());
+          mRecyclerView.invalidate(mPopup.updateFastScrollerBounds(mRecyclerView, mThumbPosition.y));
+        }
+        break;
+      case MotionEvent.ACTION_UP:
+      case MotionEvent.ACTION_CANCEL:
+        mTouchOffset = 0;
+        if (mIsDragging) {
+          mIsDragging = false;
+          mPopup.animateVisibility(false);
+          if (stateChangeListener != null) {
+            stateChangeListener.onFastScrollStop();
+          }
+        }
+        if (mThumbInactiveState) {
+          mThumb.setColor(mThumbInactiveColor);
+        }
+        break;
+    }
+  }
+
+  public void draw(Canvas canvas) {
+
+    if (mThumbPosition.x < 0 || mThumbPosition.y < 0) {
+      return;
+    }
+
+    //Background
+    canvas.drawRect(
+      mThumbPosition.x + mOffset.x,
+      mOffset.y,
+      mWidth + mThumbPosition.x + mOffset.x,
+      mRecyclerView.getHeight() + mOffset.y,
+      mTrack
+    );
+
+    //Handle
+    canvas.drawRect(
+      mThumbPosition.x + mOffset.x,
+      mThumbPosition.y + mOffset.y,
+      mWidth + mThumbPosition.x + mOffset.x,
+      mThumbHeight + mThumbPosition.y + mOffset.y,
+      mThumb
+    );
+
+    //Popup
+    mPopup.draw(canvas);
+  }
+
+  /**
+   * Returns whether the specified points are near the scroll bar bounds.
+   */
+  private boolean isNearPoint(int x, int y) {
+    mTmpRect.set(mThumbPosition.x, mThumbPosition.y, mThumbPosition.x + mWidth,
+      mThumbPosition.y + mThumbHeight);
+    mTmpRect.inset(mTouchInset, mTouchInset);
+    return mTmpRect.contains(x, y);
+  }
+
+  public void setThumbPosition(int x, int y) {
+    if (mThumbPosition.x == x && mThumbPosition.y == y) {
+      return;
+    }
+    // do not create new objects here, this is called quite often
+    mInvalidateRect.set(mRecyclerView.getPaddingLeft() + mThumbPosition.x + mOffset.x, mRecyclerView.getPaddingTop() + mOffset.y, mThumbPosition.x + mOffset.x + mWidth - mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingTop() + mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom() + mOffset.y);
+    mThumbPosition.set(
+      x * (mRecyclerView.getWidth() - mRecyclerView.getPaddingLeft() - mRecyclerView.getPaddingLeft()) / mRecyclerView.getWidth() + mRecyclerView.getPaddingLeft(),
+      y * (mRecyclerView.getHeight() - mRecyclerView.getPaddingTop() - mRecyclerView.getPaddingBottom()) / mRecyclerView.getHeight() + mRecyclerView.getPaddingTop()
+    );
+    mInvalidateTmpRect.set(mRecyclerView.getPaddingLeft() + mThumbPosition.x + mOffset.x, mRecyclerView.getPaddingTop() + mOffset.y, mThumbPosition.x + mOffset.x + mWidth - mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingTop() + mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom() + mOffset.y);
+    mInvalidateRect.union(mInvalidateTmpRect);
+    mRecyclerView.invalidate(mInvalidateRect);
+  }
+
+  public void setOffset(int x, int y) {
+    if (mOffset.x == x && mOffset.y == y) {
+      return;
+    }
+    // do not create new objects here, this is called quite often
+    mInvalidateRect.set(mRecyclerView.getPaddingLeft() + mThumbPosition.x + mOffset.x, mRecyclerView.getPaddingTop() + mOffset.y, mThumbPosition.x + mOffset.x + mWidth - mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingTop() + mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom() + mOffset.y);
+    mOffset.set(x, y);
+    mInvalidateTmpRect.set(mRecyclerView.getPaddingLeft() + mThumbPosition.x + mOffset.x, mRecyclerView.getPaddingTop() + mOffset.y, mThumbPosition.x + mOffset.x + mWidth - mRecyclerView.getPaddingRight(), mRecyclerView.getPaddingTop() + mRecyclerView.getHeight() - mRecyclerView.getPaddingBottom() + mOffset.y);
+    mInvalidateRect.union(mInvalidateTmpRect);
+    mRecyclerView.invalidate(mInvalidateRect);
+  }
+
+  @Keep
+  public int getOffsetX() {
+    return mOffset.x;
+  }
+
+  // Setter/getter for the popup alpha for animations
+  @Keep
+  public void setOffsetX(int x) {
+    setOffset(x, mOffset.y);
+  }
+
+  public void show() {
+    if (!mAnimatingShow) {
+      if (mAutoHideAnimator != null) {
+        mAutoHideAnimator.cancel();
+      }
+      mAutoHideAnimator = ObjectAnimator.ofInt(this, "offsetX", 0);
+      mAutoHideAnimator.setInterpolator(new LinearOutSlowInInterpolator());
+      mAutoHideAnimator.setDuration(150);
+      mAutoHideAnimator.addListener(new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationCancel(Animator animation) {
+          super.onAnimationCancel(animation);
+          mAnimatingShow = false;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+          super.onAnimationEnd(animation);
+          mAnimatingShow = false;
+        }
+      });
+      mAnimatingShow = true;
+      mAutoHideAnimator.start();
+    }
+    if (mAutoHideEnabled) {
+      postAutoHideDelayed();
+    } else {
+      cancelAutoHide();
+    }
+  }
+
+  protected void postAutoHideDelayed() {
+    if (mRecyclerView != null) {
+      cancelAutoHide();
+      mRecyclerView.postDelayed(mHideRunnable, mAutoHideDelay);
+    }
+  }
+
+  protected void cancelAutoHide() {
+    if (mRecyclerView != null) {
+      mRecyclerView.removeCallbacks(mHideRunnable);
+    }
+  }
+
+  public void setThumbColor(@ColorInt int color) {
+    mThumb.setColor(color);
+    mRecyclerView.invalidate(mInvalidateRect);
+  }
+
+  public void setTrackColor(@ColorInt int color) {
+    mTrack.setColor(color);
+    mRecyclerView.invalidate(mInvalidateRect);
+  }
+
+  public void setPopupBgColor(@ColorInt int color) {
+    mPopup.setBgColor(color);
+  }
+
+  public void setPopupTextColor(@ColorInt int color) {
+    mPopup.setTextColor(color);
+  }
+
+  public void setPopupTypeface(Typeface typeface) {
+    mPopup.setTypeface(typeface);
+  }
+
+  public void setPopupTextSize(int size) {
+    mPopup.setTextSize(size);
+  }
+
+  public void setAutoHideDelay(int hideDelay) {
+    mAutoHideDelay = hideDelay;
+    if (mAutoHideEnabled) {
+      postAutoHideDelayed();
+    }
+  }
+
+  public void setAutoHideEnabled(boolean autoHideEnabled) {
+    mAutoHideEnabled = autoHideEnabled;
+    if (autoHideEnabled) {
+      postAutoHideDelayed();
+    } else {
+      cancelAutoHide();
+    }
+  }
+
+  public void setPopupPosition(@FastScrollerPopupPosition int popupPosition) {
+    mPopup.setPopupPosition(popupPosition);
+  }
+
+  public void setThumbInactiveColor(@ColorInt int color) {
+    mThumbInactiveColor = color;
+    enableThumbInactiveColor(true);
+  }
+
+  public void enableThumbInactiveColor(boolean enableInactiveColor) {
+    mThumbInactiveState = enableInactiveColor;
+    mThumb.setColor(mThumbInactiveState ? mThumbInactiveColor : mThumbActiveColor);
+  }
+
+  @Deprecated
+  public void setThumbInactiveColor(boolean thumbInactiveColor) {
+    enableThumbInactiveColor(thumbInactiveColor);
+  }
+
+  @Retention(SOURCE)
+  @IntDef({FastScrollerPopupPosition.ADJACENT, FastScrollerPopupPosition.CENTER})
+  public @interface FastScrollerPopupPosition {
+    int ADJACENT = 0;
+    int CENTER = 1;
+  }
+}
diff --git a/app/src/main/res/layout/fragment_chat_list.xml b/app/src/main/res/layout/fragment_chat_list.xml
index e67970b4af5429ab20b4002f370425bdefc33625..7a9e4f6bb3cd77aa78722cb48e76d08169bbde6b 100644
--- a/app/src/main/res/layout/fragment_chat_list.xml
+++ b/app/src/main/res/layout/fragment_chat_list.xml
@@ -49,7 +49,7 @@
   </android.support.design.widget.AppBarLayout>
 
 
-  <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView
+  <de.kuschku.quasseldroid.util.ui.fastscroll.views.FastScrollRecyclerView
     android:id="@+id/chatList"
     style="@style/Widget.FastScroller"
     android:layout_width="match_parent"
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 6efa6e004d543c800e3c6737b23867844c219221..94405b9f6bdbab903a71db47ebfac3c705f4e5b0 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -88,4 +88,52 @@
 
   <!-- InsetLayouts -->
   <attr name="insetBackground" format="color|reference" />
+
+  <!-- DrawerRecyclerView -->
+  <declare-styleable name="DrawerRecyclerView">
+    <attr name="insetBackground" />
+  </declare-styleable>
+
+  <!-- ShadowView -->
+  <declare-styleable name="ShadowView">
+    <attr name="android:gravity" />
+  </declare-styleable>
+
+  <!-- RingtonePreference -->
+  <declare-styleable name="RingtonePreference">
+    <!-- Which ringtone type(s) to show in the picker. -->
+    <attr name="ringtoneType">
+      <!-- Ringtones. -->
+      <flag name="ringtone" value="1" />
+      <!-- Notification sounds. -->
+      <flag name="notification" value="2" />
+      <!-- Alarm sounds. -->
+      <flag name="alarm" value="4" />
+      <!-- All available ringtone sounds. -->
+      <flag name="all" value="7" />
+    </attr>
+    <!-- Whether to show an item for a default sound. -->
+    <attr name="showDefault" format="boolean" />
+    <!-- Whether to show an item for 'Silent'. -->
+    <attr name="showSilent" format="boolean" />
+  </declare-styleable>
+
+  <!-- FastScroll RecyclerView -->
+  <declare-styleable name="FastScrollRecyclerView">
+    <attr name="fastScrollThumbColor" format="reference|color" />
+    <attr name="fastScrollThumbInactiveColor" format="reference|color" />
+    <attr name="fastScrollTrackColor" format="reference|color" />
+    <attr name="fastScrollPopupBgColor" format="reference|color" />
+    <attr name="fastScrollPopupTextColor" format="reference|color" />
+    <attr name="fastScrollPopupTextSize" format="reference|dimension" />
+    <attr name="fastScrollPopupBackgroundSize" format="reference|dimension" />
+    <attr name="fastScrollPopupPosition" format="enum">
+      <enum name="adjacent" value="0" />
+      <enum name="center" value="1" />
+    </attr>
+    <attr name="fastScrollAutoHide" format="reference|boolean" />
+    <attr name="fastScrollAutoHideDelay" format="reference|integer" />
+    <attr name="fastScrollEnableThumbInactiveColor" format="reference|boolean" />
+    <attr name="fastScrollThumbEnabled" format="reference|boolean" />
+  </declare-styleable>
 </resources>
diff --git a/app/src/main/res/values/styles_widgets.xml b/app/src/main/res/values/styles_widgets.xml
index ccb58f75eb82fe71d1ee013d9d258b3915179be7..4e88a61ea92954086417f51052a45701a9286644 100644
--- a/app/src/main/res/values/styles_widgets.xml
+++ b/app/src/main/res/values/styles_widgets.xml
@@ -335,33 +335,4 @@
   <style name="Widget.NavigationDrawerLayout" parent="">
     <item name="insetBackground">#4000</item>
   </style>
-
-  <!-- DrawerRecyclerView -->
-  <declare-styleable name="DrawerRecyclerView">
-    <attr name="insetBackground" />
-  </declare-styleable>
-
-  <!-- ShadowView -->
-  <declare-styleable name="ShadowView">
-    <attr name="android:gravity" />
-  </declare-styleable>
-
-  <!-- RingtonePreference -->
-  <declare-styleable name="RingtonePreference">
-    <!-- Which ringtone type(s) to show in the picker. -->
-    <attr name="ringtoneType">
-      <!-- Ringtones. -->
-      <flag name="ringtone" value="1" />
-      <!-- Notification sounds. -->
-      <flag name="notification" value="2" />
-      <!-- Alarm sounds. -->
-      <flag name="alarm" value="4" />
-      <!-- All available ringtone sounds. -->
-      <flag name="all" value="7" />
-    </attr>
-    <!-- Whether to show an item for a default sound. -->
-    <attr name="showDefault" format="boolean" />
-    <!-- Whether to show an item for 'Silent'. -->
-    <attr name="showSilent" format="boolean" />
-  </declare-styleable>
 </resources>
diff --git a/build.gradle.kts b/build.gradle.kts
index 03fdb3062583cbf8e16f500a142f8103b3c86eb8..d5c316a7c308ada9733edc04c6dd2180194a7344 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -24,7 +24,7 @@ buildscript {
   }
   dependencies {
     classpath("com.android.tools.build:gradle:3.1.3")
-    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.50")
+    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.51")
   }
 }
 
diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts
index 8fd9f554ff6eaa21cc31388f8c12b4ecf9bcf905..1fb6ecc290ef0765c891cc73b9f29dd2dc8fa8b3 100644
--- a/lib/build.gradle.kts
+++ b/lib/build.gradle.kts
@@ -29,7 +29,7 @@ plugins {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   withVersion("27.1.1") {
     implementation("com.android.support", "support-annotations", version)
diff --git a/lifecycle-ktx/build.gradle.kts b/lifecycle-ktx/build.gradle.kts
index d4ab27137d11262265321fedd1f64f29aec4f42b..5686bb4e6b25cc7b894918c2a0af6b459d02e752 100644
--- a/lifecycle-ktx/build.gradle.kts
+++ b/lifecycle-ktx/build.gradle.kts
@@ -27,7 +27,7 @@ plugins {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   withVersion("27.1.1") {
     implementation("com.android.support", "support-annotations", version)
diff --git a/malheur/build.gradle.kts b/malheur/build.gradle.kts
index 3c7d79c5cf1c9d64dcc30b5be43feca17dc96558..83cce734e4789505552f0fc3f1ab70f6fb74510e 100644
--- a/malheur/build.gradle.kts
+++ b/malheur/build.gradle.kts
@@ -45,7 +45,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   implementation("com.google.code.gson", "gson", "2.8.2")
 }
diff --git a/persistence/build.gradle.kts b/persistence/build.gradle.kts
index 5b6d07e2f0a06bbd4ef69eb94906d07c33deadef..9838a3e8bf25d9f84d8b04a67bde20fb4f6123df 100644
--- a/persistence/build.gradle.kts
+++ b/persistence/build.gradle.kts
@@ -52,7 +52,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   // App Compat
   withVersion("27.1.1") {
diff --git a/viewmodel/build.gradle.kts b/viewmodel/build.gradle.kts
index de116f3272fd139faf23fa3cad67f1aac43ceb3f..4f7be5fd726e2045363776908762c4f3130e0978 100644
--- a/viewmodel/build.gradle.kts
+++ b/viewmodel/build.gradle.kts
@@ -45,7 +45,7 @@ android {
 }
 
 dependencies {
-  implementation(kotlin("stdlib", "1.2.50"))
+  implementation(kotlin("stdlib", "1.2.51"))
 
   // App Compat
   withVersion("27.1.1") {