mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 18:41:48 +01:00
Search suggestions in library
Will automatically turn on in 24 hours Then every 2 hours new suggestions to search for will show Long pressing on it will automatically search that suggestion There's an added option to turn it off, or turn it on earlier as well
This commit is contained in:
parent
62c4ee22a4
commit
7f72eabfc3
@ -69,7 +69,7 @@ fun limitAndOffset(endless: Boolean, isResuming: Boolean, offset: Int): String {
|
||||
return when {
|
||||
isResuming && endless && offset > 0 -> "LIMIT $offset"
|
||||
endless -> "LIMIT ${RecentsPresenter.ENDLESS_LIMIT}\nOFFSET $offset"
|
||||
else -> "LIMIT 25"
|
||||
else -> "LIMIT ${RecentsPresenter.SHORT_LIMIT}"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.data.preference
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class DelayedLibrarySuggestionsJob(context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
if (preferences.showLibrarySearchSuggestions().isNotSet()) {
|
||||
preferences.showLibrarySearchSuggestions().set(true)
|
||||
LibraryPresenter.setSearchSuggestion(preferences, Injekt.get(), Injekt.get())
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DelayedLibrarySuggestions"
|
||||
|
||||
fun setupTask(enabled: Boolean) {
|
||||
if (enabled) {
|
||||
val request = OneTimeWorkRequestBuilder<DelayedLibrarySuggestionsJob>()
|
||||
.setInitialDelay(1, TimeUnit.DAYS)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request)
|
||||
} else {
|
||||
WorkManager.getInstance().cancelAllWorkByTag(TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -106,6 +106,10 @@ object PreferenceKeys {
|
||||
|
||||
const val folderPerManga = "create_folder_per_manga"
|
||||
|
||||
const val showLibrarySearchSuggestions = "show_library_search_suggestions"
|
||||
|
||||
const val librarySearchSuggestion = "library_search_suggestion"
|
||||
|
||||
const val numberOfBackups = "backup_slots"
|
||||
|
||||
const val backupInterval = "backup_interval"
|
||||
|
@ -234,6 +234,12 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun folderPerManga() = prefs.getBoolean(Keys.folderPerManga, false)
|
||||
|
||||
fun librarySearchSuggestion() = flowPrefs.getString(Keys.librarySearchSuggestion, "")
|
||||
|
||||
fun showLibrarySearchSuggestions() = flowPrefs.getBoolean(Keys.showLibrarySearchSuggestions, false)
|
||||
|
||||
fun lastLibrarySuggestion() = flowPrefs.getLong("last_library_suggestion", 0L)
|
||||
|
||||
fun numberOfBackups() = flowPrefs.getInt(Keys.numberOfBackups, 1)
|
||||
|
||||
fun backupInterval() = flowPrefs.getInt(Keys.backupInterval, 0)
|
||||
|
@ -203,7 +203,15 @@ class LibraryController(
|
||||
|
||||
override fun getTitle(): String? {
|
||||
setSubtitle()
|
||||
return searchTitle(view?.context?.getString(R.string.your_library)?.lowercase(Locale.ROOT))
|
||||
return searchTitle(
|
||||
if (preferences.showLibrarySearchSuggestions().get() &&
|
||||
preferences.librarySearchSuggestion().get().isNotBlank()
|
||||
) {
|
||||
"\"${preferences.librarySearchSuggestion().get()}\""
|
||||
} else {
|
||||
view?.context?.getString(R.string.your_library)?.lowercase(Locale.ROOT)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private var scrollListener = object : RecyclerView.OnScrollListener() {
|
||||
@ -849,6 +857,23 @@ class LibraryController(
|
||||
binding.recyclerCover.isFocusable = false
|
||||
singleCategory = presenter.categories.size <= 1
|
||||
showDropdown()
|
||||
|
||||
if (preferences.showLibrarySearchSuggestions().get()) {
|
||||
activityBinding?.cardToolbar?.setOnLongClickListener {
|
||||
val suggestion = preferences.librarySearchSuggestion().get()
|
||||
if (suggestion.isNotBlank()) {
|
||||
val searchItem =
|
||||
activityBinding?.cardToolbar?.menu?.findItem(R.id.action_search)
|
||||
val searchView = searchItem?.actionView as? SearchView
|
||||
?: return@setOnLongClickListener false
|
||||
searchItem.expandActionView()
|
||||
searchView.setQuery(suggestion, false)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateFilterSheetY()
|
||||
closeTip()
|
||||
@ -856,6 +881,7 @@ class LibraryController(
|
||||
binding.filterBottomSheet.filterBottomSheet.isInvisible = true
|
||||
}
|
||||
activityBinding?.toolbar?.hideDropdown()
|
||||
activityBinding?.cardToolbar?.setOnLongClickListener(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||
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.preference.DelayedLibrarySuggestionsJob
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.preference.minusAssign
|
||||
@ -28,18 +29,26 @@ import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet
|
||||
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_EXCLUDE
|
||||
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_IGNORE
|
||||
import eu.kanade.tachiyomi.ui.library.filter.FilterBottomSheet.Companion.STATE_INCLUDE
|
||||
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
|
||||
import eu.kanade.tachiyomi.util.lang.capitalizeWords
|
||||
import eu.kanade.tachiyomi.util.lang.chopByWords
|
||||
import eu.kanade.tachiyomi.util.lang.removeArticles
|
||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.ArrayList
|
||||
import java.util.Calendar
|
||||
import java.util.Comparator
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* Presenter of [LibraryController].
|
||||
@ -98,6 +107,17 @@ class LibraryPresenter(
|
||||
lastCategories = null
|
||||
lastLibraryItems = null
|
||||
getLibrary()
|
||||
if (preferences.showLibrarySearchSuggestions().isNotSet()) {
|
||||
DelayedLibrarySuggestionsJob.setupTask(true)
|
||||
} else if (preferences.showLibrarySearchSuggestions().get() &&
|
||||
Date().time >= preferences.lastLibrarySuggestion().get() + TimeUnit.HOURS.toMillis(2)
|
||||
) {
|
||||
// Doing this instead of a job in case the app isn't used often
|
||||
presenterScope.launchIO {
|
||||
setSearchSuggestion(preferences, db, sourceManager)
|
||||
withUIContext { view.setTitle() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Get favorited manga for library and sort and filter it */
|
||||
@ -1026,6 +1046,82 @@ class LibraryPresenter(
|
||||
private const val sourceSplitter = "◘•◘"
|
||||
private const val dynamicCategorySplitter = "▄╪\t▄╪\t▄"
|
||||
|
||||
private val randomTags = arrayOf(0, 1, 2)
|
||||
private const val randomSource = 4
|
||||
private const val randomTitle = 3
|
||||
private const val randomTag = 0
|
||||
private val randomGroupOfTags = arrayOf(1, 2)
|
||||
private const val randomGroupOfTagsNormal = 1
|
||||
private const val randomGroupOfTagsNegate = 2
|
||||
|
||||
suspend fun setSearchSuggestion(
|
||||
preferences: PreferencesHelper,
|
||||
db: DatabaseHelper,
|
||||
sourceManager: SourceManager
|
||||
) {
|
||||
val random: Random = {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = Date()
|
||||
cal[Calendar.MINUTE] = 0
|
||||
cal[Calendar.SECOND] = 0
|
||||
cal[Calendar.MILLISECOND] = 0
|
||||
Random(cal.time.time)
|
||||
}()
|
||||
|
||||
val recentManga by lazy {
|
||||
runBlocking {
|
||||
RecentsPresenter.getRecentManga(true).map { it.first }
|
||||
}
|
||||
}
|
||||
val libraryManga by lazy { db.getLibraryMangas().executeAsBlocking() }
|
||||
preferences.librarySearchSuggestion().set(
|
||||
when (val value = random.nextInt(0, 5)) {
|
||||
randomSource -> {
|
||||
val distinctSources = libraryManga.distinctBy { it.source }
|
||||
val randomSource =
|
||||
sourceManager.get(
|
||||
distinctSources.randomOrNull(random)?.source ?: 0L
|
||||
)?.name
|
||||
randomSource?.chopByWords(15)
|
||||
}
|
||||
randomTitle -> {
|
||||
libraryManga.randomOrNull(random)?.title?.chopByWords(15)
|
||||
}
|
||||
in randomTags -> {
|
||||
val tags = recentManga.map {
|
||||
it.genre.orEmpty().split(",").map(String::trim)
|
||||
}
|
||||
.flatten()
|
||||
.filter { it.isNotBlank() }
|
||||
val distinctTags = tags.distinct()
|
||||
if (value in randomGroupOfTags && distinctTags.size > 6) {
|
||||
val shortestTagsSort = distinctTags.sortedBy { it.length }
|
||||
val offset = random.nextInt(0, distinctTags.size / 2 - 2)
|
||||
var offset2 = random.nextInt(0, distinctTags.size / 2 - 2)
|
||||
while (offset2 == offset) {
|
||||
offset2 = random.nextInt(0, distinctTags.size / 2 - 2)
|
||||
}
|
||||
if (value == randomGroupOfTagsNormal) {
|
||||
"${shortestTagsSort[offset]}, " + shortestTagsSort[offset2]
|
||||
} else {
|
||||
"${shortestTagsSort[offset]}, -" + shortestTagsSort[offset2]
|
||||
}
|
||||
} else {
|
||||
val group = tags.groupingBy { it }.eachCount()
|
||||
val groupedTags = distinctTags.sortedByDescending { group[it] }
|
||||
groupedTags.take(8).randomOrNull(random)
|
||||
}
|
||||
}
|
||||
else -> ""
|
||||
} ?: ""
|
||||
)
|
||||
|
||||
if (preferences.showLibrarySearchSuggestions().isNotSet()) {
|
||||
preferences.showLibrarySearchSuggestions().set(true)
|
||||
}
|
||||
preferences.lastLibrarySuggestion().set(Date().time)
|
||||
}
|
||||
|
||||
/** Give library manga to a date added based on min chapter fetch */
|
||||
fun updateDB() {
|
||||
val db: DatabaseHelper = Injekt.get()
|
||||
|
@ -112,7 +112,8 @@ class RecentsPresenter(
|
||||
retryCount: Int = 0,
|
||||
itemCount: Int = 0,
|
||||
limit: Boolean = false,
|
||||
customViewType: Int? = null
|
||||
customViewType: Int? = null,
|
||||
includeReadAnyway: Boolean = false
|
||||
) {
|
||||
if (retryCount > 5) {
|
||||
finished = true
|
||||
@ -127,7 +128,8 @@ class RecentsPresenter(
|
||||
}
|
||||
val viewType = customViewType ?: viewType
|
||||
|
||||
val showRead = (preferences.showReadInAllRecents().get() || query.isNotEmpty()) && !limit
|
||||
val showRead = ((preferences.showReadInAllRecents().get() || query.isNotEmpty()) && !limit) ||
|
||||
includeReadAnyway == true
|
||||
val isUngrouped = viewType > VIEW_TYPE_GROUP_ALL || query.isNotEmpty()
|
||||
val groupChaptersUpdates = preferences.groupChaptersUpdates().get()
|
||||
val groupChaptersHistory = preferences.groupChaptersHistory().get()
|
||||
@ -487,11 +489,15 @@ class RecentsPresenter(
|
||||
const val VIEW_TYPE_ONLY_HISTORY = 2
|
||||
const val VIEW_TYPE_ONLY_UPDATES = 3
|
||||
const val ENDLESS_LIMIT = 50
|
||||
var SHORT_LIMIT = 25
|
||||
private set
|
||||
|
||||
suspend fun getRecentManga(): List<Pair<Manga, Long>> {
|
||||
suspend fun getRecentManga(includeRead: Boolean = false): List<Pair<Manga, Long>> {
|
||||
val presenter = RecentsPresenter(null)
|
||||
presenter.viewType = 1
|
||||
presenter.runRecents(limit = true)
|
||||
SHORT_LIMIT = if (includeRead) 50 else 25
|
||||
presenter.runRecents(limit = true, includeReadAnyway = includeRead)
|
||||
SHORT_LIMIT = 25
|
||||
return presenter.recentItems.filter { it.mch.manga.id != null }.map { it.mch.manga to it.mch.history.last_read }
|
||||
}
|
||||
}
|
||||
|
@ -6,8 +6,11 @@ import eu.kanade.tachiyomi.R
|
||||
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.preference.DelayedLibrarySuggestionsJob
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
|
||||
import eu.kanade.tachiyomi.ui.library.display.TabbedLibraryDisplaySheet
|
||||
import eu.kanade.tachiyomi.util.system.launchIO
|
||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@ -28,6 +31,25 @@ class SettingsLibraryController : SettingsController() {
|
||||
defaultValue = false
|
||||
}
|
||||
|
||||
switchPreference {
|
||||
key = Keys.showLibrarySearchSuggestions
|
||||
titleRes = R.string.search_suggestions
|
||||
summaryRes = R.string.search_tips_show_periodically
|
||||
|
||||
onChange {
|
||||
it as Boolean
|
||||
if (it) {
|
||||
launchIO {
|
||||
LibraryPresenter.setSearchSuggestion(preferences, db, Injekt.get())
|
||||
}
|
||||
} else {
|
||||
DelayedLibrarySuggestionsJob.setupTask(false)
|
||||
preferences.librarySearchSuggestion().set("")
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
preference {
|
||||
key = "library_display_options"
|
||||
isPersistent = false
|
||||
|
@ -29,6 +29,26 @@ fun String.chop(count: Int, replacement: String = "…"): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun String.chopByWords(count: Int): String {
|
||||
return if (length > count) {
|
||||
val splitWords = split(" ")
|
||||
val iterator = splitWords.iterator()
|
||||
var newString = iterator.next()
|
||||
return if (newString.length > count) {
|
||||
chop(count)
|
||||
} else {
|
||||
var next = iterator.next()
|
||||
while ("$newString $next".length <= count) {
|
||||
newString = "$newString $next"
|
||||
next = iterator.next()
|
||||
}
|
||||
newString
|
||||
}
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fun String.removeArticles(): String {
|
||||
return when {
|
||||
startsWith("a ", true) -> substring(2)
|
||||
|
@ -213,6 +213,8 @@
|
||||
<string name="show_notification_error">Show a notification for errors</string>
|
||||
<string name="display_buttons_bottom_reader">Buttons at bottom of reader</string>
|
||||
<string name="certain_buttons_can_be_found">Certain buttons can be found in other places if disabled here</string>
|
||||
<string name="search_suggestions">Search suggestions</string>
|
||||
<string name="search_tips_show_periodically">Search tips will show up periodically. Long press the suggestion to search it.</string>
|
||||
|
||||
<!-- Recents -->
|
||||
<string name="recents">Recents</string>
|
||||
|
Loading…
Reference in New Issue
Block a user