Add manga straight into a category from catalogues (#737)

* Add feature mention in issue #625
This commit is contained in:
lifeweaver 2017-04-07 14:39:09 -04:00 committed by Bram van de Kerkhof
parent bb9e230b35
commit f6a79bde6f
11 changed files with 254 additions and 11 deletions

View File

@ -115,4 +115,6 @@ class PreferenceKeys(context: Context) {
val lang = context.getString(R.string.pref_language_key) val lang = context.getString(R.string.pref_language_key)
val defaultCategory = context.getString(R.string.default_category_key)
} }

View File

@ -158,4 +158,6 @@ class PreferencesHelper(val context: Context) {
fun lang() = prefs.getString(keys.lang, "") fun lang() = prefs.getString(keys.lang, "")
fun defaultCategory() = prefs.getInt(keys.defaultCategory, -1)
} }

View File

@ -34,6 +34,10 @@ import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import android.widget.Toast
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.injectLazy
/** /**
* Fragment that shows the manga from the catalogue. * Fragment that shows the manga from the catalogue.
@ -45,6 +49,11 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(),
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
FlexibleAdapter.EndlessScrollListener<ProgressItem> { FlexibleAdapter.EndlessScrollListener<ProgressItem> {
/**
* Preferences helper.
*/
private val preferences: PreferencesHelper by injectLazy()
/** /**
* Spinner shown in the toolbar to change the selected source. * Spinner shown in the toolbar to change the selected source.
*/ */
@ -530,23 +539,62 @@ open class CatalogueFragment : BaseRxFragment<CataloguePresenter>(),
/** /**
* Called when a manga is long clicked. * Called when a manga is long clicked.
* *
* Adds the manga to the default category if none is set it shows a list of categories for the user to put the manga
* in, the list consists of the default category plus the user's categories. The default category is preselected on
* new manga, and on already favorited manga the manga's categories are preselected.
*
* @param position the position of the element clicked. * @param position the position of the element clicked.
*/ */
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
val manga = (adapter.getItem(position) as? CatalogueItem?)?.manga ?: return val manga = (adapter.getItem(position) as? CatalogueItem?)?.manga ?: return
val categories = presenter.getCategories()
val textRes = if (manga.favorite) R.string.remove_from_library else R.string.add_to_library val defaultCategory = categories.find { it.id == preferences.defaultCategory()}
if(defaultCategory != null) {
MaterialDialog.Builder(activity) if(!manga.favorite) {
.items(getString(textRes))
.itemsCallback { dialog, itemView, which, text ->
when (which) {
0 -> {
presenter.changeMangaFavorite(manga) presenter.changeMangaFavorite(manga)
}
presenter.moveMangaToCategory(defaultCategory, manga)
} else {
MaterialDialog.Builder(activity)
.title(R.string.action_move_category)
.items(categories.map { it.name })
.itemsCallbackMultiChoice(presenter.getMangaCategoryIds(manga)) { dialog, position, _ ->
if (defaultSelectedWithOtherCategory(position)) {
// Deselect default category
dialog.setSelectedIndices(position.filter {it > 0}.toTypedArray())
Toast.makeText(dialog.context, R.string.invalid_combination, Toast.LENGTH_SHORT).show()
}
true
}
.alwaysCallMultiChoiceCallback()
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { dialog, _ ->
updateMangaCategories(manga, dialog, categories, position)
}
.build()
.show()
}
}
private fun defaultSelectedWithOtherCategory(position: Array<Int>): Boolean {
return position.contains(0) && position.count() > 1
}
private fun updateMangaCategories(manga: Manga, dialog: MaterialDialog, categories: List<Category>, position: Int) {
val selectedCategories = dialog.selectedIndices?.map { categories[it] } ?: emptyList()
if(!selectedCategories.isEmpty()) {
if(!manga.favorite) {
presenter.changeMangaFavorite(manga)
}
presenter.moveMangaToCategories(selectedCategories.filter { it.id != 0}, manga)
} else {
presenter.changeMangaFavorite(manga)
}
adapter.notifyItemChanged(position) adapter.notifyItemChanged(position)
} }
}
}.show()
}
} }

View File

@ -5,7 +5,9 @@ import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -24,6 +26,7 @@ import rx.schedulers.Schedulers
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.ArrayList
/** /**
* Presenter of [CatalogueFragment]. * Presenter of [CatalogueFragment].
@ -396,4 +399,49 @@ open class CataloguePresenter : BasePresenter<CatalogueFragment>() {
} }
} }
/**
* Get the default, and user categories.
*
* @return List of categories, default plus user categories
*/
fun getCategories(): List<Category> {
return arrayListOf(Category.createDefault()) + db.getCategories().executeAsBlocking()
}
/**
* Gets the category id's the manga is in, if the manga is not in a category, returns the default id.
*
* @param manga the manga to get categories from.
* @return Array of category ids the manga is in, if none returns default id
*/
fun getMangaCategoryIds(manga: Manga): Array<Int?> {
val categories = db.getCategoriesForManga(manga).executeAsBlocking()
if(categories.isEmpty()) {
return arrayListOf(Category.createDefault().id).toTypedArray()
}
return categories.map { it.id }.toTypedArray()
}
/**
* Move the given manga to categories.
*
* @param categories the selected categories.
* @param manga the manga to move.
*/
fun moveMangaToCategories(categories: List<Category>, manga: Manga) {
val mc = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mc, arrayListOf(manga))
}
/**
* Move the given manga to the category.
*
* @param category the selected category.
* @param manga the manga to move.
*/
fun moveMangaToCategory(category: Category, manga: Manga) {
moveMangaToCategories(arrayListOf(category), manga)
}
} }

