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:
Jobobby04 2020-09-15 13:00:40 -04:00
parent 89427ff37e
commit de05f88d5f
20 changed files with 688 additions and 24 deletions

View File

@ -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

View File

@ -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 <--

View File

@ -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"
}

View File

@ -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())
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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) }
}
}

View File

@ -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")
}

View File

@ -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")
}

View File

@ -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")

View File

@ -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

View File

@ -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()

View File

@ -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 {

View File

@ -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)

View File

@ -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())

View File

@ -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>

View File

@ -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>