Migrate saved searches to the db
This commit is contained in:
parent
1ebcfc53d4
commit
5d330c4f75
@ -25,7 +25,7 @@ android {
|
|||||||
applicationId = "eu.kanade.tachiyomi.sy"
|
applicationId = "eu.kanade.tachiyomi.sy"
|
||||||
minSdk = AndroidConfig.minSdk
|
minSdk = AndroidConfig.minSdk
|
||||||
targetSdk = AndroidConfig.targetSdk
|
targetSdk = AndroidConfig.targetSdk
|
||||||
versionCode = 30
|
versionCode = 31
|
||||||
versionName = "1.8.1"
|
versionName = "1.8.1"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
|
@ -38,13 +38,11 @@ import eu.kanade.tachiyomi.util.lang.launchIO
|
|||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||||
import exh.savedsearches.JsonSavedSearch
|
import exh.savedsearches.models.SavedSearch
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.util.executeOnIO
|
import exh.util.executeOnIO
|
||||||
import kotlinx.serialization.decodeFromString
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.protobuf.ProtoBuf
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
@ -164,14 +162,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
* @return list of [BackupSavedSearch] to be backed up
|
* @return list of [BackupSavedSearch] to be backed up
|
||||||
*/
|
*/
|
||||||
private fun backupSavedSearches(): List<BackupSavedSearch> {
|
private fun backupSavedSearches(): List<BackupSavedSearch> {
|
||||||
return preferences.savedSearches().get().mapNotNull {
|
return databaseHelper.getSavedSearches().executeAsBlocking().map {
|
||||||
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
|
||||||
val content = Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
|
||||||
BackupSavedSearch(
|
BackupSavedSearch(
|
||||||
content.name,
|
it.name,
|
||||||
content.query,
|
it.query.orEmpty(),
|
||||||
content.filters.toString(),
|
it.filtersJson ?: "[]",
|
||||||
sourceId
|
it.source
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -431,34 +427,25 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
|
internal fun restoreSavedSearches(backupSavedSearches: List<BackupSavedSearch>) {
|
||||||
val currentSavedSearches = preferences.savedSearches().get().mapNotNull {
|
val currentSavedSearches = databaseHelper.getSavedSearches()
|
||||||
val sourceId = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
.executeAsBlocking()
|
||||||
val content = try {
|
|
||||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return@mapNotNull null
|
|
||||||
}
|
|
||||||
BackupSavedSearch(
|
|
||||||
content.name,
|
|
||||||
content.query,
|
|
||||||
content.filters.toString(),
|
|
||||||
sourceId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val newSavedSearches = backupSavedSearches.filter { backupSavedSearch ->
|
val newSavedSearches = backupSavedSearches.filter { backupSavedSearch ->
|
||||||
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
||||||
}.map {
|
}.map {
|
||||||
"${it.source}:" + Json.encodeToString(
|
SavedSearch(
|
||||||
JsonSavedSearch(
|
id = null,
|
||||||
|
it.source,
|
||||||
it.name,
|
it.name,
|
||||||
it.query,
|
it.query.nullIfBlank(),
|
||||||
Json.decodeFromString(it.filterList)
|
filtersJson = it.filterList.nullIfBlank()
|
||||||
|
?.takeUnless { it == "[]" }
|
||||||
)
|
)
|
||||||
)
|
}.ifEmpty { null }
|
||||||
}.toSet()
|
|
||||||
|
|
||||||
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 eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||||
import exh.eh.EHentaiThrottleManager
|
import exh.eh.EHentaiThrottleManager
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
import exh.savedsearches.JsonSavedSearch
|
import exh.savedsearches.models.SavedSearch
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
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.SerializersModule
|
||||||
import kotlinx.serialization.modules.contextual
|
import kotlinx.serialization.modules.contextual
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@ -287,34 +292,26 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
|
|||||||
internal fun restoreSavedSearches(jsonSavedSearches: String) {
|
internal fun restoreSavedSearches(jsonSavedSearches: String) {
|
||||||
val backupSavedSearches = jsonSavedSearches.split("***").toSet()
|
val backupSavedSearches = jsonSavedSearches.split("***").toSet()
|
||||||
|
|
||||||
|
val currentSavedSearches = databaseHelper.getSavedSearches().executeAsBlocking()
|
||||||
|
|
||||||
val newSavedSearches = backupSavedSearches.mapNotNull {
|
val newSavedSearches = backupSavedSearches.mapNotNull {
|
||||||
runCatching {
|
runCatching {
|
||||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
val content = parser.decodeFromString<JsonObject>(it.substringAfter(':'))
|
||||||
val content = parser.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
SavedSearch(
|
||||||
id to content
|
id = null,
|
||||||
|
source = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null,
|
||||||
|
content["name"]!!.jsonPrimitive.content,
|
||||||
|
content["query"]!!.jsonPrimitive.contentOrNull?.nullIfBlank(),
|
||||||
|
Json.encodeToString(content["filters"]!!.jsonArray)
|
||||||
|
)
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}.toMutableSet()
|
}.filter { backupSavedSearch ->
|
||||||
|
currentSavedSearches.none { it.name == backupSavedSearch.name && it.source == backupSavedSearch.source }
|
||||||
|
}.ifEmpty { null }
|
||||||
|
|
||||||
val currentSources = newSavedSearches.map(Pair<Long, *>::first).toSet()
|
if (newSavedSearches != null) {
|
||||||
|
databaseHelper.insertSavedSearches(newSavedSearches)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.SearchMetadataQueries
|
||||||
import exh.metadata.sql.queries.SearchTagQueries
|
import exh.metadata.sql.queries.SearchTagQueries
|
||||||
import exh.metadata.sql.queries.SearchTitleQueries
|
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
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides operations to manage the database through its interfaces.
|
* This class provides operations to manage the database through its interfaces.
|
||||||
*/
|
*/
|
||||||
open class DatabaseHelper(context: Context) :
|
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)
|
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
.name(DbOpenCallback.DATABASE_NAME)
|
||||||
@ -63,6 +66,7 @@ open class DatabaseHelper(context: Context) :
|
|||||||
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
.addTypeMapping(SearchTitle::class.java, SearchTitleTypeMapping())
|
||||||
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
.addTypeMapping(MergedMangaReference::class.java, MergedMangaTypeMapping())
|
||||||
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
|
.addTypeMapping(FavoriteEntry::class.java, FavoriteEntryTypeMapping())
|
||||||
|
.addTypeMapping(SavedSearch::class.java, SavedSearchTypeMapping())
|
||||||
// SY <--
|
// SY <--
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import exh.merged.sql.tables.MergedTable
|
|||||||
import exh.metadata.sql.tables.SearchMetadataTable
|
import exh.metadata.sql.tables.SearchMetadataTable
|
||||||
import exh.metadata.sql.tables.SearchTagTable
|
import exh.metadata.sql.tables.SearchTagTable
|
||||||
import exh.metadata.sql.tables.SearchTitleTable
|
import exh.metadata.sql.tables.SearchTitleTable
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable
|
||||||
|
|
||||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
/**
|
/**
|
||||||
* Version of the database.
|
* 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) {
|
override fun onCreate(db: SupportSQLiteDatabase) = with(db) {
|
||||||
@ -41,6 +42,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(SearchTitleTable.createTableQuery)
|
execSQL(SearchTitleTable.createTableQuery)
|
||||||
execSQL(MergedTable.createTableQuery)
|
execSQL(MergedTable.createTableQuery)
|
||||||
execSQL(FavoriteEntryTable.createTableQuery)
|
execSQL(FavoriteEntryTable.createTableQuery)
|
||||||
|
execSQL(SavedSearchTable.createTableQuery)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// DB indexes
|
// DB indexes
|
||||||
@ -101,6 +103,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
if (oldVersion < 12) {
|
if (oldVersion < 12) {
|
||||||
db.execSQL(FavoriteEntryTable.fixTableQuery)
|
db.execSQL(FavoriteEntryTable.fixTableQuery)
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 13) {
|
||||||
|
db.execSQL(SavedSearchTable.createTableQuery)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigure(db: SupportSQLiteDatabase) {
|
override fun onConfigure(db: SupportSQLiteDatabase) {
|
||||||
|
@ -411,8 +411,6 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun ehLastVersionCode() = flowPrefs.getInt("eh_last_version_code", 0)
|
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 logLevel() = flowPrefs.getInt(Keys.eh_logLevel, 0)
|
||||||
|
|
||||||
fun enableSourceBlacklist() = flowPrefs.getBoolean("eh_enable_source_blacklist", true)
|
fun enableSourceBlacklist() = flowPrefs.getBoolean("eh_enable_source_blacklist", true)
|
||||||
|
@ -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.base.controller.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
|
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
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.browse.source.globalsearch.GlobalSearchController
|
||||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||||
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting
|
||||||
@ -84,6 +83,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
// SY -->
|
// SY -->
|
||||||
smartSearchConfig: SourceController.SmartSearchConfig? = null,
|
smartSearchConfig: SourceController.SmartSearchConfig? = null,
|
||||||
|
savedSearch: Long? = null,
|
||||||
filterList: String? = null
|
filterList: String? = null
|
||||||
// SY <--
|
// SY <--
|
||||||
) : this(
|
) : this(
|
||||||
@ -99,6 +99,10 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
|
putParcelable(SMART_SEARCH_CONFIG_KEY, smartSearchConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (savedSearch != null) {
|
||||||
|
putLong(SAVED_SEARCH_CONFIG_KEY, savedSearch)
|
||||||
|
}
|
||||||
|
|
||||||
if (filterList != null) {
|
if (filterList != null) {
|
||||||
putString(FILTERS_CONFIG_KEY, filterList)
|
putString(FILTERS_CONFIG_KEY, filterList)
|
||||||
}
|
}
|
||||||
@ -154,7 +158,8 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
return BrowseSourcePresenter(
|
return BrowseSourcePresenter(
|
||||||
args.getLong(SOURCE_ID_KEY),
|
args.getLong(SOURCE_ID_KEY),
|
||||||
args.getString(SEARCH_QUERY_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 <--
|
// SY <--
|
||||||
}
|
}
|
||||||
@ -179,6 +184,10 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setSavedSearches(savedSearches: List<EXHSavedSearch>) {
|
||||||
|
filterSheet?.setSavedSearches(savedSearches)
|
||||||
|
}
|
||||||
|
|
||||||
open fun initFilterSheet() {
|
open fun initFilterSheet() {
|
||||||
if (presenter.sourceFilters.isEmpty()) {
|
if (presenter.sourceFilters.isEmpty()) {
|
||||||
// SY -->
|
// SY -->
|
||||||
@ -207,6 +216,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
// EXH -->
|
// EXH -->
|
||||||
onSaveClicked = {
|
onSaveClicked = {
|
||||||
filterSheet?.context?.let {
|
filterSheet?.context?.let {
|
||||||
|
val names = presenter.loadSearches().map { it.name }
|
||||||
var searchName = ""
|
var searchName = ""
|
||||||
MaterialAlertDialogBuilder(it)
|
MaterialAlertDialogBuilder(it)
|
||||||
.setTitle(R.string.save_search)
|
.setTitle(R.string.save_search)
|
||||||
@ -214,27 +224,18 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
searchName = input
|
searchName = input
|
||||||
}
|
}
|
||||||
.setPositiveButton(R.string.action_save) { _, _ ->
|
.setPositiveButton(R.string.action_save) { _, _ ->
|
||||||
val oldSavedSearches = presenter.loadSearches()
|
if (searchName.isNotBlank() && searchName !in names) {
|
||||||
if (searchName.isNotBlank() &&
|
presenter.saveSearch(searchName.trim(), presenter.query, presenter.sourceFilters)
|
||||||
oldSavedSearches.size < MAX_SAVED_SEARCHES
|
} else {
|
||||||
) {
|
it.toast(R.string.save_search_invalid_name)
|
||||||
val newSearches = oldSavedSearches + EXHSavedSearch(
|
|
||||||
searchName.trim(),
|
|
||||||
presenter.query,
|
|
||||||
presenter.sourceFilters
|
|
||||||
)
|
|
||||||
presenter.saveSearches(newSearches)
|
|
||||||
filterSheet?.setSavedSearches(newSearches)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.action_cancel, null)
|
.setNegativeButton(R.string.action_cancel, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSavedSearchClicked = cb@{ indexToSearch ->
|
onSavedSearchClicked = cb@{ idOfSearch ->
|
||||||
val savedSearches = presenter.loadSearches()
|
val search = presenter.loadSearch(idOfSearch)
|
||||||
|
|
||||||
val search = savedSearches.getOrNull(indexToSearch)
|
|
||||||
|
|
||||||
if (search == null) {
|
if (search == null) {
|
||||||
filterSheet?.context?.let {
|
filterSheet?.context?.let {
|
||||||
@ -261,32 +262,14 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters)
|
presenter.restartPager(search.query, if (allDefault) FilterList() else presenter.sourceFilters)
|
||||||
activity?.invalidateOptionsMenu()
|
activity?.invalidateOptionsMenu()
|
||||||
},
|
},
|
||||||
onSavedSearchDeleteClicked = cb@{ indexToDelete, name ->
|
onSavedSearchDeleteClicked = cb@{ idToDelete, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
filterSheet?.context?.let {
|
filterSheet?.context?.let {
|
||||||
MaterialAlertDialogBuilder(it)
|
MaterialAlertDialogBuilder(it)
|
||||||
.setTitle(R.string.save_search_delete)
|
.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)
|
.setPositiveButton(R.string.action_cancel, null)
|
||||||
.setNegativeButton(android.R.string.ok) { _, _ ->
|
.setNegativeButton(android.R.string.ok) { _, _ ->
|
||||||
val newSearches = savedSearches.filterIndexed { index, _ ->
|
presenter.deleteSearch(idToDelete)
|
||||||
index != indexToDelete
|
|
||||||
}
|
|
||||||
presenter.saveSearches(newSearches)
|
|
||||||
filterSheet?.setSavedSearches(newSearches)
|
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@ -836,6 +819,7 @@ open class BrowseSourceController(bundle: Bundle) :
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
|
const val SMART_SEARCH_CONFIG_KEY = "smartSearchConfig"
|
||||||
|
const val SAVED_SEARCH_CONFIG_KEY = "savedSearch"
|
||||||
const val FILTERS_CONFIG_KEY = "filters"
|
const val FILTERS_CONFIG_KEY = "filters"
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,9 @@ import eu.kanade.tachiyomi.util.removeCovers
|
|||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.log.xLogE
|
import exh.log.xLogE
|
||||||
import exh.savedsearches.EXHSavedSearch
|
import exh.savedsearches.EXHSavedSearch
|
||||||
import exh.savedsearches.JsonSavedSearch
|
import exh.savedsearches.models.SavedSearch
|
||||||
import exh.source.isEhBasedSource
|
import exh.source.isEhBasedSource
|
||||||
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
@ -73,6 +74,7 @@ open class BrowseSourcePresenter(
|
|||||||
searchQuery: String? = null,
|
searchQuery: String? = null,
|
||||||
// SY -->
|
// SY -->
|
||||||
private val filters: String? = null,
|
private val filters: String? = null,
|
||||||
|
private val savedSearch: Long? = null,
|
||||||
// SY <--
|
// SY <--
|
||||||
private val sourceManager: SourceManager = Injekt.get(),
|
private val sourceManager: SourceManager = Injekt.get(),
|
||||||
private val db: DatabaseHelper = Injekt.get(),
|
private val db: DatabaseHelper = Injekt.get(),
|
||||||
@ -134,21 +136,45 @@ open class BrowseSourcePresenter(
|
|||||||
sourceFilters = source.getFilterList()
|
sourceFilters = source.getFilterList()
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
val savedSearchFilters = savedSearch
|
||||||
val jsonFilters = filters
|
val jsonFilters = filters
|
||||||
if (jsonFilters != null) {
|
if (savedSearchFilters != null) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val filters = Json.decodeFromString<JsonSavedSearch>(jsonFilters)
|
val savedSearch = db.getSavedSearch(savedSearchFilters).executeAsBlocking() ?: return@runCatching
|
||||||
filterSerializer.deserialize(sourceFilters, filters.filters)
|
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 <--
|
// SY <--
|
||||||
|
|
||||||
if (savedState != null) {
|
if (savedState != null) {
|
||||||
query = savedState.getString(::query.name, "")
|
query = savedState.getString(::query.name, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
restartPager(/* SY -->*/ filters = if (allDefault) this.appliedFilters else sourceFilters /* SY <--*/)
|
restartPager()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSave(state: Bundle) {
|
override fun onSave(state: Bundle) {
|
||||||
@ -319,7 +345,7 @@ open class BrowseSourcePresenter(
|
|||||||
.forEach { service ->
|
.forEach { service ->
|
||||||
launchIO {
|
launchIO {
|
||||||
try {
|
try {
|
||||||
service.match(manga)?.let { track ->
|
service.match(source, manga)?.let { track ->
|
||||||
track.manga_id = manga.id!!
|
track.manga_id = manga.id!!
|
||||||
(service as TrackService).bind(track)
|
(service as TrackService).bind(track)
|
||||||
db.insertTrack(track).executeAsBlocking()
|
db.insertTrack(track).executeAsBlocking()
|
||||||
@ -456,48 +482,84 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EXH -->
|
// EXH -->
|
||||||
fun saveSearches(searches: List<EXHSavedSearch>) {
|
fun saveSearch(name: String, query: String, filterList: FilterList) {
|
||||||
val otherSerialized = prefs.savedSearches().get().filterNot {
|
launchIO {
|
||||||
it.startsWith("${source.id}:")
|
kotlin.runCatching {
|
||||||
}.toSet()
|
val savedSearch = SavedSearch(
|
||||||
val newSerialized = searches.map {
|
id = null,
|
||||||
"${source.id}:" + Json.encodeToString(
|
source = source.id,
|
||||||
JsonSavedSearch(
|
name = name.trim(),
|
||||||
it.name,
|
query = query.nullIfBlank(),
|
||||||
it.query,
|
filtersJson = filterSerializer.serialize(filterList).ifEmpty { null }?.let { Json.encodeToString(it) }
|
||||||
if (it.filterList != null) {
|
|
||||||
filterSerializer.serialize(it.filterList)
|
|
||||||
} else JsonArray(emptyList())
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
db.insertSavedSearch(savedSearch).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
prefs.savedSearches().set(otherSerialized + newSerialized)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadSearches(): List<EXHSavedSearch> {
|
fun deleteSearch(searchId: Long) {
|
||||||
return prefs.savedSearches().get().mapNotNull {
|
launchIO {
|
||||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
db.deleteSavedSearch(searchId).executeAsBlocking()
|
||||||
if (id != source.id) return@mapNotNull null
|
|
||||||
val content = try {
|
|
||||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return@mapNotNull null
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
xLogE("Failed to load saved search!", e)
|
||||||
|
null
|
||||||
|
} ?: return@map EXHSavedSearch(
|
||||||
|
id = it.id!!,
|
||||||
|
name = it.name,
|
||||||
|
query = it.query.orEmpty(),
|
||||||
|
filterList = null
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val originalFilters = source.getFilterList()
|
val originalFilters = source.getFilterList()
|
||||||
filterSerializer.deserialize(originalFilters, content.filters)
|
filterSerializer.deserialize(originalFilters, filters)
|
||||||
EXHSavedSearch(
|
EXHSavedSearch(
|
||||||
content.name,
|
id = it.id!!,
|
||||||
content.query,
|
name = it.name,
|
||||||
originalFilters
|
query = it.query.orEmpty(),
|
||||||
|
filterList = originalFilters
|
||||||
)
|
)
|
||||||
} catch (t: RuntimeException) {
|
} catch (t: RuntimeException) {
|
||||||
// Load failed
|
// Load failed
|
||||||
xLogE("Failed to load saved search!", t)
|
xLogE("Failed to load saved search!", t)
|
||||||
EXHSavedSearch(
|
EXHSavedSearch(
|
||||||
content.name,
|
id = it.id!!,
|
||||||
content.query,
|
name = it.name,
|
||||||
null
|
query = it.query.orEmpty(),
|
||||||
|
filterList = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.widget.SimpleNavigationView
|
|||||||
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||||
import exh.savedsearches.EXHSavedSearch
|
import exh.savedsearches.EXHSavedSearch
|
||||||
import exh.source.getMainSource
|
import exh.source.getMainSource
|
||||||
import exh.util.under
|
|
||||||
|
|
||||||
class SourceFilterSheet(
|
class SourceFilterSheet(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
@ -32,8 +31,8 @@ class SourceFilterSheet(
|
|||||||
private val onResetClicked: () -> Unit,
|
private val onResetClicked: () -> Unit,
|
||||||
// EXH -->
|
// EXH -->
|
||||||
private val onSaveClicked: () -> Unit,
|
private val onSaveClicked: () -> Unit,
|
||||||
var onSavedSearchClicked: (Int) -> Unit = {},
|
var onSavedSearchClicked: (Long) -> Unit = {},
|
||||||
var onSavedSearchDeleteClicked: (Int, String) -> Unit = { _, _ -> }
|
var onSavedSearchDeleteClicked: (Long, String) -> Unit = { _, _ -> }
|
||||||
// EXH <--
|
// EXH <--
|
||||||
) : BaseBottomSheetDialog(activity) {
|
) : BaseBottomSheetDialog(activity) {
|
||||||
|
|
||||||
@ -97,9 +96,9 @@ class SourceFilterSheet(
|
|||||||
// SY -->
|
// SY -->
|
||||||
var onSaveClicked = {}
|
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))
|
private val savedSearchesAdapter = SavedSearchesAdapter(getSavedSearchesChips(searches))
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -143,17 +142,13 @@ class SourceFilterSheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> {
|
private fun getSavedSearchesChips(searches: List<EXHSavedSearch>): List<Chip> {
|
||||||
recycler.post {
|
return searches
|
||||||
binding.saveSearchBtn.isVisible = searches.size under MAX_SAVED_SEARCHES
|
.map { search ->
|
||||||
}
|
|
||||||
return searches.withIndex()
|
|
||||||
.sortedBy { it.value.name }
|
|
||||||
.map { (index, search) ->
|
|
||||||
Chip(context).apply {
|
Chip(context).apply {
|
||||||
text = search.name
|
text = search.name
|
||||||
setOnClickListener { onSavedSearchClicked(index) }
|
setOnClickListener { onSavedSearchClicked(search.id) }
|
||||||
setOnLongClickListener {
|
setOnLongClickListener {
|
||||||
onSavedSearchDeleteClicked(index, search.name); true
|
onSavedSearchDeleteClicked(search.id, search.name); true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,10 +158,6 @@ class SourceFilterSheet(
|
|||||||
fun hideFilterButton() {
|
fun hideFilterButton() {
|
||||||
binding.filterBtn.isVisible = false
|
binding.filterBtn.isVisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val MAX_SAVED_SEARCHES = 500 // if you want more than this, fuck you, i guess
|
|
||||||
}
|
|
||||||
// EXH <--
|
// 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.browse.source.latest.LatestUpdatesController
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.savedsearches.JsonSavedSearch
|
|
||||||
import exh.util.nullIfBlank
|
import exh.util.nullIfBlank
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@ -196,25 +195,21 @@ open class IndexController :
|
|||||||
onFilterClicked = {
|
onFilterClicked = {
|
||||||
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
val allDefault = presenter.sourceFilters == presenter.source.getFilterList()
|
||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
val json = if (allDefault) {
|
if (allDefault) {
|
||||||
null
|
onBrowseClick(
|
||||||
} else {
|
presenter.query.nullIfBlank()
|
||||||
Json.encodeToString(
|
|
||||||
JsonSavedSearch(
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
filterSerializer.serialize(presenter.sourceFilters)
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
onBrowseClick(
|
||||||
|
presenter.query.nullIfBlank(),
|
||||||
|
filters = Json.encodeToString(filterSerializer.serialize(presenter.sourceFilters))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onBrowseClick(presenter.query.nullIfBlank(), json)
|
|
||||||
},
|
},
|
||||||
onResetClicked = {},
|
onResetClicked = {},
|
||||||
onSaveClicked = {},
|
onSaveClicked = {},
|
||||||
onSavedSearchClicked = cb@{ indexToSearch ->
|
onSavedSearchClicked = cb@{ idOfSearch ->
|
||||||
val savedSearches = presenter.loadSearches()
|
val search = presenter.loadSearch(idOfSearch)
|
||||||
|
|
||||||
val search = savedSearches.getOrNull(indexToSearch)
|
|
||||||
|
|
||||||
if (search == null) {
|
if (search == null) {
|
||||||
filterSheet?.context?.let {
|
filterSheet?.context?.let {
|
||||||
@ -237,14 +232,10 @@ open class IndexController :
|
|||||||
filterSheet?.dismiss()
|
filterSheet?.dismiss()
|
||||||
|
|
||||||
if (!allDefault) {
|
if (!allDefault) {
|
||||||
val json = Json.encodeToString(
|
onBrowseClick(
|
||||||
JsonSavedSearch(
|
search = presenter.query.nullIfBlank(),
|
||||||
"",
|
savedSearch = search.id
|
||||||
"",
|
|
||||||
filterSerializer.serialize(presenter.sourceFilters)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
onBrowseClick(presenter.query.nullIfBlank(), json)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSavedSearchDeleteClicked = { _, _ -> }
|
onSavedSearchDeleteClicked = { _, _ -> }
|
||||||
@ -325,8 +316,8 @@ open class IndexController :
|
|||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBrowseClick(search: String? = null, filters: String? = null) {
|
fun onBrowseClick(search: String? = null, savedSearch: Long? = null, filters: String? = null) {
|
||||||
router.replaceTopController(BrowseSourceController(presenter.source, search, filterList = filters).withFadeTransaction())
|
router.replaceTopController(BrowseSourceController(presenter.source, search, savedSearch = savedSearch, filterList = filters).withFadeTransaction())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLatestClick() {
|
private fun onLatestClick() {
|
||||||
|
@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
|||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
import exh.log.xLogE
|
import exh.log.xLogE
|
||||||
import exh.savedsearches.EXHSavedSearch
|
import exh.savedsearches.EXHSavedSearch
|
||||||
import exh.savedsearches.JsonSavedSearch
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonArray
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
@ -212,30 +212,61 @@ open class IndexPresenter(
|
|||||||
|
|
||||||
private val filterSerializer = FilterSerializer()
|
private val filterSerializer = FilterSerializer()
|
||||||
|
|
||||||
fun loadSearches(): List<EXHSavedSearch> {
|
fun loadSearch(searchId: Long): EXHSavedSearch? {
|
||||||
return preferences.savedSearches().get().mapNotNull {
|
val search = db.getSavedSearch(searchId).executeAsBlocking() ?: return null
|
||||||
val id = it.substringBefore(':').toLongOrNull() ?: return@mapNotNull null
|
return EXHSavedSearch(
|
||||||
if (id != source.id) return@mapNotNull null
|
id = search.id!!,
|
||||||
val content = try {
|
name = search.name,
|
||||||
Json.decodeFromString<JsonSavedSearch>(it.substringAfter(':'))
|
query = search.query.orEmpty(),
|
||||||
} catch (e: Exception) {
|
filterList = runCatching {
|
||||||
return@mapNotNull null
|
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 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) {
|
||||||
|
null
|
||||||
|
} ?: return@map EXHSavedSearch(
|
||||||
|
id = it.id!!,
|
||||||
|
name = it.name,
|
||||||
|
query = it.query.orEmpty(),
|
||||||
|
filterList = null
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val originalFilters = source.getFilterList()
|
val originalFilters = source.getFilterList()
|
||||||
filterSerializer.deserialize(originalFilters, content.filters)
|
filterSerializer.deserialize(originalFilters, filters)
|
||||||
EXHSavedSearch(
|
EXHSavedSearch(
|
||||||
content.name,
|
id = it.id!!,
|
||||||
content.query,
|
name = it.name,
|
||||||
originalFilters
|
query = it.query.orEmpty(),
|
||||||
|
filterList = originalFilters
|
||||||
)
|
)
|
||||||
} catch (t: RuntimeException) {
|
} catch (t: RuntimeException) {
|
||||||
// Load failed
|
// Load failed
|
||||||
xLogE("Failed to load saved search!", t)
|
xLogE("Failed to load saved search!", t)
|
||||||
EXHSavedSearch(
|
EXHSavedSearch(
|
||||||
content.name,
|
id = it.id!!,
|
||||||
content.query,
|
name = it.name,
|
||||||
null
|
query = it.query.orEmpty(),
|
||||||
|
filterList = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import exh.eh.EHentaiUpdateWorker
|
|||||||
import exh.log.xLogE
|
import exh.log.xLogE
|
||||||
import exh.log.xLogW
|
import exh.log.xLogW
|
||||||
import exh.merged.sql.models.MergedMangaReference
|
import exh.merged.sql.models.MergedMangaReference
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.HBROWSE_SOURCE_ID
|
import exh.source.HBROWSE_SOURCE_ID
|
||||||
@ -47,11 +48,17 @@ import exh.source.MERGED_SOURCE_ID
|
|||||||
import exh.source.PERV_EDEN_EN_SOURCE_ID
|
import exh.source.PERV_EDEN_EN_SOURCE_ID
|
||||||
import exh.source.PERV_EDEN_IT_SOURCE_ID
|
import exh.source.PERV_EDEN_IT_SOURCE_ID
|
||||||
import exh.source.TSUMINO_SOURCE_ID
|
import exh.source.TSUMINO_SOURCE_ID
|
||||||
|
import exh.util.nullIfBlank
|
||||||
import exh.util.under
|
import exh.util.under
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
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 uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -395,6 +402,26 @@ object EXHMigrations {
|
|||||||
if (oldVersion under 30) {
|
if (oldVersion under 30) {
|
||||||
BackupCreatorJob.setupTask(context)
|
BackupCreatorJob.setupTask(context)
|
||||||
}
|
}
|
||||||
|
if (oldVersion under 31) {
|
||||||
|
val savedSearches = prefs.getStringSet("eh_saved_searches", emptySet())?.mapNotNull {
|
||||||
|
kotlin.runCatching {
|
||||||
|
val content = Json.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()
|
||||||
|
}?.ifEmpty { null }
|
||||||
|
if (savedSearches != null) {
|
||||||
|
db.insertSavedSearches(savedSearches)
|
||||||
|
}
|
||||||
|
prefs.edit(commit = true) {
|
||||||
|
remove("eh_saved_searches")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (oldVersion under 1) { } (1 is current release version)
|
// if (oldVersion under 1) { } (1 is current release version)
|
||||||
// do stuff here when releasing changed crap
|
// do stuff here when releasing changed crap
|
||||||
|
@ -7,18 +7,15 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
import eu.kanade.tachiyomi.source.online.all.NHentai
|
||||||
import exh.EXHMigrations
|
import exh.EXHMigrations
|
||||||
import exh.eh.EHentaiThrottleManager
|
import exh.eh.EHentaiThrottleManager
|
||||||
import exh.eh.EHentaiUpdateWorker
|
import exh.eh.EHentaiUpdateWorker
|
||||||
import exh.log.xLogE
|
|
||||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||||
import exh.savedsearches.JsonSavedSearch
|
|
||||||
import exh.source.EH_SOURCE_ID
|
import exh.source.EH_SOURCE_ID
|
||||||
import exh.source.EXH_SOURCE_ID
|
import exh.source.EXH_SOURCE_ID
|
||||||
import exh.source.isEhBasedManga
|
import exh.source.isEhBasedManga
|
||||||
@ -30,11 +27,7 @@ import kotlinx.coroutines.flow.asFlow
|
|||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.lang.RuntimeException
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@ -171,7 +164,7 @@ object DebugFunctions {
|
|||||||
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
|
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearSavedSearches() = prefs.savedSearches().set(emptySet())
|
fun clearSavedSearches() = db.deleteAllSavedSearches().executeAsBlocking()
|
||||||
|
|
||||||
fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") {
|
fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") {
|
||||||
"${it.id}: ${it.name} (${it.lang.uppercase()})"
|
"${it.id}: ${it.name} (${it.lang.uppercase()})"
|
||||||
@ -249,7 +242,7 @@ object DebugFunctions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyEHentaiSavedSearchesToExhentai() {
|
/*fun copyEHentaiSavedSearchesToExhentai() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val source = sourceManager.get(EH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
|
val source = sourceManager.get(EH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
|
||||||
val newSource = sourceManager.get(EXH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
|
val newSource = sourceManager.get(EXH_SOURCE_ID) as? CatalogueSource ?: return@runBlocking
|
||||||
@ -325,7 +318,7 @@ object DebugFunctions {
|
|||||||
}
|
}
|
||||||
prefs.savedSearches().set((otherSerialized + newSerialized).toSet())
|
prefs.savedSearches().set((otherSerialized + newSerialized).toSet())
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
fun fixReaderViewerBackupBug() {
|
fun fixReaderViewerBackupBug() {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
|
@ -3,6 +3,7 @@ package exh.savedsearches
|
|||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
data class EXHSavedSearch(
|
data class EXHSavedSearch(
|
||||||
|
val id: Long,
|
||||||
val name: String,
|
val name: String,
|
||||||
val query: String,
|
val query: String,
|
||||||
val filterList: FilterList?
|
val filterList: FilterList?
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package exh.savedsearches
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.json.JsonArray
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class JsonSavedSearch(
|
|
||||||
val name: String,
|
|
||||||
val query: String,
|
|
||||||
val filters: JsonArray
|
|
||||||
)
|
|
@ -0,0 +1,66 @@
|
|||||||
|
package exh.savedsearches.mappers
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import androidx.core.content.contentValuesOf
|
||||||
|
import androidx.core.database.getStringOrNull
|
||||||
|
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.COL_FILTERS_JSON
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.COL_ID
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.COL_NAME
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.COL_QUERY
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.COL_SOURCE
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable.TABLE
|
||||||
|
|
||||||
|
class SavedSearchTypeMapping : SQLiteTypeMapping<SavedSearch>(
|
||||||
|
SavedSearchPutResolver(),
|
||||||
|
SavedSearchGetResolver(),
|
||||||
|
SavedSearchDeleteResolver()
|
||||||
|
)
|
||||||
|
|
||||||
|
class SavedSearchPutResolver : DefaultPutResolver<SavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapToInsertQuery(obj: SavedSearch) = InsertQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun mapToUpdateQuery(obj: SavedSearch) = UpdateQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.where("$COL_ID = ?")
|
||||||
|
.whereArgs(obj.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun mapToContentValues(obj: SavedSearch) = contentValuesOf(
|
||||||
|
COL_ID to obj.id,
|
||||||
|
COL_SOURCE to obj.source,
|
||||||
|
COL_NAME to obj.name,
|
||||||
|
COL_QUERY to obj.query,
|
||||||
|
COL_FILTERS_JSON to obj.filtersJson
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SavedSearchGetResolver : DefaultGetResolver<SavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapFromCursor(cursor: Cursor): SavedSearch = SavedSearch(
|
||||||
|
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
|
||||||
|
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE)),
|
||||||
|
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME)),
|
||||||
|
query = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_QUERY)),
|
||||||
|
filtersJson = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(COL_FILTERS_JSON))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SavedSearchDeleteResolver : DefaultDeleteResolver<SavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapToDeleteQuery(obj: SavedSearch) = DeleteQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.where("$COL_ID = ?")
|
||||||
|
.whereArgs(obj.id)
|
||||||
|
.build()
|
||||||
|
}
|
18
app/src/main/java/exh/savedsearches/models/SavedSearch.kt
Normal file
18
app/src/main/java/exh/savedsearches/models/SavedSearch.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package exh.savedsearches.models
|
||||||
|
|
||||||
|
data class SavedSearch(
|
||||||
|
// Tag identifier, unique
|
||||||
|
var id: Long?,
|
||||||
|
|
||||||
|
// The source the saved search is for
|
||||||
|
var source: Long,
|
||||||
|
|
||||||
|
// If false the manga will not grab chapter updates
|
||||||
|
var name: String,
|
||||||
|
|
||||||
|
// The query if there is any
|
||||||
|
var query: String?,
|
||||||
|
|
||||||
|
// The filter list
|
||||||
|
var filtersJson: String?,
|
||||||
|
)
|
@ -0,0 +1,82 @@
|
|||||||
|
package exh.savedsearches.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable
|
||||||
|
|
||||||
|
interface SavedSearchQueries : DbProvider {
|
||||||
|
fun getSavedSearches(source: Long) = db.get()
|
||||||
|
.listOfObjects(SavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.where("${SavedSearchTable.COL_SOURCE} = ?")
|
||||||
|
.whereArgs(source)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun deleteSavedSearches(source: Long) = db.delete()
|
||||||
|
.byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.where("${SavedSearchTable.COL_SOURCE} = ?")
|
||||||
|
.whereArgs(source)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getSavedSearches() = db.get()
|
||||||
|
.listOfObjects(SavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.orderBy(SavedSearchTable.COL_ID)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getSavedSearch(id: Long) = db.get()
|
||||||
|
.`object`(SavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.where("${SavedSearchTable.COL_ID} = ?")
|
||||||
|
.whereArgs(id)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertSavedSearch(savedSearch: SavedSearch) = db.put().`object`(savedSearch).prepare()
|
||||||
|
|
||||||
|
fun insertSavedSearches(savedSearches: List<SavedSearch>) = db.put().objects(savedSearches).prepare()
|
||||||
|
|
||||||
|
fun deleteSavedSearch(savedSearch: SavedSearch) = db.delete().`object`(savedSearch).prepare()
|
||||||
|
|
||||||
|
fun deleteSavedSearch(id: Long) = db.delete()
|
||||||
|
.byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.where("${SavedSearchTable.COL_ID} = ?")
|
||||||
|
.whereArgs(id)
|
||||||
|
.build()
|
||||||
|
).prepare()
|
||||||
|
|
||||||
|
fun deleteAllSavedSearches() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
/*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List<SavedSearch>) {
|
||||||
|
db.inTransaction {
|
||||||
|
deleteSavedSearches(mergedMangaId).executeAsBlocking()
|
||||||
|
mergedMangases.chunked(100) { chunk ->
|
||||||
|
insertSavedSearches(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package exh.savedsearches.tables
|
||||||
|
|
||||||
|
object SavedSearchTable {
|
||||||
|
|
||||||
|
const val TABLE = "saved_search"
|
||||||
|
|
||||||
|
const val COL_ID = "_id"
|
||||||
|
|
||||||
|
const val COL_SOURCE = "source"
|
||||||
|
|
||||||
|
const val COL_NAME = "name"
|
||||||
|
|
||||||
|
const val COL_QUERY = "query"
|
||||||
|
|
||||||
|
const val COL_FILTERS_JSON = "filters_json"
|
||||||
|
|
||||||
|
val createTableQuery: String
|
||||||
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
$COL_SOURCE INTEGER NOT NULL,
|
||||||
|
$COL_NAME TEXT NOT NULL,
|
||||||
|
$COL_QUERY TEXT,
|
||||||
|
$COL_FILTERS_JSON TEXT
|
||||||
|
)"""
|
||||||
|
}
|
@ -355,6 +355,7 @@
|
|||||||
<string name="save_search_delete">Delete saved search query?</string>
|
<string name="save_search_delete">Delete saved search query?</string>
|
||||||
<string name="save_search_delete_message">Are you sure you wish to delete your saved search query: \'%1$s\'?</string>
|
<string name="save_search_delete_message">Are you sure you wish to delete your saved search query: \'%1$s\'?</string>
|
||||||
<string name="save_search_invalid">Saved search invalid, filters have changed</string>
|
<string name="save_search_invalid">Saved search invalid, filters have changed</string>
|
||||||
|
<string name="save_search_invalid_name">Invalid saved search name</string>
|
||||||
|
|
||||||
<!-- Source Categories -->
|
<!-- Source Categories -->
|
||||||
<string name="no_source_categories">No source categories available</string>
|
<string name="no_source_categories">No source categories available</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user