From 3a1699f0b379febf529bb2b8e13d0c079a904667 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 31 Dec 2016 16:19:32 +0100 Subject: [PATCH 01/75] Fix #373 and a few crashes --- .../tachiyomi/ui/base/activity/ActivityMixin.kt | 8 +++++++- .../tachiyomi/ui/base/activity/BaseActivity.kt | 5 ++--- .../tachiyomi/ui/base/activity/BaseRxActivity.kt | 5 ++--- .../kanade/tachiyomi/ui/library/LibraryPresenter.kt | 9 ++++++--- .../tachiyomi/ui/manga/chapter/ChaptersFragment.kt | 2 +- .../kanade/tachiyomi/ui/reader/ReaderPresenter.kt | 13 +++++++++++++ 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt index a259c3adb..ea1da77e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/ActivityMixin.kt @@ -15,11 +15,17 @@ import uy.kohesive.injekt.api.get interface ActivityMixin { + var resumed: Boolean + fun setupToolbar(toolbar: Toolbar, backNavigation: Boolean = true) { setSupportActionBar(toolbar) getSupportActionBar()?.setDisplayHomeAsUpEnabled(true) if (backNavigation) { - toolbar.setNavigationOnClickListener { onBackPressed() } + toolbar.setNavigationOnClickListener { + if (resumed) { + onBackPressed() + } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt index 6282d010f..38a4568d0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt @@ -5,15 +5,14 @@ import eu.kanade.tachiyomi.util.LocaleHelper abstract class BaseActivity : AppCompatActivity(), ActivityMixin { + override var resumed = false + init { LocaleHelper.updateConfiguration(this) } override fun getActivity() = this - var resumed = false - private set - override fun onResume() { super.onResume() resumed = true diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt index 03420a510..71e598ded 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt @@ -8,6 +8,8 @@ import nucleus.view.NucleusAppCompatActivity abstract class BaseRxActivity

> : NucleusAppCompatActivity