View File

@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.view.* import android.view.*
import android.widget.Toast
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bumptech.glide.BitmapRequestBuilder import com.bumptech.glide.BitmapRequestBuilder
import com.bumptech.glide.BitmapTypeRequest import com.bumptech.glide.BitmapTypeRequest
@ -14,6 +15,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@ -31,6 +33,7 @@ import nucleus.factory.RequiresPresenter
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
/** /**
* Fragment that shows manga information. * Fragment that shows manga information.
@ -52,6 +55,11 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
} }
/**
* Preferences helper.
*/
private val preferences: PreferencesHelper by injectLazy()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -63,7 +71,19 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
override fun onViewCreated(view: View?, savedState: Bundle?) { override fun onViewCreated(view: View?, savedState: Bundle?) {
// Set onclickListener to toggle favorite when FAB clicked. // Set onclickListener to toggle favorite when FAB clicked.
fab_favorite.setOnClickListener { toggleFavorite() } fab_favorite.setOnClickListener {
if(!presenter.manga.favorite) {
val defaultCategory = presenter.getCategories().find { it.id == preferences.defaultCategory()}
if(defaultCategory == null) {
onFabClick()
} else {
toggleFavorite()
presenter.moveMangaToCategory(defaultCategory, presenter.manga)
}
} else {
toggleFavorite()
}
}
// Set SwipeRefresh to refresh manga data. // Set SwipeRefresh to refresh manga data.
swipe_refresh.setOnRefreshListener { fetchMangaFromSource() } swipe_refresh.setOnRefreshListener { fetchMangaFromSource() }
@ -334,4 +354,40 @@ class MangaInfoFragment : BaseRxFragment<MangaInfoPresenter>() {
swipe_refresh.isRefreshing = value swipe_refresh.isRefreshing = value
} }
/**
* Called when the fab is clicked.
*/
private fun onFabClick() {
val categories = presenter.getCategories()
MaterialDialog.Builder(activity)
.title(R.string.action_move_category)
.items(categories.map { it.name })
.itemsCallbackMultiChoice(presenter.getMangaCategoryIds(presenter.manga)) { dialog, position, text ->
if (position.contains(0) && position.count() > 1) {
dialog.setSelectedIndices(position.filter {it > 0}.toTypedArray())
Toast.makeText(dialog.context, R.string.invalid_combination, Toast.LENGTH_SHORT).show()
}
true
}
.alwaysCallMultiChoiceCallback()
.positiveText(android.R.string.ok)
.negativeText(android.R.string.cancel)
.onPositive { dialog, _ ->
val selectedCategories = dialog.selectedIndices?.map { categories[it] } ?: emptyList()
if(!selectedCategories.isEmpty()) {
if(!presenter.manga.favorite) {
toggleFavorite()
}
presenter.moveMangaToCategories(selectedCategories.filter { it.id != 0}, presenter.manga)
} else {
toggleFavorite()
}
}
.build()
.show()
}
} }

View File

@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.ui.manga.info
import android.os.Bundle import android.os.Bundle
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -16,6 +18,7 @@ import rx.Subscription
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.ArrayList
/** /**
* Presenter of MangaInfoFragment. * Presenter of MangaInfoFragment.
@ -148,4 +151,49 @@ class MangaInfoPresenter : BasePresenter<MangaInfoFragment>() {
downloadManager.findMangaDir(source, manga)?.delete() downloadManager.findMangaDir(source, manga)?.delete()
} }
/**
* Get the default, and user categories.
*
* @return List of categories, default plus user categories
*/
fun getCategories(): List<Category> {
return arrayListOf(Category.createDefault()) + db.getCategories().executeAsBlocking()
}
/**
* Gets the category id's the manga is in, if the manga is not in a category, returns the default id.
*
* @param manga the manga to get categories from.
* @return Array of category ids the manga is in, if none returns default id
*/
fun getMangaCategoryIds(manga: Manga): Array<Int?> {
val categories = db.getCategoriesForManga(manga).executeAsBlocking()
if(categories.isEmpty()) {
return arrayListOf(Category.createDefault().id).toTypedArray()
}
return categories.map { it.id }.toTypedArray()
}
/**
* Move the given manga to categories.
*
* @param categories the selected categories.
* @param manga the manga to move.
*/
fun moveMangaToCategories(categories: List<Category>, manga: Manga) {
val mc = categories.map { MangaCategory.create(manga, it) }
db.setMangaCategories(mc, arrayListOf(manga))
}
/**
* Move the given manga to the category.
*
* @param category the selected category.
* @param manga the manga to move.
*/
fun moveMangaToCategory(category: Category, manga: Manga) {
moveMangaToCategories(arrayListOf(category), manga)
}
} }

