From 7f72eabfc326bd1d42ee62c3ac2edee65afc5395 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Wed, 28 Apr 2021 16:10:23 -0400 Subject: [PATCH] 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 --- .../data/database/queries/RawQueries.kt | 2 +- .../DelayedLibrarySuggestionsJob.kt | 42 ++++++++ .../data/preference/PreferenceKeys.kt | 4 + .../data/preference/PreferencesHelper.kt | 6 ++ .../tachiyomi/ui/library/LibraryController.kt | 28 +++++- .../tachiyomi/ui/library/LibraryPresenter.kt | 96 +++++++++++++++++++ .../tachiyomi/ui/recents/RecentsPresenter.kt | 14 ++- .../ui/setting/SettingsLibraryController.kt | 22 +++++ .../tachiyomi/util/lang/StringExtensions.kt | 20 ++++ app/src/main/res/values/strings.xml | 2 + 10 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/preference/DelayedLibrarySuggestionsJob.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index e698fe107a..eed83429e4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -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}" } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/DelayedLibrarySuggestionsJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/DelayedLibrarySuggestionsJob.kt new file mode 100644 index 0000000000..91c8f08b44 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/DelayedLibrarySuggestionsJob.kt @@ -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() + 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() + .setInitialDelay(1, TimeUnit.DAYS) + .addTag(TAG) + .build() + + WorkManager.getInstance().enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request) + } else { + WorkManager.getInstance().cancelAllWorkByTag(TAG) + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index e98f07b192..f0e6d79be4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -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" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 3156dadf8c..7d54f767b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 762d0275a0..87a03cfb83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 524ca34d38..3a4bf1135a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index d2c237ecc1..457b4edb64 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -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> { + suspend fun getRecentManga(includeRead: Boolean = false): List> { 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 } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index 53f0d1a680..f630a82a2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index b528ee0733..b698b11b1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -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) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 11ea9d97a6..04f7e19a44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,6 +213,8 @@ Show a notification for errors Buttons at bottom of reader Certain buttons can be found in other places if disabled here + Search suggestions + Search tips will show up periodically. Long press the suggestion to search it. Recents