(), ActivityMixin { + override var resumed = false + init { LocaleHelper.updateConfiguration(this) } @@ -25,9 +27,6 @@ abstract class BaseRxActivity

> : NucleusAppCompatActivity

() { * * @param mangas the list of manga. */ - fun getCommonCategories(mangas: List): Collection = mangas.toSet() - .map { db.getCategoriesForManga(it).executeAsBlocking() } - .reduce { set1: Iterable, set2 -> set1.intersect(set2) } + fun getCommonCategories(mangas: List): Collection { + if (mangas.isEmpty()) return emptyList() + return mangas.toSet() + .map { db.getCategoriesForManga(it).executeAsBlocking() } + .reduce { set1: Iterable, set2 -> set1.intersect(set2) } + } /** * Remove the selected manga from the library. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt index 04cb5bac0..540b95d95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersFragment.kt @@ -118,7 +118,7 @@ class ChaptersFragment : BaseRxFragment(), ActionMode.Callbac override fun onPrepareOptionsMenu(menu: Menu) { // Initialize menu items. - val menuFilterRead = menu.findItem(R.id.action_filter_read) + val menuFilterRead = menu.findItem(R.id.action_filter_read) ?: return val menuFilterUnread = menu.findItem(R.id.action_filter_unread) val menuFilterDownloaded = menu.findItem(R.id.action_filter_downloaded) val menuFilterBookmarked = menu.findItem(R.id.action_filter_bookmarked) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index f85ab3665..d04a9c56f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -141,6 +141,11 @@ class ReaderPresenter : BasePresenter() { */ private var adjacentChaptersSubscription: Subscription? = null + /** + * Whether the active chapter has been loaded. + */ + private var chapterLoaded = false + companion object { /** * Id of the restartable that loads the active chapter. @@ -211,6 +216,7 @@ class ReaderPresenter : BasePresenter() { return loader.loadChapter(chapter) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { chapterLoaded = true } } /** @@ -298,6 +304,7 @@ class ReaderPresenter : BasePresenter() { nextChapter = null prevChapter = null + chapterLoaded = false start(LOAD_ACTIVE_CHAPTER) getAdjacentChapters(chapter) } @@ -474,6 +481,9 @@ class ReaderPresenter : BasePresenter() { * @return true if the next chapter is being loaded, false if there is no next chapter. */ fun loadNextChapter(): Boolean { + // Avoid skipping chapters. + if (!chapterLoaded) return true + nextChapter?.let { onChapterLeft() loadChapter(it, 0) @@ -488,6 +498,9 @@ class ReaderPresenter : BasePresenter() { * @return true if the previous chapter is being loaded, false if there is no previous chapter. */ fun loadPreviousChapter(): Boolean { + // Avoid skipping chapters. + if (!chapterLoaded) return true + prevChapter?.let { onChapterLeft() loadChapter(it, if (it.read) -1 else 0) From beca2b429cf61557adc98fed52d9959e9e77c1ea Mon Sep 17 00:00:00 2001 From: len Date: Sun, 1 Jan 2017 20:54:41 +0100 Subject: [PATCH 02/75] Minor changes --- .../java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt | 1 + .../main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt | 5 +++++ app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt | 3 +-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt index 449cb9ff0..b8e4cc467 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsActivity.kt @@ -28,6 +28,7 @@ class SettingsActivity : BaseActivity(), override fun onCreate(savedState: Bundle?) { setAppTheme() super.onCreate(savedState) + setTitle(R.string.label_settings) setContentView(R.layout.activity_preferences) replaceFragmentStrategy = ReplaceFragment(this, diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt index 672a94691..1f7e96776 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/ChapterSourceSync.kt @@ -44,6 +44,11 @@ fun syncChaptersWithSource(db: DatabaseHelper, // Chapters from the db not in the source. val toDelete = dbChapters.filterNot { it in sourceChapters } + // Return if there's nothing to add or delete, avoiding unnecessary db transactions. + if (toAdd.isEmpty() && toDelete.isEmpty()) { + return Pair(emptyList(), emptyList()) + } + val readded = mutableListOf() db.inTransaction { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt index b3dd5fb41..df9c038ea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/LocaleHelper.kt @@ -78,8 +78,7 @@ object LocaleHelper { if (systemLocale == null) { systemLocale = getConfigLocale(config) } - // In API 16 and lower [systemLocale] can't be changed. - if (configChange && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (configChange) { val configLocale = getConfigLocale(config) if (currentLocale == configLocale) { return From 9bcde69ee03c1865d5e35b4705e280b11ca52f4b Mon Sep 17 00:00:00 2001 From: len Date: Sun, 1 Jan 2017 21:00:52 +0100 Subject: [PATCH 03/75] Release 0.4.2 --- app/build.gradle | 4 ++-- app/src/main/res/raw/changelog_release.xml | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index dd93f3e49..72439233e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,8 +38,8 @@ android { minSdkVersion 16 targetSdkVersion 25 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - versionCode 17 - versionName "0.4.1" + versionCode 18 + versionName "0.4.2" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" diff --git a/app/src/main/res/raw/changelog_release.xml b/app/src/main/res/raw/changelog_release.xml index 45567a143..f8b188608 100644 --- a/app/src/main/res/raw/changelog_release.xml +++ b/app/src/main/res/raw/changelog_release.xml @@ -1,6 +1,20 @@ + + Added support for Anilist and Kitsu. + + Added library refresh option to library updates tab. + + Back button closes drawers before exiting the app. + + Fixed issues when using custom app language. + + Fixed updater in Android N. + + Fixed Mangafox search. + + Added an app's language selector. From 2032ba3ba3552229e8f4ac259b00dfd531ac75ae Mon Sep 17 00:00:00 2001 From: len Date: Sun, 1 Jan 2017 21:30:29 +0100 Subject: [PATCH 04/75] Now using subsampling 3.6.0 --- app/build.gradle | 2 +- .../java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt | 2 +- .../kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 72439233e..64df37b38 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -98,7 +98,7 @@ android { dependencies { // Modified dependencies - compile 'com.github.inorichi:subsampling-scale-image-view:c4db85c' + compile 'com.github.inorichi:subsampling-scale-image-view:e8b72cb' // Android support library final support_library_version = '25.0.1' diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt index bab6603c1..a7dc205f7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PageView.kt @@ -61,7 +61,7 @@ class PageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? } with(image_view) { - setMaxBitmapDimensions((reader.activity as ReaderActivity).maxBitmapSize) + setMaxTileSize((reader.activity as ReaderActivity).maxBitmapSize) setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setMinimumScaleType(reader.scaleType) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt index 3b2557647..207ae2658 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonHolder.kt @@ -54,7 +54,7 @@ class WebtoonHolder(private val view: View, private val adapter: WebtoonAdapter) init { with(view.image_view) { - setMaxBitmapDimensions(readerActivity.maxBitmapSize) + setMaxTileSize(readerActivity.maxBitmapSize) setDoubleTapZoomStyle(SubsamplingScaleImageView.ZOOM_FOCUS_FIXED) setPanLimit(SubsamplingScaleImageView.PAN_LIMIT_INSIDE) setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_FIT_WIDTH) From d3e9200a7f82f7a6d463487b39016b739bfe1026 Mon Sep 17 00:00:00 2001 From: paronos Date: Mon, 2 Jan 2017 18:30:10 +0100 Subject: [PATCH 05/75] Improve catalog search filters (#615) * Add three state (include/exclude/ignore) search filters (works for now only on MangaFox and MangaHere) * checkbox icons in xml format * fix checkbox icons referencing * fix three states filters in remaining catalogs * use Spinner for filter with more than three states (Mangasee) * use EditText for freetext filters (Mangasee) * remove pngs * Filter class/subclass * add Filter.Header * English catalogs --- .../data/source/online/OnlineSource.kt | 35 ++-- .../data/source/online/ParsedOnlineSource.kt | 2 +- .../data/source/online/YamlOnlineSource.kt | 8 +- .../data/source/online/english/Batoto.kt | 144 +++++++++++------ .../data/source/online/english/Kissmanga.kt | 116 ++++++------- .../data/source/online/english/Mangafox.kt | 112 ++++++++----- .../data/source/online/english/Mangahere.kt | 107 +++++++----- .../data/source/online/english/Mangasee.kt | 130 +++++++++------ .../source/online/english/Readmangatoday.kt | 117 ++++++++------ .../data/source/online/german/WieManga.kt | 11 +- .../data/source/online/russian/Mangachan.kt | 145 +++++++++-------- .../data/source/online/russian/Mintmanga.kt | 97 +++++------ .../data/source/online/russian/Readmanga.kt | 95 +++++------ .../ui/catalogue/CatalogueFragment.kt | 22 +-- .../tachiyomi/ui/catalogue/CataloguePager.kt | 2 +- .../ui/catalogue/CataloguePresenter.kt | 18 +-- .../tachiyomi/ui/catalogue/FilterAdapter.kt | 153 ++++++++++++++++++ .../latest_updates/LatestUpdatesPresenter.kt | 2 +- .../main/res/drawable/ic_check_box_24dp.xml | 9 ++ .../ic_check_box_outline_blank_24dp.xml | 9 ++ .../main/res/drawable/ic_check_box_set.xml | 5 + .../main/res/drawable/ic_check_box_x_24dp.xml | 9 ++ 22 files changed, 853 insertions(+), 495 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt create mode 100644 app/src/main/res/drawable/ic_check_box_24dp.xml create mode 100644 app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml create mode 100644 app/src/main/res/drawable/ic_check_box_set.xml create mode 100644 app/src/main/res/drawable/ic_check_box_x_24dp.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt index 611fcc06d..5b8936838 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/OnlineSource.kt @@ -53,7 +53,7 @@ abstract class OnlineSource() : Source { /** * Whether the source has support for latest updates. */ - abstract val supportsLatest : Boolean + abstract val supportsLatest: Boolean /** * Headers used for requests. @@ -133,7 +133,7 @@ abstract class OnlineSource() : Source { * the current page and the next page url. * @param query the search query. */ - open fun fetchSearchManga(page: MangasPage, query: String, filters: List): Observable = client + open fun fetchSearchManga(page: MangasPage, query: String, filters: List>): Observable = client .newCall(searchMangaRequest(page, query, filters)) .asObservableSuccess() .map { response -> @@ -148,7 +148,7 @@ abstract class OnlineSource() : Source { * @param page the page object. * @param query the search query. */ - open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + open protected fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -160,7 +160,7 @@ abstract class OnlineSource() : Source { * * @param query the search query. */ - abstract protected fun searchMangaInitialUrl(query: String, filters: List): String + abstract protected fun searchMangaInitialUrl(query: String, filters: List>): String /** * Parse the response from the site. It should add a list of manga and the absolute url to the @@ -170,7 +170,7 @@ abstract class OnlineSource() : Source { * @param page the page object to be filled. * @param query the search query. */ - abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) + abstract protected fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) /** * Returns an observable containing a page with a list of latest manga. @@ -365,10 +365,10 @@ abstract class OnlineSource() : Source { * @param page the page whose source image has to be downloaded. */ final override fun fetchImage(page: Page): Observable = - if (page.imageUrl.isNullOrEmpty()) - fetchImageUrl(page).flatMap { getCachedImage(it) } - else - getCachedImage(page) + if (page.imageUrl.isNullOrEmpty()) + fetchImageUrl(page).flatMap { getCachedImage(it) } + else + getCachedImage(page) /** * Returns an observable with the response of the source image. @@ -460,10 +460,21 @@ abstract class OnlineSource() : Source { * @param manga the manga of the chapter. */ open fun prepareNewChapter(chapter: Chapter, manga: Manga) { - } - data class Filter(val id: String, val name: String) + sealed class Filter(val name: String, var state: T) { + open class Header(name: String) : Filter(name, 0) + abstract class List(name: String, val values: Array, state: Int = 0) : Filter(name, state) + abstract class Text(name: String, state: String = "") : Filter(name, state) + abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state) + abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) { + companion object { + const val STATE_IGNORE = 0 + const val STATE_INCLUDE = 1 + const val STATE_EXCLUDE = 2 + } + } + } - open fun getFilterList(): List = emptyList() + open fun getFilterList(): List> = emptyList() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt index 6b0e21158..519c9e3d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/ParsedOnlineSource.kt @@ -61,7 +61,7 @@ abstract class ParsedOnlineSource() : OnlineSource() { * @param page the page object to be filled. * @param query the search query. */ - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt index 4b2f3bcb2..85efe6412 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/YamlOnlineSource.kt @@ -30,7 +30,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { override val supportsLatest = map.latestupdates != null - override val client = when(map.client) { + override val client = when (map.client) { "cloudflare" -> network.cloudflareClient else -> network.client } @@ -66,7 +66,7 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { } } - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -76,9 +76,9 @@ class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { } } - override fun searchMangaInitialUrl(query: String, filters: List) = map.search.url.replace("\$query", query) + override fun searchMangaInitialUrl(query: String, filters: List>) = map.search.url.replace("\$query", query) - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(map.search.manga_css)) { Manga.create(id).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt index 19c678087..c880023af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.source.online.english -import android.net.Uri import android.text.Html import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga @@ -14,6 +13,7 @@ import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.selectText import okhttp3.FormBody +import okhttp3.HttpUrl import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document @@ -107,26 +107,46 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { override fun latestUpdatesNextPageSelector() = "#show_more_row" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=1${getFilterParams(filters)}" + override fun searchMangaInitialUrl(query: String, filters: List>) = searchMangaUrl(query, filters, 1) - private fun getFilterParams(filters: List): String { + private fun searchMangaUrl(query: String, filterStates: List>, page: Int): String { + val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") var genres = "" - var completed = "" - for (filter in filters) { - if (filter.equals(completedFilter)) completed = "&completed=c" - else genres += ";i" + filter.id + for (filter in if (filterStates.isEmpty()) filters else filterStates) { + when (filter) { + is Status -> if (filter.state != Filter.TriState.STATE_IGNORE) { + url.addQueryParameter("completed", if (filter.state == Filter.TriState.STATE_EXCLUDE) "i" else "c") + } + is Genre -> if (filter.state != Filter.TriState.STATE_IGNORE) { + genres += (if (filter.state == Filter.TriState.STATE_EXCLUDE) ";e" else ";i") + filter.id + } + is TextField -> { + if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + } + is ListField -> { + val sel = filter.values[filter.state].value + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + is Flag -> { + val sel = if (filter.state) filter.valTrue else filter.valFalse + if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) + } + } } - return if (genres.isEmpty()) completed else "&genres=$genres&genre_cond=and$completed" + if (!genres.isEmpty()) url.addQueryParameter("genres", genres) + url.addQueryParameter("p", page.toString()) + return url.toString() } - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } return GET(page.url, headers) } - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -136,7 +156,7 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { } page.nextPageUrl = document.select(searchMangaNextPageSelector()).first()?.let { - "$baseUrl/search_ajax?name=${Uri.encode(query)}&order_cond=views&order=desc&p=${page.page + 1}${getFilterParams(filters)}" + searchMangaUrl(query, filters, page.page + 1) } } @@ -304,51 +324,69 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { } } - private val completedFilter = Filter("completed", "Completed") + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) + // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { - // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Filter("${id}", "${el.textContent.trim()}")` + // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` // }).join(',\n') // on https://bato.to/search - override fun getFilterList(): List = listOf( - completedFilter, - Filter("40", "4-Koma"), - Filter("1", "Action"), - Filter("2", "Adventure"), - Filter("39", "Award Winning"), - Filter("3", "Comedy"), - Filter("41", "Cooking"), - Filter("9", "Doujinshi"), - Filter("10", "Drama"), - Filter("12", "Ecchi"), - Filter("13", "Fantasy"), - Filter("15", "Gender Bender"), - Filter("17", "Harem"), - Filter("20", "Historical"), - Filter("22", "Horror"), - Filter("34", "Josei"), - Filter("27", "Martial Arts"), - Filter("30", "Mecha"), - Filter("42", "Medical"), - Filter("37", "Music"), - Filter("4", "Mystery"), - Filter("38", "Oneshot"), - Filter("5", "Psychological"), - Filter("6", "Romance"), - Filter("7", "School Life"), - Filter("8", "Sci-fi"), - Filter("32", "Seinen"), - Filter("35", "Shoujo"), - Filter("16", "Shoujo Ai"), - Filter("33", "Shounen"), - Filter("19", "Shounen Ai"), - Filter("21", "Slice of Life"), - Filter("23", "Smut"), - Filter("25", "Sports"), - Filter("26", "Supernatural"), - Filter("28", "Tragedy"), - Filter("36", "Webtoon"), - Filter("29", "Yaoi"), - Filter("31", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "artist_name"), + ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), + Status(), + Flag("Exclude mature", "mature", "m", ""), + Filter.Header(""), + ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4), + Flag("Ascending order", "order", "asc", "desc"), + Filter.Header("Genres"), + ListField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), + Genre("4-Koma", 40), + Genre("Action", 1), + Genre("Adventure", 2), + Genre("Award Winning", 39), + Genre("Comedy", 3), + Genre("Cooking", 41), + Genre("Doujinshi", 9), + Genre("Drama", 10), + Genre("Ecchi", 12), + Genre("Fantasy", 13), + Genre("Gender Bender", 15), + Genre("Harem", 17), + Genre("Historical", 20), + Genre("Horror", 22), + Genre("Josei", 34), + Genre("Martial Arts", 27), + Genre("Mecha", 30), + Genre("Medical", 42), + Genre("Music", 37), + Genre("Mystery", 4), + Genre("Oneshot", 38), + Genre("Psychological", 5), + Genre("Romance", 6), + Genre("School Life", 7), + Genre("Sci-fi", 8), + Genre("Seinen", 32), + Genre("Shoujo", 35), + Genre("Shoujo Ai", 16), + Genre("Shounen", 33), + Genre("Shounen Ai", 19), + Genre("Slice of Life", 21), + Genre("Smut", 23), + Genre("Sports", 25), + Genre("Supernatural", 26), + Genre("Tragedy", 28), + Genre("Webtoon", 36), + Genre("Yaoi", 29), + Genre("Yuri", 31), + Genre("[no chapters]", 44) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt index 767ae0137..1068f6efb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Kissmanga.kt @@ -51,25 +51,26 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } val form = FormBody.Builder().apply { - add("authorArtist", "") add("mangaName", query) - this@Kissmanga.filters.forEach { filter -> - if (filter.equals(completedFilter)) add("status", if (filter in filters) filter.id else "") - else add("genres", if (filter in filters) "1" else "0") + for (filter in if (filters.isEmpty()) this@Kissmanga.filters else filters) { + when (filter) { + is Author -> add("authorArtist", filter.state) + is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) + is Genre -> add("genres", filter.state.toString()) + } } } - return POST(page.url, headers, form.build()) } - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/AdvanceSearch" + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/AdvanceSearch" override fun searchMangaSelector() = popularMangaSelector() @@ -128,54 +129,59 @@ class Kissmanga(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = "" - private val completedFilter = Filter("Completed", "Completed") - // $("select[name=\"genres\"]").map((i,el) => `Filter("${i}", "${$(el).next().text().trim()}")`).get().join(',\n') + private class Status() : Filter.TriState("Completed") + private class Author() : Filter.Text("Author") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + + // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') // on http://kissmanga.com/AdvanceSearch - override fun getFilterList(): List = listOf( - completedFilter, - Filter("0", "Action"), - Filter("1", "Adult"), - Filter("2", "Adventure"), - Filter("3", "Comedy"), - Filter("4", "Comic"), - Filter("5", "Cooking"), - Filter("6", "Doujinshi"), - Filter("7", "Drama"), - Filter("8", "Ecchi"), - Filter("9", "Fantasy"), - Filter("10", "Gender Bender"), - Filter("11", "Harem"), - Filter("12", "Historical"), - Filter("13", "Horror"), - Filter("14", "Josei"), - Filter("15", "Lolicon"), - Filter("16", "Manga"), - Filter("17", "Manhua"), - Filter("18", "Manhwa"), - Filter("19", "Martial Arts"), - Filter("20", "Mature"), - Filter("21", "Mecha"), - Filter("22", "Medical"), - Filter("23", "Music"), - Filter("24", "Mystery"), - Filter("25", "One shot"), - Filter("26", "Psychological"), - Filter("27", "Romance"), - Filter("28", "School Life"), - Filter("29", "Sci-fi"), - Filter("30", "Seinen"), - Filter("31", "Shotacon"), - Filter("32", "Shoujo"), - Filter("33", "Shoujo Ai"), - Filter("34", "Shounen"), - Filter("35", "Shounen Ai"), - Filter("36", "Slice of Life"), - Filter("37", "Smut"), - Filter("38", "Sports"), - Filter("39", "Supernatural"), - Filter("40", "Tragedy"), - Filter("41", "Webtoon"), - Filter("42", "Yaoi"), - Filter("43", "Yuri") + override fun getFilterList(): List> = listOf( + Author(), + Status(), + Filter.Header("Genres"), + Genre("Action", 0), + Genre("Adult", 1), + Genre("Adventure", 2), + Genre("Comedy", 3), + Genre("Comic", 4), + Genre("Cooking", 5), + Genre("Doujinshi", 6), + Genre("Drama", 7), + Genre("Ecchi", 8), + Genre("Fantasy", 9), + Genre("Gender Bender", 10), + Genre("Harem", 11), + Genre("Historical", 12), + Genre("Horror", 13), + Genre("Josei", 14), + Genre("Lolicon", 15), + Genre("Manga", 16), + Genre("Manhua", 17), + Genre("Manhwa", 18), + Genre("Martial Arts", 19), + Genre("Mature", 20), + Genre("Mecha", 21), + Genre("Medical", 22), + Genre("Music", 23), + Genre("Mystery", 24), + Genre("One shot", 25), + Genre("Psychological", 26), + Genre("Romance", 27), + Genre("School Life", 28), + Genre("Sci-fi", 29), + Genre("Seinen", 30), + Genre("Shotacon", 31), + Genre("Shoujo", 32), + Genre("Shoujo Ai", 33), + Genre("Shounen", 34), + Genre("Shounen Ai", 35), + Genre("Slice of Life", 36), + Genre("Smut", 37), + Genre("Sports", 38), + Genre("Supernatural", 39), + Genre("Tragedy", 40), + Genre("Webtoon", 41), + Genre("Yaoi", 42), + Genre("Yuri", 43) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt index 31b580c7a..f0474371c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element @@ -45,8 +46,18 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = "a:has(span.next)" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search.php?name_method=cw&advopts=1&order=za&sort=views&name=$query&page=1&${filters.map { it.id + "=1" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + for (filter in if (filters.isEmpty()) this@Mangafox.filters else filters) { + when (filter) { + is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) + is TextField -> url.addQueryParameter(filter.key, filter.state) + is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) + is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") + } + } + return url.toString() + } override fun searchMangaSelector() = "div#mangalist > ul.list > li" @@ -123,49 +134,66 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { } // Not used, overrides parent. - override fun pageListParse(document: Document, pages: MutableList) {} + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") - // $('select.genres').map((i,el)=>`Filter("${$(el).attr('name')}", "${$(el).next().text().trim()}")`).get().join(',\n') - // on http://kissmanga.com/AdvanceSearch - override fun getFilterList(): List = listOf( - Filter("is_completed", "Completed"), - Filter("genres[Action]", "Action"), - Filter("genres[Adult]", "Adult"), - Filter("genres[Adventure]", "Adventure"), - Filter("genres[Comedy]", "Comedy"), - Filter("genres[Doujinshi]", "Doujinshi"), - Filter("genres[Drama]", "Drama"), - Filter("genres[Ecchi]", "Ecchi"), - Filter("genres[Fantasy]", "Fantasy"), - Filter("genres[Gender Bender]", "Gender Bender"), - Filter("genres[Harem]", "Harem"), - Filter("genres[Historical]", "Historical"), - Filter("genres[Horror]", "Horror"), - Filter("genres[Josei]", "Josei"), - Filter("genres[Martial Arts]", "Martial Arts"), - Filter("genres[Mature]", "Mature"), - Filter("genres[Mecha]", "Mecha"), - Filter("genres[Mystery]", "Mystery"), - Filter("genres[One Shot]", "One Shot"), - Filter("genres[Psychological]", "Psychological"), - Filter("genres[Romance]", "Romance"), - Filter("genres[School Life]", "School Life"), - Filter("genres[Sci-fi]", "Sci-fi"), - Filter("genres[Seinen]", "Seinen"), - Filter("genres[Shoujo]", "Shoujo"), - Filter("genres[Shoujo Ai]", "Shoujo Ai"), - Filter("genres[Shounen]", "Shounen"), - Filter("genres[Shounen Ai]", "Shounen Ai"), - Filter("genres[Slice of Life]", "Slice of Life"), - Filter("genres[Smut]", "Smut"), - Filter("genres[Sports]", "Sports"), - Filter("genres[Supernatural]", "Supernatural"), - Filter("genres[Tragedy]", "Tragedy"), - Filter("genres[Webtoons]", "Webtoons"), - Filter("genres[Yaoi]", "Yaoi"), - Filter("genres[Yuri]", "Yuri") + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Order() : Filter.CheckBox("Ascending order") + + // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') + // on http://mangafox.me/search.php + override fun getFilterList(): List> = listOf( + TextField("Author", "author"), + TextField("Artist", "artist"), + ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), + Genre("Completed", "is_completed"), + Filter.Header(""), + ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), + Order(), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Webtoons"), + Genre("Yaoi"), + Genre("Yuri") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index b5d8c9989..83b93b6fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource +import okhttp3.HttpUrl import org.jsoup.nodes.Document import org.jsoup.nodes.Element import java.text.ParseException @@ -47,7 +48,20 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = "div.next-page > a.next" - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search.php?name=$query&page=1&sort=views&order=za&${filters.map { it.id + "=1" }.joinToString("&")}&advopts=1" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) + for (filter in if (filters.isEmpty()) this@Mangahere.filters else filters) { + when (filter) { + is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) + is Genre -> url.addQueryParameter(filter.id, filter.state.toString()) + is TextField -> url.addQueryParameter(filter.key, filter.state) + is ListField -> url.addQueryParameter(filter.key, filter.values[filter.state].value) + is Order -> url.addQueryParameter("order", if (filter.state) "az" else "za") + } + } + return url.toString() + } + override fun searchMangaSelector() = "div.result_search > dl:has(dt)" @@ -82,12 +96,12 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { val urlElement = parentEl.select("a").first() - var volume = parentEl.select("span.mr6")?.first()?.text()?.trim()?:"" + var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" if (volume.length > 0) { volume = " - " + volume } - var title = parentEl?.textNodes()?.last()?.text()?.trim()?:"" + var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" if (title.length > 0) { title = " - " + title } @@ -131,42 +145,59 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") - // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Filter("${el.getAttribute('name')}", "${el.nextSibling.nextSibling.textContent.trim()}")`).join(',\n') + private data class ListValue(val name: String, val value: String) { + override fun toString(): String = name + } + + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Order() : Filter.CheckBox("Ascending order") + + // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') // http://www.mangahere.co/advsearch.htm - override fun getFilterList(): List = listOf( - Filter("is_completed", "Completed"), - Filter("genres[Action]", "Action"), - Filter("genres[Adventure]", "Adventure"), - Filter("genres[Comedy]", "Comedy"), - Filter("genres[Doujinshi]", "Doujinshi"), - Filter("genres[Drama]", "Drama"), - Filter("genres[Ecchi]", "Ecchi"), - Filter("genres[Fantasy]", "Fantasy"), - Filter("genres[Gender Bender]", "Gender Bender"), - Filter("genres[Harem]", "Harem"), - Filter("genres[Historical]", "Historical"), - Filter("genres[Horror]", "Horror"), - Filter("genres[Josei]", "Josei"), - Filter("genres[Martial Arts]", "Martial Arts"), - Filter("genres[Mature]", "Mature"), - Filter("genres[Mecha]", "Mecha"), - Filter("genres[Mystery]", "Mystery"), - Filter("genres[One Shot]", "One Shot"), - Filter("genres[Psychological]", "Psychological"), - Filter("genres[Romance]", "Romance"), - Filter("genres[School Life]", "School Life"), - Filter("genres[Sci-fi]", "Sci-fi"), - Filter("genres[Seinen]", "Seinen"), - Filter("genres[Shoujo]", "Shoujo"), - Filter("genres[Shoujo Ai]", "Shoujo Ai"), - Filter("genres[Shounen]", "Shounen"), - Filter("genres[Shounen Ai]", "Shounen Ai"), - Filter("genres[Slice of Life]", "Slice of Life"), - Filter("genres[Sports]", "Sports"), - Filter("genres[Supernatural]", "Supernatural"), - Filter("genres[Tragedy]", "Tragedy"), - Filter("genres[Yaoi]", "Yaoi"), - Filter("genres[Yuri]", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "author"), + TextField("Artist", "artist"), + ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))), + Status(), + Filter.Header(""), + ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), + Order(), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("One Shot"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt index 6e29363bd..c4e56d639 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangasee.kt @@ -30,7 +30,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { private val indexPattern = Pattern.compile("-index-(.*?)-") - override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending" + override fun popularMangaInitialUrl() = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&todo=1" override fun popularMangaSelector() = "div.requested > div.row" @@ -64,20 +64,32 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { // Not used, overrides parent. override fun popularMangaNextPageSelector() = "" - override fun searchMangaInitialUrl(query: String, filters: List): String { - var url = "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending&keyword=$query" + override fun searchMangaInitialUrl(query: String, filters: List>): String { + val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() + if (!query.isEmpty()) url.addQueryParameter("keyword", query) var genres: String? = null - for (filter in filters) { - if (filter.equals(completedFilter)) url += "&status=Complete" - else if (genres == null) genres = filter.id - else genres += "," + filter.id + var genresNo: String? = null + for (filter in if (filters.isEmpty()) this@Mangasee.filters else filters) { + when (filter) { + is Sort -> filter.values[filter.state].keys.forEachIndexed { i, s -> + url.addQueryParameter(s, filter.values[filter.state].values[i]) + } + is ListField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) + is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) + is Genre -> when (filter.state) { + Filter.TriState.STATE_INCLUDE -> genres = if (genres == null) filter.id else genres + "," + filter.id + Filter.TriState.STATE_EXCLUDE -> genresNo = if (genresNo == null) filter.id else genresNo + "," + filter.id + } + } } - return if (genres == null) url else url + "&genre=$genres" + if (genres != null) url.addQueryParameter("genre", genres) + if (genresNo != null) url.addQueryParameter("genreNo", genresNo) + return url.toString() } override fun searchMangaSelector() = "div.searchResults > div.requested > div.row" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } @@ -95,7 +107,7 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { return Pair(body, requestUrl) } - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(popularMangaSelector())) { Manga.create(id).apply { @@ -174,47 +186,67 @@ class Mangasee(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") - private val completedFilter = Filter("Complete", "Completed") + private data class SortOption(val name: String, val keys: Array, val values: Array) { + override fun toString(): String = name + } + + private class Sort(name: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class ListField(name: String, val key: String, values: Array, state: Int = 0) : Filter.List(name, values, state) + // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') // http://mangasee.co/advanced-search/ - override fun getFilterList(): List = listOf( - completedFilter, - Filter("Action", "Action"), - Filter("Adult", "Adult"), - Filter("Adventure", "Adventure"), - Filter("Comedy", "Comedy"), - Filter("Doujinshi", "Doujinshi"), - Filter("Drama", "Drama"), - Filter("Ecchi", "Ecchi"), - Filter("Fantasy", "Fantasy"), - Filter("Gender_Bender", "Gender Bender"), - Filter("Harem", "Harem"), - Filter("Hentai", "Hentai"), - Filter("Historical", "Historical"), - Filter("Horror", "Horror"), - Filter("Josei", "Josei"), - Filter("Lolicon", "Lolicon"), - Filter("Martial_Arts", "Martial Arts"), - Filter("Mature", "Mature"), - Filter("Mecha", "Mecha"), - Filter("Mystery", "Mystery"), - Filter("Psychological", "Psychological"), - Filter("Romance", "Romance"), - Filter("School_Life", "School Life"), - Filter("Sci-fi", "Sci-fi"), - Filter("Seinen", "Seinen"), - Filter("Shotacon", "Shotacon"), - Filter("Shoujo", "Shoujo"), - Filter("Shoujo_Ai", "Shoujo Ai"), - Filter("Shounen", "Shounen"), - Filter("Shounen_Ai", "Shounen Ai"), - Filter("Slice_of_Life", "Slice of Life"), - Filter("Smut", "Smut"), - Filter("Sports", "Sports"), - Filter("Supernatural", "Supernatural"), - Filter("Tragedy", "Tragedy"), - Filter("Yaoi", "Yaoi"), - Filter("Yuri", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Years", "year"), + TextField("Author", "author"), + Sort("Sort By", arrayOf(SortOption("Alphabetical A-Z", emptyArray(), emptyArray()), + SortOption("Alphabetical Z-A", arrayOf("sortOrder"), arrayOf("descending")), + SortOption("Newest", arrayOf("sortBy", "sortOrder"), arrayOf("dateUpdated", "descending")), + SortOption("Oldest", arrayOf("sortBy"), arrayOf("dateUpdated")), + SortOption("Most Popular", arrayOf("sortBy", "sortOrder"), arrayOf("popularity", "descending")), + SortOption("Least Popular", arrayOf("sortBy"), arrayOf("popularity")) + ), 4), + ListField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), + ListField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), + ListField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), + Filter.Header("Genres"), + Genre("Action"), + Genre("Adult"), + Genre("Adventure"), + Genre("Comedy"), + Genre("Doujinshi"), + Genre("Drama"), + Genre("Ecchi"), + Genre("Fantasy"), + Genre("Gender Bender"), + Genre("Harem"), + Genre("Hentai"), + Genre("Historical"), + Genre("Horror"), + Genre("Josei"), + Genre("Lolicon"), + Genre("Martial Arts"), + Genre("Mature"), + Genre("Mecha"), + Genre("Mystery"), + Genre("Psychological"), + Genre("Romance"), + Genre("School Life"), + Genre("Sci-fi"), + Genre("Seinen"), + Genre("Shotacon"), + Genre("Shoujo"), + Genre("Shoujo Ai"), + Genre("Shounen"), + Genre("Shounen Ai"), + Genre("Slice of Life"), + Genre("Smut"), + Genre("Sports"), + Genre("Supernatural"), + Genre("Tragedy"), + Genre("Yaoi"), + Genre("Yuri") ) override fun latestUpdatesInitialUrl(): String = "http://mangaseeonline.net/home/latest.request.php" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt index 5422c7ddb..09b520d14 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Readmangatoday.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.data.source.model.Page -import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import okhttp3.Headers import okhttp3.OkHttpClient @@ -57,25 +56,29 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector(): String = "div.hot-manga > ul.pagination > li > a:contains(»)" - override fun searchMangaInitialUrl(query: String, filters: List) = + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/service/advanced_search" - override fun searchMangaRequest(page: MangasPage, query: String, filters: List): Request { + override fun searchMangaRequest(page: MangasPage, query: String, filters: List>): Request { if (page.page == 1) { page.url = searchMangaInitialUrl(query, filters) } val builder = okhttp3.FormBody.Builder() builder.add("manga-name", query) - builder.add("type", "all") - var status = "both" - for (filter in filters) { - if (filter.equals(completedFilter)) status = filter.id - else builder.add("include[]", filter.id) - } - builder.add("status", status) + for (filter in if (filters.isEmpty()) this@Readmangatoday.filters else filters) { + when (filter) { + is TextField -> builder.add(filter.key, filter.state) + is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) + is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) + is Genre -> when (filter.state) { + Filter.TriState.STATE_INCLUDE -> builder.add("include[]", filter.id.toString()) + Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", filter.id.toString()) + } + } + } return POST(page.url, headers, builder.build()) } @@ -118,16 +121,16 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { } private fun parseChapterDate(date: String): Long { - val dateWords : List = date.split(" ") + val dateWords: List = date.split(" ") if (dateWords.size == 3) { val timeAgo = Integer.parseInt(dateWords[0]) - var date : Calendar = Calendar.getInstance() + var date: Calendar = Calendar.getInstance() if (dateWords[1].contains("Minute")) { - date.add(Calendar.MINUTE, - timeAgo) + date.add(Calendar.MINUTE, -timeAgo) } else if (dateWords[1].contains("Hour")) { - date.add(Calendar.HOUR_OF_DAY, - timeAgo) + date.add(Calendar.HOUR_OF_DAY, -timeAgo) } else if (dateWords[1].contains("Day")) { date.add(Calendar.DAY_OF_YEAR, -timeAgo) } else if (dateWords[1].contains("Week")) { @@ -153,45 +156,53 @@ class Readmangatoday(override val id: Int) : ParsedOnlineSource() { override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") - private val completedFilter = Filter("completed", "Completed") - // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Filter("${el.getAttribute('data-id')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') + private class Status() : Filter.TriState("Completed") + private class Genre(name: String, val id: Int) : Filter.TriState(name) + private class TextField(name: String, val key: String) : Filter.Text(name) + private class Type() : Filter.List("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) + + // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') // http://www.readmanga.today/advanced-search - override fun getFilterList(): List = listOf( - completedFilter, - Filter("2", "Action"), - Filter("4", "Adventure"), - Filter("5", "Comedy"), - Filter("6", "Doujinshi"), - Filter("7", "Drama"), - Filter("8", "Ecchi"), - Filter("9", "Fantasy"), - Filter("10", "Gender Bender"), - Filter("11", "Harem"), - Filter("12", "Historical"), - Filter("13", "Horror"), - Filter("14", "Josei"), - Filter("15", "Lolicon"), - Filter("16", "Martial Arts"), - Filter("17", "Mature"), - Filter("18", "Mecha"), - Filter("19", "Mystery"), - Filter("20", "One shot"), - Filter("21", "Psychological"), - Filter("22", "Romance"), - Filter("23", "School Life"), - Filter("24", "Sci-fi"), - Filter("25", "Seinen"), - Filter("26", "Shotacon"), - Filter("27", "Shoujo"), - Filter("28", "Shoujo Ai"), - Filter("29", "Shounen"), - Filter("30", "Shounen Ai"), - Filter("31", "Slice of Life"), - Filter("32", "Smut"), - Filter("33", "Sports"), - Filter("34", "Supernatural"), - Filter("35", "Tragedy"), - Filter("36", "Yaoi"), - Filter("37", "Yuri") + override fun getFilterList(): List> = listOf( + TextField("Author", "author-name"), + TextField("Artist", "artist-name"), + Type(), + Status(), + Filter.Header("Genres"), + Genre("Action", 2), + Genre("Adventure", 4), + Genre("Comedy", 5), + Genre("Doujinshi", 6), + Genre("Drama", 7), + Genre("Ecchi", 8), + Genre("Fantasy", 9), + Genre("Gender Bender", 10), + Genre("Harem", 11), + Genre("Historical", 12), + Genre("Horror", 13), + Genre("Josei", 14), + Genre("Lolicon", 15), + Genre("Martial Arts", 16), + Genre("Mature", 17), + Genre("Mecha", 18), + Genre("Mystery", 19), + Genre("One shot", 20), + Genre("Psychological", 21), + Genre("Romance", 22), + Genre("School Life", 23), + Genre("Sci-fi", 24), + Genre("Seinen", 25), + Genre("Shotacon", 26), + Genre("Shoujo", 27), + Genre("Shoujo Ai", 28), + Genre("Shounen", 29), + Genre("Shounen Ai", 30), + Genre("Slice of Life", 31), + Genre("Smut", 32), + Genre("Sports", 33), + Genre("Supernatural", 34), + Genre("Tragedy", 35), + Genre("Yaoi", 36), + Genre("Yuri", 37) ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt index 9958792dc..25c3dcf6a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/german/WieManga.kt @@ -45,7 +45,7 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesNextPageSelector() = null - override fun searchMangaInitialUrl(query: String, filters: List) = "$baseUrl/search/?wd=$query" + override fun searchMangaInitialUrl(query: String, filters: List>) = "$baseUrl/search/?wd=$query" override fun searchMangaSelector() = ".searchresult td > div" @@ -70,10 +70,10 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { manga.thumbnail_url = imageElement.select("img").first()?.attr("src") if (manga.author == "RSS") - manga.author = null + manga.author = null if (manga.artist == "RSS") - manga.artist = null + manga.artist = null } override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" @@ -95,11 +95,12 @@ class WieManga(override val id: Int) : ParsedOnlineSource() { val document = response.asJsoup() document.select("select#page").first().select("option").forEach { - pages.add(Page(pages.size, it.attr("value"))) + pages.add(Page(pages.size, it.attr("value"))) } } - override fun pageListParse(document: Document, pages: MutableList) {} + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt index 9075fa6f8..e26317a93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mangachan.kt @@ -26,15 +26,18 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/newestch" - override fun searchMangaInitialUrl(query: String, filters: List): String { + override fun searchMangaInitialUrl(query: String, filters: List>): String { if (query.isNotEmpty()) { return "$baseUrl/?do=search&subaction=search&story=$query" - } else if (filters.isNotEmpty()) { - var genres = "" - filters.forEach { genres = genres + it.name + '+' } - return "$baseUrl/tags/${genres.dropLast(1)}" } else { - return "$baseUrl/?do=search&subaction=search&story=$query" + val filt = filters.filter { it.state != Filter.TriState.STATE_IGNORE } + if (filt.isNotEmpty()) { + var genres = "" + filt.forEach { genres += (if (it.state == Filter.TriState.STATE_EXCLUDE) "-" else "") + (it as Genre).id + '+' } + return "$baseUrl/tags/${genres.dropLast(1)}" + } else { + return "$baseUrl/?do=search&subaction=search&story=$query" + } } } @@ -70,7 +73,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() - override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List) { + override fun searchMangaParse(response: Response, page: MangasPage, query: String, filters: List>) { val document = response.asJsoup() for (element in document.select(searchMangaSelector())) { Manga.create(id).apply { @@ -78,9 +81,9 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { page.mangas.add(this) } } - + val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } searchMangaNextPageSelector().let { selector -> - if (page.nextPageUrl.isNullOrEmpty() && filters.isEmpty()) { + if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { val onClick = document.select(selector).first()?.attr("onclick") val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum @@ -88,7 +91,7 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { } searchGenresNextPageSelector().let { selector -> - if (page.nextPageUrl.isNullOrEmpty() && filters.isNotEmpty()) { + if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { val url = document.select(selector).first()?.attr("href") page.nextPageUrl = searchMangaInitialUrl(query, filters) + url } @@ -137,71 +140,75 @@ class Mangachan(override val id: Int) : ParsedOnlineSource() { pageUrls.mapIndexedTo(pages) { i, url -> Page(i, "", url) } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) + /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => * { const link=el.getAttribute('href');const id=link.substr(6,link.length); - * return `Filter("${id}", "${id}")` }).join(',\n') + * return `Genre("${id.replace("_", " ")}")` }).join(',\n') * on http://mangachan.me/ */ - override fun getFilterList(): List = listOf( - Filter("18_плюс", "18_плюс"), - Filter("bdsm", "bdsm"), - Filter("арт", "арт"), - Filter("биография", "биография"), - Filter("боевик", "боевик"), - Filter("боевые_искусства", "боевые_искусства"), - Filter("вампиры", "вампиры"), - Filter("веб", "веб"), - Filter("гарем", "гарем"), - Filter("гендерная_интрига", "гендерная_интрига"), - Filter("героическое_фэнтези", "героическое_фэнтези"), - Filter("детектив", "детектив"), - Filter("дзёсэй", "дзёсэй"), - Filter("додзинси", "додзинси"), - Filter("драма", "драма"), - Filter("игра", "игра"), - Filter("инцест", "инцест"), - Filter("искусство", "искусство"), - Filter("история", "история"), - Filter("киберпанк", "киберпанк"), - Filter("кодомо", "кодомо"), - Filter("комедия", "комедия"), - Filter("литРПГ", "литРПГ"), - Filter("махо-сёдзё", "махо-сёдзё"), - Filter("меха", "меха"), - Filter("мистика", "мистика"), - Filter("музыка", "музыка"), - Filter("научная_фантастика", "научная_фантастика"), - Filter("повседневность", "повседневность"), - Filter("постапокалиптика", "постапокалиптика"), - Filter("приключения", "приключения"), - Filter("психология", "психология"), - Filter("романтика", "романтика"), - Filter("самурайский_боевик", "самурайский_боевик"), - Filter("сборник", "сборник"), - Filter("сверхъестественное", "сверхъестественное"), - Filter("сказка", "сказка"), - Filter("спорт", "спорт"), - Filter("супергерои", "супергерои"), - Filter("сэйнэн", "сэйнэн"), - Filter("сёдзё", "сёдзё"), - Filter("сёдзё-ай", "сёдзё-ай"), - Filter("сёнэн", "сёнэн"), - Filter("сёнэн-ай", "сёнэн-ай"), - Filter("тентакли", "тентакли"), - Filter("трагедия", "трагедия"), - Filter("триллер", "триллер"), - Filter("ужасы", "ужасы"), - Filter("фантастика", "фантастика"), - Filter("фурри", "фурри"), - Filter("фэнтези", "фэнтези"), - Filter("школа", "школа"), - Filter("эротика", "эротика"), - Filter("юри", "юри"), - Filter("яой", "яой"), - Filter("ёнкома", "ёнкома") + override fun getFilterList(): List> = listOf( + Genre("18 плюс"), + Genre("bdsm"), + Genre("арт"), + Genre("биография"), + Genre("боевик"), + Genre("боевые искусства"), + Genre("вампиры"), + Genre("веб"), + Genre("гарем"), + Genre("гендерная интрига"), + Genre("героическое фэнтези"), + Genre("детектив"), + Genre("дзёсэй"), + Genre("додзинси"), + Genre("драма"), + Genre("игра"), + Genre("инцест"), + Genre("искусство"), + Genre("история"), + Genre("киберпанк"), + Genre("кодомо"), + Genre("комедия"), + Genre("литРПГ"), + Genre("магия"), + Genre("махо-сёдзё"), + Genre("меха"), + Genre("мистика"), + Genre("музыка"), + Genre("научная фантастика"), + Genre("повседневность"), + Genre("постапокалиптика"), + Genre("приключения"), + Genre("психология"), + Genre("романтика"), + Genre("самурайский боевик"), + Genre("сборник"), + Genre("сверхъестественное"), + Genre("сказка"), + Genre("спорт"), + Genre("супергерои"), + Genre("сэйнэн"), + Genre("сёдзё"), + Genre("сёдзё-ай"), + Genre("сёнэн"), + Genre("сёнэн-ай"), + Genre("тентакли"), + Genre("трагедия"), + Genre("триллер"), + Genre("ужасы"), + Genre("фантастика"), + Genre("фурри"), + Genre("фэнтези"), + Genre("школа"), + Genre("эротика"), + Genre("юри"), + Genre("яой"), + Genre("ёнкома") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt index 2e1755676..cab2def3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Mintmanga.kt @@ -25,8 +25,8 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>) = + "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -107,57 +107,60 @@ class Mintmanga(override val id: Int) : ParsedOnlineSource() { } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String) : Filter.TriState(name) + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * on http://mintmanga.com/search */ - override fun getFilterList(): List = listOf( - Filter("el_2220", "арт"), - Filter("el_1353", "бара"), - Filter("el_1346", "боевик"), - Filter("el_1334", "боевые искусства"), - Filter("el_1339", "вампиры"), - Filter("el_1333", "гарем"), - Filter("el_1347", "гендерная интрига"), - Filter("el_1337", "героическое фэнтези"), - Filter("el_1343", "детектив"), - Filter("el_1349", "дзёсэй"), - Filter("el_1332", "додзинси"), - Filter("el_1310", "драма"), - Filter("el_5229", "игра"), - Filter("el_1311", "история"), - Filter("el_1351", "киберпанк"), - Filter("el_1328", "комедия"), - Filter("el_1318", "меха"), - Filter("el_1324", "мистика"), - Filter("el_1325", "научная фантастика"), - Filter("el_1327", "повседневность"), - Filter("el_1342", "постапокалиптика"), - Filter("el_1322", "приключения"), - Filter("el_1335", "психология"), - Filter("el_1313", "романтика"), - Filter("el_1316", "самурайский боевик"), - Filter("el_1350", "сверхъестественное"), - Filter("el_1314", "сёдзё"), - Filter("el_1320", "сёдзё-ай"), - Filter("el_1326", "сёнэн"), - Filter("el_1330", "сёнэн-ай"), - Filter("el_1321", "спорт"), - Filter("el_1329", "сэйнэн"), - Filter("el_1344", "трагедия"), - Filter("el_1341", "триллер"), - Filter("el_1317", "ужасы"), - Filter("el_1331", "фантастика"), - Filter("el_1323", "фэнтези"), - Filter("el_1319", "школа"), - Filter("el_1340", "эротика"), - Filter("el_1354", "этти"), - Filter("el_1315", "юри"), - Filter("el_1336", "яой") + override fun getFilterList(): List> = listOf( + Genre("арт", "el_2220"), + Genre("бара", "el_1353"), + Genre("боевик", "el_1346"), + Genre("боевые искусства", "el_1334"), + Genre("вампиры", "el_1339"), + Genre("гарем", "el_1333"), + Genre("гендерная интрига", "el_1347"), + Genre("героическое фэнтези", "el_1337"), + Genre("детектив", "el_1343"), + Genre("дзёсэй", "el_1349"), + Genre("додзинси", "el_1332"), + Genre("драма", "el_1310"), + Genre("игра", "el_5229"), + Genre("история", "el_1311"), + Genre("киберпанк", "el_1351"), + Genre("комедия", "el_1328"), + Genre("меха", "el_1318"), + Genre("мистика", "el_1324"), + Genre("научная фантастика", "el_1325"), + Genre("повседневность", "el_1327"), + Genre("постапокалиптика", "el_1342"), + Genre("приключения", "el_1322"), + Genre("психология", "el_1335"), + Genre("романтика", "el_1313"), + Genre("самурайский боевик", "el_1316"), + Genre("сверхъестественное", "el_1350"), + Genre("сёдзё", "el_1314"), + Genre("сёдзё-ай", "el_1320"), + Genre("сёнэн", "el_1326"), + Genre("сёнэн-ай", "el_1330"), + Genre("спорт", "el_1321"), + Genre("сэйнэн", "el_1329"), + Genre("трагедия", "el_1344"), + Genre("триллер", "el_1341"), + Genre("ужасы", "el_1317"), + Genre("фантастика", "el_1331"), + Genre("фэнтези", "el_1323"), + Genre("школа", "el_1319"), + Genre("эротика", "el_1340"), + Genre("этти", "el_1354"), + Genre("юри", "el_1315"), + Genre("яой", "el_1336") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt index f3005a502..0b75bc0da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/russian/Readmanga.kt @@ -25,8 +25,8 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { override fun latestUpdatesInitialUrl() = "$baseUrl/list?sortType=updated" - override fun searchMangaInitialUrl(query: String, filters: List) = - "$baseUrl/search?q=$query&${filters.map { it.id + "=in" }.joinToString("&")}" + override fun searchMangaInitialUrl(query: String, filters: List>) = + "$baseUrl/search?q=$query&${filters.map { (it as Genre).id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")}" override fun popularMangaSelector() = "div.desc" @@ -107,56 +107,59 @@ class Readmanga(override val id: Int) : ParsedOnlineSource() { } } - override fun pageListParse(document: Document, pages: MutableList) { } + override fun pageListParse(document: Document, pages: MutableList) { + } override fun imageUrlParse(document: Document) = "" + private class Genre(name: String, val id: String) : Filter.TriState(name) + /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); - * return `Filter("${id}", "${el.textContent.trim()}")` }).join(',\n') + * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * on http://readmanga.me/search */ - override fun getFilterList(): List = listOf( - Filter("el_5685", "арт"), - Filter("el_2155", "боевик"), - Filter("el_2143", "боевые искусства"), - Filter("el_2148", "вампиры"), - Filter("el_2142", "гарем"), - Filter("el_2156", "гендерная интрига"), - Filter("el_2146", "героическое фэнтези"), - Filter("el_2152", "детектив"), - Filter("el_2158", "дзёсэй"), - Filter("el_2141", "додзинси"), - Filter("el_2118", "драма"), - Filter("el_2154", "игра"), - Filter("el_2119", "история"), - Filter("el_8032", "киберпанк"), - Filter("el_2137", "кодомо"), - Filter("el_2136", "комедия"), - Filter("el_2147", "махо-сёдзё"), - Filter("el_2126", "меха"), - Filter("el_2132", "мистика"), - Filter("el_2133", "научная фантастика"), - Filter("el_2135", "повседневность"), - Filter("el_2151", "постапокалиптика"), - Filter("el_2130", "приключения"), - Filter("el_2144", "психология"), - Filter("el_2121", "романтика"), - Filter("el_2124", "самурайский боевик"), - Filter("el_2159", "сверхъестественное"), - Filter("el_2122", "сёдзё"), - Filter("el_2128", "сёдзё-ай"), - Filter("el_2134", "сёнэн"), - Filter("el_2139", "сёнэн-ай"), - Filter("el_2129", "спорт"), - Filter("el_2138", "сэйнэн"), - Filter("el_2153", "трагедия"), - Filter("el_2150", "триллер"), - Filter("el_2125", "ужасы"), - Filter("el_2140", "фантастика"), - Filter("el_2131", "фэнтези"), - Filter("el_2127", "школа"), - Filter("el_2149", "этти"), - Filter("el_2123", "юри") + override fun getFilterList(): List> = listOf( + Genre("арт", "el_5685"), + Genre("боевик", "el_2155"), + Genre("боевые искусства", "el_2143"), + Genre("вампиры", "el_2148"), + Genre("гарем", "el_2142"), + Genre("гендерная интрига", "el_2156"), + Genre("героическое фэнтези", "el_2146"), + Genre("детектив", "el_2152"), + Genre("дзёсэй", "el_2158"), + Genre("додзинси", "el_2141"), + Genre("драма", "el_2118"), + Genre("игра", "el_2154"), + Genre("история", "el_2119"), + Genre("киберпанк", "el_8032"), + Genre("кодомо", "el_2137"), + Genre("комедия", "el_2136"), + Genre("махо-сёдзё", "el_2147"), + Genre("меха", "el_2126"), + Genre("мистика", "el_2132"), + Genre("научная фантастика", "el_2133"), + Genre("повседневность", "el_2135"), + Genre("постапокалиптика", "el_2151"), + Genre("приключения", "el_2130"), + Genre("психология", "el_2144"), + Genre("романтика", "el_2121"), + Genre("самурайский боевик", "el_2124"), + Genre("сверхъестественное", "el_2159"), + Genre("сёдзё", "el_2122"), + Genre("сёдзё-ай", "el_2128"), + Genre("сёнэн", "el_2134"), + Genre("сёнэн-ай", "el_2139"), + Genre("спорт", "el_2129"), + Genre("сэйнэн", "el_2138"), + Genre("трагедия", "el_2153"), + Genre("триллер", "el_2150"), + Genre("ужасы", "el_2125"), + Genre("фантастика", "el_2140"), + Genre("фэнтези", "el_2131"), + Genre("школа", "el_2127"), + Genre("этти", "el_2149"), + Genre("юри", "el_2123") ) } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 68f2ee128..8ee045875 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -452,19 +452,21 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie * Show the filter dialog for the source. */ private fun showFiltersDialog() { - val allFilters = presenter.source.filters - val selectedFilters = presenter.filters - .map { filter -> allFilters.indexOf(filter) } - .toTypedArray() - + val adapter = FilterAdapter(if (presenter.filters.isEmpty()) presenter.source.getFilterList() // make a copy + else presenter.filters) MaterialDialog.Builder(context) .title(R.string.action_set_filter) - .items(allFilters.map { it.name }) - .itemsCallbackMultiChoice(selectedFilters) { dialog, positions, text -> - val newFilters = positions.map { allFilters[it] } + .adapter(adapter, null) + .onPositive() { dialog, which -> showProgressBar() - presenter.setSourceFilter(newFilters) - true + var allDefault = true + for (i in 0..adapter.filters.lastIndex) { + if (adapter.filters[i].state != presenter.source.filters[i].state) { + allDefault = false + break + } + } + presenter.setSourceFilter(if (allDefault) emptyList() else adapter.filters) } .positiveText(android.R.string.ok) .negativeText(android.R.string.cancel) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt index 301960414..3653f91c6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePager.kt @@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter import rx.Observable -open class CataloguePager(val source: OnlineSource, val query: String, val filters: List): Pager() { +open class CataloguePager(val source: OnlineSource, val query: String, val filters: List>) : Pager() { override fun requestNext(transformer: (Observable) -> Observable): Observable { val lastPage = lastPage diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index a35507376..e0ec27c8e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -65,9 +65,9 @@ open class CataloguePresenter : BasePresenter() { private set /** - * Active filters. + * Filters states. */ - var filters: List = emptyList() + var filters: List> = emptyList() /** * Pager containing a list of manga results. @@ -128,9 +128,9 @@ open class CataloguePresenter : BasePresenter() { * Restarts the pager for the active source with the provided query and filters. * * @param query the query. - * @param filters the list of active filters (for search mode). + * @param filters the current state of the filters (for search mode). */ - fun restartPager(query: String = this.query, filters: List = this.filters) { + fun restartPager(query: String = this.query, filters: List> = this.filters) { this.query = query this.filters = filters @@ -362,15 +362,15 @@ open class CataloguePresenter : BasePresenter() { } /** - * Set the active filters for the current source. + * Set the filter states for the current source. * - * @param selectedFilters a list of active filters. + * @param filterStates a list of active filters. */ - fun setSourceFilter(selectedFilters: List) { - restartPager(filters = selectedFilters) + fun setSourceFilter(filters: List>) { + restartPager(filters = filters) } - open fun createPager(query: String, filters: List): Pager { + open fun createPager(query: String, filters: List>): Pager { return CataloguePager(source, query, filters) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt new file mode 100644 index 000000000..bf83dffba --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt @@ -0,0 +1,153 @@ +package eu.kanade.tachiyomi.ui.catalogue + +import android.content.Context +import android.graphics.Typeface +import android.support.graphics.drawable.VectorDrawableCompat +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import android.widget.* +import android.widget.AdapterView.OnItemSelectedListener +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter +import android.text.TextWatcher +import android.text.Editable +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import eu.kanade.tachiyomi.util.inflate + + +class FilterAdapter(val filters: List>) : RecyclerView.Adapter() { + private companion object { + const val HEADER = 0 + const val CHECKBOX = 1 + const val TRISTATE = 2 + const val LIST = 3 + const val TEXT = 4 + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterAdapter.ViewHolder { + return when (viewType) { + HEADER -> ViewHolder(SepText(parent)) + LIST -> ViewHolder(TextSpinner(parent.context)) + TEXT -> ViewHolder(TextEditText(parent.context)) + else -> ViewHolder(CheckBox(parent.context)) + } + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val filter = filters[position] + when (filter) { + is Filter.Header -> { + if (filter.name.isEmpty()) (holder.view as SepText).textView.visibility = View.GONE + else (holder.view as SepText).textView.text = filter.name + } + is Filter.CheckBox -> { + var checkBox = holder.view as CheckBox + checkBox.text = filter.name + checkBox.isChecked = filter.state + checkBox.setButtonDrawable(VectorDrawableCompat.create(checkBox.getResources(), R.drawable.ic_check_box_set, null)) + checkBox.setOnCheckedChangeListener { buttonView, isChecked -> + filter.state = isChecked + } + } + is Filter.TriState -> { + var triCheckBox = holder.view as CheckBox + triCheckBox.text = filter.name + val icons = arrayOf(VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_outline_blank_24dp, null), + VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_24dp, null), + VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_x_24dp, null)) + triCheckBox.setButtonDrawable(icons[filter.state]) + triCheckBox.invalidate() + triCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> + filter.state = (filter.state + 1) % 3 + triCheckBox.setButtonDrawable(icons[filter.state]) + triCheckBox.invalidate() + } + } + is Filter.List<*> -> { + var txtSpin = holder.view as TextSpinner + if (filter.name.isEmpty()) txtSpin.textView.visibility = View.GONE + else txtSpin.textView.text = filter.name + ":" + txtSpin.spinner.adapter = ArrayAdapter(holder.view.context, + android.R.layout.simple_spinner_item, filter.values) + txtSpin.spinner.setSelection(filter.state) + txtSpin.spinner.onItemSelectedListener = object : OnItemSelectedListener { + override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, pos: Int, id: Long) { + filter.state = pos + } + + override fun onNothingSelected(parentView: AdapterView<*>) { + } + } + } + is Filter.Text -> { + var txtEdTx = holder.view as TextEditText + if (filter.name.isEmpty()) txtEdTx.textView.visibility = View.GONE + else txtEdTx.textView.text = filter.name + ":" + txtEdTx.editText.setText(filter.state) + txtEdTx.editText.addTextChangedListener(object : TextWatcher { + override fun afterTextChanged(s: Editable) { + } + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + filter.state = s.toString() + } + }) + } + } + } + + override fun getItemCount(): Int { + return filters.size + } + + override fun getItemViewType(position: Int): Int { + return when (filters[position]) { + is Filter.Header -> HEADER + is Filter.CheckBox -> CHECKBOX + is Filter.TriState -> TRISTATE + is Filter.List<*> -> LIST + is Filter.Text -> TEXT + } + } + + class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) + + private class SepText(parent: ViewGroup) : LinearLayout(parent.context) { + val separator: View = parent.inflate(R.layout.design_navigation_item_separator) + val textView: TextView = TextView(context) + + init { + orientation = LinearLayout.VERTICAL + textView.setTypeface(null, Typeface.BOLD); + addView(separator) + addView(textView) + } + } + + private class TextSpinner(context: Context?) : LinearLayout(context) { + val textView: TextView = TextView(context) + val spinner: Spinner = Spinner(context) + + init { + addView(textView) + addView(spinner) + } + } + + private class TextEditText(context: Context?) : LinearLayout(context) { + val textView: TextView = TextView(context) + val editText: EditText = EditText(context) + + init { + addView(textView) + editText.setSingleLine() + editText.setImeOptions(EditorInfo.IME_ACTION_DONE); + addView(editText) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt index 8df1196e6..1d224bdd1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/latest_updates/LatestUpdatesPresenter.kt @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter */ class LatestUpdatesPresenter : CataloguePresenter() { - override fun createPager(query: String, filters: List): Pager { + override fun createPager(query: String, filters: List>): Pager { return LatestUpdatesPager(source) } diff --git a/app/src/main/res/drawable/ic_check_box_24dp.xml b/app/src/main/res/drawable/ic_check_box_24dp.xml new file mode 100644 index 000000000..9948171c2 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml b/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml new file mode 100644 index 000000000..cf8bfa24b --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_outline_blank_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_box_set.xml b/app/src/main/res/drawable/ic_check_box_set.xml new file mode 100644 index 000000000..c2d7e4c86 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_set.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check_box_x_24dp.xml b/app/src/main/res/drawable/ic_check_box_x_24dp.xml new file mode 100644 index 000000000..1b2c9be12 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_box_x_24dp.xml @@ -0,0 +1,9 @@ + + + From c25af3d5adf8873259bccb629da52a7c6690b034 Mon Sep 17 00:00:00 2001 From: len Date: Mon, 2 Jan 2017 12:09:23 +0100 Subject: [PATCH 06/75] Change filters dialog with a drawer --- .../data/source/online/english/Batoto.kt | 1 - .../data/source/online/english/Mangafox.kt | 1 - .../data/source/online/english/Mangahere.kt | 1 - .../ui/catalogue/CatalogueFragment.kt | 85 +++++++--- .../ui/catalogue/CatalogueNavigationView.kt | 151 +++++++++++++++++ .../ui/catalogue/CataloguePresenter.kt | 15 +- .../tachiyomi/ui/catalogue/FilterAdapter.kt | 153 ------------------ .../ui/library/LibraryNavigationView.kt | 1 + .../ui/reader/viewer/pager/PagerReader.kt | 1 - .../tachiyomi/util/DimensionExtensions.kt | 9 ++ .../widget/ExtendedNavigationView.kt | 134 +-------------- .../tachiyomi/widget/SimpleNavigationView.kt | 152 +++++++++++++++++ .../main/res/drawable/ic_check_box_set.xml | 5 - app/src/main/res/layout/catalogue_drawer.xml | 9 ++ .../res/layout/catalogue_drawer_content.xml | 28 ++++ .../res/layout/navigation_view_spinner.xml | 25 +++ .../main/res/layout/navigation_view_text.xml | 29 ++++ app/src/main/res/values/strings.xml | 1 + 18 files changed, 481 insertions(+), 320 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/DimensionExtensions.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt delete mode 100644 app/src/main/res/drawable/ic_check_box_set.xml create mode 100644 app/src/main/res/layout/catalogue_drawer.xml create mode 100644 app/src/main/res/layout/catalogue_drawer_content.xml create mode 100644 app/src/main/res/layout/navigation_view_spinner.xml create mode 100644 app/src/main/res/layout/navigation_view_text.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt index c880023af..fa19cf950 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Batoto.kt @@ -343,7 +343,6 @@ class Batoto(override val id: Int) : ParsedOnlineSource(), LoginSource { ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), Status(), Flag("Exclude mature", "mature", "m", ""), - Filter.Header(""), ListField("Order by", "order_cond", arrayOf(ListValue("Title", "title"), ListValue("Author", "author"), ListValue("Artist", "artist"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Last Update", "update")), 4), Flag("Ascending order", "order", "asc", "desc"), Filter.Header("Genres"), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt index f0474371c..c98cd4692 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangafox.kt @@ -155,7 +155,6 @@ class Mangafox(override val id: Int) : ParsedOnlineSource() { TextField("Artist", "artist"), ListField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga", "1"), ListValue("Korean Manhwa", "2"), ListValue("Chinese Manhua", "3"))), Genre("Completed", "is_completed"), - Filter.Header(""), ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), Order(), Filter.Header("Genres"), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt index 83b93b6fc..b4c683883 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/source/online/english/Mangahere.kt @@ -162,7 +162,6 @@ class Mangahere(override val id: Int) : ParsedOnlineSource() { TextField("Artist", "artist"), ListField("Type", "direction", arrayOf(ListValue("Any", ""), ListValue("Japanese Manga (read from right to left)", "rl"), ListValue("Korean Manhwa (read from left to right)", "lr"))), Status(), - Filter.Header(""), ListField("Order by", "sort", arrayOf(ListValue("Series name", "name"), ListValue("Rating", "rating"), ListValue("Views", "views"), ListValue("Total chapters", "total_chapters"), ListValue("Last chapter", "last_chapter_time")), 2), Order(), Filter.Header("Genres"), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt index 8ee045875..55b8c4b29 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueFragment.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.catalogue import android.content.res.Configuration import android.os.Bundle import android.support.design.widget.Snackbar +import android.support.v4.widget.DrawerLayout import android.support.v7.widget.* import android.view.* import android.view.animation.AnimationUtils @@ -18,10 +19,12 @@ import eu.kanade.tachiyomi.ui.base.adapter.FlexibleViewHolder import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity +import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.widget.EndlessScrollListener import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener +import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_catalogue.* import kotlinx.android.synthetic.main.toolbar.* import nucleus.factory.RequiresPresenter @@ -100,6 +103,30 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie private val toolbar: Toolbar get() = (activity as MainActivity).toolbar + /** + * Navigation view containing filter items. + */ + private var navView: CatalogueNavigationView? = null + + /** + * Drawer listener to allow swipe only for closing the drawer. + */ + private val drawerListener by lazy { + object : DrawerLayout.SimpleDrawerListener() { + override fun onDrawerClosed(drawerView: View) { + if (drawerView == navView) { + activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView) + } + } + + override fun onDrawerOpened(drawerView: View) { + if (drawerView == navView) { + activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, navView) + } + } + } + } + companion object { /** * Creates a new instance of this fragment. @@ -176,6 +203,7 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie glm.scrollToPositionWithOffset(0, 0) llm.scrollToPositionWithOffset(0, 0) presenter.setActiveSource(source) + navView?.setFilters(presenter.sourceFilters) activity.invalidateOptionsMenu() } } @@ -191,6 +219,32 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie setToolbarTitle("") toolbar.addView(spinner) + // Inflate and prepare drawer + val navView = activity.drawer.inflate(R.layout.catalogue_drawer) as CatalogueNavigationView + this.navView = navView + activity.drawer.addView(navView) + activity.drawer.addDrawerListener(drawerListener) + navView.setFilters(presenter.sourceFilters) + + navView.post { + if (isAdded && !activity.drawer.isDrawerOpen(navView)) + activity.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, navView) + } + + navView.onSearchClicked = { + val allDefault = (0..navView.adapter.items.lastIndex) + .none { navView.adapter.items[it].state != presenter.source.filters[it].state } + + presenter.setSourceFilter(if (allDefault) emptyList() else navView.adapter.items) + } + + navView.onResetClicked = { + presenter.appliedFilters = emptyList() + val newFilters = presenter.source.getFilterList() + presenter.sourceFilters = newFilters + navView.setFilters(newFilters) + } + showProgressBar() } @@ -244,7 +298,7 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.action_display_mode -> swapDisplayMode() - R.id.action_set_filter -> showFiltersDialog() + R.id.action_set_filter -> navView?.let { activity.drawer.openDrawer(Gravity.END) } else -> return super.onOptionsItemSelected(item) } return true @@ -263,6 +317,10 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie } override fun onDestroyView() { + navView?.let { + activity.drawer.removeDrawerListener(drawerListener) + activity.drawer.removeView(it) + } numColumnsSubscription?.unsubscribe() searchItem?.let { if (it.isActionViewExpanded) it.collapseActionView() @@ -448,29 +506,4 @@ open class CatalogueFragment : BaseRxFragment(), FlexibleVie }.show() } - /** - * Show the filter dialog for the source. - */ - private fun showFiltersDialog() { - val adapter = FilterAdapter(if (presenter.filters.isEmpty()) presenter.source.getFilterList() // make a copy - else presenter.filters) - MaterialDialog.Builder(context) - .title(R.string.action_set_filter) - .adapter(adapter, null) - .onPositive() { dialog, which -> - showProgressBar() - var allDefault = true - for (i in 0..adapter.filters.lastIndex) { - if (adapter.filters[i].state != presenter.source.filters[i].state) { - allDefault = false - break - } - } - presenter.setSourceFilter(if (allDefault) emptyList() else adapter.filters) - } - .positiveText(android.R.string.ok) - .negativeText(android.R.string.cancel) - .show() - } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt new file mode 100644 index 000000000..34cce832a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueNavigationView.kt @@ -0,0 +1,151 @@ +package eu.kanade.tachiyomi.ui.catalogue + +import android.content.Context +import android.support.graphics.drawable.VectorDrawableCompat +import android.support.v7.widget.RecyclerView +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter +import eu.kanade.tachiyomi.util.dpToPx +import eu.kanade.tachiyomi.util.getResourceColor +import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener +import eu.kanade.tachiyomi.widget.SimpleNavigationView +import eu.kanade.tachiyomi.widget.SimpleTextWatcher +import kotlinx.android.synthetic.main.catalogue_drawer_content.view.* + + +class CatalogueNavigationView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) + : SimpleNavigationView(context, attrs) { + + val adapter = Adapter() + + var onSearchClicked = {} + + var onResetClicked = {} + + init { + recycler.adapter = adapter + val view = inflate(R.layout.catalogue_drawer_content) + (view as ViewGroup).addView(recycler) + addView(view) + + search_btn.setOnClickListener { onSearchClicked() } + reset_btn.setOnClickListener { onResetClicked() } + } + + fun setFilters(items: List>) { + adapter.items = items + adapter.notifyDataSetChanged() + } + + inner class Adapter : RecyclerView.Adapter() { + + var items: List> = emptyList() + + override fun getItemCount(): Int { + return items.size + } + + override fun getItemViewType(position: Int): Int { + return when (items[position]) { + is Filter.Header -> VIEW_TYPE_HEADER + is Filter.CheckBox -> VIEW_TYPE_CHECKBOX + is Filter.TriState -> VIEW_TYPE_MULTISTATE + is Filter.List<*> -> VIEW_TYPE_LIST + is Filter.Text -> VIEW_TYPE_TEXT + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { + return when (viewType) { + VIEW_TYPE_HEADER -> HeaderHolder(parent) + VIEW_TYPE_CHECKBOX -> CheckboxHolder(parent, null) + VIEW_TYPE_MULTISTATE -> MultiStateHolder(parent, null).apply { + // Adjust view with checkbox + text.setPadding(4.dpToPx, 0, 0, 0) + text.compoundDrawablePadding = 20.dpToPx + } + VIEW_TYPE_LIST -> SpinnerHolder(parent) + VIEW_TYPE_TEXT -> EditTextHolder(parent) + else -> throw Exception("Unknown view type") + } + } + + override fun onBindViewHolder(holder: Holder, position: Int) { + val filter = items[position] + when (filter) { + is Filter.Header -> { + val view = holder.itemView as TextView + view.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE + view.text = filter.name + } + is Filter.CheckBox -> { + val view = (holder as CheckboxHolder).check + view.text = filter.name + view.isChecked = filter.state + holder.itemView.setOnClickListener { + view.toggle() + filter.state = view.isChecked + } + } + is Filter.TriState -> { + val view = (holder as MultiStateHolder).text + view.text = filter.name + + fun getIcon() = VectorDrawableCompat.create(view.resources, when (filter.state) { + Filter.TriState.STATE_IGNORE -> R.drawable.ic_check_box_outline_blank_24dp + Filter.TriState.STATE_INCLUDE -> R.drawable.ic_check_box_24dp + Filter.TriState.STATE_EXCLUDE -> R.drawable.ic_check_box_x_24dp + else -> throw Exception("Unknown state") + }, null)?.apply { + val color = if (filter.state == Filter.TriState.STATE_INCLUDE) + R.attr.colorAccent + else + android.R.attr.textColorSecondary + + setTint(view.context.theme.getResourceColor(color)) + } + + view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) + holder.itemView.setOnClickListener { + filter.state = (filter.state + 1) % 3 + view.setCompoundDrawablesWithIntrinsicBounds(getIcon(), null, null, null) + } + } + is Filter.List<*> -> { + holder as SpinnerHolder + holder.text.text = filter.name + ": " + + val spinner = holder.spinner + spinner.prompt = filter.name + spinner.adapter = ArrayAdapter(holder.itemView.context, + android.R.layout.simple_spinner_item, filter.values).apply { + setDropDownViewResource(R.layout.spinner_item) + } + spinner.onItemSelectedListener = IgnoreFirstSpinnerListener { position -> + filter.state = position + } + spinner.setSelection(filter.state) + } + is Filter.Text -> { + holder as EditTextHolder + holder.wrapper.visibility = if (filter.name.isEmpty()) View.GONE else View.VISIBLE + holder.wrapper.hint = filter.name + holder.edit.setText(filter.state) + holder.edit.addTextChangedListener(object : SimpleTextWatcher() { + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + filter.state = s.toString() + } + }) + } + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt index e0ec27c8e..03adb70e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CataloguePresenter.kt @@ -65,9 +65,14 @@ open class CataloguePresenter : BasePresenter() { private set /** - * Filters states. + * Modifiable list of filters. */ - var filters: List> = emptyList() + var sourceFilters: List> = emptyList() + + /** + * List of filters used by the [Pager]. If empty alongside [query], the popular query is used. + */ + var appliedFilters: List> = emptyList() /** * Pager containing a list of manga results. @@ -105,6 +110,7 @@ open class CataloguePresenter : BasePresenter() { try { source = getLastUsedSource() + sourceFilters = source.getFilterList() } catch (error: NoSuchElementException) { return } @@ -130,9 +136,9 @@ open class CataloguePresenter : BasePresenter() { * @param query the query. * @param filters the current state of the filters (for search mode). */ - fun restartPager(query: String = this.query, filters: List> = this.filters) { + fun restartPager(query: String = this.query, filters: List> = this.appliedFilters) { this.query = query - this.filters = filters + this.appliedFilters = filters if (!isListMode) { subscribeToMangaInitializer() @@ -182,6 +188,7 @@ open class CataloguePresenter : BasePresenter() { fun setActiveSource(source: OnlineSource) { prefs.lastUsedCatalogueSource().set(source.id) this.source = source + sourceFilters = source.getFilterList() restartPager(query = "", filters = emptyList()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt deleted file mode 100644 index bf83dffba..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/FilterAdapter.kt +++ /dev/null @@ -1,153 +0,0 @@ -package eu.kanade.tachiyomi.ui.catalogue - -import android.content.Context -import android.graphics.Typeface -import android.support.graphics.drawable.VectorDrawableCompat -import android.support.v7.widget.RecyclerView -import android.view.View -import android.view.ViewGroup -import android.widget.* -import android.widget.AdapterView.OnItemSelectedListener -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.source.online.OnlineSource.Filter -import android.text.TextWatcher -import android.text.Editable -import android.view.inputmethod.EditorInfo -import android.widget.TextView -import eu.kanade.tachiyomi.util.inflate - - -class FilterAdapter(val filters: List>) : RecyclerView.Adapter() { - private companion object { - const val HEADER = 0 - const val CHECKBOX = 1 - const val TRISTATE = 2 - const val LIST = 3 - const val TEXT = 4 - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterAdapter.ViewHolder { - return when (viewType) { - HEADER -> ViewHolder(SepText(parent)) - LIST -> ViewHolder(TextSpinner(parent.context)) - TEXT -> ViewHolder(TextEditText(parent.context)) - else -> ViewHolder(CheckBox(parent.context)) - } - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val filter = filters[position] - when (filter) { - is Filter.Header -> { - if (filter.name.isEmpty()) (holder.view as SepText).textView.visibility = View.GONE - else (holder.view as SepText).textView.text = filter.name - } - is Filter.CheckBox -> { - var checkBox = holder.view as CheckBox - checkBox.text = filter.name - checkBox.isChecked = filter.state - checkBox.setButtonDrawable(VectorDrawableCompat.create(checkBox.getResources(), R.drawable.ic_check_box_set, null)) - checkBox.setOnCheckedChangeListener { buttonView, isChecked -> - filter.state = isChecked - } - } - is Filter.TriState -> { - var triCheckBox = holder.view as CheckBox - triCheckBox.text = filter.name - val icons = arrayOf(VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_outline_blank_24dp, null), - VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_24dp, null), - VectorDrawableCompat.create(triCheckBox.getResources(), R.drawable.ic_check_box_x_24dp, null)) - triCheckBox.setButtonDrawable(icons[filter.state]) - triCheckBox.invalidate() - triCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> - filter.state = (filter.state + 1) % 3 - triCheckBox.setButtonDrawable(icons[filter.state]) - triCheckBox.invalidate() - } - } - is Filter.List<*> -> { - var txtSpin = holder.view as TextSpinner - if (filter.name.isEmpty()) txtSpin.textView.visibility = View.GONE - else txtSpin.textView.text = filter.name + ":" - txtSpin.spinner.adapter = ArrayAdapter(holder.view.context, - android.R.layout.simple_spinner_item, filter.values) - txtSpin.spinner.setSelection(filter.state) - txtSpin.spinner.onItemSelectedListener = object : OnItemSelectedListener { - override fun onItemSelected(parentView: AdapterView<*>, selectedItemView: View, pos: Int, id: Long) { - filter.state = pos - } - - override fun onNothingSelected(parentView: AdapterView<*>) { - } - } - } - is Filter.Text -> { - var txtEdTx = holder.view as TextEditText - if (filter.name.isEmpty()) txtEdTx.textView.visibility = View.GONE - else txtEdTx.textView.text = filter.name + ":" - txtEdTx.editText.setText(filter.state) - txtEdTx.editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable) { - } - - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - } - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - filter.state = s.toString() - } - }) - } - } - } - - override fun getItemCount(): Int { - return filters.size - } - - override fun getItemViewType(position: Int): Int { - return when (filters[position]) { - is Filter.Header -> HEADER - is Filter.CheckBox -> CHECKBOX - is Filter.TriState -> TRISTATE - is Filter.List<*> -> LIST - is Filter.Text -> TEXT - } - } - - class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) - - private class SepText(parent: ViewGroup) : LinearLayout(parent.context) { - val separator: View = parent.inflate(R.layout.design_navigation_item_separator) - val textView: TextView = TextView(context) - - init { - orientation = LinearLayout.VERTICAL - textView.setTypeface(null, Typeface.BOLD); - addView(separator) - addView(textView) - } - } - - private class TextSpinner(context: Context?) : LinearLayout(context) { - val textView: TextView = TextView(context) - val spinner: Spinner = Spinner(context) - - init { - addView(textView) - addView(spinner) - } - } - - private class TextEditText(context: Context?) : LinearLayout(context) { - val textView: TextView = TextView(context) - val editText: EditText = EditText(context) - - init { - addView(textView) - editText.setSingleLine() - editText.setImeOptions(EditorInfo.IME_ACTION_DONE); - addView(editText) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt index 449550a5b..8c3cb176c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryNavigationView.kt @@ -39,6 +39,7 @@ class LibraryNavigationView @JvmOverloads constructor(context: Context, attrs: A init { recycler.adapter = adapter + addView(recycler) groups.forEach { it.initModels() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt index 5e53aebed..529436eb9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerReader.kt @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader -import eu.kanade.tachiyomi.util.toast import rx.subscriptions.CompositeSubscription /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/DimensionExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/DimensionExtensions.kt new file mode 100644 index 000000000..83e191cd3 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/DimensionExtensions.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.util + +import android.content.res.Resources + +val Int.pxToDp: Int + get() = (this / Resources.getSystem().displayMetrics.density).toInt() + +val Int.dpToPx: Int + get() = (this * Resources.getSystem().displayMetrics.density).toInt() \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt index 16ce78595..afb033ccf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/ExtendedNavigationView.kt @@ -1,88 +1,27 @@ package eu.kanade.tachiyomi.widget -import android.annotation.SuppressLint import android.content.Context import android.graphics.drawable.Drawable import android.support.annotation.CallSuper -import android.support.design.R -import android.support.design.internal.ScrimInsetsFrameLayout import android.support.graphics.drawable.VectorDrawableCompat import android.support.v4.content.ContextCompat -import android.support.v4.view.ViewCompat -import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView -import android.support.v7.widget.TintTypedArray import android.util.AttributeSet import android.view.View import android.view.ViewGroup -import android.widget.CheckBox -import android.widget.CheckedTextView -import android.widget.RadioButton import android.widget.TextView +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.getResourceColor -import eu.kanade.tachiyomi.util.inflate -import eu.kanade.tachiyomi.R as TR /** * An alternative implementation of [android.support.design.widget.NavigationView], without menu * inflation and allowing customizable items (multiple selections, custom views, etc). */ -@Suppress("LeakingThis") -@SuppressLint("PrivateResource") open class ExtendedNavigationView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) -: ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { - - /** - * Max width of the navigation view. - */ - private var maxWidth: Int - - /** - * Recycler view containing all the items. - */ - protected val recycler = RecyclerView(context) - - init { - // Custom attributes - val a = TintTypedArray.obtainStyledAttributes(context, attrs, - R.styleable.NavigationView, defStyleAttr, - R.style.Widget_Design_NavigationView) - - ViewCompat.setBackground( - this, a.getDrawable(R.styleable.NavigationView_android_background)) - - if (a.hasValue(R.styleable.NavigationView_elevation)) { - ViewCompat.setElevation(this, a.getDimensionPixelSize( - R.styleable.NavigationView_elevation, 0).toFloat()) - } - - ViewCompat.setFitsSystemWindows(this, - a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false)) - - maxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0) - - a.recycle() - - recycler.layoutManager = LinearLayoutManager(context) - addView(recycler) - } - - /** - * Overriden to measure the width of the navigation view. - */ - override fun onMeasure(widthSpec: Int, heightSpec: Int) { - val width = when (MeasureSpec.getMode(widthSpec)) { - MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec( - Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY) - MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY) - else -> widthSpec - } - // Let super sort out the height - super.onMeasure(width, heightSpec) - } +: SimpleNavigationView(context, attrs, defStyleAttr) { /** * Every item of the nav view. Generic items must belong to this list, custom items could be @@ -136,7 +75,7 @@ open class ExtendedNavigationView @JvmOverloads constructor( */ fun tintVector(context: Context, resId: Int): Drawable { return VectorDrawableCompat.create(context.resources, resId, context.theme)!!.apply { - setTint(context.theme.getResourceColor(TR.attr.colorAccent)) + setTint(context.theme.getResourceColor(R.attr.colorAccent)) } } } @@ -161,9 +100,9 @@ open class ExtendedNavigationView @JvmOverloads constructor( override fun getStateDrawable(context: Context): Drawable? { return when (state) { - SORT_ASC -> tintVector(context, TR.drawable.ic_keyboard_arrow_up_black_32dp) - SORT_DESC -> tintVector(context, TR.drawable.ic_keyboard_arrow_down_black_32dp) - SORT_NONE -> ContextCompat.getDrawable(context, TR.drawable.empty_drawable_32dp) + SORT_ASC -> tintVector(context, R.drawable.ic_keyboard_arrow_up_black_32dp) + SORT_DESC -> tintVector(context, R.drawable.ic_keyboard_arrow_down_black_32dp) + SORT_NONE -> ContextCompat.getDrawable(context, R.drawable.empty_drawable_32dp) else -> null } } @@ -218,59 +157,6 @@ open class ExtendedNavigationView @JvmOverloads constructor( } - /** - * Base view holder. - */ - abstract class Holder(view: View) : RecyclerView.ViewHolder(view) - - /** - * Separator view holder. - */ - class SeparatorHolder(parent: ViewGroup) - : Holder(parent.inflate(R.layout.design_navigation_item_separator)) - - /** - * Header view holder. - */ - class HeaderHolder(parent: ViewGroup) - : Holder(parent.inflate(R.layout.design_navigation_item_subheader)) - - /** - * Clickable view holder. - */ - abstract class ClickableHolder(view: View, listener: View.OnClickListener?) : Holder(view) { - init { - itemView.setOnClickListener(listener) - } - } - - /** - * Radio view holder. - */ - class RadioHolder(parent: ViewGroup, listener: View.OnClickListener?) - : ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) { - - val radio = itemView.findViewById(TR.id.nav_view_item) as RadioButton - } - - /** - * Checkbox view holder. - */ - class CheckboxHolder(parent: ViewGroup, listener: View.OnClickListener?) - : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) { - - val check = itemView.findViewById(TR.id.nav_view_item) as CheckBox - } - - /** - * Multi state view holder. - */ - class MultiStateHolder(parent: ViewGroup, listener: View.OnClickListener?) - : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) { - - val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView - } - /** * Base adapter for the navigation view. It knows how to create and render every subclass of * [Item]. @@ -352,12 +238,4 @@ open class ExtendedNavigationView @JvmOverloads constructor( } - companion object { - private const val VIEW_TYPE_HEADER = 100 - private const val VIEW_TYPE_SEPARATOR = 101 - private const val VIEW_TYPE_RADIO = 102 - private const val VIEW_TYPE_CHECKBOX = 103 - private const val VIEW_TYPE_MULTISTATE = 104 - } - } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt new file mode 100644 index 000000000..40b1cfa01 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/SimpleNavigationView.kt @@ -0,0 +1,152 @@ +package eu.kanade.tachiyomi.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.support.design.R +import android.support.design.internal.ScrimInsetsFrameLayout +import android.support.design.widget.TextInputLayout +import android.support.v4.view.ViewCompat +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.TintTypedArray +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import android.widget.* +import eu.kanade.tachiyomi.util.inflate +import eu.kanade.tachiyomi.R as TR + +@Suppress("LeakingThis") +@SuppressLint("PrivateResource") +open class SimpleNavigationView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0) +: ScrimInsetsFrameLayout(context, attrs, defStyleAttr) { + + /** + * Max width of the navigation view. + */ + private var maxWidth: Int + + /** + * Recycler view containing all the items. + */ + protected val recycler = RecyclerView(context) + + init { + // Custom attributes + val a = TintTypedArray.obtainStyledAttributes(context, attrs, + R.styleable.NavigationView, defStyleAttr, + R.style.Widget_Design_NavigationView) + + ViewCompat.setBackground( + this, a.getDrawable(R.styleable.NavigationView_android_background)) + + if (a.hasValue(R.styleable.NavigationView_elevation)) { + ViewCompat.setElevation(this, a.getDimensionPixelSize( + R.styleable.NavigationView_elevation, 0).toFloat()) + } + + ViewCompat.setFitsSystemWindows(this, + a.getBoolean(R.styleable.NavigationView_android_fitsSystemWindows, false)) + + maxWidth = a.getDimensionPixelSize(R.styleable.NavigationView_android_maxWidth, 0) + + a.recycle() + + recycler.layoutManager = LinearLayoutManager(context) + } + + /** + * Overriden to measure the width of the navigation view. + */ + override fun onMeasure(widthSpec: Int, heightSpec: Int) { + val width = when (MeasureSpec.getMode(widthSpec)) { + MeasureSpec.AT_MOST -> MeasureSpec.makeMeasureSpec( + Math.min(MeasureSpec.getSize(widthSpec), maxWidth), MeasureSpec.EXACTLY) + MeasureSpec.UNSPECIFIED -> MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.EXACTLY) + else -> widthSpec + } + // Let super sort out the height + super.onMeasure(width, heightSpec) + } + + /** + * Base view holder. + */ + abstract class Holder(view: View) : RecyclerView.ViewHolder(view) + + /** + * Separator view holder. + */ + class SeparatorHolder(parent: ViewGroup) + : Holder(parent.inflate(R.layout.design_navigation_item_separator)) + + /** + * Header view holder. + */ + class HeaderHolder(parent: ViewGroup) + : Holder(parent.inflate(R.layout.design_navigation_item_subheader)) + + /** + * Clickable view holder. + */ + abstract class ClickableHolder(view: View, listener: View.OnClickListener?) : Holder(view) { + init { + itemView.setOnClickListener(listener) + } + } + + /** + * Radio view holder. + */ + class RadioHolder(parent: ViewGroup, listener: View.OnClickListener?) + : ClickableHolder(parent.inflate(TR.layout.navigation_view_radio), listener) { + + val radio = itemView.findViewById(TR.id.nav_view_item) as RadioButton + } + + /** + * Checkbox view holder. + */ + class CheckboxHolder(parent: ViewGroup, listener: View.OnClickListener?) + : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkbox), listener) { + + val check = itemView.findViewById(TR.id.nav_view_item) as CheckBox + } + + /** + * Multi state view holder. + */ + class MultiStateHolder(parent: ViewGroup, listener: View.OnClickListener?) + : ClickableHolder(parent.inflate(TR.layout.navigation_view_checkedtext), listener) { + + val text = itemView.findViewById(TR.id.nav_view_item) as CheckedTextView + } + + class SpinnerHolder(parent: ViewGroup, listener: OnClickListener? = null) + : ClickableHolder(parent.inflate(TR.layout.navigation_view_spinner), listener) { + + val text = itemView.findViewById(TR.id.nav_view_item_text) as TextView + val spinner = itemView.findViewById(TR.id.nav_view_item) as Spinner + } + + class EditTextHolder(parent: ViewGroup) + : Holder(parent.inflate(TR.layout.navigation_view_text)) { + + val wrapper = itemView.findViewById(TR.id.nav_view_item_wrapper) as TextInputLayout + val edit = itemView.findViewById(TR.id.nav_view_item) as EditText + } + + protected companion object { + const val VIEW_TYPE_HEADER = 100 + const val VIEW_TYPE_SEPARATOR = 101 + const val VIEW_TYPE_RADIO = 102 + const val VIEW_TYPE_CHECKBOX = 103 + const val VIEW_TYPE_MULTISTATE = 104 + const val VIEW_TYPE_TEXT = 105 + const val VIEW_TYPE_LIST = 106 + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_check_box_set.xml b/app/src/main/res/drawable/ic_check_box_set.xml deleted file mode 100644 index c2d7e4c86..000000000 --- a/app/src/main/res/drawable/ic_check_box_set.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout/catalogue_drawer.xml b/app/src/main/res/layout/catalogue_drawer.xml new file mode 100644 index 000000000..e348bf34d --- /dev/null +++ b/app/src/main/res/layout/catalogue_drawer.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/catalogue_drawer_content.xml b/app/src/main/res/layout/catalogue_drawer_content.xml new file mode 100644 index 000000000..62d4a9803 --- /dev/null +++ b/app/src/main/res/layout/catalogue_drawer_content.xml @@ -0,0 +1,28 @@ + + + + + +