mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Migrate library to Kotlin.
This commit is contained in:
		@@ -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.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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" />
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ public class LibraryUpdateServiceTest {
 | 
			
		||||
        source = mock(Source.class);
 | 
			
		||||
        when(service.sourceManager.get(anyInt())).thenReturn(source);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testLifecycle() {
 | 
			
		||||
        // Smoke test
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user