Fix Mangadex 2 factor auth
Fix Backups with merge manga breaking(I think) Tweak preload settings, make the max 20, defaults to 10 Add tag based sorting
This commit is contained in:
parent
89427ff37e
commit
de05f88d5f
@ -17,6 +17,7 @@ import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonObject
|
||||
import com.google.gson.JsonParser
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CATEGORY_MASK
|
||||
import eu.kanade.tachiyomi.data.backup.BackupCreateService.Companion.BACKUP_CHAPTER
|
||||
@ -154,7 +155,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
|
||||
databaseHelper.inTransaction {
|
||||
// Get manga from database
|
||||
val mangas = getFavoriteManga() /* SY --> */ + getMergedManga() /* SY <-- */
|
||||
val mangas = getFavoriteManga().filterNot { it.source == MERGED_SOURCE_ID } /* SY --> */ + getMergedManga() /* SY <-- */
|
||||
|
||||
val extensions: MutableSet<String> = mutableSetOf()
|
||||
|
||||
@ -652,7 +653,15 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) {
|
||||
// Store the inserted id in the mergedMangaReference
|
||||
if (!found) {
|
||||
// Let the db assign the id
|
||||
val mergedManga = (if (mergedMangaReference.mergeUrl != lastMergeManga?.url) databaseHelper.getManga(mergedMangaReference.mergeUrl, MERGED_SOURCE_ID).executeAsBlocking() else lastMergeManga) ?: return@forEach
|
||||
var mergedManga = if (mergedMangaReference.mergeUrl != lastMergeManga?.url) databaseHelper.getManga(mergedMangaReference.mergeUrl, MERGED_SOURCE_ID).executeAsBlocking() else lastMergeManga
|
||||
if (mergedManga == null) {
|
||||
mergedManga = Manga.create(MERGED_SOURCE_ID).apply {
|
||||
url = mergedMangaReference.mergeUrl
|
||||
title = context.getString(R.string.refresh_merge)
|
||||
}
|
||||
mergedManga.id = databaseHelper.insertManga(mergedManga).executeAsBlocking().insertedId()
|
||||
}
|
||||
|
||||
val manga = databaseHelper.getManga(mergedMangaReference.mangaUrl, mergedMangaReference.mangaSourceId).executeAsBlocking() ?: return@forEach
|
||||
lastMergeManga = mergedManga
|
||||
|
||||
|
@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.MERGEDMANGAREFERENCES
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.SAVEDSEARCHES
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK
|
||||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
||||
@ -250,6 +251,8 @@ class BackupRestoreService : Service() {
|
||||
|
||||
// SY -->
|
||||
json.get(SAVEDSEARCHES)?.let { restoreSavedSearches(it) }
|
||||
|
||||
json.get(MERGEDMANGAREFERENCES)?.let { restoreMergedMangaReferences(it) }
|
||||
// SY <--
|
||||
|
||||
// Store source mapping for error messages
|
||||
@ -296,7 +299,7 @@ class BackupRestoreService : Service() {
|
||||
}
|
||||
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.categories))
|
||||
showRestoreProgress(restoreProgress, restoreAmount, getString(R.string.merged_references))
|
||||
}
|
||||
// SY <--
|
||||
|
||||
|
@ -316,4 +316,6 @@ object PreferenceKeys {
|
||||
const val allowLocalSourceHiddenFolders = "allow_local_source_hidden_folders"
|
||||
|
||||
const val biometricTimeRanges = "biometric_time_ranges"
|
||||
|
||||
const val sortTagsForLibrary = "sort_tags_for_library"
|
||||
}
|
||||
|
@ -356,7 +356,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun eh_aggressivePageLoading() = flowPrefs.getBoolean(Keys.eh_aggressivePageLoading, false)
|
||||
|
||||
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 4)
|
||||
fun eh_preload_size() = flowPrefs.getInt(Keys.eh_preload_size, 10)
|
||||
|
||||
fun eh_useAutoWebtoon() = flowPrefs.getBoolean(Keys.eh_use_auto_webtoon, true)
|
||||
|
||||
@ -392,7 +392,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun groupLibraryUpdateType() = flowPrefs.getEnum(Keys.groupLibraryUpdateType, Values.GroupLibraryMode.GLOBAL)
|
||||
|
||||
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, false)
|
||||
fun useNewSourceNavigation() = flowPrefs.getBoolean(Keys.useNewSourceNavigation, true)
|
||||
|
||||
fun mangaDexLowQualityCovers() = flowPrefs.getBoolean(Keys.mangaDexLowQualityCovers, false)
|
||||
|
||||
@ -421,4 +421,6 @@ class PreferencesHelper(val context: Context) {
|
||||
fun allowLocalSourceHiddenFolders() = flowPrefs.getBoolean(Keys.allowLocalSourceHiddenFolders, false)
|
||||
|
||||
fun biometricTimeRanges() = flowPrefs.getStringSet(Keys.biometricTimeRanges, mutableSetOf())
|
||||
|
||||
fun sortTagsForLibrary() = flowPrefs.getStringSet(Keys.sortTagsForLibrary, mutableSetOf())
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
|
||||
/**
|
||||
* Custom adapter for categories.
|
||||
*
|
||||
* @param controller The containing controller.
|
||||
*/
|
||||
class SortTagAdapter(controller: SortTagController) :
|
||||
FlexibleAdapter<SortTagItem>(null, controller, true) {
|
||||
|
||||
/**
|
||||
* Listener called when an item of the list is released.
|
||||
*/
|
||||
val onItemReleaseListener: OnItemReleaseListener = controller
|
||||
|
||||
/**
|
||||
* Clears the active selections from the list and the model.
|
||||
*/
|
||||
override fun clearSelection() {
|
||||
super.clearSelection()
|
||||
(0 until itemCount).forEach { getItem(it)?.isSelected = false }
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the selection of the given position.
|
||||
*
|
||||
* @param position The position to toggle.
|
||||
*/
|
||||
override fun toggleSelection(position: Int) {
|
||||
super.toggleSelection(position)
|
||||
getItem(position)?.isSelected = isSelected(position)
|
||||
}
|
||||
|
||||
interface OnItemReleaseListener {
|
||||
/**
|
||||
* Called when an item of the list is released.
|
||||
*/
|
||||
fun onItemReleased(position: Int)
|
||||
}
|
||||
}
|
@ -0,0 +1,323 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.helpers.UndoHelper
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.databinding.CategoriesControllerBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.shrinkOnScroll
|
||||
import kotlinx.android.synthetic.main.main_activity.root_coordinator
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import reactivecircus.flowbinding.android.view.clicks
|
||||
|
||||
/**
|
||||
* Controller to manage the categories for the users' library.
|
||||
*/
|
||||
class SortTagController :
|
||||
NucleusController<CategoriesControllerBinding, SortTagPresenter>(),
|
||||
FabController,
|
||||
ActionMode.Callback,
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener,
|
||||
SortTagAdapter.OnItemReleaseListener,
|
||||
SortTagCreateDialog.Listener,
|
||||
UndoHelper.OnActionListener {
|
||||
|
||||
/**
|
||||
* Object used to show ActionMode toolbar.
|
||||
*/
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
/**
|
||||
* Adapter containing category items.
|
||||
*/
|
||||
private var adapter: SortTagAdapter? = null
|
||||
|
||||
private var actionFab: ExtendedFloatingActionButton? = null
|
||||
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
|
||||
|
||||
/**
|
||||
* Undo helper used for restoring a deleted category.
|
||||
*/
|
||||
private var undoHelper: UndoHelper? = null
|
||||
|
||||
/**
|
||||
* Creates the presenter for this controller. Not to be manually called.
|
||||
*/
|
||||
override fun createPresenter() = SortTagPresenter()
|
||||
|
||||
/**
|
||||
* Returns the toolbar title to show when this controller is attached.
|
||||
*/
|
||||
override fun getTitle(): String? {
|
||||
return resources?.getString(R.string.action_edit_tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the view of this controller.
|
||||
*
|
||||
* @param inflater The layout inflater to create the view from XML.
|
||||
* @param container The parent view for this one.
|
||||
*/
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = CategoriesControllerBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after view inflation. Used to initialize the view.
|
||||
*
|
||||
* @param view The view of this controller.
|
||||
*/
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
adapter = SortTagAdapter(this@SortTagController)
|
||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||
binding.recycler.setHasFixedSize(true)
|
||||
binding.recycler.adapter = adapter
|
||||
adapter?.isPermanentDelete = false
|
||||
|
||||
actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler)
|
||||
}
|
||||
|
||||
override fun configureFab(fab: ExtendedFloatingActionButton) {
|
||||
actionFab = fab
|
||||
fab.setText(R.string.action_add)
|
||||
fab.setIconResource(R.drawable.ic_add_24dp)
|
||||
fab.clicks()
|
||||
.onEach {
|
||||
SortTagCreateDialog(this@SortTagController).showDialog(router, null)
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
|
||||
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) }
|
||||
actionFab = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the view is being destroyed. Used to release references and remove callbacks.
|
||||
*
|
||||
* @param view The view of this controller.
|
||||
*/
|
||||
override fun onDestroyView(view: View) {
|
||||
// Manually call callback to delete categories if required
|
||||
undoHelper?.onDeleteConfirmed(Snackbar.Callback.DISMISS_EVENT_MANUAL)
|
||||
undoHelper = null
|
||||
actionMode = null
|
||||
adapter = null
|
||||
super.onDestroyView(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the presenter when the categories are updated.
|
||||
*
|
||||
* @param categories The new list of categories to display.
|
||||
*/
|
||||
fun setCategories(categories: List<SortTagItem>) {
|
||||
actionMode?.finish()
|
||||
adapter?.updateDataSet(categories)
|
||||
if (categories.isNotEmpty()) {
|
||||
binding.emptyView.hide()
|
||||
val selected = categories.filter { it.isSelected }
|
||||
if (selected.isNotEmpty()) {
|
||||
selected.forEach { onItemLongClick(categories.indexOf(it)) }
|
||||
}
|
||||
} else {
|
||||
binding.emptyView.show(R.string.information_empty_tags)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when action mode is first created. The menu supplied will be used to generate action
|
||||
* buttons for the action mode.
|
||||
*
|
||||
* @param mode ActionMode being created.
|
||||
* @param menu Menu used to populate action buttons.
|
||||
* @return true if the action mode should be created, false if entering this mode should be
|
||||
* aborted.
|
||||
*/
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
// Inflate menu.
|
||||
mode.menuInflater.inflate(R.menu.category_selection, menu)
|
||||
// Enable adapter multi selection.
|
||||
adapter?.mode = SelectableAdapter.Mode.MULTI
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to refresh an action mode's action menu whenever it is invalidated.
|
||||
*
|
||||
* @param mode ActionMode being prepared.
|
||||
* @param menu Menu used to populate action buttons.
|
||||
* @return true if the menu or action mode was updated, false otherwise.
|
||||
*/
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
val adapter = adapter ?: return false
|
||||
val count = adapter.selectedItemCount
|
||||
mode.title = count.toString()
|
||||
|
||||
// Show edit button only when one item is selected
|
||||
mode.menu.findItem(R.id.action_edit).isVisible = false
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to report a user click on an action button.
|
||||
*
|
||||
* @param mode The current ActionMode.
|
||||
* @param item The item that was clicked.
|
||||
* @return true if this callback handled the event, false if the standard MenuItem invocation
|
||||
* should continue.
|
||||
*/
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
val adapter = adapter ?: return false
|
||||
|
||||
when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
undoHelper = UndoHelper(adapter, this)
|
||||
undoHelper?.start(
|
||||
adapter.selectedPositions,
|
||||
activity!!.root_coordinator,
|
||||
R.string.snack_tags_deleted,
|
||||
R.string.action_undo,
|
||||
3000
|
||||
)
|
||||
mode.finish()
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an action mode is about to be exited and destroyed.
|
||||
*
|
||||
* @param mode The current ActionMode being destroyed.
|
||||
*/
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
// Reset adapter to single selection
|
||||
adapter?.mode = SelectableAdapter.Mode.IDLE
|
||||
adapter?.clearSelection()
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item in the list is clicked.
|
||||
*
|
||||
* @param position The position of the clicked item.
|
||||
* @return true if this click should enable selection mode.
|
||||
*/
|
||||
override fun onItemClick(view: View, position: Int): Boolean {
|
||||
// Check if action mode is initialized and selected item exist.
|
||||
return if (actionMode != null && position != RecyclerView.NO_POSITION) {
|
||||
toggleSelection(position)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item in the list is long clicked.
|
||||
*
|
||||
* @param position The position of the clicked item.
|
||||
*/
|
||||
override fun onItemLongClick(position: Int) {
|
||||
val activity = activity as? AppCompatActivity ?: return
|
||||
|
||||
// Check if action mode is initialized.
|
||||
if (actionMode == null) {
|
||||
// Initialize action mode
|
||||
actionMode = activity.startSupportActionMode(this)
|
||||
}
|
||||
|
||||
// Set item as selected
|
||||
toggleSelection(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the selection state of an item.
|
||||
* If the item was the last one in the selection and is unselected, the ActionMode is finished.
|
||||
*
|
||||
* @param position The position of the item to toggle.
|
||||
*/
|
||||
private fun toggleSelection(position: Int) {
|
||||
val adapter = adapter ?: return
|
||||
|
||||
// Mark the position selected
|
||||
adapter.toggleSelection(position)
|
||||
|
||||
if (adapter.selectedItemCount == 0) {
|
||||
actionMode?.finish()
|
||||
} else {
|
||||
actionMode?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item is released from a drag.
|
||||
*
|
||||
* @param position The position of the released item.
|
||||
*/
|
||||
override fun onItemReleased(position: Int) {
|
||||
val adapter = adapter ?: return
|
||||
val tags = (0 until adapter.itemCount).mapNotNull { adapter.getItem(it)?.tag }
|
||||
presenter.reorderTags(tags)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the undo action is clicked in the snackbar.
|
||||
*
|
||||
* @param action The action performed.
|
||||
*/
|
||||
override fun onActionCanceled(action: Int, positions: MutableList<Int>?) {
|
||||
adapter?.restoreDeletedItems()
|
||||
undoHelper = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the time to restore the items expires.
|
||||
*
|
||||
* @param action The action performed.
|
||||
* @param event The event that triggered the action
|
||||
*/
|
||||
override fun onActionConfirmed(action: Int, event: Int) {
|
||||
val adapter = adapter ?: return
|
||||
presenter.deleteTags(adapter.deletedItems.map { it.tag })
|
||||
undoHelper = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new category with the given name.
|
||||
*
|
||||
* @param name The name of the new category.
|
||||
*/
|
||||
override fun createCategory(name: String) {
|
||||
presenter.createCategory(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the presenter when a category with the given name already exists.
|
||||
*/
|
||||
fun onTagExistsError() {
|
||||
activity?.toast(R.string.error_tag_exists)
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
|
||||
/**
|
||||
* Dialog to create a new category for the library.
|
||||
*/
|
||||
class SortTagCreateDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
||||
where T : Controller, T : SortTagCreateDialog.Listener {
|
||||
|
||||
/**
|
||||
* Name of the new category. Value updated with each input from the user.
|
||||
*/
|
||||
private var currentName = ""
|
||||
|
||||
constructor(target: T) : this() {
|
||||
targetController = target
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when creating the dialog for this controller.
|
||||
*
|
||||
* @param savedViewState The saved state of this dialog.
|
||||
* @return a new dialog instance.
|
||||
*/
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
return MaterialDialog(activity!!)
|
||||
.title(R.string.action_add_category)
|
||||
.message(R.string.action_add_tags_message)
|
||||
.negativeButton(android.R.string.cancel)
|
||||
.input(
|
||||
hint = resources?.getString(R.string.name),
|
||||
prefill = currentName
|
||||
) { _, input ->
|
||||
currentName = input.toString()
|
||||
}
|
||||
.positiveButton(android.R.string.ok) {
|
||||
(targetController as? Listener)?.createCategory(currentName)
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun createCategory(name: String)
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import kotlinx.android.synthetic.main.categories_item.title
|
||||
|
||||
/**
|
||||
* Holder used to display category items.
|
||||
*
|
||||
* @param view The view used by category items.
|
||||
* @param adapter The adapter containing this holder.
|
||||
*/
|
||||
class SortTagHolder(view: View, val adapter: SortTagAdapter) : BaseFlexibleViewHolder(view, adapter) {
|
||||
/**
|
||||
* Binds this holder with the given category.
|
||||
*
|
||||
* @param tag The tag to bind.
|
||||
*/
|
||||
fun bind(tag: String) {
|
||||
// Set capitalized title.
|
||||
title.text = tag
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
/**
|
||||
* Category item for a recycler view.
|
||||
*/
|
||||
class SortTagItem(val tag: String) : AbstractFlexibleItem<SortTagHolder>() {
|
||||
|
||||
/**
|
||||
* Whether this item is currently selected.
|
||||
*/
|
||||
var isSelected = false
|
||||
|
||||
/**
|
||||
* Returns the layout resource for this item.
|
||||
*/
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.categories_item
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new view holder for this item.
|
||||
*
|
||||
* @param view The view of this item.
|
||||
* @param adapter The adapter of this item.
|
||||
*/
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SortTagHolder {
|
||||
return SortTagHolder(view, adapter as SortTagAdapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds the given view holder with this item.
|
||||
*
|
||||
* @param adapter The adapter of this item.
|
||||
* @param holder The holder to bind.
|
||||
* @param position The position of this item in the adapter.
|
||||
* @param payloads List of partial changes.
|
||||
*/
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: SortTagHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
holder.bind(tag)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this item is draggable.
|
||||
*/
|
||||
override fun isDraggable(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return tag.hashCode()
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package eu.kanade.tachiyomi.ui.category.genre
|
||||
|
||||
import android.os.Bundle
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.minusAssign
|
||||
import eu.kanade.tachiyomi.data.preference.plusAssign
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [SortTagController]. Used to manage the categories of the library.
|
||||
*/
|
||||
class SortTagPresenter : BasePresenter<SortTagController>() {
|
||||
|
||||
/**
|
||||
* List containing categories.
|
||||
*/
|
||||
private var tags: List<String> = emptyList()
|
||||
|
||||
val preferences: PreferencesHelper = Injekt.get()
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
/**
|
||||
* Called when the presenter is created.
|
||||
*
|
||||
* @param savedState The saved state of this presenter.
|
||||
*/
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
preferences.sortTagsForLibrary().asFlow().onEach { tags ->
|
||||
this.tags = tags.toList()
|
||||
|
||||
Observable.just(this.tags)
|
||||
.map { it.map(::SortTagItem) }
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(SortTagController::setCategories)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and adds a new category to the database.
|
||||
*
|
||||
* @param name The name of the category to create.
|
||||
*/
|
||||
fun createCategory(name: String) {
|
||||
// Do not allow duplicate categories.
|
||||
if (tagExists(name)) {
|
||||
Observable.just(Unit).subscribeFirst({ view, _ -> view.onTagExistsError() })
|
||||
return
|
||||
}
|
||||
|
||||
preferences.sortTagsForLibrary() += name
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given categories from the database.
|
||||
*
|
||||
* @param categories The list of categories to delete.
|
||||
*/
|
||||
fun deleteTags(categories: List<String>) {
|
||||
categories.forEach {
|
||||
preferences.sortTagsForLibrary() -= it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders the given categories in the database.
|
||||
*
|
||||
* @param categories The list of categories to reorder.
|
||||
*/
|
||||
fun reorderTags(categories: List<String>) {
|
||||
preferences.sortTagsForLibrary().set(categories.toSet())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a category with the given name already exists.
|
||||
*/
|
||||
private fun tagExists(name: String): Boolean {
|
||||
return tags.any { it.equals(name, true) }
|
||||
}
|
||||
}
|
@ -290,6 +290,10 @@ class LibraryPresenter(
|
||||
db.getLatestChapterManga().executeAsBlocking().associate { it.id!! to counter++ }
|
||||
}
|
||||
|
||||
val listOfTags by lazy {
|
||||
preferences.sortTagsForLibrary().get().toList().map { ("(, |^)$it").toRegex(RegexOption.IGNORE_CASE) }
|
||||
}
|
||||
|
||||
val sortFn: (LibraryItem, LibraryItem) -> Int = { i1, i2 ->
|
||||
when (sortingMode) {
|
||||
LibrarySort.ALPHA -> i1.manga.title.compareTo(i2.manga.title, true)
|
||||
@ -318,6 +322,11 @@ class LibraryPresenter(
|
||||
LibrarySort.DRAG_AND_DROP -> {
|
||||
0
|
||||
}
|
||||
LibrarySort.TAG_LIST -> {
|
||||
val manga1IndexOfTag = listOfTags.indexOfFirst { i1.manga.genre?.let { tagString -> it.containsMatchIn(tagString) } ?: false }
|
||||
val manga2IndexOfTag = listOfTags.indexOfFirst { i2.manga.genre?.let { tagString -> it.containsMatchIn(tagString) } ?: false }
|
||||
manga1IndexOfTag.compareTo(manga2IndexOfTag)
|
||||
}
|
||||
// SY <--
|
||||
else -> throw Exception("Unknown sorting mode")
|
||||
}
|
||||
|
@ -172,11 +172,12 @@ class LibrarySettingsSheet(
|
||||
private val dateAdded = Item.MultiSort(R.string.action_sort_date_added, this)
|
||||
// SY -->
|
||||
private val dragAndDrop = Item.MultiSort(R.string.action_sort_drag_and_drop, this)
|
||||
private val tagList = Item.MultiSort(R.string.tag_sorting, this)
|
||||
// SY <--
|
||||
|
||||
override val header = null
|
||||
override val items =
|
||||
listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter, dateAdded /* SY --> */, dragAndDrop /* SY <-- */)
|
||||
listOf(alphabetically, lastRead, lastChecked, unread, total, latestChapter, dateAdded /* SY --> */, dragAndDrop) + if (preferences.sortTagsForLibrary().get().isNotEmpty()) listOf(tagList) else emptyList() /* SY <-- */
|
||||
override val footer = null
|
||||
|
||||
override fun initModels() {
|
||||
@ -203,6 +204,8 @@ class LibrarySettingsSheet(
|
||||
if (sorting == LibrarySort.DATE_ADDED) order else Item.MultiSort.SORT_NONE
|
||||
// SY -->
|
||||
dragAndDrop.state = if (sorting == LibrarySort.DRAG_AND_DROP) order else Item.MultiSort.SORT_NONE
|
||||
tagList.state =
|
||||
if (sorting == LibrarySort.TAG_LIST) order else Item.MultiSort.SORT_NONE
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@ -241,6 +244,7 @@ class LibrarySettingsSheet(
|
||||
dateAdded -> LibrarySort.DATE_ADDED
|
||||
// SY -->
|
||||
dragAndDrop -> LibrarySort.DRAG_AND_DROP
|
||||
tagList -> LibrarySort.TAG_LIST
|
||||
// SY <--
|
||||
else -> throw Exception("Unknown sorting")
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ object LibrarySort {
|
||||
const val DATE_ADDED = 8
|
||||
// SY -->
|
||||
const val DRAG_AND_DROP = 7
|
||||
const val TAG_LIST = 9
|
||||
// SY <--
|
||||
|
||||
@Deprecated("Removed in favor of searching by source")
|
||||
|
@ -38,7 +38,7 @@ class SettingsBrowseController : SettingsController() {
|
||||
key = Keys.useNewSourceNavigation
|
||||
titleRes = R.string.pref_source_navigation
|
||||
summaryRes = R.string.pref_source_navigation_summery
|
||||
defaultValue = false
|
||||
defaultValue = true
|
||||
}
|
||||
switchPreference {
|
||||
key = Keys.allowLocalSourceHiddenFolders
|
||||
|
@ -257,6 +257,15 @@ class SettingsLibraryController : SettingsController() {
|
||||
}
|
||||
}
|
||||
|
||||
preferenceCategory {
|
||||
titleRes = R.string.pref_sorting_settings
|
||||
preference {
|
||||
titleRes = R.string.pref_tag_sorting
|
||||
val count = preferences.sortTagsForLibrary().get().size
|
||||
summary = resources!!.getQuantityString(R.plurals.pref_tag_sorting_desc, count, count)
|
||||
}
|
||||
}
|
||||
|
||||
// SY -->
|
||||
if (preferences.skipPreMigration().get() || preferences.migrationSources().get()
|
||||
.isNotEmpty()
|
||||
|
@ -162,30 +162,26 @@ class SettingsReaderController : SettingsController() {
|
||||
key = Keys.eh_preload_size
|
||||
titleRes = R.string.reader_preload_amount
|
||||
entryValues = arrayOf(
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"6",
|
||||
"8",
|
||||
"10",
|
||||
"12",
|
||||
"14",
|
||||
"16"
|
||||
"16",
|
||||
"20",
|
||||
)
|
||||
entriesRes = arrayOf(
|
||||
R.string.reader_preload_amount_1_page,
|
||||
R.string.reader_preload_amount_2_pages,
|
||||
R.string.reader_preload_amount_3_pages,
|
||||
R.string.reader_preload_amount_4_pages,
|
||||
R.string.reader_preload_amount_6_pages,
|
||||
R.string.reader_preload_amount_8_pages,
|
||||
R.string.reader_preload_amount_10_pages,
|
||||
R.string.reader_preload_amount_12_pages,
|
||||
R.string.reader_preload_amount_14_pages,
|
||||
R.string.reader_preload_amount_16_pages
|
||||
R.string.reader_preload_amount_16_pages,
|
||||
R.string.reader_preload_amount_20_pages
|
||||
)
|
||||
defaultValue = "4"
|
||||
defaultValue = "10"
|
||||
summaryRes = R.string.reader_preload_amount_summary
|
||||
}
|
||||
listPreference {
|
||||
|
@ -45,7 +45,7 @@ abstract class LoginDialogPreference(
|
||||
return dialog
|
||||
}
|
||||
|
||||
fun onViewCreated(view: View) {
|
||||
/* SY --> */ open /* SY <-- */ fun onViewCreated(view: View) {
|
||||
v = view.apply {
|
||||
if (usernameLabelRes != null) {
|
||||
username_label.hint = context.getString(usernameLabelRes)
|
||||
|
@ -3,6 +3,7 @@ package exh.widget.preference
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.view.isVisible
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import eu.kanade.tachiyomi.R
|
||||
@ -15,7 +16,9 @@ import exh.md.utils.MdUtil
|
||||
import kotlinx.android.synthetic.main.pref_account_login.view.login
|
||||
import kotlinx.android.synthetic.main.pref_account_login.view.password
|
||||
import kotlinx.android.synthetic.main.pref_account_login.view.username
|
||||
import kotlinx.android.synthetic.main.pref_site_login_two_factor_auth.view.*
|
||||
import kotlinx.android.synthetic.main.pref_site_login_two_factor_auth.view.two_factor_check
|
||||
import kotlinx.android.synthetic.main.pref_site_login_two_factor_auth.view.two_factor_edit
|
||||
import kotlinx.android.synthetic.main.pref_site_login_two_factor_auth.view.two_factor_holder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@ -50,6 +53,15 @@ class MangadexLoginDialog(bundle: Bundle? = null) : LoginDialogPreference(bundle
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
v?.apply {
|
||||
two_factor_check?.setOnCheckedChangeListener { _, isChecked ->
|
||||
two_factor_holder.isVisible = isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCredentialsOnView(view: View) = with(view) {
|
||||
username.setText(service.getUsername())
|
||||
password.setText(service.getPassword())
|
||||
|
@ -194,9 +194,6 @@
|
||||
<string name="skip_queue_on_retry">Ignorar fila ao tentar novamente</string>
|
||||
<string name="skip_queue_on_retry_summary">Normalmente, tocar no botão Tentar novamente, aguardará até terminar de baixar a última página antes de baixar novamente a página com falha. Ativar isso forçará a baixar a página falhada assim que tocar em Tentar novamente.</string>
|
||||
<string name="reader_preload_amount">Quantidade de pré-carregamento do leitor</string>
|
||||
<string name="reader_preload_amount_1_page">1 Página</string>
|
||||
<string name="reader_preload_amount_2_pages">2 Páginas</string>
|
||||
<string name="reader_preload_amount_3_pages">3 Páginas</string>
|
||||
<string name="reader_preload_amount_4_pages">4 Páginas</string>
|
||||
<string name="reader_preload_amount_6_pages">6 Páginas</string>
|
||||
<string name="reader_preload_amount_8_pages">8 Páginas</string>
|
||||
@ -204,6 +201,8 @@
|
||||
<string name="reader_preload_amount_12_pages">12 Páginas</string>
|
||||
<string name="reader_preload_amount_14_pages">14 Páginas</string>
|
||||
<string name="reader_preload_amount_16_pages">16 Páginas</string>
|
||||
<string name="reader_preload_amount_18_pages">18 Páginas</string>
|
||||
<string name="reader_preload_amount_20_pages">20 Páginas</string>
|
||||
<string name="reader_preload_amount_summary">Quantidade de páginas a serem pré-carregadas enquanto lê. Valores mais altos resultarão em uma experiência de leitura mais suave, ao custo do uso do cache mais alto, é recomendável aumentar a quantidade de cache que você aloca ao usar valores maiores</string>
|
||||
<string name="reader_cache_size">Tamanho de cache do leitor</string>
|
||||
<string name="reader_cache_size_summary">Quantidade de imagens para salvar no dispositivo enquanto lê. Valores mais altos resultarão em uma experiência de leitura mais suave, com o custo do uso de espaço maior em disco</string>
|
||||
|
@ -166,6 +166,7 @@
|
||||
<string name="put_recommends_in_overflow_summary">Put the recommendations button in the overflow menu instead of on the manga page</string>
|
||||
|
||||
<!-- Library settings -->
|
||||
<string name="pref_sorting_settings">Sorting Settings</string>
|
||||
<string name="pref_skip_pre_migration_summary">Use last saved pre-migration preferences and sources to mass migrate</string>
|
||||
<string name="library_settings_sheet">Library settings sheet</string>
|
||||
<string name="library_settings_sheet_summary">More library display settings</string>
|
||||
@ -209,9 +210,6 @@
|
||||
<string name="skip_queue_on_retry">Skip queue on retry</string>
|
||||
<string name="skip_queue_on_retry_summary">Normally, pressing the retry button on a failed download will wait until the downloader has finished downloading the last page before beginning to re-download the failed page. Enabling this will force the downloader to begin re-downloading the failed page as soon as you press the retry button.</string>
|
||||
<string name="reader_preload_amount">Reader Preload amount</string>
|
||||
<string name="reader_preload_amount_1_page">1 Page</string>
|
||||
<string name="reader_preload_amount_2_pages">2 Pages</string>
|
||||
<string name="reader_preload_amount_3_pages">3 Pages</string>
|
||||
<string name="reader_preload_amount_4_pages">4 Pages</string>
|
||||
<string name="reader_preload_amount_6_pages">6 Pages</string>
|
||||
<string name="reader_preload_amount_8_pages">8 Pages</string>
|
||||
@ -219,6 +217,8 @@
|
||||
<string name="reader_preload_amount_12_pages">12 Pages</string>
|
||||
<string name="reader_preload_amount_14_pages">14 Pages</string>
|
||||
<string name="reader_preload_amount_16_pages">16 Pages</string>
|
||||
<string name="reader_preload_amount_18_pages">18 Pages</string>
|
||||
<string name="reader_preload_amount_20_pages">20 Pages</string>
|
||||
<string name="reader_preload_amount_summary">The amount of pages to preload when reading. Higher values will result in a smoother reading experience, at the cost of higher cache usage, it is recommended to increase the amount of cache you allocate when using larger values</string>
|
||||
<string name="reader_cache_size">Reader cache size</string>
|
||||
<string name="reader_cache_size_summary">The amount of images to save on device while reading. Higher values will result in a smoother reading experience, at the cost of higher disk space usage</string>
|
||||
@ -284,6 +284,22 @@
|
||||
<string name="too_many_watched">Too many watched sources, cannot add more then 5</string>
|
||||
<string name="latest_tab_empty">You don\'t have any watched sources, go to the sources tab and long press a source to watch it</string>
|
||||
|
||||
<!-- Sort by tags -->
|
||||
<string name="pref_tag_sorting">Tag sorting tags</string>
|
||||
<string name="tag_sorting">Tag sorting</string>
|
||||
<plurals name="pref_tag_sorting_desc">
|
||||
<item quantity="zero">No tags in sorting list. This adds a option in the library to sort by a priority based tag list, which means manga will be sorted in a way to prioritise the ones with the tags you want</item>
|
||||
<item quantity="one">%1$d tag in sorting list. This adds a option in the library to sort by a priority based tag list, which means manga will be sorted in a way to prioritise the ones with the tags you want</item>
|
||||
<item quantity="other">%1$d tags in sorting list. This adds a option in the library to sort by a priority based tag list, which means manga will be sorted in a way to prioritise the ones with the tags you want</item>
|
||||
</plurals>
|
||||
<string name="pref_tag_sorting_desc">Tag sorting list</string>
|
||||
<string name="action_add_tags">Add tag</string>
|
||||
<string name="action_add_tags_message">Read this! Tags must be exact, there are no partial matches, you cannot do netorare to filter out female:netorare or similar!\nThe style for namespace tags is "female: sole female" without quotes!\nAdding multiple variants of the same tag is supported, so feel free to do "tag: netorare" for NHentai and "female: netorare" for E-Hentai!</string>
|
||||
<string name="action_edit_tags">Edit tags</string>
|
||||
<string name="information_empty_tags">You have no tags. Tap the plus button to create one for sorting your library by tags</string>
|
||||
<string name="error_tag_exists">This tag exists!</string>
|
||||
<string name="snack_tags_deleted">Tags deleted</string>
|
||||
|
||||
<!-- Extension section -->
|
||||
<string name="ext_redundant">Redundant</string>
|
||||
<string name="redundant_extension_message">This extension is redundant and will not be used inside this version of Tachiyomi.</string>
|
||||
@ -513,6 +529,7 @@
|
||||
<string name="download_merged_manga_desc">Toggling this will disable or enable chapter downloads for this merged manga</string>
|
||||
<string name="merged_references_invalid">Merged references invalid</string>
|
||||
<string name="merged_chapter_updates_error">Toggle chapter updates error</string>
|
||||
<string name="merged_references">Merged references</string>
|
||||
<string name="merged_toggle_chapter_updates_find_error">Could not find manga to toggle chapter updates</string>
|
||||
<string name="merged_toggle_download_chapters_error">Toggle download chapters error</string>
|
||||
<string name="merged_toggle_download_chapters_find_error">Could not find manga to toggle chapter downloads</string>
|
||||
@ -520,6 +537,7 @@
|
||||
<string name="deduplication_mode">Dedupe mode:</string>
|
||||
<string name="manga_info_manga">Info manga:</string>
|
||||
<string name="toggle_dedupe">Toggle dedupe</string>
|
||||
<string name="refresh_merge">Refresh to get proper info</string>
|
||||
|
||||
<!-- MangaDex -->
|
||||
<string name="md_follows_unfollowed">Unfollowed</string>
|
||||
@ -537,4 +555,5 @@
|
||||
<string name="mangadex_follows">MangaDex follows</string>
|
||||
<string name="random">Random</string>
|
||||
|
||||
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user