Migrate saved searches to the db
This commit is contained in:
@@ -38,13 +38,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||
import exh.savedsearches.JsonSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
import exh.util.executeOnIO
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import logcat.LogPriority
|
||||
import okio.buffer
|
||||
@@ -164,14 +162,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||
* @return list of [BackupSavedSearch] to be backed up
|
||||
*/
|
||||
private fun backupSavedSearches(): List<BackupSavedSearch> {
|
||||
return preferences.savedSearches().get().mapNotNull {
|
||||
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
return databaseHelper.getSavedSearches().executeAsBlocking().map {
|
||||
BackupSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
content.filters.toString(),
|
||||
sourceId
|
||||
it.name,
|
||||
it.query.orEmpty(),
|
||||
it.filtersJson ?: "[]",
|
||||
it.source
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -431,34 +427,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||
|
||||
// SY -->
|
||||
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
|
||||
val currentSavedSearches = preferences.savedSearches().get().mapNotNull {
|
||||
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
val content = try {
|
||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
} catch (e: Exception) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
BackupSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
content.filters.toString(),
|
||||
sourceId
|
||||
)
|
||||
}
|
||||
val currentSavedSearches = databaseHelper.getSavedSearches()
|
||||
.executeAsBlocking()
|
||||
|
||||
val newSavedSearches = backupSavedSearches.filter { backupSavedSearch ->
|
||||
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
||||
}.map {
|
||||
"${it.source}:" + Json.encodeToString(
|
||||
JsonSavedSearch(
|
||||
it.name,
|
||||
it.query,
|
||||
Json.decodeFromString(it.filterList)
|
||||
)
|
||||
SavedSearch(
|
||||
id = null,
|
||||
it.source,
|
||||
it.name,
|
||||
it.query.nullIfBlank(),
|
||||
filtersJson = it.filterList.nullIfBlank()
|
||||
?.takeUnless { it == "[]" }
|
||||
)
|
||||
}.toSet()
|
||||
}.ifEmpty { null }
|
||||
|
||||
preferences.savedSearches().set(newSavedSearches + preferences.savedSearches().get())
|
||||
if (newSavedSearches != null) {
|
||||
databaseHelper.insertSavedSearches(newSavedSearches)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,11 +28,16 @@ import eu.kanade.tachiyomi.source.model.toSManga
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import exh.merged.sql.models.MergedMangaReference
|
||||
import exh.savedsearches.JsonSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import kotlin.math.max
|
||||
@@ -287,34 +292,26 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
|
||||
internal fun restoreSavedSearches(jsonSavedSearches: String) {
|
||||
val backupSavedSearches = jsonSavedSearches.split("***").toSet()
|
||||
|
||||
val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking()
|
||||
|
||||
val newSavedSearches = backupSavedSearches.mapNotNull {
|
||||
runCatching {
|
||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
val content = parser.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
id to content
|
||||
val content = parser.decodeFromString<JsonObject>(it.substringAfter(':'))
|
||||
SavedSearch(
|
||||
id = null,
|
||||
source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null,
|
||||
content["name"]!!.jsonPrimitive.content,
|
||||
content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
|
||||
Json.encodeToString(content["filters"]!!.jsonArray)
|
||||
)
|
||||
}.getOrNull()
|
||||
}.toMutableSet()
|
||||
}.filter { backupSavedSearch ->
|
||||
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
||||
}.ifEmpty { null }
|
||||
|
||||
val currentSources = newSavedSearches.map(Pair<Long, *>::first).toSet()
|
||||
|
||||
newSavedSearches += preferences.savedSearches().get().mapNotNull {
|
||||
kotlin.runCatching {
|
||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
val content = parser.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
id to content
|
||||
}.getOrNull()
|
||||
if (newSavedSearches != null) {
|
||||
databaseHelper.insertSavedSearches(newSavedSearches)
|
||||
}
|
||||
|
||||
val otherSerialized = preferences.savedSearches().get().mapNotNull {
|
||||
val sourceId = it.substringBefore(":").toLongOrNull() ?: return@mapNotNull null
|
||||
if (sourceId in currentSources) return@mapNotNull null
|
||||
it
|
||||
}.toSet()
|
||||
|
||||
val newSerialized = newSavedSearches.map { (source, savedSearch) ->
|
||||
"$source:" + Json.encodeToString(savedSearch)
|
||||
}.toSet()
|
||||
preferences.savedSearches().set(otherSerialized + newSerialized)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,13 +36,16 @@ import exh.metadata.sql.models.SearchTitle
|
||||
import exh.metadata.sql.queries.SearchMetadataQueries
|
||||
import exh.metadata.sql.queries.SearchTagQueries
|
||||
import exh.metadata.sql.queries.SearchTitleQueries
|
||||
import exh.savedsearches.mappers.SavedSearchTypeMapping
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.savedsearches.queries.SavedSearchQueries
|
||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||
|
||||
/**
|
||||
* This class provides operations to manage the database through its interfaces.
|
||||
*/
|
||||
open class DatabaseHelper(context: Context) :
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, FavoriteEntryQueries /* SY <-- */ {
|
||||
MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries, HistoryQueries /* SY --> */, SearchMetadataQueries, SearchTagQueries, SearchTitleQueries, MergedQueries, FavoriteEntryQueries, SavedSearchQueries /* SY <-- */ {
|
||||
|
||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||
.name(DbOpenCallback.DATABASE_NAME)
|
||||
@@ -63,6 +66,7 @@ open class DatabaseHelper(context: Context) :
|
||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
||||
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
|
||||
.addTypeMapping(SavedSearch::class.java, SavedSearchTypeMapping())
|
||||
// SY <--
|
||||
.build()
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import exh.merged.sql.tables.MergedTable
|
||||
import exh.metadata.sql.tables.SearchMetadataTable
|
||||
import exh.metadata.sql.tables.SearchTagTable
|
||||
import exh.metadata.sql.tables.SearchTitleTable
|
||||
import exh.savedsearches.tables.SavedSearchTable
|
||||
|
||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
|
||||
@@ -25,7 +26,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
/**
|
||||
* Version of the database.
|
||||
*/
|
||||
const val DATABASE_VERSION = /* SY --> */ 12 /* SY <-- */
|
||||
const val DATABASE_VERSION = /* SY --> */ 13 /* SY <-- */
|
||||
}
|
||||
|
||||
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||
@@ -41,6 +42,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
execSQL(SearchTitleTable.createTableQuery)
|
||||
execSQL(MergedTable.createTableQuery)
|
||||
execSQL(FavoriteEntryTable.createTableQuery)
|
||||
execSQL(SavedSearchTable.createTableQuery)
|
||||
// SY <--
|
||||
|
||||
// DB indexes
|
||||
@@ -101,6 +103,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||
if (oldVersion < 12) {
|
||||
db.execSQL(FavoriteEntryTable.fixTableQuery)
|
||||
}
|
||||
if (oldVersion < 13) {
|
||||
db.execSQL(SavedSearchTable.createTableQuery)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||
|
||||
@@ -411,8 +411,6 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun ehLastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
|
||||
|
||||
fun savedSearches() = flowPrefs.getStringSet("eh_saved_searches", emptySet())
|
||||
|
||||
fun logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
|
||||
|
||||
fun enableSourceBlacklist() = flowPrefs.getBoolean("eh_enable_source_blacklist", true)
|
||||
|
||||
+22
-38
@@ -37,7 +37,6 @@ import eu.kanade.tachiyomi.ui.base.controller.SearchableNucleusController
|
||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet.FilterNavigationView.Companion.MAX_SAVED_SEARCHES
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
||||
@@ -84,6 +83,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
searchQuery: String? = null,
|
||||
// SY -->
|
||||
smartSearchConfig: SourceController.SmartSearchConfig? = null,
|
||||
savedSearch: Long? = null,
|
||||
filterList: String? = null
|
||||
// SY <--
|
||||
) : this(
|
||||
@@ -99,6 +99,10 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
|
||||
}
|
||||
|
||||
if (savedSearch != null) {
|
||||
putLong(SAVED_SEARCH_CONFIG_KEY, savedSearch)
|
||||
}
|
||||
|
||||
if (filterList != null) {
|
||||
putString(FILTERS_CONFIG_KEY, filterList)
|
||||
}
|
||||
@@ -154,7 +158,8 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
return BrowseSourcePresenter(
|
||||
args.getLong(SOURCE_ID_KEY),
|
||||
args.getString(SEARCH_QUERY_KEY),
|
||||
filters = args.getString(FILTERS_CONFIG_KEY)
|
||||
filters = args.getString(FILTERS_CONFIG_KEY),
|
||||
savedSearch = args.getLong(SAVED_SEARCH_CONFIG_KEY, 0).takeUnless { it == 0L }
|
||||
)
|
||||
// SY <--
|
||||
}
|
||||
@@ -179,6 +184,10 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
// SY <--
|
||||
}
|
||||
|
||||
fun setSavedSearches(savedSearches: List<EXHSavedSearch>) {
|
||||
filterSheet?.setSavedSearches(savedSearches)
|
||||
}
|
||||
|
||||
open fun initFilterSheet() {
|
||||
if (presenter.sourceFilters.isEmpty()) {
|
||||
// SY -->
|
||||
@@ -207,6 +216,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
// EXH -->
|
||||
onSaveClicked = {
|
||||
filterSheet?.context?.let {
|
||||
val names = presenter.loadSearches().map { it.name }
|
||||
var searchName = ""
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(R.string.save_search)
|
||||
@@ -214,27 +224,18 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
searchName = input
|
||||
}
|
||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||
val oldSavedSearches = presenter.loadSearches()
|
||||
if (searchName.isNotBlank() &&
|
||||
oldSavedSearches.size < MAX_SAVED_SEARCHES
|
||||
) {
|
||||
val newSearches = oldSavedSearches + EXHSavedSearch(
|
||||
searchName.trim(),
|
||||
presenter.query,
|
||||
presenter.sourceFilters
|
||||
)
|
||||
presenter.saveSearches(newSearches)
|
||||
filterSheet?.setSavedSearches(newSearches)
|
||||
if (searchName.isNotBlank() && searchName !in names) {
|
||||
presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters)
|
||||
} else {
|
||||
it.toast(R.string.save_search_invalid_name)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.action_cancel, null)
|
||||
.show()
|
||||
}
|
||||
},
|
||||
onSavedSearchClicked = cb@{ indexToSearch ->
|
||||
val savedSearches = presenter.loadSearches()
|
||||
|
||||
val search = savedSearches.getOrNull(indexToSearch)
|
||||
onSavedSearchClicked = cb@{ idOfSearch ->
|
||||
val search = presenter.loadSearch(idOfSearch)
|
||||
|
||||
if (search == null) {
|
||||
filterSheet?.context?.let {
|
||||
@@ -261,32 +262,14 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters)
|
||||
activity?.invalidateOptionsMenu()
|
||||
},
|
||||
onSavedSearchDeleteClicked = cb@{ indexToDelete, name ->
|
||||
val savedSearches = presenter.loadSearches()
|
||||
|
||||
val search = savedSearches.getOrNull(indexToDelete)
|
||||
|
||||
if (search == null || search.name != name) {
|
||||
filterSheet?.context?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(R.string.save_search_failed_to_delete)
|
||||
.setMessage(R.string.save_search_failed_to_delete_message)
|
||||
.show()
|
||||
}
|
||||
return@cb
|
||||
}
|
||||
|
||||
onSavedSearchDeleteClicked = cb@{ idToDelete, name ->
|
||||
filterSheet?.context?.let {
|
||||
MaterialAlertDialogBuilder(it)
|
||||
.setTitle(R.string.save_search_delete)
|
||||
.setMessage(it.getString(R.string.save_search_delete_message, search.name))
|
||||
.setMessage(it.getString(R.string.save_search_delete_message, name))
|
||||
.setPositiveButton(R.string.action_cancel, null)
|
||||
.setNegativeButton(android.R.string.ok) { _, _ ->
|
||||
val newSearches = savedSearches.filterIndexed { index, _ ->
|
||||
index != indexToDelete
|
||||
}
|
||||
presenter.saveSearches(newSearches)
|
||||
filterSheet?.setSavedSearches(newSearches)
|
||||
presenter.deleteSearch(idToDelete)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
@@ -836,6 +819,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
||||
|
||||
// SY -->
|
||||
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
|
||||
const val SAVED_SEARCH_CONFIG_KEY = "savedSearch"
|
||||
const val FILTERS_CONFIG_KEY = "filters"
|
||||
// SY <--
|
||||
}
|
||||
|
||||
+98
-36
@@ -42,8 +42,9 @@ import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import exh.log.xLogE
|
||||
import exh.savedsearches.EXHSavedSearch
|
||||
import exh.savedsearches.JsonSavedSearch
|
||||
import exh.savedsearches.models.SavedSearch
|
||||
import exh.source.isEhBasedSource
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
@@ -73,6 +74,7 @@ open class BrowseSourcePresenter(
|
||||
searchQuery: String? = null,
|
||||
// SY -->
|
||||
private val filters: String? = null,
|
||||
private val savedSearch: Long? = null,
|
||||
// SY <--
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val db: DatabaseHelper = Injekt.get(),
|
||||
@@ -134,21 +136,45 @@ open class BrowseSourcePresenter(
|
||||
sourceFilters = source.getFilterList()
|
||||
|
||||
// SY -->
|
||||
val savedSearchFilters = savedSearch
|
||||
val jsonFilters = filters
|
||||
if (jsonFilters != null) {
|
||||
if (savedSearchFilters != null) {
|
||||
runCatching {
|
||||
val filters = Json.decodeFromString<JsonSavedSearch>(jsonFilters)
|
||||
filterSerializer.deserialize(sourceFilters, filters.filters)
|
||||
val savedSearch = db.getSavedSearch(savedSearchFilters).executeAsBlocking() ?: return@runCatching
|
||||
query = savedSearch.query.orEmpty()
|
||||
val filtersJson = savedSearch.filtersJson
|
||||
?: return@runCatching
|
||||
val filters = Json.decodeFromString<JsonArray>(filtersJson)
|
||||
filterSerializer.deserialize(sourceFilters, filters)
|
||||
appliedFilters = sourceFilters
|
||||
}
|
||||
} else if (jsonFilters != null) {
|
||||
runCatching {
|
||||
val filters = Json.decodeFromString<JsonArray>(jsonFilters)
|
||||
filterSerializer.deserialize(sourceFilters, filters)
|
||||
appliedFilters = sourceFilters
|
||||
}
|
||||
}
|
||||
val allDefault = sourceFilters == source.getFilterList()
|
||||
|
||||
db.getSavedSearches(source.id)
|
||||
.asRxObservable()
|
||||
.map {
|
||||
loadSearches(it)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeLatestCache(
|
||||
{ controller, savedSearches ->
|
||||
controller.setSavedSearches(savedSearches)
|
||||
}
|
||||
)
|
||||
// SY <--
|
||||
|
||||
if (savedState != null) {
|
||||
query = savedState.getString(::query.name, "")
|
||||
}
|
||||
|
||||
restartPager(/* SY -->*/ filters = if (allDefault) this.appliedFilters else sourceFilters /* SY <--*/)
|
||||
restartPager()
|
||||
}
|
||||
|
||||
override fun onSave(state: Bundle) {
|
||||
@@ -319,7 +345,7 @@ open class BrowseSourcePresenter(
|
||||
.forEach { service ->
|
||||
launchIO {
|
||||
try {
|
||||
service.match(manga)?.let { track ->
|
||||
service.match(source, manga)?.let { track ->
|
||||
track.manga_id = manga.id!!
|
||||
(service as TrackService).bind(track)
|
||||
db.insertTrack(track).executeAsBlocking()
|
||||
@@ -456,48 +482,84 @@ open class BrowseSourcePresenter(
|
||||
}
|
||||
|
||||
// EXH -->
|
||||
fun saveSearches(searches: List<EXHSavedSearch>) {
|
||||
val otherSerialized = prefs.savedSearches().get().filterNot {
|
||||
it.startsWith("${source.id}:")
|
||||
}.toSet()
|
||||
val newSerialized = searches.map {
|
||||
"${source.id}:" + Json.encodeToString(
|
||||
JsonSavedSearch(
|
||||
it.name,
|
||||
it.query,
|
||||
if (it.filterList != null) {
|
||||
filterSerializer.serialize(it.filterList)
|
||||
} else JsonArray(emptyList())
|
||||
fun saveSearch(name: String, query: String, filterList: FilterList) {
|
||||
launchIO {
|
||||
kotlin.runCatching {
|
||||
val savedSearch = SavedSearch(
|
||||
id = null,
|
||||
source = source.id,
|
||||
name = name.trim(),
|
||||
query = query.nullIfBlank(),
|
||||
filtersJson = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }
|
||||
)
|
||||
)
|
||||
|
||||
db.insertSavedSearch(savedSearch).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
prefs.savedSearches().set(otherSerialized + newSerialized)
|
||||
}
|
||||
|
||||
fun loadSearches(): List<EXHSavedSearch> {
|
||||
return prefs.savedSearches().get().mapNotNull {
|
||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
if (id != source.id) return@mapNotNull null
|
||||
val content = try {
|
||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
fun deleteSearch(searchId: Long) {
|
||||
launchIO {
|
||||
db.deleteSavedSearch(searchId).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
fun loadSearch(searchId: Long): EXHSavedSearch? {
|
||||
val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null
|
||||
return EXHSavedSearch(
|
||||
id = search.id!!,
|
||||
name = search.name,
|
||||
query = search.query.orEmpty(),
|
||||
filterList = runCatching {
|
||||
val originalFilters = source.getFilterList()
|
||||
filterSerializer.deserialize(
|
||||
filters = originalFilters,
|
||||
json = search.filtersJson
|
||||
?.let { Json.decodeFromString<JsonArray>(it) }
|
||||
?: return@runCatching null
|
||||
)
|
||||
originalFilters
|
||||
}.getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
fun loadSearches(searches: List<SavedSearch> = db.getSavedSearches(source.id).executeAsBlocking()): List<EXHSavedSearch> {
|
||||
return searches.map {
|
||||
val filtersJson = it.filtersJson ?: return@map EXHSavedSearch(
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
val filters = try {
|
||||
Json.decodeFromString<JsonArray>(filtersJson)
|
||||
} catch (e: Exception) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
xLogE("Failed to load saved search!", e)
|
||||
null
|
||||
} ?: return@map EXHSavedSearch(
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
|
||||
try {
|
||||
val originalFilters = source.getFilterList()
|
||||
filterSerializer.deserialize(originalFilters, content.filters)
|
||||
filterSerializer.deserialize(originalFilters, filters)
|
||||
EXHSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
originalFilters
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = originalFilters
|
||||
)
|
||||
} catch (t: RuntimeException) {
|
||||
// Load failed
|
||||
xLogE("Failed to load saved search!", t)
|
||||
EXHSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
null
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
import exh.savedsearches.EXHSavedSearch
|
||||
import exh.source.getMainSource
|
||||
import exh.util.under
|
||||
|
||||
class SourceFilterSheet(
|
||||
activity: Activity,
|
||||
@@ -32,8 +31,8 @@ class SourceFilterSheet(
|
||||
private val onResetClicked: () -> Unit,
|
||||
// EXH -->
|
||||
private val onSaveClicked: () -> Unit,
|
||||
var onSavedSearchClicked: (Int) -> Unit = {},
|
||||
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> }
|
||||
var onSavedSearchClicked: (Long) -> Unit = {},
|
||||
var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> }
|
||||
// EXH <--
|
||||
) : BaseBottomSheetDialog(activity) {
|
||||
|
||||
@@ -97,9 +96,9 @@ class SourceFilterSheet(
|
||||
// SY -->
|
||||
var onSaveClicked = {}
|
||||
|
||||
var onSavedSearchClicked: (Int) -> Unit = {}
|
||||
var onSavedSearchClicked: (Long) -> Unit = {}
|
||||
|
||||
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> }
|
||||
var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> }
|
||||
|
||||
private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches))
|
||||
// SY <--
|
||||
@@ -143,17 +142,13 @@ class SourceFilterSheet(
|
||||
}
|
||||
|
||||
private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> {
|
||||
recycler.post {
|
||||
binding.saveSearchBtn.isVisible = searches.size under MAX_SAVED_SEARCHES
|
||||
}
|
||||
return searches.withIndex()
|
||||
.sortedBy { it.value.name }
|
||||
.map { (index, search) ->
|
||||
return searches
|
||||
.map { search ->
|
||||
Chip(context).apply {
|
||||
text = search.name
|
||||
setOnClickListener { onSavedSearchClicked(index) }
|
||||
setOnClickListener { onSavedSearchClicked(search.id) }
|
||||
setOnLongClickListener {
|
||||
onSavedSearchDeleteClicked(index, search.name); true
|
||||
onSavedSearchDeleteClicked(search.id, search.name); true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,10 +158,6 @@ class SourceFilterSheet(
|
||||
fun hideFilterButton() {
|
||||
binding.filterBtn.isVisible = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_SAVED_SEARCHES = 500 // if you want more than this, fuck you, i guess
|
||||
}
|
||||
// EXH <--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterSheet
|
||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.savedsearches.JsonSavedSearch
|
||||
import exh.util.nullIfBlank
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -196,25 +195,21 @@ open class IndexController :
|
||||
onFilterClicked = {
|
||||
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
||||
filterSheet?.dismiss()
|
||||
val json = if (allDefault) {
|
||||
null
|
||||
if (allDefault) {
|
||||
onBrowseClick(
|
||||
presenter.query.nullIfBlank()
|
||||
)
|
||||
} else {
|
||||
Json.encodeToString(
|
||||
JsonSavedSearch(
|
||||
"",
|
||||
"",
|
||||
filterSerializer.serialize(presenter.sourceFilters)
|
||||
)
|
||||
onBrowseClick(
|
||||
presenter.query.nullIfBlank(),
|
||||
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters))
|
||||
)
|
||||
}
|
||||
onBrowseClick(presenter.query.nullIfBlank(), json)
|
||||
},
|
||||
onResetClicked = {},
|
||||
onSaveClicked = {},
|
||||
onSavedSearchClicked = cb@{ indexToSearch ->
|
||||
val savedSearches = presenter.loadSearches()
|
||||
|
||||
val search = savedSearches.getOrNull(indexToSearch)
|
||||
onSavedSearchClicked = cb@{ idOfSearch ->
|
||||
val search = presenter.loadSearch(idOfSearch)
|
||||
|
||||
if (search == null) {
|
||||
filterSheet?.context?.let {
|
||||
@@ -237,14 +232,10 @@ open class IndexController :
|
||||
filterSheet?.dismiss()
|
||||
|
||||
if (!allDefault) {
|
||||
val json = Json.encodeToString(
|
||||
JsonSavedSearch(
|
||||
"",
|
||||
"",
|
||||
filterSerializer.serialize(presenter.sourceFilters)
|
||||
)
|
||||
onBrowseClick(
|
||||
search = presenter.query.nullIfBlank(),
|
||||
savedSearch = search.id
|
||||
)
|
||||
onBrowseClick(presenter.query.nullIfBlank(), json)
|
||||
}
|
||||
},
|
||||
onSavedSearchDeleteClicked = { _, _ -> }
|
||||
@@ -325,8 +316,8 @@ open class IndexController :
|
||||
super.onDestroyView(view)
|
||||
}
|
||||
|
||||
fun onBrowseClick(search: String? = null, filters: String? = null) {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, search, filterList = filters).withFadeTransaction())
|
||||
fun onBrowseClick(search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||
router.replaceTopController(BrowseSourceController(presenter.source, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
||||
}
|
||||
|
||||
private fun onLatestClick() {
|
||||
|
||||
@@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import exh.log.xLogE
|
||||
import exh.savedsearches.EXHSavedSearch
|
||||
import exh.savedsearches.JsonSavedSearch
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
@@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import logcat.LogPriority
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@@ -212,30 +212,61 @@ open class IndexPresenter(
|
||||
|
||||
private val filterSerializer = FilterSerializer()
|
||||
|
||||
fun loadSearch(searchId: Long): EXHSavedSearch? {
|
||||
val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null
|
||||
return EXHSavedSearch(
|
||||
id = search.id!!,
|
||||
name = search.name,
|
||||
query = search.query.orEmpty(),
|
||||
filterList = runCatching {
|
||||
val originalFilters = source.getFilterList()
|
||||
filterSerializer.deserialize(
|
||||
filters = originalFilters,
|
||||
json = search.filtersJson
|
||||
?.let { Json.decodeFromString<JsonArray>(it) }
|
||||
?: return@runCatching null
|
||||
)
|
||||
originalFilters
|
||||
}.getOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
fun loadSearches(): List<EXHSavedSearch> {
|
||||
return preferences.savedSearches().get().mapNotNull {
|
||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
||||
if (id != source.id) return@mapNotNull null
|
||||
val content = try {
|
||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
||||
return db.getSavedSearches(source.id).executeAsBlocking().map {
|
||||
val filtersJson = it.filtersJson ?: return@map EXHSavedSearch(
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
val filters = try {
|
||||
Json.decodeFromString<JsonArray>(filtersJson)
|
||||
} catch (e: Exception) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
null
|
||||
} ?: return@map EXHSavedSearch(
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
|
||||
try {
|
||||
val originalFilters = source.getFilterList()
|
||||
filterSerializer.deserialize(originalFilters, content.filters)
|
||||
filterSerializer.deserialize(originalFilters, filters)
|
||||
EXHSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
originalFilters
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = originalFilters
|
||||
)
|
||||
} catch (t: RuntimeException) {
|
||||
// Load failed
|
||||
xLogE("Failed to load saved search!", t)
|
||||
EXHSavedSearch(
|
||||
content.name,
|
||||
content.query,
|
||||
null
|
||||
id = it.id!!,
|
||||
name = it.name,
|
||||
query = it.query.orEmpty(),
|
||||
filterList = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user