mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 21:11:52 +01:00
Migrate library to Kotlin.
This commit is contained in:
parent
9062e40ec5
commit
59276b7160
@ -1,6 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.data.library
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
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.AndroidComponentUtil
|
||||||
import eu.kanade.tachiyomi.util.NetworkUtil
|
import eu.kanade.tachiyomi.util.NetworkUtil
|
||||||
import eu.kanade.tachiyomi.util.notification
|
import eu.kanade.tachiyomi.util.notification
|
||||||
|
import eu.kanade.tachiyomi.util.notificationManager
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
@ -310,12 +310,6 @@ class LibraryUpdateService : Service() {
|
|||||||
notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
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.
|
* 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.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.ui.base.adapter.OnStartDragListener
|
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.activity_edit_categories.*
|
||||||
import kotlinx.android.synthetic.main.toolbar.*
|
import kotlinx.android.synthetic.main.toolbar.*
|
||||||
import nucleus.factory.RequiresPresenter
|
import nucleus.factory.RequiresPresenter
|
||||||
@ -93,7 +93,7 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
|
|||||||
* Call this when action mode action is finished.
|
* Call this when action mode action is finished.
|
||||||
*/
|
*/
|
||||||
fun destroyActionModeIfNeeded() {
|
fun destroyActionModeIfNeeded() {
|
||||||
actionMode?.finish()
|
actionMode?.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -163,8 +163,8 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
|
|||||||
it.invalidate()
|
it.invalidate()
|
||||||
|
|
||||||
// Show edit button only when one item is selected
|
// Show edit button only when one item is selected
|
||||||
val editItem = it.menu?.findItem(R.id.action_edit)
|
val editItem = it.menu.findItem(R.id.action_edit)
|
||||||
editItem?.isVisible = count == 1
|
editItem.isVisible = count == 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -192,15 +192,14 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
|
|||||||
R.id.action_delete -> {
|
R.id.action_delete -> {
|
||||||
// Delete select categories.
|
// Delete select categories.
|
||||||
deleteCategories(getSelectedCategories())
|
deleteCategories(getSelectedCategories())
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
R.id.action_edit -> {
|
R.id.action_edit -> {
|
||||||
// Edit selected category
|
// Edit selected category
|
||||||
editCategory(getSelectedCategories()?.get(0))
|
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.
|
// Inflate menu.
|
||||||
mode.menuInflater.inflate(R.menu.category_selection, menu)
|
mode.menuInflater.inflate(R.menu.category_selection, menu)
|
||||||
// Enable adapter multi selection.
|
// Enable adapter multi selection.
|
||||||
adapter.mode = LibraryCategoryAdapter.MODE_MULTI
|
adapter.mode = FlexibleAdapter.MODE_MULTI
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,7 +225,7 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
|
|||||||
*/
|
*/
|
||||||
override fun onDestroyActionMode(mode: ActionMode?) {
|
override fun onDestroyActionMode(mode: ActionMode?) {
|
||||||
// Reset adapter to single selection
|
// Reset adapter to single selection
|
||||||
adapter.mode = LibraryCategoryAdapter.MODE_SINGLE
|
adapter.mode = FlexibleAdapter.MODE_SINGLE
|
||||||
// Clear selected items
|
// Clear selected items
|
||||||
adapter.clearSelection()
|
adapter.clearSelection()
|
||||||
actionMode = null
|
actionMode = null
|
||||||
@ -239,8 +238,9 @@ class CategoryActivity : BaseRxActivity<CategoryPresenter>(), ActionMode.Callbac
|
|||||||
*/
|
*/
|
||||||
override fun onListItemClick(position: Int): Boolean {
|
override fun onListItemClick(position: Int): Boolean {
|
||||||
// Check if action mode is initialized and selected item exist.
|
// Check if action mode is initialized and selected item exist.
|
||||||
if (actionMode != null && position != -1) {
|
if (position == -1) {
|
||||||
// Toggle selection of clicked item.
|
return false
|
||||||
|
} else if (actionMode != null) {
|
||||||
toggleSelection(position)
|
toggleSelection(position)
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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.AlarmManager
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.support.annotation.StringRes
|
import android.support.annotation.StringRes
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
@ -27,6 +28,12 @@ inline fun Context.notification(func: NotificationCompat.Builder.() -> Unit): No
|
|||||||
return builder.build()
|
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.
|
* Property to get the alarm manager from the context.
|
||||||
* @return the alarm manager.
|
* @return the alarm manager.
|
||||||
|
@ -12,4 +12,4 @@ import android.view.ViewGroup
|
|||||||
*/
|
*/
|
||||||
fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View {
|
fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): View {
|
||||||
return LayoutInflater.from(context).inflate(layout, this, attachToRoot)
|
return LayoutInflater.from(context).inflate(layout, this, attachToRoot)
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
<eu.kanade.tachiyomi.widget.AutofitRecyclerView
|
||||||
android:id="@+id/library_mangas"
|
android:id="@+id/recycler"
|
||||||
style="@style/AppTheme.GridView"
|
style="@style/AppTheme.GridView"
|
||||||
android:columnWidth="140dp"
|
android:columnWidth="140dp"
|
||||||
tools:listitem="@layout/item_catalogue_grid" />
|
tools:listitem="@layout/item_catalogue_grid" />
|
||||||
|
@ -47,7 +47,7 @@ public class LibraryUpdateServiceTest {
|
|||||||
source = mock(Source.class);
|
source = mock(Source.class);
|
||||||
when(service.sourceManager.get(anyInt())).thenReturn(source);
|
when(service.sourceManager.get(anyInt())).thenReturn(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testLifecycle() {
|
public void testLifecycle() {
|
||||||
// Smoke test
|
// Smoke test
|
||||||
|
Loading…
Reference in New Issue
Block a user