Replace Latest tab with Feed
This commit is contained in:
parent
5d330c4f75
commit
6a41d96ddf
@ -36,8 +36,11 @@ 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.FeedSavedSearchTypeMapping
|
||||||
import exh.savedsearches.mappers.SavedSearchTypeMapping
|
import exh.savedsearches.mappers.SavedSearchTypeMapping
|
||||||
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
import exh.savedsearches.models.SavedSearch
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import exh.savedsearches.queries.FeedSavedSearchQueries
|
||||||
import exh.savedsearches.queries.SavedSearchQueries
|
import exh.savedsearches.queries.SavedSearchQueries
|
||||||
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
|
||||||
|
|
||||||
@ -45,7 +48,21 @@ 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, SavedSearchQueries /* SY <-- */ {
|
MangaQueries,
|
||||||
|
ChapterQueries,
|
||||||
|
TrackQueries,
|
||||||
|
CategoryQueries,
|
||||||
|
MangaCategoryQueries,
|
||||||
|
HistoryQueries
|
||||||
|
/* SY --> */,
|
||||||
|
SearchMetadataQueries,
|
||||||
|
SearchTagQueries,
|
||||||
|
SearchTitleQueries,
|
||||||
|
MergedQueries,
|
||||||
|
FavoriteEntryQueries,
|
||||||
|
SavedSearchQueries,
|
||||||
|
FeedSavedSearchQueries
|
||||||
|
/* SY <-- */ {
|
||||||
|
|
||||||
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
private val configuration = SupportSQLiteOpenHelper.Configuration.builder(context)
|
||||||
.name(DbOpenCallback.DATABASE_NAME)
|
.name(DbOpenCallback.DATABASE_NAME)
|
||||||
@ -67,6 +84,7 @@ open class DatabaseHelper(context: Context) :
|
|||||||
.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())
|
.addTypeMapping(SavedSearch::class.java, SavedSearchTypeMapping())
|
||||||
|
.addTypeMapping(FeedSavedSearch::class.java, FeedSavedSearchTypeMapping())
|
||||||
// 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.FeedSavedSearchTable
|
||||||
import exh.savedsearches.tables.SavedSearchTable
|
import exh.savedsearches.tables.SavedSearchTable
|
||||||
|
|
||||||
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
||||||
@ -43,6 +44,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(MergedTable.createTableQuery)
|
execSQL(MergedTable.createTableQuery)
|
||||||
execSQL(FavoriteEntryTable.createTableQuery)
|
execSQL(FavoriteEntryTable.createTableQuery)
|
||||||
execSQL(SavedSearchTable.createTableQuery)
|
execSQL(SavedSearchTable.createTableQuery)
|
||||||
|
execSQL(FeedSavedSearchTable.createTableQuery)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
// DB indexes
|
// DB indexes
|
||||||
@ -59,6 +61,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
execSQL(SearchTitleTable.createMangaIdIndexQuery)
|
||||||
execSQL(SearchTitleTable.createTitleIndexQuery)
|
execSQL(SearchTitleTable.createTitleIndexQuery)
|
||||||
execSQL(MergedTable.createIndexQuery)
|
execSQL(MergedTable.createIndexQuery)
|
||||||
|
execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery)
|
||||||
// SY <--
|
// SY <--
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,6 +108,8 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) {
|
|||||||
}
|
}
|
||||||
if (oldVersion < 13) {
|
if (oldVersion < 13) {
|
||||||
db.execSQL(SavedSearchTable.createTableQuery)
|
db.execSQL(SavedSearchTable.createTableQuery)
|
||||||
|
db.execSQL(FeedSavedSearchTable.createTableQuery)
|
||||||
|
db.execSQL(FeedSavedSearchTable.createSavedSearchIdIndexQuery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package eu.kanade.tachiyomi.data.database.queries
|
package eu.kanade.tachiyomi.data.database.queries
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.SourceIdMangaCountGetResolver
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable
|
||||||
|
import exh.savedsearches.tables.SavedSearchTable
|
||||||
import exh.source.MERGED_SOURCE_ID
|
import exh.source.MERGED_SOURCE_ID
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
|
||||||
@ -74,6 +76,19 @@ fun getReadMangaNotInLibraryQuery() =
|
|||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query to get the manga merged into a merged manga
|
||||||
|
*/
|
||||||
|
fun getFeedSavedSearchQuery() =
|
||||||
|
"""
|
||||||
|
SELECT ${SavedSearchTable.TABLE}.*
|
||||||
|
FROM (
|
||||||
|
SELECT ${FeedSavedSearchTable.COL_SAVED_SEARCH_ID} FROM ${FeedSavedSearchTable.TABLE}
|
||||||
|
) AS M
|
||||||
|
JOIN ${SavedSearchTable.TABLE}
|
||||||
|
ON ${SavedSearchTable.TABLE}.${SavedSearchTable.COL_ID} = M.${FeedSavedSearchTable.COL_SAVED_SEARCH_ID}
|
||||||
|
"""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query to get the manga from the library, with their categories, read and unread count.
|
* Query to get the manga from the library, with their categories, read and unread count.
|
||||||
*/
|
*/
|
||||||
|
@ -439,9 +439,7 @@ class PreferencesHelper(val context: Context) {
|
|||||||
"false,false,false,false,false,false,false,false,false,false"
|
"false,false,false,false,false,false,false,false,false,false"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun latestTabSources() = flowPrefs.getStringSet("latest_tab_sources", mutableSetOf())
|
fun feedTabInFront() = flowPrefs.getBoolean("latest_tab_position", false)
|
||||||
|
|
||||||
fun latestTabInFront() = flowPrefs.getBoolean("latest_tab_position", false)
|
|
||||||
|
|
||||||
fun sourcesTabCategories() = flowPrefs.getStringSet("sources_tab_categories", mutableSetOf())
|
fun sourcesTabCategories() = flowPrefs.getStringSet("sources_tab_categories", mutableSetOf())
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|||||||
import eu.kanade.tachiyomi.ui.base.controller.RxController
|
import eu.kanade.tachiyomi.ui.base.controller.RxController
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
|
||||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionController
|
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionController
|
||||||
import eu.kanade.tachiyomi.ui.browse.latest.LatestController
|
import eu.kanade.tachiyomi.ui.browse.feed.FeedController
|
||||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
|
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
import eu.kanade.tachiyomi.ui.browse.source.SourceController
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
@ -116,9 +116,9 @@ class BrowseController :
|
|||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private val tabTitles = (
|
private val tabTitles = (
|
||||||
if (preferences.latestTabInFront().get()) {
|
if (preferences.feedTabInFront().get()) {
|
||||||
listOf(
|
listOf(
|
||||||
R.string.latest,
|
R.string.feed,
|
||||||
R.string.label_sources,
|
R.string.label_sources,
|
||||||
R.string.label_extensions,
|
R.string.label_extensions,
|
||||||
R.string.label_migration
|
R.string.label_migration
|
||||||
@ -127,7 +127,7 @@ class BrowseController :
|
|||||||
} else {
|
} else {
|
||||||
listOf(
|
listOf(
|
||||||
R.string.label_sources,
|
R.string.label_sources,
|
||||||
R.string.latest,
|
R.string.feed,
|
||||||
R.string.label_extensions,
|
R.string.label_extensions,
|
||||||
R.string.label_migration
|
R.string.label_migration
|
||||||
)
|
)
|
||||||
@ -144,8 +144,8 @@ class BrowseController :
|
|||||||
if (!router.hasRootController()) {
|
if (!router.hasRootController()) {
|
||||||
val controller: Controller = when (position) {
|
val controller: Controller = when (position) {
|
||||||
// SY -->
|
// SY -->
|
||||||
SOURCES_CONTROLLER -> if (preferences.latestTabInFront().get()) LatestController() else SourceController()
|
SOURCES_CONTROLLER -> if (preferences.feedTabInFront().get()) FeedController() else SourceController()
|
||||||
LATEST_CONTROLLER -> if (!preferences.latestTabInFront().get()) LatestController() else SourceController()
|
FEED_CONTROLLER -> if (!preferences.feedTabInFront().get()) FeedController() else SourceController()
|
||||||
// SY <--
|
// SY <--
|
||||||
EXTENSIONS_CONTROLLER -> ExtensionController()
|
EXTENSIONS_CONTROLLER -> ExtensionController()
|
||||||
MIGRATION_CONTROLLER -> MigrationSourcesController()
|
MIGRATION_CONTROLLER -> MigrationSourcesController()
|
||||||
@ -166,7 +166,7 @@ class BrowseController :
|
|||||||
const val SOURCES_CONTROLLER = 0
|
const val SOURCES_CONTROLLER = 0
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
const val LATEST_CONTROLLER = 1
|
const val FEED_CONTROLLER = 1
|
||||||
const val EXTENSIONS_CONTROLLER = 2
|
const val EXTENSIONS_CONTROLLER = 2
|
||||||
const val MIGRATION_CONTROLLER = 3
|
const val MIGRATION_CONTROLLER = 3
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
@ -6,16 +6,18 @@ import android.util.SparseArray
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter that holds the search cards.
|
* Adapter that holds the search cards.
|
||||||
*
|
*
|
||||||
* @param controller instance of [LatestController].
|
* @param controller instance of [FeedController].
|
||||||
*/
|
*/
|
||||||
class LatestAdapter(val controller: LatestController) :
|
class FeedAdapter(val controller: FeedController) :
|
||||||
FlexibleAdapter<LatestItem>(null, controller, true) {
|
FlexibleAdapter<FeedItem>(null, controller, true) {
|
||||||
|
|
||||||
val titleClickListener: OnTitleClickListener = controller
|
val feedClickListener: OnFeedClickListener = controller
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bundle where the view state of the holders is saved.
|
* Bundle where the view state of the holders is saved.
|
||||||
@ -71,8 +73,10 @@ class LatestAdapter(val controller: LatestController) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnTitleClickListener {
|
interface OnFeedClickListener {
|
||||||
fun onTitleClick(source: CatalogueSource)
|
fun onSourceClick(source: CatalogueSource)
|
||||||
|
fun onSavedSearchClick(savedSearch: SavedSearch, source: CatalogueSource)
|
||||||
|
fun onRemoveClick(feedSavedSearch: FeedSavedSearch)
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
@ -6,10 +6,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
/**
|
/**
|
||||||
* Adapter that holds the manga items from search results.
|
* Adapter that holds the manga items from search results.
|
||||||
*
|
*
|
||||||
* @param controller instance of [LatestController].
|
* @param controller instance of [FeedController].
|
||||||
*/
|
*/
|
||||||
class LatestCardAdapter(controller: LatestController) :
|
class FeedCardAdapter(controller: FeedController) :
|
||||||
FlexibleAdapter<LatestCardItem>(null, controller, true) {
|
FlexibleAdapter<FeedCardItem>(null, controller, true) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for browse item clicks.
|
* Listen for browse item clicks.
|
||||||
@ -18,7 +18,7 @@ class LatestCardAdapter(controller: LatestController) :
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener which should be called when user clicks browse.
|
* Listener which should be called when user clicks browse.
|
||||||
* Note: Should only be handled by [LatestController]
|
* Note: Should only be handled by [FeedController]
|
||||||
*/
|
*/
|
||||||
interface OnMangaClickListener {
|
interface OnMangaClickListener {
|
||||||
fun onMangaClick(manga: Manga)
|
fun onMangaClick(manga: Manga)
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
|||||||
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
|
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
|
||||||
import eu.kanade.tachiyomi.util.view.loadAutoPause
|
import eu.kanade.tachiyomi.util.view.loadAutoPause
|
||||||
|
|
||||||
class LatestCardHolder(view: View, adapter: LatestCardAdapter) :
|
class FeedCardHolder(view: View, adapter: FeedCardAdapter) :
|
||||||
FlexibleViewHolder(view, adapter) {
|
FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
private val binding = GlobalSearchControllerCardItemBinding.bind(view)
|
private val binding = GlobalSearchControllerCardItemBinding.bind(view)
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -7,21 +7,20 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class LatestCardItem(val manga: Manga) : AbstractFlexibleItem<LatestCardHolder>() {
|
class FeedCardItem(val manga: Manga) : AbstractFlexibleItem<FeedCardHolder>() {
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
override fun getLayoutRes(): Int {
|
||||||
return R.layout.global_search_controller_card_item
|
return R.layout.global_search_controller_card_item
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LatestCardHolder {
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): FeedCardHolder {
|
||||||
return LatestCardHolder(view, adapter as LatestCardAdapter)
|
return FeedCardHolder(view, adapter as FeedCardAdapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun bindViewHolder(
|
override fun bindViewHolder(
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||||
holder: LatestCardHolder,
|
holder: FeedCardHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
payloads: List<Any?>?
|
payloads: List<Any?>?
|
||||||
) {
|
) {
|
||||||
@ -29,7 +28,7 @@ class LatestCardItem(val manga: Manga) : AbstractFlexibleItem<LatestCardHolder>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other is LatestCardItem) {
|
if (other is FeedCardItem) {
|
||||||
return manga.id == other.manga.id
|
return manga.id == other.manga.id
|
||||||
}
|
}
|
||||||
return false
|
return false
|
@ -0,0 +1,230 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import dev.chrisbanes.insetter.applyInsetter
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.databinding.LatestControllerBinding
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
||||||
|
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.models.FeedSavedSearch
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller shows and manages the different search result in global search.
|
||||||
|
* This controller should only handle UI actions, IO actions should be done by [FeedPresenter]
|
||||||
|
* [FeedCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
||||||
|
*/
|
||||||
|
open class FeedController :
|
||||||
|
NucleusController<LatestControllerBinding, FeedPresenter>(),
|
||||||
|
FeedCardAdapter.OnMangaClickListener,
|
||||||
|
FeedAdapter.OnFeedClickListener {
|
||||||
|
|
||||||
|
init {
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter containing search results grouped by lang.
|
||||||
|
*/
|
||||||
|
protected var adapter: FeedAdapter? = null
|
||||||
|
|
||||||
|
override fun getTitle(): String? {
|
||||||
|
return applicationContext?.getString(R.string.feed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the [FeedPresenter] used in controller.
|
||||||
|
*
|
||||||
|
* @return instance of [FeedPresenter]
|
||||||
|
*/
|
||||||
|
override fun createPresenter(): FeedPresenter {
|
||||||
|
return FeedPresenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.feed, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_add_feed -> addFeed()
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFeed() {
|
||||||
|
if (presenter.hasTooManyFeeds()) {
|
||||||
|
activity?.toast(R.string.too_many_in_feed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val items = presenter.getEnabledSources()
|
||||||
|
val itemsStrings = items.map { it.toString() }
|
||||||
|
var selectedIndex = 0
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity!!)
|
||||||
|
.setTitle(R.string.feed)
|
||||||
|
.setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which ->
|
||||||
|
selectedIndex = which
|
||||||
|
}
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
addFeedSearch(items[selectedIndex])
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFeedSearch(source: CatalogueSource) {
|
||||||
|
val items = presenter.getSourceSavedSearches(source)
|
||||||
|
val itemsStrings = listOf(activity!!.getString(R.string.latest)) + items.map { it.name }
|
||||||
|
var selectedIndex = 0
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity!!)
|
||||||
|
.setTitle(R.string.feed)
|
||||||
|
.setSingleChoiceItems(itemsStrings.toTypedArray(), selectedIndex) { _, which ->
|
||||||
|
selectedIndex = which
|
||||||
|
}
|
||||||
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
presenter.createFeed(source, items.getOrNull(selectedIndex - 1))
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when manga in global search is clicked, opens manga.
|
||||||
|
*
|
||||||
|
* @param manga clicked item containing manga information.
|
||||||
|
*/
|
||||||
|
override fun onMangaClick(manga: Manga) {
|
||||||
|
// Open MangaController.
|
||||||
|
parentController?.router?.pushController(MangaController(manga, true).withFadeTransaction())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when manga in global search is long clicked.
|
||||||
|
*
|
||||||
|
* @param manga clicked item containing manga information.
|
||||||
|
*/
|
||||||
|
override fun onMangaLongClick(manga: Manga) {
|
||||||
|
// Delegate to single click by default.
|
||||||
|
onMangaClick(manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the view is created
|
||||||
|
*
|
||||||
|
* @param view view of controller
|
||||||
|
*/
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
binding.recycler.applyInsetter {
|
||||||
|
type(navigationBars = true) {
|
||||||
|
padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = FeedAdapter(this)
|
||||||
|
|
||||||
|
// Create recycler and set adapter.
|
||||||
|
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
binding.recycler.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView(view: View) {
|
||||||
|
adapter = null
|
||||||
|
super.onDestroyView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveViewState(view: View, outState: Bundle) {
|
||||||
|
super.onSaveViewState(view, outState)
|
||||||
|
adapter?.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
||||||
|
super.onRestoreViewState(view, savedViewState)
|
||||||
|
adapter?.onRestoreInstanceState(savedViewState)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the view holder for the given manga.
|
||||||
|
*
|
||||||
|
* @param source used to find holder containing source
|
||||||
|
* @return the holder of the manga or null if it's not bound.
|
||||||
|
*/
|
||||||
|
private fun getHolder(source: CatalogueSource): FeedHolder? {
|
||||||
|
val adapter = adapter ?: return null
|
||||||
|
|
||||||
|
adapter.allBoundViewHolders.forEach { holder ->
|
||||||
|
val item = adapter.getItem(holder.bindingAdapterPosition)
|
||||||
|
if (item != null && source.id == item.feed.id) {
|
||||||
|
return holder as FeedHolder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add search result to adapter.
|
||||||
|
*
|
||||||
|
* @param feedManga the source items containing the latest manga.
|
||||||
|
*/
|
||||||
|
fun setItems(feedManga: List<FeedItem>) {
|
||||||
|
adapter?.updateDataSet(feedManga)
|
||||||
|
|
||||||
|
if (feedManga.isEmpty()) {
|
||||||
|
binding.emptyView.show(R.string.feed_tab_empty)
|
||||||
|
} else {
|
||||||
|
binding.emptyView.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from the presenter when a manga is initialized.
|
||||||
|
*
|
||||||
|
* @param manga the initialized manga.
|
||||||
|
*/
|
||||||
|
fun onMangaInitialized(source: CatalogueSource, manga: Manga) {
|
||||||
|
getHolder(source)?.setImage(manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a catalogue with the given search.
|
||||||
|
*/
|
||||||
|
override fun onSourceClick(source: CatalogueSource) {
|
||||||
|
presenter.preferences.lastUsedSource().set(source.id)
|
||||||
|
parentController?.router?.pushController(LatestUpdatesController(source).withFadeTransaction())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSavedSearchClick(savedSearch: SavedSearch, source: CatalogueSource) {
|
||||||
|
presenter.preferences.lastUsedSource().set(savedSearch.source)
|
||||||
|
parentController?.router?.pushController(BrowseSourceController(source, savedSearch = savedSearch.id).withFadeTransaction())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRemoveClick(feedSavedSearch: FeedSavedSearch) {
|
||||||
|
MaterialAlertDialogBuilder(activity!!)
|
||||||
|
.setTitle(R.string.feed)
|
||||||
|
.setMessage(R.string.feed_delete)
|
||||||
|
.setPositiveButton(R.string.action_delete) { _, _ ->
|
||||||
|
presenter.deleteFeed(feedSavedSearch)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -9,12 +10,12 @@ import eu.kanade.tachiyomi.databinding.LatestControllerCardBinding
|
|||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holder that binds the [LatestItem] containing catalogue cards.
|
* Holder that binds the [FeedItem] containing catalogue cards.
|
||||||
*
|
*
|
||||||
* @param view view of [LatestItem]
|
* @param view view of [FeedItem]
|
||||||
* @param adapter instance of [LatestAdapter]
|
* @param adapter instance of [FeedAdapter]
|
||||||
*/
|
*/
|
||||||
class LatestHolder(view: View, val adapter: LatestAdapter) :
|
class FeedHolder(view: View, val adapter: FeedAdapter) :
|
||||||
FlexibleViewHolder(view, adapter) {
|
FlexibleViewHolder(view, adapter) {
|
||||||
|
|
||||||
private val binding = LatestControllerCardBinding.bind(view)
|
private val binding = LatestControllerCardBinding.bind(view)
|
||||||
@ -22,9 +23,9 @@ class LatestHolder(view: View, val adapter: LatestAdapter) :
|
|||||||
/**
|
/**
|
||||||
* Adapter containing manga from search results.
|
* Adapter containing manga from search results.
|
||||||
*/
|
*/
|
||||||
private val mangaAdapter = LatestCardAdapter(adapter.controller)
|
private val mangaAdapter = FeedCardAdapter(adapter.controller)
|
||||||
|
|
||||||
private var lastBoundResults: List<LatestCardItem>? = null
|
private var lastBoundResults: List<FeedCardItem>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Set layout horizontal.
|
// Set layout horizontal.
|
||||||
@ -33,25 +34,43 @@ class LatestHolder(view: View, val adapter: LatestAdapter) :
|
|||||||
|
|
||||||
binding.titleWrapper.setOnClickListener {
|
binding.titleWrapper.setOnClickListener {
|
||||||
adapter.getItem(bindingAdapterPosition)?.let {
|
adapter.getItem(bindingAdapterPosition)?.let {
|
||||||
adapter.titleClickListener.onTitleClick(it.source)
|
if (it.savedSearch != null) {
|
||||||
|
adapter.feedClickListener.onSavedSearchClick(it.savedSearch, it.source ?: return@let)
|
||||||
|
} else {
|
||||||
|
adapter.feedClickListener.onSourceClick(it.source ?: return@let)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
binding.titleWrapper.setOnLongClickListener {
|
||||||
|
adapter.getItem(bindingAdapterPosition)?.let {
|
||||||
|
adapter.feedClickListener.onRemoveClick(it.feed)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the loading of source search result.
|
* Show the loading of source search result.
|
||||||
*
|
*
|
||||||
* @param item item of card.
|
* @param item item of card.
|
||||||
*/
|
*/
|
||||||
fun bind(item: LatestItem) {
|
@SuppressLint("SetTextI18n")
|
||||||
val source = item.source
|
fun bind(item: FeedItem) {
|
||||||
val results = item.results
|
val results = item.results
|
||||||
|
|
||||||
val titlePrefix = if (item.highlighted) "▶ " else ""
|
val titlePrefix = if (item.highlighted) "▶ " else ""
|
||||||
|
|
||||||
binding.title.text = titlePrefix + source.name
|
binding.title.text = titlePrefix + if (item.savedSearch != null) {
|
||||||
|
item.savedSearch.name
|
||||||
|
} else {
|
||||||
|
item.source?.name ?: item.feed.source.toString()
|
||||||
|
}
|
||||||
binding.subtitle.isVisible = true
|
binding.subtitle.isVisible = true
|
||||||
binding.subtitle.text = LocaleHelper.getDisplayName(source.lang)
|
binding.subtitle.text = if (item.savedSearch != null) {
|
||||||
|
item.source?.name ?: item.feed.source.toString()
|
||||||
|
} else {
|
||||||
|
LocaleHelper.getDisplayName(item.source?.lang)
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
results == null -> {
|
results == null -> {
|
||||||
@ -88,11 +107,11 @@ class LatestHolder(view: View, val adapter: LatestAdapter) :
|
|||||||
* @param manga the manga to find.
|
* @param manga the manga to find.
|
||||||
* @return the holder of the manga or null if it's not bound.
|
* @return the holder of the manga or null if it's not bound.
|
||||||
*/
|
*/
|
||||||
private fun getHolder(manga: Manga): LatestCardHolder? {
|
private fun getHolder(manga: Manga): FeedCardHolder? {
|
||||||
mangaAdapter.allBoundViewHolders.forEach { holder ->
|
mangaAdapter.allBoundViewHolders.forEach { holder ->
|
||||||
val item = mangaAdapter.getItem(holder.bindingAdapterPosition)
|
val item = mangaAdapter.getItem(holder.bindingAdapterPosition)
|
||||||
if (item != null && item.manga.id!! == manga.id!!) {
|
if (item != null && item.manga.id!! == manga.id!!) {
|
||||||
return holder as LatestCardHolder
|
return holder as FeedCardHolder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@ -8,16 +8,23 @@ import eu.davidea.flexibleadapter.items.IFlexible
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
|
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
|
||||||
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Item that contains search result information.
|
* Item that contains search result information.
|
||||||
*
|
*
|
||||||
* @param source the source for the search results.
|
* @param feed the source for the search results.
|
||||||
* @param results the search results.
|
* @param results the search results.
|
||||||
* @param highlighted whether this search item should be highlighted/marked in the catalogue search view.
|
* @param highlighted whether this search item should be highlighted/marked in the catalogue search view.
|
||||||
*/
|
*/
|
||||||
class LatestItem(val source: CatalogueSource, val results: List<LatestCardItem>?, val highlighted: Boolean = false) :
|
class FeedItem(
|
||||||
AbstractFlexibleItem<LatestHolder>() {
|
val feed: FeedSavedSearch,
|
||||||
|
val savedSearch: SavedSearch?,
|
||||||
|
val source: CatalogueSource?,
|
||||||
|
val results: List<FeedCardItem>?,
|
||||||
|
val highlighted: Boolean = false
|
||||||
|
) : AbstractFlexibleItem<FeedHolder>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set view.
|
* Set view.
|
||||||
@ -29,12 +36,12 @@ class LatestItem(val source: CatalogueSource, val results: List<LatestCardItem>?
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create view holder (see [LatestAdapter].
|
* Create view holder (see [FeedAdapter].
|
||||||
*
|
*
|
||||||
* @return holder of view.
|
* @return holder of view.
|
||||||
*/
|
*/
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): LatestHolder {
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): FeedHolder {
|
||||||
return LatestHolder(view, adapter as LatestAdapter)
|
return FeedHolder(view, adapter as FeedAdapter)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +49,7 @@ class LatestItem(val source: CatalogueSource, val results: List<LatestCardItem>?
|
|||||||
*/
|
*/
|
||||||
override fun bindViewHolder(
|
override fun bindViewHolder(
|
||||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||||
holder: LatestHolder,
|
holder: FeedHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
payloads: List<Any?>?
|
payloads: List<Any?>?
|
||||||
) {
|
) {
|
||||||
@ -56,7 +63,7 @@ class LatestItem(val source: CatalogueSource, val results: List<LatestCardItem>?
|
|||||||
*/
|
*/
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other is GlobalSearchItem) {
|
if (other is GlobalSearchItem) {
|
||||||
return source.id == other.source.id
|
return feed.id == other.source.id
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -67,6 +74,6 @@ class LatestItem(val source: CatalogueSource, val results: List<LatestCardItem>?
|
|||||||
* @return hashcode
|
* @return hashcode
|
||||||
*/
|
*/
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return source.id.toInt()
|
return feed.id!!.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
package eu.kanade.tachiyomi.ui.browse.feed
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||||
@ -7,12 +8,18 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.toSManga
|
import eu.kanade.tachiyomi.source.model.toSManga
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
import eu.kanade.tachiyomi.util.system.logcat
|
||||||
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
@ -21,20 +28,21 @@ import rx.schedulers.Schedulers
|
|||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import xyz.nulldev.ts.api.http.serializer.FilterSerializer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presenter of [LatestController]
|
* Presenter of [FeedController]
|
||||||
* Function calls should be done from here. UI calls should be done from the controller.
|
* Function calls should be done from here. UI calls should be done from the controller.
|
||||||
*
|
*
|
||||||
* @param sourceManager manages the different sources.
|
* @param sourceManager manages the different sources.
|
||||||
* @param db manages the database calls.
|
* @param db manages the database calls.
|
||||||
* @param preferences manages the preference calls.
|
* @param preferences manages the preference calls.
|
||||||
*/
|
*/
|
||||||
open class LatestPresenter(
|
open class FeedPresenter(
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
val preferences: PreferencesHelper = Injekt.get()
|
val preferences: PreferencesHelper = Injekt.get()
|
||||||
) : BasePresenter<LatestController>() {
|
) : BasePresenter<FeedController>() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the different sources by user settings.
|
* Fetches the different sources by user settings.
|
||||||
@ -51,71 +59,128 @@ open class LatestPresenter(
|
|||||||
*/
|
*/
|
||||||
private var fetchImageSubscription: Subscription? = null
|
private var fetchImageSubscription: Subscription? = null
|
||||||
|
|
||||||
|
override fun onCreate(savedState: Bundle?) {
|
||||||
|
super.onCreate(savedState)
|
||||||
|
|
||||||
|
db.getFeedSavedSearches()
|
||||||
|
.asRxObservable()
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnEach {
|
||||||
|
getFeed()
|
||||||
|
}
|
||||||
|
.subscribe()
|
||||||
|
.let(::add)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
fetchSourcesSubscription?.unsubscribe()
|
fetchSourcesSubscription?.unsubscribe()
|
||||||
fetchImageSubscription?.unsubscribe()
|
fetchImageSubscription?.unsubscribe()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun hasTooManyFeeds(): Boolean {
|
||||||
* Returns a list of enabled sources ordered by language and name, with pinned catalogues
|
return db.getFeedSavedSearches().executeAsBlocking().size > 10
|
||||||
* prioritized.
|
}
|
||||||
*
|
|
||||||
* @return list containing enabled sources.
|
fun getEnabledSources(): List<CatalogueSource> {
|
||||||
*/
|
|
||||||
protected open fun getEnabledSources(): List<CatalogueSource> {
|
|
||||||
val languages = preferences.enabledLanguages().get()
|
val languages = preferences.enabledLanguages().get()
|
||||||
val watchedSources = preferences.latestTabSources().get()
|
|
||||||
val pinnedSources = preferences.pinnedSources().get()
|
val pinnedSources = preferences.pinnedSources().get()
|
||||||
|
|
||||||
val list = sourceManager.getVisibleCatalogueSources()
|
val list = sourceManager.getVisibleCatalogueSources()
|
||||||
.filter { it.lang in languages }
|
.filter { it.lang in languages }
|
||||||
.sortedBy { "(${it.lang}) ${it.name}" }
|
.sortedBy { "(${it.lang}) ${it.name}" }
|
||||||
|
|
||||||
return list.filter { it.id.toString() in watchedSources }
|
return list.sortedBy { it.id.toString() !in pinnedSources }
|
||||||
.sortedBy { it.id.toString() !in pinnedSources }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSourcesToGetLatest(): List<CatalogueSource> {
|
fun getSourceSavedSearches(source: CatalogueSource): List<SavedSearch> {
|
||||||
return getEnabledSources()
|
return db.getSavedSearches(source.id).executeAsBlocking()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createFeed(source: CatalogueSource, savedSearch: SavedSearch?) {
|
||||||
|
launchIO {
|
||||||
|
db.insertFeedSavedSearch(
|
||||||
|
FeedSavedSearch(
|
||||||
|
id = null,
|
||||||
|
source = source.id,
|
||||||
|
savedSearch = savedSearch?.id
|
||||||
|
)
|
||||||
|
).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteFeed(feed: FeedSavedSearch) {
|
||||||
|
launchIO {
|
||||||
|
db.deleteFeedSavedSearch(feed).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSourcesToGetFeed(): List<Pair<FeedSavedSearch, SavedSearch?>> {
|
||||||
|
val savedSearches = db.getSavedSearchesFeed().executeAsBlocking()
|
||||||
|
.associateBy { it.id!! }
|
||||||
|
return db.getFeedSavedSearches().executeAsBlocking()
|
||||||
|
.map { it to savedSearches[it.savedSearch] }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a catalogue search item
|
* Creates a catalogue search item
|
||||||
*/
|
*/
|
||||||
protected open fun createCatalogueSearchItem(source: CatalogueSource, results: List<LatestCardItem>?): LatestItem {
|
protected open fun createCatalogueSearchItem(
|
||||||
return LatestItem(source, results)
|
feed: FeedSavedSearch,
|
||||||
|
savedSearch: SavedSearch?,
|
||||||
|
source: CatalogueSource?,
|
||||||
|
results: List<FeedCardItem>?
|
||||||
|
): FeedItem {
|
||||||
|
return FeedItem(feed, savedSearch, source, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiates get latest per watching source.
|
* Initiates get manga per feed.
|
||||||
*/
|
*/
|
||||||
fun getLatest() {
|
fun getFeed() {
|
||||||
// Create image fetch subscription
|
// Create image fetch subscription
|
||||||
initializeFetchImageSubscription()
|
initializeFetchImageSubscription()
|
||||||
|
|
||||||
// Create items with the initial state
|
// Create items with the initial state
|
||||||
val initialItems = getSourcesToGetLatest().map { createCatalogueSearchItem(it, null) }
|
val initialItems = getSourcesToGetFeed().map { (feed, savedSearch) ->
|
||||||
|
createCatalogueSearchItem(
|
||||||
|
feed,
|
||||||
|
savedSearch,
|
||||||
|
sourceManager.get(feed.source) as? CatalogueSource,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
var items = initialItems
|
var items = initialItems
|
||||||
|
|
||||||
fetchSourcesSubscription?.unsubscribe()
|
fetchSourcesSubscription?.unsubscribe()
|
||||||
fetchSourcesSubscription = Observable.from(getSourcesToGetLatest())
|
fetchSourcesSubscription = Observable.from(getSourcesToGetFeed())
|
||||||
.flatMap(
|
.flatMap(
|
||||||
{ source ->
|
{ (feed, savedSearch) ->
|
||||||
Observable.defer { source.fetchLatestUpdates(1) }
|
val source = sourceManager.get(feed.source) as? CatalogueSource
|
||||||
|
if (source != null) {
|
||||||
|
Observable.defer {
|
||||||
|
if (savedSearch == null) {
|
||||||
|
source.fetchLatestUpdates(1)
|
||||||
|
} else {
|
||||||
|
source.fetchSearchManga(1, savedSearch.query.orEmpty(), getFilterList(savedSearch, source))
|
||||||
|
}
|
||||||
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
|
.onErrorReturn { MangasPage(emptyList(), false) } // Ignore timeouts or other exceptions
|
||||||
.map { it.mangas.take(10) } // Get at most 10 manga from search result.
|
.map { it.mangas } // Get manga from search result.
|
||||||
.map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga.
|
.map { list -> list.map { networkToLocalManga(it, source.id) } } // Convert to local manga.
|
||||||
.doOnNext { fetchImage(it, source) } // Load manga covers.
|
.doOnNext { fetchImage(it, source) } // Load manga covers.
|
||||||
.map { list -> createCatalogueSearchItem(source, list.map { LatestCardItem(it) }) }
|
.map { list -> createCatalogueSearchItem(feed, savedSearch, source, list.map { FeedCardItem(it) }) }
|
||||||
|
} else {
|
||||||
|
Observable.just(createCatalogueSearchItem(feed, null, null, emptyList()))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
5
|
5
|
||||||
)
|
)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
// Update matching source with the obtained results
|
// Update matching source with the obtained results
|
||||||
.map { result ->
|
.map { result ->
|
||||||
items.map { item -> if (item.source == result.source) result else item }
|
items.map { item -> if (item.feed == result.feed) result else item }
|
||||||
}
|
}
|
||||||
// Update current state
|
// Update current state
|
||||||
.doOnNext { items = it }
|
.doOnNext { items = it }
|
||||||
@ -131,6 +196,20 @@ open class LatestPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val filterSerializer = FilterSerializer()
|
||||||
|
|
||||||
|
private fun getFilterList(savedSearch: SavedSearch, source: CatalogueSource): FilterList {
|
||||||
|
val filters = savedSearch.filtersJson ?: return FilterList()
|
||||||
|
return runCatching {
|
||||||
|
val originalFilters = source.getFilterList()
|
||||||
|
filterSerializer.deserialize(
|
||||||
|
filters = originalFilters,
|
||||||
|
json = Json.decodeFromString(filters)
|
||||||
|
)
|
||||||
|
originalFilters
|
||||||
|
}.getOrElse { FilterList() }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a list of manga.
|
* Initialize a list of manga.
|
||||||
*
|
*
|
@ -1,159 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.browse.latest
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.databinding.LatestControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.latest.LatestUpdatesController
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
||||||
import eu.kanade.tachiyomi.util.preference.asImmediateFlow
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This controller shows and manages the different search result in global search.
|
|
||||||
* This controller should only handle UI actions, IO actions should be done by [LatestPresenter]
|
|
||||||
* [LatestCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
|
||||||
*/
|
|
||||||
open class LatestController :
|
|
||||||
NucleusController<LatestControllerBinding, LatestPresenter>(),
|
|
||||||
LatestCardAdapter.OnMangaClickListener,
|
|
||||||
LatestAdapter.OnTitleClickListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adapter containing search results grouped by lang.
|
|
||||||
*/
|
|
||||||
protected var adapter: LatestAdapter? = null
|
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
|
||||||
return applicationContext?.getString(R.string.latest)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the [LatestPresenter] used in controller.
|
|
||||||
*
|
|
||||||
* @return instance of [LatestPresenter]
|
|
||||||
*/
|
|
||||||
override fun createPresenter(): LatestPresenter {
|
|
||||||
return LatestPresenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when manga in global search is clicked, opens manga.
|
|
||||||
*
|
|
||||||
* @param manga clicked item containing manga information.
|
|
||||||
*/
|
|
||||||
override fun onMangaClick(manga: Manga) {
|
|
||||||
// Open MangaController.
|
|
||||||
parentController?.router?.pushController(MangaController(manga, true).withFadeTransaction())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when manga in global search is long clicked.
|
|
||||||
*
|
|
||||||
* @param manga clicked item containing manga information.
|
|
||||||
*/
|
|
||||||
override fun onMangaLongClick(manga: Manga) {
|
|
||||||
// Delegate to single click by default.
|
|
||||||
onMangaClick(manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater): LatestControllerBinding = LatestControllerBinding.inflate(inflater)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the view is created
|
|
||||||
*
|
|
||||||
* @param view view of controller
|
|
||||||
*/
|
|
||||||
override fun onViewCreated(view: View) {
|
|
||||||
super.onViewCreated(view)
|
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = LatestAdapter(this)
|
|
||||||
|
|
||||||
// Create recycler and set adapter.
|
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
|
||||||
binding.recycler.adapter = adapter
|
|
||||||
|
|
||||||
presenter.preferences.latestTabSources()
|
|
||||||
.asImmediateFlow { presenter.getLatest() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
adapter = null
|
|
||||||
super.onDestroyView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveViewState(view: View, outState: Bundle) {
|
|
||||||
super.onSaveViewState(view, outState)
|
|
||||||
adapter?.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
|
||||||
super.onRestoreViewState(view, savedViewState)
|
|
||||||
adapter?.onRestoreInstanceState(savedViewState)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the view holder for the given manga.
|
|
||||||
*
|
|
||||||
* @param source used to find holder containing source
|
|
||||||
* @return the holder of the manga or null if it's not bound.
|
|
||||||
*/
|
|
||||||
private fun getHolder(source: CatalogueSource): LatestHolder? {
|
|
||||||
val adapter = adapter ?: return null
|
|
||||||
|
|
||||||
adapter.allBoundViewHolders.forEach { holder ->
|
|
||||||
val item = adapter.getItem(holder.bindingAdapterPosition)
|
|
||||||
if (item != null && source.id == item.source.id) {
|
|
||||||
return holder as LatestHolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add search result to adapter.
|
|
||||||
*
|
|
||||||
* @param latestManga the source items containing the latest manga.
|
|
||||||
*/
|
|
||||||
fun setItems(latestManga: List<LatestItem>) {
|
|
||||||
adapter?.updateDataSet(latestManga)
|
|
||||||
|
|
||||||
if (latestManga.isEmpty()) {
|
|
||||||
binding.emptyView.show(R.string.latest_tab_empty)
|
|
||||||
} else {
|
|
||||||
binding.emptyView.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from the presenter when a manga is initialized.
|
|
||||||
*
|
|
||||||
* @param manga the initialized manga.
|
|
||||||
*/
|
|
||||||
fun onMangaInitialized(source: CatalogueSource, manga: Manga) {
|
|
||||||
getHolder(source)?.setImage(manga)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a catalogue with the given search.
|
|
||||||
*/
|
|
||||||
override fun onTitleClick(source: CatalogueSource) {
|
|
||||||
presenter.preferences.lastUsedSource().set(source.id)
|
|
||||||
parentController?.router?.pushController(LatestUpdatesController(source).withFadeTransaction())
|
|
||||||
}
|
|
||||||
}
|
|
@ -175,16 +175,6 @@ class SourceController(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
val isWatched = item.source.id.toString() in preferences.latestTabSources().get()
|
|
||||||
|
|
||||||
if (item.source.supportsLatest) {
|
|
||||||
items.add(
|
|
||||||
activity.getString(if (isWatched) R.string.unwatch else R.string.watch) to {
|
|
||||||
watchCatalogue(item.source, isWatched)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
items.add(
|
items.add(
|
||||||
activity.getString(R.string.categories) to { addToCategories(item.source) }
|
activity.getString(R.string.categories) to { addToCategories(item.source) }
|
||||||
)
|
)
|
||||||
@ -222,18 +212,6 @@ class SourceController(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private fun watchCatalogue(source: Source, isWatched: Boolean) {
|
|
||||||
if (isWatched) {
|
|
||||||
preferences.latestTabSources() -= source.id.toString()
|
|
||||||
} else {
|
|
||||||
if (preferences.latestTabSources().get().size + 1 !in 0..5) {
|
|
||||||
applicationContext?.toast(R.string.too_many_watched)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
preferences.latestTabSources() += source.id.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addToCategories(source: Source) {
|
private fun addToCategories(source: Source) {
|
||||||
val categories = preferences.sourcesTabCategories().get()
|
val categories = preferences.sourcesTabCategories().get()
|
||||||
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it }))
|
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it }))
|
||||||
|
@ -7,7 +7,6 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.ui.browse.latest.LatestCardItem
|
|
||||||
|
|
||||||
class IndexCardItem(val manga: Manga) : AbstractFlexibleItem<IndexCardHolder>() {
|
class IndexCardItem(val manga: Manga) : AbstractFlexibleItem<IndexCardHolder>() {
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ class IndexCardItem(val manga: Manga) : AbstractFlexibleItem<IndexCardHolder>()
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other is LatestCardItem) {
|
if (other is IndexCardItem) {
|
||||||
return manga.id == other.manga.id
|
return manga.id == other.manga.id
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -59,12 +59,12 @@ class SettingsBrowseController : SettingsController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preferenceCategory {
|
preferenceCategory {
|
||||||
titleRes = R.string.latest
|
titleRes = R.string.feed
|
||||||
|
|
||||||
switchPreference {
|
switchPreference {
|
||||||
bindTo(preferences.latestTabInFront())
|
bindTo(preferences.feedTabInFront())
|
||||||
titleRes = R.string.pref_latest_position
|
titleRes = R.string.pref_feed_position
|
||||||
summaryRes = R.string.pref_latest_position_summery
|
summaryRes = R.string.pref_feed_position_summery
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -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.FeedSavedSearch
|
||||||
import exh.savedsearches.models.SavedSearch
|
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
|
||||||
@ -416,10 +417,21 @@ object EXHMigrations {
|
|||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
}?.ifEmpty { null }
|
}?.ifEmpty { null }
|
||||||
if (savedSearches != null) {
|
if (savedSearches != null) {
|
||||||
db.insertSavedSearches(savedSearches)
|
db.insertSavedSearches(savedSearches).executeAsBlocking()
|
||||||
|
}
|
||||||
|
val feed = prefs.getStringSet("latest_tab_sources", emptySet())?.map {
|
||||||
|
FeedSavedSearch(
|
||||||
|
id = null,
|
||||||
|
source = it.toLong(),
|
||||||
|
savedSearch = null
|
||||||
|
)
|
||||||
|
}?.ifEmpty { null }
|
||||||
|
if (feed != null) {
|
||||||
|
db.insertFeedSavedSearches(feed).executeAsBlocking()
|
||||||
}
|
}
|
||||||
prefs.edit(commit = true) {
|
prefs.edit(commit = true) {
|
||||||
remove("eh_saved_searches")
|
remove("eh_saved_searches")
|
||||||
|
remove("latest_tab_sources")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,8 +373,4 @@ object DebugFunctions {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unwatchAllSources() {
|
|
||||||
prefs.latestTabSources().delete()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
package exh.savedsearches.mappers
|
||||||
|
|
||||||
|
import android.database.Cursor
|
||||||
|
import androidx.core.content.contentValuesOf
|
||||||
|
import androidx.core.database.getLongOrNull
|
||||||
|
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.FeedSavedSearch
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable.COL_ID
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable.COL_SAVED_SEARCH_ID
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable.COL_SOURCE
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable.TABLE
|
||||||
|
|
||||||
|
class FeedSavedSearchTypeMapping : SQLiteTypeMapping<FeedSavedSearch>(
|
||||||
|
FeedSavedSearchPutResolver(),
|
||||||
|
FeedSavedSearchGetResolver(),
|
||||||
|
FeedSavedSearchDeleteResolver()
|
||||||
|
)
|
||||||
|
|
||||||
|
class FeedSavedSearchPutResolver : DefaultPutResolver<FeedSavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapToInsertQuery(obj: FeedSavedSearch) = InsertQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun mapToUpdateQuery(obj: FeedSavedSearch) = UpdateQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.where("$COL_ID = ?")
|
||||||
|
.whereArgs(obj.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun mapToContentValues(obj: FeedSavedSearch) = contentValuesOf(
|
||||||
|
COL_ID to obj.id,
|
||||||
|
COL_SOURCE to obj.source,
|
||||||
|
COL_SAVED_SEARCH_ID to obj.savedSearch
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeedSavedSearchGetResolver : DefaultGetResolver<FeedSavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapFromCursor(cursor: Cursor): FeedSavedSearch = FeedSavedSearch(
|
||||||
|
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID)),
|
||||||
|
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE)),
|
||||||
|
savedSearch = cursor.getLongOrNull(cursor.getColumnIndexOrThrow(COL_SAVED_SEARCH_ID))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
class FeedSavedSearchDeleteResolver : DefaultDeleteResolver<FeedSavedSearch>() {
|
||||||
|
|
||||||
|
override fun mapToDeleteQuery(obj: FeedSavedSearch) = DeleteQuery.builder()
|
||||||
|
.table(TABLE)
|
||||||
|
.where("$COL_ID = ?")
|
||||||
|
.whereArgs(obj.id)
|
||||||
|
.build()
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package exh.savedsearches.models
|
||||||
|
|
||||||
|
data class FeedSavedSearch(
|
||||||
|
// Tag identifier, unique
|
||||||
|
var id: Long?,
|
||||||
|
|
||||||
|
// Source for the saved search
|
||||||
|
var source: Long,
|
||||||
|
|
||||||
|
// If -1 then get latest, if set get the saved search
|
||||||
|
var savedSearch: Long?
|
||||||
|
)
|
@ -0,0 +1,84 @@
|
|||||||
|
package exh.savedsearches.queries
|
||||||
|
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.Query
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.DbProvider
|
||||||
|
import eu.kanade.tachiyomi.data.database.queries.getFeedSavedSearchQuery
|
||||||
|
import exh.savedsearches.models.FeedSavedSearch
|
||||||
|
import exh.savedsearches.models.SavedSearch
|
||||||
|
import exh.savedsearches.tables.FeedSavedSearchTable
|
||||||
|
|
||||||
|
interface FeedSavedSearchQueries : DbProvider {
|
||||||
|
fun getFeedSavedSearches() = db.get()
|
||||||
|
.listOfObjects(FeedSavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(FeedSavedSearchTable.TABLE)
|
||||||
|
.orderBy(FeedSavedSearchTable.COL_ID)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getFeedSavedSearch(id: Long) = db.get()
|
||||||
|
.`object`(FeedSavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(FeedSavedSearchTable.TABLE)
|
||||||
|
.where("${FeedSavedSearchTable.COL_ID} = ?")
|
||||||
|
.whereArgs(id)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getFeedSavedSearches(ids: List<Long>) = db.get()
|
||||||
|
.listOfObjects(FeedSavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(FeedSavedSearchTable.TABLE)
|
||||||
|
.where("${FeedSavedSearchTable.COL_ID} IN (?)")
|
||||||
|
.whereArgs(ids.joinToString())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun insertFeedSavedSearch(savedSearch: FeedSavedSearch) = db.put().`object`(savedSearch).prepare()
|
||||||
|
|
||||||
|
fun insertFeedSavedSearches(savedSearches: List<FeedSavedSearch>) = db.put().objects(savedSearches).prepare()
|
||||||
|
|
||||||
|
fun deleteFeedSavedSearch(savedSearch: FeedSavedSearch) = db.delete().`object`(savedSearch).prepare()
|
||||||
|
|
||||||
|
fun deleteFeedSavedSearch(id: Long) = db.delete()
|
||||||
|
.byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
|
.table(FeedSavedSearchTable.TABLE)
|
||||||
|
.where("${FeedSavedSearchTable.COL_ID} = ?")
|
||||||
|
.whereArgs(id)
|
||||||
|
.build()
|
||||||
|
).prepare()
|
||||||
|
|
||||||
|
fun deleteAllFeedSavedSearches() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
|
.table(FeedSavedSearchTable.TABLE)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
fun getSavedSearchesFeed() = db.get()
|
||||||
|
.listOfObjects(SavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
|
.query(getFeedSavedSearchQuery())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
|
/*fun setMangasForMergedManga(mergedMangaId: Long, mergedMangases: List<SavedSearch>) {
|
||||||
|
db.inTransaction {
|
||||||
|
deleteSavedSearches(mergedMangaId).executeAsBlocking()
|
||||||
|
mergedMangases.chunked(100) { chunk ->
|
||||||
|
insertSavedSearches(chunk).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
@ -49,6 +49,17 @@ interface SavedSearchQueries : DbProvider {
|
|||||||
)
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun getSavedSearches(ids: List<Long>) = db.get()
|
||||||
|
.listOfObjects(SavedSearch::class.java)
|
||||||
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
|
.table(SavedSearchTable.TABLE)
|
||||||
|
.where("${SavedSearchTable.COL_ID} IN (?)")
|
||||||
|
.whereArgs(ids.joinToString())
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertSavedSearch(savedSearch: SavedSearch) = db.put().`object`(savedSearch).prepare()
|
fun insertSavedSearch(savedSearch: SavedSearch) = db.put().`object`(savedSearch).prepare()
|
||||||
|
|
||||||
fun insertSavedSearches(savedSearches: List<SavedSearch>) = db.put().objects(savedSearches).prepare()
|
fun insertSavedSearches(savedSearches: List<SavedSearch>) = db.put().objects(savedSearches).prepare()
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package exh.savedsearches.tables
|
||||||
|
|
||||||
|
object FeedSavedSearchTable {
|
||||||
|
|
||||||
|
const val TABLE = "feed_saved_search"
|
||||||
|
|
||||||
|
const val COL_ID = "_id"
|
||||||
|
|
||||||
|
const val COL_SOURCE = "source"
|
||||||
|
|
||||||
|
const val COL_SAVED_SEARCH_ID = "saved_search"
|
||||||
|
|
||||||
|
val createTableQuery: String
|
||||||
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
$COL_SOURCE INTEGER NOT NULL,
|
||||||
|
$COL_SAVED_SEARCH_ID INTEGER,
|
||||||
|
FOREIGN KEY($COL_SAVED_SEARCH_ID) REFERENCES ${SavedSearchTable.TABLE} (${SavedSearchTable.COL_ID})
|
||||||
|
ON DELETE CASCADE
|
||||||
|
)"""
|
||||||
|
|
||||||
|
val createSavedSearchIdIndexQuery: String
|
||||||
|
get() = "CREATE INDEX ${TABLE}_${COL_SAVED_SEARCH_ID}_index ON $TABLE($COL_SAVED_SEARCH_ID)"
|
||||||
|
}
|
11
app/src/main/res/menu/feed.xml
Normal file
11
app/src/main/res/menu/feed.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_add_feed"
|
||||||
|
android:icon="@drawable/ic_add_24dp"
|
||||||
|
android:title="@string/action_add"
|
||||||
|
app:iconTint="?attr/colorOnSurface"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
@ -192,8 +192,8 @@
|
|||||||
<string name="library_group_updates_all">Launch category updates all the time</string>
|
<string name="library_group_updates_all">Launch category updates all the time</string>
|
||||||
|
|
||||||
<!-- Browse settings -->
|
<!-- Browse settings -->
|
||||||
<string name="pref_latest_position">Latest tab position</string>
|
<string name="pref_feed_position">Feed tab position</string>
|
||||||
<string name="pref_latest_position_summery">Do you want the latest tab to be the first tab in browse? This will make it the default tab when opening browse, not recommended if you\'re on data or a metered network</string>
|
<string name="pref_feed_position_summery">Do you want the feed tab to be the first tab in browse? This will make it the default tab when opening browse, not recommended if you\'re on data or a metered network</string>
|
||||||
<string name="pref_source_source_filtering">Filter sources in categories</string>
|
<string name="pref_source_source_filtering">Filter sources in categories</string>
|
||||||
<string name="pref_source_source_filtering_summery">Filter the sources that are in categories, making the sources not get put under the language if they are in a category</string>
|
<string name="pref_source_source_filtering_summery">Filter the sources that are in categories, making the sources not get put under the language if they are in a category</string>
|
||||||
<string name="pref_source_navigation">Replace latest button</string>
|
<string name="pref_source_navigation">Replace latest button</string>
|
||||||
@ -361,11 +361,11 @@
|
|||||||
<string name="no_source_categories">No source categories available</string>
|
<string name="no_source_categories">No source categories available</string>
|
||||||
<string name="invalid_category_name">Invalid category name</string>
|
<string name="invalid_category_name">Invalid category name</string>
|
||||||
|
|
||||||
<!-- Latest Tab -->
|
<!-- Feed Tab -->
|
||||||
<string name="watch">Watch</string>
|
<string name="feed">Feed</string>
|
||||||
<string name="unwatch">Unwatch</string>
|
<string name="feed_delete">Delete feed item?</string>
|
||||||
<string name="too_many_watched">Too many watched sources, cannot add more then 5</string>
|
<string name="too_many_in_feed">Too many sources in your feed, cannot add more then 10</string>
|
||||||
<string name="latest_tab_empty">You don\'t have any watched sources, go to the sources tab and long press a source to watch it</string>
|
<string name="feed_tab_empty">You don\'t have any sources in your feed, go to the sources tab and long press a source to watch it</string>
|
||||||
|
|
||||||
<!-- Sort by tags -->
|
<!-- Sort by tags -->
|
||||||
<string name="pref_tag_sorting">Tag sorting tags</string>
|
<string name="pref_tag_sorting">Tag sorting tags</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user