View File

@ -7,6 +7,7 @@ import android.support.v7.preference.XpPreferenceFragment
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
@ -46,6 +47,8 @@ class SettingsGeneralFragment : SettingsFragment(),
val categoryUpdate: MultiSelectListPreference by bindPref(R.string.pref_library_update_categories_key) val categoryUpdate: MultiSelectListPreference by bindPref(R.string.pref_library_update_categories_key)
val defaultCategory: IntListPreference by bindPref(R.string.default_category_key)
val langPreference: ListPreference by bindPref(R.string.pref_language_key) val langPreference: ListPreference by bindPref(R.string.pref_language_key)
override fun onViewCreated(view: View, savedState: Bundle?) { override fun onViewCreated(view: View, savedState: Bundle?) {
@ -100,6 +103,22 @@ class SettingsGeneralFragment : SettingsFragment(),
categoryUpdate.summary = summary categoryUpdate.summary = summary
} }
defaultCategory.apply {
val selectedCategory = dbCategories.find { it.id == preferences.defaultCategory()}
value = selectedCategory?.id?.toString() ?: value
entries += dbCategories.map { it.name }.toTypedArray()
entryValues += dbCategories.map { it.id.toString() }.toTypedArray()
summary = selectedCategory?.name ?: summary
}
defaultCategory.setOnPreferenceChangeListener { _, newValue ->
defaultCategory.summary = dbCategories.find {
it.id == (newValue as String).toInt()
}?.name ?: getString(R.string.default_category_summary)
true
}
themePreference.setOnPreferenceChangeListener { preference, newValue -> themePreference.setOnPreferenceChangeListener { preference, newValue ->
(activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_THEME_CHANGED (activity as SettingsActivity).parentFlags = SettingsActivity.FLAG_THEME_CHANGED
activity.recreate() activity.recreate()

View File

@ -242,4 +242,12 @@
<item>vi</item> <item>vi</item>
</string-array> </string-array>
<string-array name="default_category_entry">
<item>@string/default_category_summary</item>
</string-array>
<string-array name="default_category_entry_value">
<item>-1</item>
</string-array>
</resources> </resources>

View File

@ -21,6 +21,7 @@
<string name="pref_library_update_restriction_key" translatable="false">library_update_restriction</string> <string name="pref_library_update_restriction_key" translatable="false">library_update_restriction</string>
<string name="pref_start_screen_key" translatable="false">start_screen</string> <string name="pref_start_screen_key" translatable="false">start_screen</string>
<string name="pref_language_key" translatable="false">app_language</string> <string name="pref_language_key" translatable="false">app_language</string>
<string name="default_category_key" translatable="false">default_category</string>
<string name="pref_default_viewer_key" translatable="false">pref_default_viewer_key</string> <string name="pref_default_viewer_key" translatable="false">pref_default_viewer_key</string>
<string name="pref_image_scale_type_key" translatable="false">pref_image_scale_type_key</string> <string name="pref_image_scale_type_key" translatable="false">pref_image_scale_type_key</string>

View File

@ -134,6 +134,8 @@
<string name="pref_start_screen">Start screen</string> <string name="pref_start_screen">Start screen</string>
<string name="pref_language">Language</string> <string name="pref_language">Language</string>
<string name="system_default">System default</string> <string name="system_default">System default</string>
<string name="default_category">Default category</string>
<string name="default_category_summary">Always ask</string>
<!-- Reader section --> <!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string> <string name="pref_fullscreen">Fullscreen</string>
@ -269,6 +271,7 @@
<string name="no_valid_sources">Please enable at least one valid source</string> <string name="no_valid_sources">Please enable at least one valid source</string>
<string name="no_more_results">No more results</string> <string name="no_more_results">No more results</string>
<string name="local_source">Local manga</string> <string name="local_source">Local manga</string>
<string name="invalid_combination">Default can\'t be selected with other categories</string>
<!-- Manga activity --> <!-- Manga activity -->
<string name="manga_not_in_db">This manga was removed from the database!</string> <string name="manga_not_in_db">This manga was removed from the database!</string>

View File

@ -63,6 +63,14 @@
android:key="@string/pref_update_only_non_completed_key" android:key="@string/pref_update_only_non_completed_key"
android:title="@string/pref_update_only_non_completed" /> android:title="@string/pref_update_only_non_completed" />
<eu.kanade.tachiyomi.widget.preference.IntListPreference
android:defaultValue="-1"
android:entries="@array/default_category_entry"
android:entryValues="@array/default_category_entry_value"
android:key="@string/default_category_key"
android:title="@string/default_category"
android:summary="@string/default_category_summary"/>
</PreferenceScreen> </PreferenceScreen>
</PreferenceScreen> </PreferenceScreen>