From 59276b71609044bc7a748551ce334f3a159445b2 Mon Sep 17 00:00:00 2001
From: inorichi <chibilen@gmail.com>
Date: Tue, 23 Feb 2016 14:51:18 +0100
Subject: [PATCH] Migrate library to Kotlin.

---
 .../data/library/LibraryUpdateService.kt      |   8 +-
 .../tachiyomi/event/LibraryMangasEvent.java   |  27 --
 .../tachiyomi/event/LibraryMangasEvent.kt     |  11 +
 .../tachiyomi/ui/category/CategoryActivity.kt |  22 +-
 .../tachiyomi/ui/library/LibraryAdapter.java  |  47 ---
 .../tachiyomi/ui/library/LibraryAdapter.kt    |  79 ++++
 .../ui/library/LibraryCategoryAdapter.java    |  75 ----
 .../ui/library/LibraryCategoryAdapter.kt      | 112 ++++++
 .../ui/library/LibraryCategoryFragment.java   | 201 ----------
 .../ui/library/LibraryCategoryFragment.kt     | 263 ++++++++++++
 .../tachiyomi/ui/library/LibraryFragment.java | 342 ----------------
 .../tachiyomi/ui/library/LibraryFragment.kt   | 377 ++++++++++++++++++
 .../tachiyomi/ui/library/LibraryHolder.java   |  55 ---
 .../tachiyomi/ui/library/LibraryHolder.kt     |  58 +++
 .../ui/library/LibraryPresenter.java          | 157 --------
 .../tachiyomi/ui/library/LibraryPresenter.kt  | 227 +++++++++++
 .../tachiyomi/util/ContextExtensions.kt       |   7 +
 .../tachiyomi/util/ViewGroupExtensions.kt     |   2 +-
 .../res/layout/fragment_library_category.xml  |   2 +-
 .../library/LibraryUpdateServiceTest.java     |   2 +-
 20 files changed, 1149 insertions(+), 925 deletions(-)
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
 delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.java
 create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt

diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
index fa71ca68c..c5a4a22d0 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
@@ -1,6 +1,5 @@
 package eu.kanade.tachiyomi.data.library
 
-import android.app.NotificationManager
 import android.app.PendingIntent
 import android.app.Service
 import android.content.BroadcastReceiver
@@ -20,6 +19,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.util.AndroidComponentUtil
 import eu.kanade.tachiyomi.util.NetworkUtil
 import eu.kanade.tachiyomi.util.notification
+import eu.kanade.tachiyomi.util.notificationManager
 import rx.Observable
 import rx.Subscription
 import rx.schedulers.Schedulers
@@ -310,12 +310,6 @@ class LibraryUpdateService : Service() {
         notificationManager.cancel(UPDATE_NOTIFICATION_ID)
     }
 
-    /**
-     * Property that returns the notification manager.
-     */
-    private val notificationManager : NotificationManager
-        get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-
     /**
      * Property that returns an intent to open the main activity.
      */
diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.java b/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.java
deleted file mode 100644
index 1250a0d4b..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package eu.kanade.tachiyomi.event;
-
-import android.support.annotation.Nullable;
-
-import java.util.List;
-import java.util.Map;
-
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-
-public class LibraryMangasEvent {
-
-    private final Map<Integer, List<Manga>> mangas;
-
-    public LibraryMangasEvent(Map<Integer, List<Manga>> mangas) {
-        this.mangas = mangas;
-    }
-
-    public Map<Integer, List<Manga>> getMangas() {
-        return mangas;
-    }
-
-    @Nullable
-    public List<Manga> getMangasForCategory(Category category) {
-        return mangas.get(category.id);
-    }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.kt b/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.kt
new file mode 100644
index 000000000..8dfd585f9
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/event/LibraryMangasEvent.kt
@@ -0,0 +1,11 @@
+package eu.kanade.tachiyomi.event
+
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.data.database.models.Manga
+
+class LibraryMangasEvent(val mangas: Map<Int, List<Manga>>) {
+
+    fun getMangasForCategory(category: Category): List<Manga>? {
+        return mangas[category.id]
+    }
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
index 346873539..71a0fb606 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/category/CategoryActivity.kt
@@ -10,12 +10,12 @@ import android.support.v7.widget.helper.ItemTouchHelper
 import android.view.Menu
 import android.view.MenuItem
 import com.afollestad.materialdialogs.MaterialDialog
+import eu.davidea.flexibleadapter.FlexibleAdapter
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.data.database.models.Category
 import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
 import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
 import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
-import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
 import kotlinx.android.synthetic.main.activity_edit_categories.*
 import kotlinx.android.synthetic.main.toolbar.*
 import nucleus.factory.RequiresPresenter
@@ -93,7 +93,7 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
      * Call this when action mode action is finished.
      */
     fun destroyActionModeIfNeeded() {
-            actionMode?.finish()
+        actionMode?.finish()
     }
 
     /**
@@ -163,8 +163,8 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
                 it.invalidate()
 
                 // Show edit button only when one item is selected
-                val editItem = it.menu?.findItem(R.id.action_edit)
-                editItem?.isVisible = count == 1
+                val editItem = it.menu.findItem(R.id.action_edit)
+                editItem.isVisible = count == 1
             }
         }
     }
@@ -192,15 +192,14 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
             R.id.action_delete -> {
                 // Delete select categories.
                 deleteCategories(getSelectedCategories())
-                return true
             }
             R.id.action_edit -> {
                 // Edit selected category
                 editCategory(getSelectedCategories()?.get(0))
-                return true
             }
+            else -> return false
         }
-        return false
+        return true
     }
 
     /**
@@ -215,7 +214,7 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
         // Inflate menu.
         mode.menuInflater.inflate(R.menu.category_selection, menu)
         // Enable adapter multi selection.
-        adapter.mode = LibraryCategoryAdapter.MODE_MULTI
+        adapter.mode = FlexibleAdapter.MODE_MULTI
         return true
     }
 
@@ -226,7 +225,7 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
      */
     override fun onDestroyActionMode(mode: ActionMode?) {
         // Reset adapter to single selection
-        adapter.mode = LibraryCategoryAdapter.MODE_SINGLE
+        adapter.mode = FlexibleAdapter.MODE_SINGLE
         // Clear selected items
         adapter.clearSelection()
         actionMode = null
@@ -239,8 +238,9 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
      */
     override fun onListItemClick(position: Int): Boolean {
         // Check if action mode is initialized and selected item exist.
-        if (actionMode != null && position != -1) {
-            // Toggle selection of clicked item.
+        if (position == -1) {
+            return false
+        } else if (actionMode != null) {
             toggleSelection(position)
             return true
         } else {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.java
deleted file mode 100644
index bb9b3e858..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-
-import java.util.List;
-
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.ui.base.adapter.SmartFragmentStatePagerAdapter;
-
-public class LibraryAdapter extends SmartFragmentStatePagerAdapter {
-
-    protected List<Category> categories;
-
-    public LibraryAdapter(FragmentManager fm) {
-        super(fm);
-    }
-
-    @Override
-    public Fragment getItem(int position) {
-        return LibraryCategoryFragment.newInstance(position);
-    }
-
-    @Override
-    public int getCount() {
-        return categories == null ? 0 : categories.size();
-    }
-
-    @Override
-    public CharSequence getPageTitle(int position) {
-        return categories.get(position).name;
-    }
-
-    public void setCategories(List<Category> categories) {
-        if (this.categories != categories) {
-            this.categories = categories;
-            notifyDataSetChanged();
-        }
-    }
-
-    public void setSelectionMode(int mode) {
-        for (Fragment fragment : getRegisteredFragments()) {
-            ((LibraryCategoryFragment) fragment).setMode(mode);
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt
new file mode 100644
index 000000000..258f65a61
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryAdapter.kt
@@ -0,0 +1,79 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.support.v4.app.Fragment
+import android.support.v4.app.FragmentManager
+
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.ui.base.adapter.SmartFragmentStatePagerAdapter
+
+/**
+ * This adapter stores the categories from the library, used with a ViewPager.
+ *
+ * @param fm the fragment manager.
+ * @constructor creates an instance of the adapter.
+ */
+class LibraryAdapter(fm: FragmentManager) : SmartFragmentStatePagerAdapter(fm) {
+
+    /**
+     * The categories to bind in the adapter.
+     */
+    var categories: List<Category>? = null
+        // This setter helps to not refresh the adapter if the reference to the list doesn't change.
+        set(value) {
+            if (field !== value) {
+                field = value
+                notifyDataSetChanged()
+            }
+        }
+
+    /**
+     * Creates a new fragment for the given position when it's called.
+     *
+     * @param position the position to instantiate.
+     * @return a fragment for the given position.
+     */
+    override fun getItem(position: Int): Fragment {
+        return LibraryCategoryFragment.newInstance(position)
+    }
+
+    /**
+     * Returns the number of categories.
+     *
+     * @return the number of categories or 0 if the list is null.
+     */
+    override fun getCount(): Int {
+        return categories?.size ?: 0
+    }
+
+    /**
+     * Returns the title to display for a category.
+     *
+     * @param position the position of the element.
+     * @return the title to display.
+     */
+    override fun getPageTitle(position: Int): CharSequence {
+        return categories!![position].name
+    }
+
+    /**
+     * Method to enable or disable the action mode (multiple selection) for all the instantiated
+     * fragments.
+     *
+     * @param mode the mode to set.
+     */
+    fun setSelectionMode(mode: Int) {
+        for (fragment in registeredFragments) {
+            (fragment as LibraryCategoryFragment).setSelectionMode(mode)
+        }
+    }
+
+    /**
+     * Notifies the adapters in all the registered fragments to refresh their content.
+     */
+    fun refreshRegisteredAdapters() {
+        for (fragment in registeredFragments) {
+            (fragment as LibraryCategoryFragment).adapter.notifyDataSetChanged()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.java
deleted file mode 100644
index 79d77a2ef..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-
-public class LibraryCategoryAdapter extends FlexibleAdapter<LibraryHolder, Manga> {
-
-    private List<Manga> mangas;
-    private LibraryCategoryFragment fragment;
-
-    public LibraryCategoryAdapter(LibraryCategoryFragment fragment) {
-        this.fragment = fragment;
-        mItems = new ArrayList<>();
-        setHasStableIds(true);
-    }
-
-    public void setItems(List<Manga> list) {
-        mItems = list;
-
-        // A copy of manga that it's always unfiltered
-        mangas = new ArrayList<>(list);
-        updateDataSet(null);
-    }
-
-    public void clear() {
-        mItems.clear();
-    }
-
-    @Override
-    public long getItemId(int position) {
-        return mItems.get(position).id;
-    }
-
-    @Override
-    public void updateDataSet(String param) {
-        if (mangas != null) {
-            filterItems(mangas);
-            notifyDataSetChanged();
-        }
-    }
-
-    @Override
-    protected boolean filterObject(Manga manga, String query) {
-        return (manga.title != null && manga.title.toLowerCase().contains(query)) ||
-                (manga.author != null && manga.author.toLowerCase().contains(query));
-    }
-
-    @Override
-    public LibraryHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-        View v = LayoutInflater.from(fragment.getActivity()).inflate(R.layout.item_catalogue_grid, parent, false);
-        return new LibraryHolder(v, this, fragment);
-    }
-
-    @Override
-    public void onBindViewHolder(LibraryHolder holder, int position) {
-        final LibraryPresenter presenter = ((LibraryFragment) fragment.getParentFragment()).getPresenter();
-        final Manga manga = getItem(position);
-        holder.onSetValues(manga, presenter);
-        //When user scrolls this bind the correct selection status
-        holder.itemView.setActivated(isSelected(position));
-    }
-
-    public int getCoverHeight() {
-        return fragment.recycler.getItemWidth() / 3 * 4;
-    }
-
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
new file mode 100644
index 000000000..6d99d6971
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryAdapter.kt
@@ -0,0 +1,112 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.RelativeLayout
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.util.inflate
+import kotlinx.android.synthetic.main.fragment_library_category.*
+import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
+import java.util.*
+
+/**
+ * Adapter storing a list of manga in a certain category.
+ *
+ * @param fragment the fragment containing this adapter.
+ */
+class LibraryCategoryAdapter(private val fragment: LibraryCategoryFragment) :
+        FlexibleAdapter<LibraryHolder, Manga>() {
+
+    /**
+     * The list of manga in this category.
+     */
+    private var mangas: List<Manga>? = null
+
+    init {
+        setHasStableIds(true)
+    }
+
+    /**
+     * Sets a list of manga in the adapter.
+     *
+     * @param list the list to set.
+     */
+    fun setItems(list: List<Manga>) {
+        mItems = list
+
+        // A copy of manga that it's always unfiltered
+        mangas = ArrayList(list)
+        updateDataSet(null)
+    }
+
+    /**
+     * Returns the identifier for a manga.
+     *
+     * @param position the position in the adapter.
+     * @return an identifier for the item.
+     */
+    override fun getItemId(position: Int): Long {
+        return mItems[position].id
+    }
+
+    /**
+     * Filters the list of manga applying [filterObject] for each element.
+     *
+     * @param param the filter. Not used.
+     */
+    override fun updateDataSet(param: String?) {
+        mangas?.let {
+            filterItems(it)
+            notifyDataSetChanged()
+        }
+    }
+
+    /**
+     * Filters a manga depending on a query.
+     *
+     * @param manga the manga to filter.
+     * @param query the query to apply.
+     * @return true if the manga should be included, false otherwise.
+     */
+    override fun filterObject(manga: Manga, query: String): Boolean = with(manga) {
+        title != null && title.toLowerCase().contains(query) ||
+                author != null && author.toLowerCase().contains(query)
+    }
+
+    /**
+     * Creates a new view holder.
+     *
+     * @param parent the parent view.
+     * @param viewType the type of the holder.
+     * @return a new view holder for a manga.
+     */
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LibraryHolder {
+        val view = parent.inflate(R.layout.item_catalogue_grid)
+        view.image_container.layoutParams = RelativeLayout.LayoutParams(MATCH_PARENT, coverHeight)
+        return LibraryHolder(view, this, fragment)
+    }
+
+    /**
+     * Binds a holder with a new position.
+     *
+     * @param holder the holder to bind.
+     * @param position the position to bind.
+     */
+    override fun onBindViewHolder(holder: LibraryHolder, position: Int) {
+        val presenter = (fragment.parentFragment as LibraryFragment).presenter
+        val manga = getItem(position)
+
+        holder.onSetValues(manga, presenter)
+        //When user scrolls this bind the correct selection status
+        holder.itemView.isActivated = isSelected(position)
+    }
+
+    /**
+     * Property to return the height for the covers based on the width to keep an aspect ratio.
+     */
+    val coverHeight: Int
+        get() = fragment.recycler.itemWidth / 3 * 4
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.java
deleted file mode 100644
index 40cb63b87..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.java
+++ /dev/null
@@ -1,201 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.content.Intent;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.f2prateek.rx.preferences.Preference;
-
-import org.greenrobot.eventbus.Subscribe;
-import org.greenrobot.eventbus.ThreadMode;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-import eu.kanade.tachiyomi.event.LibraryMangasEvent;
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
-import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment;
-import eu.kanade.tachiyomi.ui.manga.MangaActivity;
-import eu.kanade.tachiyomi.widget.AutofitRecyclerView;
-import icepick.State;
-import rx.Subscription;
-
-public class LibraryCategoryFragment extends BaseFragment
-        implements FlexibleViewHolder.OnListItemClickListener {
-
-    @Bind(R.id.library_mangas) AutofitRecyclerView recycler;
-
-    @State int position;
-    private LibraryCategoryAdapter adapter;
-    private List<Manga> mangas;
-
-    private Subscription numColumnsSubscription;
-    private Subscription searchSubscription;
-
-    public static LibraryCategoryFragment newInstance(int position) {
-        LibraryCategoryFragment fragment = new LibraryCategoryFragment();
-        fragment.position = position;
-        return fragment;
-    }
-
-
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        // Inflate the layout for this fragment
-        View view = inflater.inflate(R.layout.fragment_library_category, container, false);
-        ButterKnife.bind(this, view);
-
-        adapter = new LibraryCategoryAdapter(this);
-        recycler.setHasFixedSize(true);
-        recycler.setAdapter(adapter);
-
-        if (getLibraryFragment().getActionMode() != null) {
-            setMode(FlexibleAdapter.MODE_MULTI);
-        }
-
-        Preference<Integer> columnsPref = getResources().getConfiguration()
-                .orientation == Configuration.ORIENTATION_PORTRAIT ?
-                getLibraryPresenter().preferences.portraitColumns() :
-                getLibraryPresenter().preferences.landscapeColumns();
-
-        numColumnsSubscription = columnsPref.asObservable()
-                .doOnNext(recycler::setSpanCount)
-                .skip(1)
-                // Set again the adapter to recalculate the covers height
-                .subscribe(count -> recycler.setAdapter(adapter));
-
-        if (savedState != null) {
-            adapter.onRestoreInstanceState(savedState);
-
-            if (adapter.getMode() == FlexibleAdapter.MODE_SINGLE) {
-                adapter.clearSelection();
-            }
-        }
-
-        searchSubscription = getLibraryPresenter().searchSubject
-                .subscribe(text -> {
-                    adapter.setSearchText(text);
-                    adapter.updateDataSet();
-                });
-
-
-
-        return view;
-
-    }
-
-    @Override
-    public void onDestroyView() {
-        numColumnsSubscription.unsubscribe();
-        searchSubscription.unsubscribe();
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        registerForEvents();
-    }
-
-    @Override
-    public void onPause() {
-        unregisterForEvents();
-        super.onPause();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle outState) {
-        adapter.onSaveInstanceState(outState);
-        super.onSaveInstanceState(outState);
-    }
-
-    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
-    public void onEvent(LibraryMangasEvent event) {
-        List<Category> categories = getLibraryFragment().getAdapter().categories;
-        // When a category is deleted, the index can be greater than the number of categories
-        if (position >= categories.size())
-            return;
-
-        Category category = categories.get(position);
-        List<Manga> mangas = event.getMangasForCategory(category);
-        if (this.mangas != mangas) {
-            this.mangas = mangas;
-            if (mangas == null) {
-                mangas = new ArrayList<>();
-            }
-            setMangas(mangas);
-        }
-    }
-
-    protected void openManga(Manga manga) {
-        getLibraryPresenter().onOpenManga(manga);
-        Intent intent = MangaActivity.newIntent(getActivity(), manga);
-        startActivity(intent);
-    }
-
-    public void setMangas(List<Manga> mangas) {
-        if (mangas != null) {
-            adapter.setItems(mangas);
-        } else {
-            adapter.clear();
-        }
-    }
-
-    @Override
-    public boolean onListItemClick(int position) {
-        if (getLibraryFragment().getActionMode() != null && position != -1) {
-            toggleSelection(position);
-            return true;
-        } else {
-            openManga(adapter.getItem(position));
-            return false;
-        }
-    }
-
-    @Override
-    public void onListItemLongClick(int position) {
-        getLibraryFragment().createActionModeIfNeeded();
-        toggleSelection(position);
-    }
-
-    private void toggleSelection(int position) {
-        LibraryFragment f = getLibraryFragment();
-        adapter.toggleSelection(position, false);
-        f.getPresenter().setSelection(adapter.getItem(position), adapter.isSelected(position));
-
-        int count = f.getPresenter().selectedMangas.size();
-        if (count == 0) {
-            f.destroyActionModeIfNeeded();
-        }
-        else {
-            f.setContextTitle(count);
-            f.setVisibilityOfCoverEdit(count);
-            f.invalidateActionMode();
-        }
-    }
-
-    public void setMode(int mode) {
-        adapter.setMode(mode);
-        if (mode == FlexibleAdapter.MODE_SINGLE) {
-            adapter.clearSelection();
-        }
-    }
-
-    private LibraryFragment getLibraryFragment() {
-        return (LibraryFragment) getParentFragment();
-    }
-
-    private LibraryPresenter getLibraryPresenter() {
-        return getLibraryFragment().getPresenter();
-    }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt
new file mode 100644
index 000000000..2ed052b6c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCategoryFragment.kt
@@ -0,0 +1,263 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.f2prateek.rx.preferences.Preference
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.event.LibraryMangasEvent
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
+import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
+import eu.kanade.tachiyomi.ui.manga.MangaActivity
+import kotlinx.android.synthetic.main.fragment_library_category.*
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import rx.Subscription
+import java.util.*
+
+/**
+ * Fragment containing the library manga for a certain category.
+ * Uses R.layout.fragment_library_category.
+ */
+class LibraryCategoryFragment : BaseFragment(), FlexibleViewHolder.OnListItemClickListener {
+
+    /**
+     * Adapter to hold the manga in this category.
+     */
+    lateinit var adapter: LibraryCategoryAdapter
+        private set
+
+    /**
+     * Position in the adapter from [LibraryAdapter].
+     */
+    private var position: Int = 0
+
+    /**
+     * Manga in this category.
+     */
+    private var mangas: List<Manga>? = null
+        set(value) {
+            field = value ?: ArrayList()
+        }
+
+    /**
+     * Subscription of the number of manga per row.
+     */
+    private var numColumnsSubscription: Subscription? = null
+
+    /**
+     * Subscription of the library search.
+     */
+    private var searchSubscription: Subscription? = null
+
+    companion object {
+        /**
+         * Key to save and restore [position] from a [Bundle].
+         */
+        const val POSITION_KEY = "position_key"
+
+        /**
+         * Creates a new instance of this class.
+         *
+         * @param position the position in the adapter from [LibraryAdapter].
+         * @return a new instance of [LibraryCategoryFragment].
+         */
+        fun newInstance(position: Int): LibraryCategoryFragment {
+            val fragment = LibraryCategoryFragment()
+            fragment.position = position
+            return fragment
+        }
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
+        return inflater.inflate(R.layout.fragment_library_category, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedState: Bundle?) {
+        adapter = LibraryCategoryAdapter(this)
+        recycler.setHasFixedSize(true)
+        recycler.adapter = adapter
+
+        if (libraryFragment.actionMode != null) {
+            setSelectionMode(FlexibleAdapter.MODE_MULTI)
+        }
+
+        numColumnsSubscription = getColumnsPreferenceForCurrentOrientation().asObservable()
+                .doOnNext { recycler.spanCount = it }
+                .skip(1)
+                // Set again the adapter to recalculate the covers height
+                .subscribe { recycler.adapter = adapter }
+
+        searchSubscription = libraryPresenter.searchSubject.subscribe { text ->
+            adapter.searchText = text
+            adapter.updateDataSet()
+        }
+
+        if (savedState != null) {
+            position = savedState.getInt(POSITION_KEY)
+            adapter.onRestoreInstanceState(savedState)
+
+            if (adapter.mode == FlexibleAdapter.MODE_SINGLE) {
+                adapter.clearSelection()
+            }
+        }
+    }
+
+    override fun onDestroyView() {
+        numColumnsSubscription?.unsubscribe()
+        searchSubscription?.unsubscribe()
+        super.onDestroyView()
+    }
+
+    override fun onResume() {
+        super.onResume()
+        registerForEvents()
+    }
+
+    override fun onPause() {
+        unregisterForEvents()
+        super.onPause()
+    }
+
+    override fun onSaveInstanceState(outState: Bundle) {
+        outState.putInt(POSITION_KEY, position)
+        adapter.onSaveInstanceState(outState)
+        super.onSaveInstanceState(outState)
+    }
+
+    /**
+     * Subscribe to [LibraryMangasEvent]. When an event is received, it updates [mangas] if needed
+     * and refresh the content of the adapter.
+     *
+     * @param event the event received.
+     */
+    @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
+    fun onEvent(event: LibraryMangasEvent) {
+        // Get the categories from the parent fragment.
+        val categories = libraryFragment.adapter.categories ?: return
+
+        // When a category is deleted, the index can be greater than the number of categories.
+        if (position >= categories.size) return
+
+        // Get the manga list for this category
+        val mangaForCategory = event.getMangasForCategory(categories[position])
+
+        // Update the list only if the reference to the list is different, avoiding reseting the
+        // adapter after every onResume.
+        if (mangas !== mangaForCategory) {
+            mangas = mangaForCategory
+            mangas?.let { adapter.setItems(it) }
+        }
+    }
+
+    /**
+     * Called when a manga is clicked.
+     *
+     * @param position the position of the element clicked.
+     * @return true if the item should be selected, false otherwise.
+     */
+    override fun onListItemClick(position: Int): Boolean {
+        // If the action mode is created and the position is valid, toggle the selection.
+        if (position == -1) {
+            return false
+        } else if (libraryFragment.actionMode != null) {
+            toggleSelection(position)
+            return true
+        } else {
+            openManga(adapter.getItem(position))
+            return false
+        }
+    }
+
+    /**
+     * Called when a manga is long clicked.
+     *
+     * @param position the position of the element clicked.
+     */
+    override fun onListItemLongClick(position: Int) {
+        libraryFragment.createActionModeIfNeeded()
+        toggleSelection(position)
+    }
+
+    /**
+     * Opens a manga.
+     *
+     * @param manga the manga to open.
+     */
+    protected fun openManga(manga: Manga) {
+        // Notify the presenter a manga is being opened.
+        libraryPresenter.onOpenManga()
+
+        // Create a new activity with the manga.
+        val intent = MangaActivity.newIntent(activity, manga)
+        startActivity(intent)
+    }
+
+    /**
+     * Toggles the selection for a manga.
+     *
+     * @param position the position to toggle.
+     */
+    private fun toggleSelection(position: Int) {
+        val library = libraryFragment
+
+        // Toggle the selection.
+        adapter.toggleSelection(position, false)
+
+        // Notify the selection to the presenter.
+        library.presenter.setSelection(adapter.getItem(position), adapter.isSelected(position))
+
+        // Get the selected count.
+        val count = library.presenter.selectedMangas.size
+        if (count == 0) {
+            // Destroy action mode if there are no items selected.
+            library.destroyActionModeIfNeeded()
+        } else {
+            // Update action mode with the new selection.
+            library.setContextTitle(count)
+            library.setVisibilityOfCoverEdit(count)
+            library.invalidateActionMode()
+        }
+    }
+
+    /**
+     * Returns a preference for the number of manga per row based on the current orientation.
+     *
+     * @return the preference.
+     */
+    fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
+        return if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT)
+            libraryPresenter.preferences.portraitColumns()
+        else
+            libraryPresenter.preferences.landscapeColumns()
+    }
+
+    /**
+     * Sets the mode for the adapter.
+     *
+     * @param mode the mode to set. It should be MODE_SINGLE or MODE_MULTI.
+     */
+    fun setSelectionMode(mode: Int) {
+        adapter.mode = mode
+        if (mode == FlexibleAdapter.MODE_SINGLE) {
+            adapter.clearSelection()
+        }
+    }
+
+    /**
+     * Property to get the library fragment.
+     */
+    private val libraryFragment: LibraryFragment
+        get() = parentFragment as LibraryFragment
+
+    /**
+     * Property to get the library presenter.
+     */
+    private val libraryPresenter: LibraryPresenter
+        get() = libraryFragment.presenter
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
deleted file mode 100644
index e397f2045..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.java
+++ /dev/null
@@ -1,342 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.Nullable;
-import android.support.design.widget.AppBarLayout;
-import android.support.design.widget.TabLayout;
-import android.support.v4.view.ViewPager;
-import android.support.v7.view.ActionMode;
-import android.support.v7.widget.SearchView;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.afollestad.materialdialogs.MaterialDialog;
-
-import org.greenrobot.eventbus.EventBus;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-import eu.kanade.tachiyomi.data.io.IOHandler;
-import eu.kanade.tachiyomi.data.library.LibraryUpdateService;
-import eu.kanade.tachiyomi.event.LibraryMangasEvent;
-import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment;
-import eu.kanade.tachiyomi.ui.category.CategoryActivity;
-import eu.kanade.tachiyomi.ui.main.MainActivity;
-import eu.kanade.tachiyomi.util.ToastUtil;
-import icepick.State;
-import nucleus.factory.RequiresPresenter;
-
-@RequiresPresenter(LibraryPresenter.class)
-public class LibraryFragment extends BaseRxFragment<LibraryPresenter>
-        implements ActionMode.Callback {
-
-
-    private static final int REQUEST_IMAGE_OPEN = 101;
-
-    protected LibraryAdapter adapter;
-
-    @Bind(R.id.view_pager) ViewPager viewPager;
-
-    @State int activeCategory;
-
-    @State String query = "";
-
-    private TabLayout tabs;
-
-    private AppBarLayout appBar;
-
-    private ActionMode actionMode;
-
-    private Manga selectedCoverManga;
-
-    public static LibraryFragment newInstance() {
-        return new LibraryFragment();
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setHasOptionsMenu(true);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
-        // Inflate the layout for this fragment
-        View view = inflater.inflate(R.layout.fragment_library, container, false);
-        setToolbarTitle(getString(R.string.label_library));
-        ButterKnife.bind(this, view);
-
-        appBar = ((MainActivity) getActivity()).getAppBar();
-        tabs = (TabLayout) inflater.inflate(R.layout.library_tab_layout, appBar, false);
-        appBar.addView(tabs);
-
-        adapter = new LibraryAdapter(getChildFragmentManager());
-        viewPager.setAdapter(adapter);
-        tabs.setupWithViewPager(viewPager);
-
-        if (savedState != null) {
-            getPresenter().searchSubject.onNext(query);
-        }
-
-        return view;
-    }
-
-    @Override
-    public void onDestroyView() {
-        appBar.removeView(tabs);
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onSaveInstanceState(Bundle bundle) {
-        activeCategory = viewPager.getCurrentItem();
-        super.onSaveInstanceState(bundle);
-    }
-
-    @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        inflater.inflate(R.menu.library, menu);
-
-        // Initialize search menu
-        MenuItem searchItem = menu.findItem(R.id.action_search);
-        final SearchView searchView = (SearchView) searchItem.getActionView();
-
-        if (!TextUtils.isEmpty(query)) {
-            searchItem.expandActionView();
-            searchView.setQuery(query, true);
-            searchView.clearFocus();
-        }
-        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
-            @Override
-            public boolean onQueryTextSubmit(String query) {
-                onSearchTextChange(query);
-                return true;
-            }
-
-            @Override
-            public boolean onQueryTextChange(String newText) {
-                onSearchTextChange(newText);
-                return true;
-            }
-        });
-    }
-
-    @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.action_refresh:
-                LibraryUpdateService.start(getActivity());
-                return true;
-            case R.id.action_edit_categories:
-                onEditCategories();
-                return true;
-        }
-
-        return super.onOptionsItemSelected(item);
-    }
-
-    private void onSearchTextChange(String query) {
-        this.query = query;
-        getPresenter().searchSubject.onNext(query);
-    }
-
-    private void onEditCategories() {
-        Intent intent = CategoryActivity.newIntent(getActivity());
-        startActivity(intent);
-    }
-
-    public void onNextLibraryUpdate(List<Category> categories, Map<Integer, List<Manga>> mangas) {
-        boolean hasMangasInDefaultCategory = mangas.get(0) != null;
-        int activeCat = adapter.categories != null ? viewPager.getCurrentItem() : activeCategory;
-
-        if (hasMangasInDefaultCategory) {
-            setCategoriesWithDefault(categories);
-        } else {
-            setCategories(categories);
-        }
-        // Restore active category
-        viewPager.setCurrentItem(activeCat, false);
-        if (tabs.getTabCount() > 0) {
-            TabLayout.Tab tab = tabs.getTabAt(viewPager.getCurrentItem());
-            if (tab != null) tab.select();
-        }
-
-        // Send the mangas to child fragments after the adapter is updated
-        EventBus.getDefault().postSticky(new LibraryMangasEvent(mangas));
-    }
-
-    private void setCategoriesWithDefault(List<Category> categories) {
-        List<Category> categoriesWithDefault = new ArrayList<>();
-        categoriesWithDefault.add(Category.createDefault());
-        categoriesWithDefault.addAll(categories);
-
-        setCategories(categoriesWithDefault);
-    }
-
-    private void setCategories(List<Category> categories) {
-        adapter.setCategories(categories);
-        tabs.setTabsFromPagerAdapter(adapter);
-        tabs.setVisibility(categories.size() <= 1 ? View.GONE : View.VISIBLE);
-    }
-
-    public void setContextTitle(int count) {
-        actionMode.setTitle(getString(R.string.label_selected, count));
-    }
-
-    public void setVisibilityOfCoverEdit(int count) {
-        // If count = 1 display edit button
-        actionMode.getMenu().findItem(R.id.action_edit_cover).setVisible((count == 1));
-    }
-
-    @Override
-    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-        mode.getMenuInflater().inflate(R.menu.library_selection, menu);
-        adapter.setSelectionMode(FlexibleAdapter.MODE_MULTI);
-        return true;
-    }
-
-    @Override
-    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-        return false;
-    }
-
-    @Override
-    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.action_edit_cover:
-                changeSelectedCover(getPresenter().selectedMangas);
-                rebuildAdapter();
-                destroyActionModeIfNeeded();
-                return true;
-            case R.id.action_move_to_category:
-                moveMangasToCategories(getPresenter().selectedMangas);
-                return true;
-            case R.id.action_delete:
-                getPresenter().deleteMangas();
-                destroyActionModeIfNeeded();
-                return true;
-        }
-        return false;
-    }
-
-    /**
-     * TODO workaround. Covers won't refresh any other way.
-     */
-    public void rebuildAdapter() {
-        adapter = new LibraryAdapter(getChildFragmentManager());
-        viewPager.setAdapter(adapter);
-        tabs.setupWithViewPager(viewPager);
-    }
-
-    @Override
-    public void onDestroyActionMode(ActionMode mode) {
-        adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE);
-        getPresenter().selectedMangas.clear();
-        actionMode = null;
-    }
-
-    public void destroyActionModeIfNeeded() {
-        if (actionMode != null) {
-            actionMode.finish();
-        }
-    }
-
-    private void changeSelectedCover(List<Manga> mangas) {
-        if (mangas.size() == 1) {
-            selectedCoverManga = mangas.get(0);
-            if (selectedCoverManga.favorite) {
-
-                Intent intent = new Intent();
-                intent.setType("image/*");
-                intent.setAction(Intent.ACTION_GET_CONTENT);
-                startActivityForResult(Intent.createChooser(intent,
-                        getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN);
-            } else {
-                ToastUtil.showShort(getContext(), R.string.notification_first_add_to_library);
-            }
-
-        }
-    }
-
-    @Override
-    public void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (resultCode == Activity.RESULT_OK) {
-            switch (requestCode) {
-                case (REQUEST_IMAGE_OPEN):
-                    if (selectedCoverManga != null) {
-                        // Get the file's content URI from the incoming Intent
-                        Uri selectedImageUri = data.getData();
-
-                        // Convert to absolute path to prevent FileNotFoundException
-                        String result = IOHandler.getFilePath(selectedImageUri,
-                                getContext().getContentResolver(), getContext());
-
-                        // Get file from filepath
-                        File picture = new File(result != null ? result : "");
-
-                        try {
-                            // Update cover to selected file, show error if something went wrong
-                            if (!getPresenter().editCoverWithLocalFile(picture, selectedCoverManga))
-                                ToastUtil.showShort(getContext(), R.string.notification_manga_update_failed);
-
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                        }
-                    }
-                    break;
-            }
-        }
-    }
-
-    private void moveMangasToCategories(List<Manga> mangas) {
-        new MaterialDialog.Builder(getActivity())
-                .title(R.string.action_move_category)
-                .items(getPresenter().getCategoriesNames())
-                .itemsCallbackMultiChoice(null, (dialog, which, text) -> {
-                    getPresenter().moveMangasToCategories(which, mangas);
-                    destroyActionModeIfNeeded();
-                    return true;
-                })
-                .positiveText(R.string.button_ok)
-                .negativeText(R.string.button_cancel)
-                .show();
-    }
-
-    @Nullable
-    public ActionMode getActionMode() {
-        return actionMode;
-    }
-
-    public LibraryAdapter getAdapter() {
-        return adapter;
-    }
-
-    public void createActionModeIfNeeded() {
-        if (actionMode == null) {
-            actionMode = getBaseActivity().startSupportActionMode(this);
-        }
-    }
-
-    public void invalidateActionMode() {
-        actionMode.invalidate();
-    }
-
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
new file mode 100644
index 000000000..6b5166cce
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryFragment.kt
@@ -0,0 +1,377 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.support.design.widget.AppBarLayout
+import android.support.design.widget.TabLayout
+import android.support.v7.view.ActionMode
+import android.support.v7.widget.SearchView
+import android.view.*
+import butterknife.ButterKnife
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.io.IOHandler
+import eu.kanade.tachiyomi.data.library.LibraryUpdateService
+import eu.kanade.tachiyomi.event.LibraryMangasEvent
+import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
+import eu.kanade.tachiyomi.ui.category.CategoryActivity
+import eu.kanade.tachiyomi.ui.main.MainActivity
+import eu.kanade.tachiyomi.util.ToastUtil
+import eu.kanade.tachiyomi.util.inflate
+import kotlinx.android.synthetic.main.fragment_library.*
+import nucleus.factory.RequiresPresenter
+import org.greenrobot.eventbus.EventBus
+import java.io.File
+import java.io.IOException
+
+/**
+ * Fragment that shows the manga from the library.
+ * Uses R.layout.fragment_library.
+ */
+@RequiresPresenter(LibraryPresenter::class)
+class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback {
+
+    /**
+     * Adapter containing the categories of the library.
+     */
+    lateinit var adapter: LibraryAdapter
+        private set
+
+    /**
+     * TabLayout of the categories.
+     */
+    private lateinit var tabs: TabLayout
+
+    /**
+     * AppBarLayout from [MainActivity].
+     */
+    private lateinit var appBar: AppBarLayout
+
+    /**
+     * Position of the active category.
+     */
+    private var activeCategory: Int = 0
+
+    /**
+     * Query of the search box.
+     */
+    private var query: String? = null
+
+    /**
+     * Action mode for manga selection.
+     */
+    var actionMode: ActionMode? = null
+        private set
+
+    /**
+     * Selected manga for editing its cover.
+     */
+    private var selectedCoverManga: Manga? = null
+
+    companion object {
+        /**
+         * Key to change the cover of a manga in [onActivityResult].
+         */
+        const val REQUEST_IMAGE_OPEN = 101
+
+        /**
+         * Key to add a manga to an [Intent].
+         */
+        const val MANGA_EXTRA = "manga_extra"
+
+        /**
+         * Key to save and restore [query] from a [Bundle].
+         */
+        const val QUERY_KEY = "query_key"
+
+        /**
+         * Key to save and restore [activeCategory] from a [Bundle].
+         */
+        const val CATEGORY_KEY = "category_key"
+
+        /**
+         * Creates a new instance of this fragment.
+         *
+         * @return a new instance of [LibraryFragment].
+         */
+        @JvmStatic
+        fun newInstance(): LibraryFragment {
+            return LibraryFragment()
+        }
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setHasOptionsMenu(true)
+    }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? {
+        return inflater.inflate(R.layout.fragment_library, container, false)
+    }
+
+    override fun onViewCreated(view: View, savedState: Bundle?) {
+        setToolbarTitle(getString(R.string.label_library))
+        ButterKnife.bind(this, view)
+
+        appBar = (activity as MainActivity).appBar
+        tabs = appBar.inflate(R.layout.library_tab_layout) as TabLayout
+        appBar.addView(tabs)
+
+        adapter = LibraryAdapter(childFragmentManager)
+        view_pager.adapter = adapter
+        tabs.setupWithViewPager(view_pager)
+
+        if (savedState != null) {
+            activeCategory = savedState.getInt(CATEGORY_KEY)
+            query = savedState.getString(QUERY_KEY)
+            presenter.searchSubject.onNext(query)
+        }
+    }
+
+    override fun onDestroyView() {
+        appBar.removeView(tabs)
+        super.onDestroyView()
+    }
+
+    override fun onSaveInstanceState(bundle: Bundle) {
+        bundle.putInt(CATEGORY_KEY, view_pager.currentItem)
+        bundle.putString(QUERY_KEY, query)
+        super.onSaveInstanceState(bundle)
+    }
+
+    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+        inflater.inflate(R.menu.library, menu)
+
+        // Initialize search menu
+        val searchItem = menu.findItem(R.id.action_search)
+        val searchView = searchItem.actionView as SearchView
+
+        if (!query.isNullOrEmpty()) {
+            searchItem.expandActionView()
+            searchView.setQuery(query, true)
+            searchView.clearFocus()
+        }
+
+        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+            override fun onQueryTextSubmit(query: String): Boolean {
+                onSearchTextChange(query)
+                return true
+            }
+
+            override fun onQueryTextChange(newText: String): Boolean {
+                onSearchTextChange(newText)
+                return true
+            }
+        })
+    }
+
+    override fun onOptionsItemSelected(item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.action_refresh -> LibraryUpdateService.start(activity)
+            R.id.action_edit_categories -> {
+                val intent = CategoryActivity.newIntent(activity)
+                startActivity(intent)
+            }
+            else -> return super.onOptionsItemSelected(item)
+        }
+
+        return true
+    }
+
+    /**
+     * Updates the query.
+     *
+     * @param query the new value of the query.
+     */
+    private fun onSearchTextChange(query: String?) {
+        this.query = query
+
+        // Notify the subject the query has changed.
+        presenter.searchSubject.onNext(query)
+    }
+
+    /**
+     * Called when the library is updated. It sets the new data and updates the view.
+     *
+     * @param categories the categories of the library.
+     * @param mangaMap a map containing the manga for each category.
+     */
+    fun onNextLibraryUpdate(categories: List<Category>, mangaMap: Map<Int, List<Manga>>) {
+        // Get the current active category.
+        val activeCat = if (adapter.categories != null) view_pager.currentItem else activeCategory
+
+        // Add the default category if it contains manga.
+        if (mangaMap[0] != null) {
+            setCategories(arrayListOf(Category.createDefault()) + categories)
+        } else {
+            setCategories(categories)
+        }
+
+        // Restore active category.
+        view_pager.setCurrentItem(activeCat, false)
+        if (tabs.tabCount > 0) {
+            tabs.getTabAt(view_pager.currentItem)?.select()
+        }
+
+        // Send the manga map to child fragments after the adapter is updated.
+        EventBus.getDefault().postSticky(LibraryMangasEvent(mangaMap))
+    }
+
+    /**
+     * Sets the categories in the adapter and the tab layout.
+     *
+     * @param categories the categories to set.
+     */
+    private fun setCategories(categories: List<Category>) {
+        adapter.categories = categories
+        tabs.setTabsFromPagerAdapter(adapter)
+        tabs.visibility = if (categories.size <= 1) View.GONE else View.VISIBLE
+    }
+
+    /**
+     * Sets the title of the action mode.
+     *
+     * @param count the number of items selected.
+     */
+    fun setContextTitle(count: Int) {
+        actionMode?.title = getString(R.string.label_selected, count)
+    }
+
+    /**
+     * Sets the visibility of the edit cover item.
+     *
+     * @param count the number of items selected.
+     */
+    fun setVisibilityOfCoverEdit(count: Int) {
+        // If count = 1 display edit button
+        actionMode?.menu?.findItem(R.id.action_edit_cover)?.isVisible = count == 1
+    }
+
+    override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
+        mode.menuInflater.inflate(R.menu.library_selection, menu)
+        adapter.setSelectionMode(FlexibleAdapter.MODE_MULTI)
+        return true
+    }
+
+    override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
+        return false
+    }
+
+    override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+        when (item.itemId) {
+            R.id.action_edit_cover -> {
+                changeSelectedCover(presenter.selectedMangas)
+                adapter.refreshRegisteredAdapters()
+                destroyActionModeIfNeeded()
+            }
+            R.id.action_move_to_category -> {
+                moveMangasToCategories(presenter.selectedMangas)
+            }
+            R.id.action_delete -> {
+                presenter.deleteMangas()
+                destroyActionModeIfNeeded()
+            }
+            else -> return false
+        }
+        return true
+    }
+
+    override fun onDestroyActionMode(mode: ActionMode) {
+        adapter.setSelectionMode(FlexibleAdapter.MODE_SINGLE)
+        presenter.selectedMangas.clear()
+        actionMode = null
+    }
+
+    /**
+     * Destroys the action mode.
+     */
+    fun destroyActionModeIfNeeded() {
+        actionMode?.finish()
+    }
+
+    /**
+     * Changes the cover for the selected manga.
+     *
+     * @param mangas a list of selected manga.
+     */
+    private fun changeSelectedCover(mangas: List<Manga>) {
+        if (mangas.size == 1) {
+            selectedCoverManga = mangas[0]
+            if (selectedCoverManga?.favorite ?: false) {
+                val intent = Intent(Intent.ACTION_GET_CONTENT)
+                intent.type = "image/*"
+                startActivityForResult(Intent.createChooser(intent,
+                        getString(R.string.file_select_cover)), REQUEST_IMAGE_OPEN)
+            } else {
+                ToastUtil.showShort(context, R.string.notification_first_add_to_library)
+            }
+
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
+        if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMAGE_OPEN) {
+            selectedCoverManga?.let { manga ->
+                // Get the file's content URI from the incoming Intent
+                val selectedImageUri = data.data
+
+                // Convert to absolute path to prevent FileNotFoundException
+                val result = IOHandler.getFilePath(selectedImageUri,
+                        context.contentResolver, context)
+
+                // Get file from filepath
+                val picture = File(result ?: "")
+
+                try {
+                    // Update cover to selected file, show error if something went wrong
+                    if (!presenter.editCoverWithLocalFile(picture, manga))
+                        ToastUtil.showShort(context, R.string.notification_manga_update_failed)
+
+                } catch (e: IOException) {
+                    e.printStackTrace()
+                }
+            }
+
+        }
+    }
+
+    /**
+     * Move the selected manga to a list of categories.
+     *
+     * @param mangas the manga list to move.
+     */
+    private fun moveMangasToCategories(mangas: List<Manga>) {
+        MaterialDialog.Builder(activity)
+                .title(R.string.action_move_category)
+                .items(presenter.getCategoryNames())
+                .itemsCallbackMultiChoice(null) { dialog, positions, text ->
+                    presenter.moveMangasToCategories(positions, mangas)
+                    destroyActionModeIfNeeded()
+                    true
+                }
+                .positiveText(R.string.button_ok)
+                .negativeText(R.string.button_cancel)
+                .show()
+    }
+
+    /**
+     * Creates the action mode if it's not created already.
+     */
+    fun createActionModeIfNeeded() {
+        if (actionMode == null) {
+            actionMode = baseActivity.startSupportActionMode(this)
+        }
+    }
+
+    /**
+     * Invalidates the action mode, forcing it to refresh its content.
+     */
+    fun invalidateActionMode() {
+        actionMode?.invalidate()
+    }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java
deleted file mode 100644
index ddc104a01..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import butterknife.Bind;
-import butterknife.ButterKnife;
-import eu.kanade.tachiyomi.R;
-import eu.kanade.tachiyomi.data.cache.CoverCache;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-import eu.kanade.tachiyomi.data.source.base.Source;
-import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.widget.RelativeLayout.LayoutParams;
-
-public class LibraryHolder extends FlexibleViewHolder {
-
-    @Bind(R.id.image_container) FrameLayout container;
-    @Bind(R.id.thumbnail) ImageView thumbnail;
-    @Bind(R.id.title) TextView title;
-    @Bind(R.id.unreadText) TextView unreadText;
-
-    public LibraryHolder(View view, LibraryCategoryAdapter adapter, OnListItemClickListener listener) {
-        super(view, adapter, listener);
-        ButterKnife.bind(this, view);
-        container.setLayoutParams(new LayoutParams(MATCH_PARENT, adapter.getCoverHeight()));
-    }
-
-    public void onSetValues(Manga manga, LibraryPresenter presenter) {
-        title.setText(manga.title);
-
-        if (manga.unread > 0) {
-            unreadText.setVisibility(View.VISIBLE);
-            unreadText.setText(Integer.toString(manga.unread));
-        } else {
-            unreadText.setVisibility(View.GONE);
-        }
-
-        loadCover(manga, presenter.sourceManager.get(manga.source), presenter.coverCache);
-    }
-
-    private void loadCover(Manga manga, Source source, CoverCache coverCache) {
-        if (manga.thumbnail_url != null) {
-            coverCache.saveOrLoadFromCache(thumbnail, manga.thumbnail_url, source.getGlideHeaders());
-        } else {
-            thumbnail.setImageResource(android.R.color.transparent);
-        }
-    }
-
-
-
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
new file mode 100644
index 000000000..1b5986469
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt
@@ -0,0 +1,58 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.view.View
+import eu.kanade.tachiyomi.data.cache.CoverCache
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.source.base.Source
+import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
+import kotlinx.android.synthetic.main.item_catalogue_grid.view.*
+
+/**
+ * Class used to hold the displayed data of a manga in the library, like the cover or the title.
+ * All the elements from the layout file "item_catalogue_grid" are available in this class.
+ *
+ * @param view the inflated view for this holder.
+ * @param adapter the adapter handling this holder.
+ * @param listener a listener to react to single tap and long tap events.
+ * @constructor creates a new library holder.
+ */
+class LibraryHolder(view: View, adapter: LibraryCategoryAdapter, listener: FlexibleViewHolder.OnListItemClickListener) :
+        FlexibleViewHolder(view, adapter, listener) {
+
+    /**
+     * Method called from [LibraryCategoryAdapter.onBindViewHolder]. It updates the data for this
+     * holder with the given manga.
+     *
+     * @param manga the manga to bind.
+     * @param presenter the library presenter.
+     */
+    fun onSetValues(manga: Manga, presenter: LibraryPresenter) {
+        // Update the title of the manga.
+        itemView.title.text = manga.title
+
+        // Update the unread count and its visibility.
+        with(itemView.unreadText) {
+            visibility = if (manga.unread > 0) View.VISIBLE else View.GONE
+            text = manga.unread.toString()
+        }
+
+        // Update the cover.
+        loadCover(manga, presenter.sourceManager.get(manga.source)!!, presenter.coverCache)
+    }
+
+    /**
+     * Load the cover of a manga in a image view.
+     *
+     * @param manga the manga to bind.
+     * @param source the source of the manga.
+     * @param coverCache the cache that stores the cover in the filesystem.
+     */
+    private fun loadCover(manga: Manga, source: Source, coverCache: CoverCache) {
+        if (manga.thumbnail_url != null) {
+            coverCache.saveOrLoadFromCache(itemView.thumbnail, manga.thumbnail_url, source.glideHeaders)
+        } else {
+            itemView.thumbnail.setImageResource(android.R.color.transparent)
+        }
+    }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.java b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.java
deleted file mode 100644
index 3d51127e5..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package eu.kanade.tachiyomi.ui.library;
-
-import android.os.Bundle;
-import android.util.Pair;
-
-import org.greenrobot.eventbus.EventBus;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import javax.inject.Inject;
-
-import eu.kanade.tachiyomi.data.cache.CoverCache;
-import eu.kanade.tachiyomi.data.database.DatabaseHelper;
-import eu.kanade.tachiyomi.data.database.models.Category;
-import eu.kanade.tachiyomi.data.database.models.Manga;
-import eu.kanade.tachiyomi.data.database.models.MangaCategory;
-import eu.kanade.tachiyomi.data.preference.PreferencesHelper;
-import eu.kanade.tachiyomi.data.source.SourceManager;
-import eu.kanade.tachiyomi.event.LibraryMangasEvent;
-import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter;
-import rx.Observable;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.subjects.BehaviorSubject;
-
-public class LibraryPresenter extends BasePresenter<LibraryFragment> {
-
-    private static final int GET_LIBRARY = 1;
-    protected List<Category> categories;
-    protected List<Manga> selectedMangas;
-    protected BehaviorSubject<String> searchSubject;
-    @Inject DatabaseHelper db;
-    @Inject PreferencesHelper preferences;
-    @Inject CoverCache coverCache;
-    @Inject SourceManager sourceManager;
-
-    @Override
-    protected void onCreate(Bundle savedState) {
-        super.onCreate(savedState);
-
-        selectedMangas = new ArrayList<>();
-
-        searchSubject = BehaviorSubject.create();
-
-        restartableLatestCache(GET_LIBRARY,
-                this::getLibraryObservable,
-                (view, pair) -> view.onNextLibraryUpdate(pair.first, pair.second));
-
-        if (savedState == null) {
-            start(GET_LIBRARY);
-        }
-    }
-
-    @Override
-    protected void onDropView() {
-        EventBus.getDefault().removeStickyEvent(LibraryMangasEvent.class);
-        super.onDropView();
-    }
-
-    @Override
-    protected void onTakeView(LibraryFragment libraryFragment) {
-        super.onTakeView(libraryFragment);
-        if (isUnsubscribed(GET_LIBRARY)) {
-            start(GET_LIBRARY);
-        }
-    }
-
-    private Observable<Pair<List<Category>, Map<Integer, List<Manga>>>> getLibraryObservable() {
-        return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
-                Pair::create)
-                .observeOn(AndroidSchedulers.mainThread());
-    }
-
-    private Observable<List<Category>> getCategoriesObservable() {
-        return db.getCategories().asRxObservable()
-                .doOnNext(categories -> this.categories = categories);
-    }
-
-    private Observable<Map<Integer, List<Manga>>> getLibraryMangasObservable() {
-        return db.getLibraryMangas().asRxObservable()
-                .flatMap(mangas -> Observable.from(mangas)
-                        .groupBy(manga -> manga.category)
-                        .flatMap(group -> group.toList()
-                                .map(list -> Pair.create(group.getKey(), list)))
-                        .toMap(pair -> pair.first, pair -> pair.second));
-    }
-
-    public void onOpenManga(Manga manga) {
-        // Avoid further db updates for the library when it's not needed
-        stop(GET_LIBRARY);
-    }
-
-    public void setSelection(Manga manga, boolean selected) {
-        if (selected) {
-            selectedMangas.add(manga);
-        } else {
-            selectedMangas.remove(manga);
-        }
-    }
-
-    public String[] getCategoriesNames() {
-        int count = categories.size();
-        String[] names = new String[count];
-
-        for (int i = 0; i < count; i++) {
-            names[i] = categories.get(i).name;
-        }
-
-        return names;
-    }
-
-    public void deleteMangas() {
-        for (Manga manga : selectedMangas) {
-            manga.favorite = false;
-        }
-
-        db.insertMangas(selectedMangas).executeAsBlocking();
-    }
-
-    public void moveMangasToCategories(Integer[] positions, List<Manga> mangas) {
-        List<Category> categoriesToAdd = new ArrayList<>();
-        for (Integer index : positions) {
-            categoriesToAdd.add(categories.get(index));
-        }
-
-        moveMangasToCategories(categoriesToAdd, mangas);
-    }
-
-    public void moveMangasToCategories(List<Category> categories, List<Manga> mangas) {
-        List<MangaCategory> mc = new ArrayList<>();
-
-        for (Manga manga : mangas) {
-            for (Category cat : categories) {
-                mc.add(MangaCategory.create(manga, cat));
-            }
-        }
-
-        db.setMangaCategories(mc, mangas);
-    }
-
-    /**
-     * Update cover with local file
-     */
-    public boolean editCoverWithLocalFile(File file, Manga manga) throws IOException {
-        if (!manga.initialized)
-            return false;
-
-        if (manga.favorite) {
-            coverCache.copyToLocalCache(manga.thumbnail_url, file);
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
new file mode 100644
index 000000000..23269398f
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
@@ -0,0 +1,227 @@
+package eu.kanade.tachiyomi.ui.library
+
+import android.os.Bundle
+import android.util.Pair
+import eu.kanade.tachiyomi.data.cache.CoverCache
+import eu.kanade.tachiyomi.data.database.DatabaseHelper
+import eu.kanade.tachiyomi.data.database.models.Category
+import eu.kanade.tachiyomi.data.database.models.Manga
+import eu.kanade.tachiyomi.data.database.models.MangaCategory
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.source.SourceManager
+import eu.kanade.tachiyomi.event.LibraryMangasEvent
+import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
+import org.greenrobot.eventbus.EventBus
+import rx.Observable
+import rx.android.schedulers.AndroidSchedulers
+import rx.subjects.BehaviorSubject
+import java.io.File
+import java.io.IOException
+import java.util.*
+import javax.inject.Inject
+
+/**
+ * Presenter of [LibraryFragment].
+ */
+class LibraryPresenter : BasePresenter<LibraryFragment>() {
+
+    /**
+     * Categories of the library.
+     */
+    lateinit var categories: List<Category>
+
+    /**
+     * Currently selected manga.
+     */
+    lateinit var selectedMangas: MutableList<Manga>
+
+    /**
+     * Search query of the library.
+     */
+    lateinit var searchSubject: BehaviorSubject<String>
+
+    /**
+     * Database.
+     */
+    @Inject lateinit var db: DatabaseHelper
+
+    /**
+     * Preferences.
+     */
+    @Inject lateinit var preferences: PreferencesHelper
+
+    /**
+     * Cover cache.
+     */
+    @Inject lateinit var coverCache: CoverCache
+
+    /**
+     * Source manager.
+     */
+    @Inject lateinit var sourceManager: SourceManager
+
+    companion object {
+        /**
+         * Id of the restartable that listens for library updates.
+         */
+        const val GET_LIBRARY = 1
+    }
+
+    override fun onCreate(savedState: Bundle?) {
+        super.onCreate(savedState)
+
+        selectedMangas = ArrayList()
+
+        searchSubject = BehaviorSubject.create()
+
+        restartableLatestCache(GET_LIBRARY,
+                { getLibraryObservable() },
+                { view, pair -> view.onNextLibraryUpdate(pair.first, pair.second) })
+
+        if (savedState == null) {
+            start(GET_LIBRARY)
+        }
+
+    }
+
+    override fun onDropView() {
+        EventBus.getDefault().removeStickyEvent(LibraryMangasEvent::class.java)
+        super.onDropView()
+    }
+
+    override fun onTakeView(libraryFragment: LibraryFragment) {
+        super.onTakeView(libraryFragment)
+        if (isUnsubscribed(GET_LIBRARY)) {
+            start(GET_LIBRARY)
+        }
+    }
+
+    /**
+     * Get the categories and all its manga from the database.
+     *
+     * @return an observable of the categories and its manga.
+     */
+    fun getLibraryObservable(): Observable<Pair<List<Category>, Map<Int, List<Manga>>>> {
+        return Observable.combineLatest(getCategoriesObservable(), getLibraryMangasObservable(),
+                { a, b -> Pair(a, b) })
+                .observeOn(AndroidSchedulers.mainThread())
+    }
+
+    /**
+     * Get the categories from the database.
+     *
+     * @return an observable of the categories.
+     */
+    fun getCategoriesObservable(): Observable<List<Category>> {
+        return db.categories.asRxObservable()
+                .doOnNext { categories -> this.categories = categories }
+    }
+
+    /**
+     * Get the manga grouped by categories.
+     *
+     * @return an observable containing a map with the category id as key and a list of manga as the
+     * value.
+     */
+    fun getLibraryMangasObservable(): Observable<Map<Int, List<Manga>>> {
+        return db.libraryMangas.asRxObservable()
+                .flatMap { mangas -> Observable.from(mangas)
+                        .groupBy { it.category }
+                        .flatMap { group -> group.toList().map { Pair(group.key, it) } }
+                        .toMap({ it.first }, { it.second })
+                }
+    }
+
+    /**
+     * Called when a manga is opened.
+     */
+    fun onOpenManga() {
+        // Avoid further db updates for the library when it's not needed
+        stop(GET_LIBRARY)
+    }
+
+    /**
+     * Sets the selection for a given manga.
+     *
+     * @param manga the manga whose selection has changed.
+     * @param selected whether it's now selected or not.
+     */
+    fun setSelection(manga: Manga, selected: Boolean) {
+        if (selected) {
+            selectedMangas.add(manga)
+        } else {
+            selectedMangas.remove(manga)
+        }
+    }
+
+    /**
+     * Get the category names as a list.
+     */
+    fun getCategoryNames(): List<String> {
+        return categories.map { it.name }
+    }
+
+    /**
+     * Remove the selected manga from the library.
+     */
+    fun deleteMangas() {
+        for (manga in selectedMangas) {
+            manga.favorite = false
+        }
+
+        db.insertMangas(selectedMangas).executeAsBlocking()
+    }
+
+    /**
+     * Move the given list of manga to categories.
+     *
+     * @param positions the indexes of the selected categories.
+     * @param mangas the list of manga to move.
+     */
+    fun moveMangasToCategories(positions: Array<Int>, mangas: List<Manga>) {
+        val categoriesToAdd = ArrayList<Category>()
+        for (index in positions) {
+            categoriesToAdd.add(categories[index])
+        }
+
+        moveMangasToCategories(categoriesToAdd, mangas)
+    }
+
+    /**
+     * Move the given list of manga to categories.
+     *
+     * @param categories the selected categories.
+     * @param mangas the list of manga to move.
+     */
+    fun moveMangasToCategories(categories: List<Category>, mangas: List<Manga>) {
+        val mc = ArrayList<MangaCategory>()
+
+        for (manga in mangas) {
+            for (cat in categories) {
+                mc.add(MangaCategory.create(manga, cat))
+            }
+        }
+
+        db.setMangaCategories(mc, mangas)
+    }
+
+    /**
+     * Update cover with local file.
+     *
+     * @param file the new cover.
+     * @param manga the manga edited.
+     * @return true if the cover is updated, false otherwise
+     */
+    @Throws(IOException::class)
+    fun editCoverWithLocalFile(file: File, manga: Manga): Boolean {
+        if (!manga.initialized)
+            return false
+
+        if (manga.favorite) {
+            coverCache.copyToLocalCache(manga.thumbnail_url, file)
+            return true
+        }
+        return false
+    }
+
+}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
index c32083918..fcd1bb714 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ContextExtensions.kt
@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.util
 
 import android.app.AlarmManager
 import android.app.Notification
+import android.app.NotificationManager
 import android.content.Context
 import android.support.annotation.StringRes
 import android.support.v4.app.NotificationCompat
@@ -27,6 +28,12 @@ inline fun Context.notification(func: NotificationCompat.Builder.() -> Unit): No
     return builder.build()
 }
 
+/**
+ * Property to get the notification manager from the context.
+ */
+val Context.notificationManager : NotificationManager
+    get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+
 /**
  * Property to get the alarm manager from the context.
  * @return the alarm manager.
diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
index 377fd1bf5..21ccf5ae3 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/util/ViewGroupExtensions.kt
@@ -12,4 +12,4 @@ import android.view.ViewGroup
  */
 fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View {
     return LayoutInflater.from(context).inflate(layout, this, attachToRoot)
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/layout/fragment_library_category.xml b/app/src/main/res/layout/fragment_library_category.xml
index f9d23bb34..06ab5a915 100644
--- a/app/src/main/res/layout/fragment_library_category.xml
+++ b/app/src/main/res/layout/fragment_library_category.xml
@@ -5,7 +5,7 @@
               android:layout_height="match_parent">
 
     <eu.kanade.tachiyomi.widget.AutofitRecyclerView
-        android:id="@+id/library_mangas"
+        android:id="@+id/recycler"
         style="@style/AppTheme.GridView"
         android:columnWidth="140dp"
         tools:listitem="@layout/item_catalogue_grid" />
diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java
index 075232404..6c159c651 100644
--- a/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java
+++ b/app/src/test/java/eu/kanade/tachiyomi/data/library/LibraryUpdateServiceTest.java
@@ -47,7 +47,7 @@ public class LibraryUpdateServiceTest {
         source = mock(Source.class);
         when(service.sourceManager.get(anyInt())).thenReturn(source);
     }
-    
+
     @Test
     public void testLifecycle() {
         // Smoke test