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 3981f5a0f..2ae0e11ae 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 @@ -33,7 +33,7 @@ import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.subjects.PublishSubject import timber.log.Timber -import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeUnit.MILLISECONDS /** * Fragment that shows the manga from the catalogue. @@ -65,7 +65,8 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold /** * Query of the search box. */ - private var query = "" + private val query: String? + get() = presenter.query /** * Selected index of the spinner (selected source). @@ -109,23 +110,11 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold get() = (activity as MainActivity).toolbar companion object { - - /** - * Key to save and restore [query] from a [Bundle]. - */ - const val QUERY_KEY = "query_key" - - /** - * Key to save and restore [selectedIndex] from a [Bundle]. - */ - const val SELECTED_INDEX_KEY = "selected_index_key" - /** * Creates a new instance of this fragment. * * @return a new instance of [CatalogueFragment]. */ - @JvmStatic fun newInstance(): CatalogueFragment { return CatalogueFragment() } @@ -134,13 +123,6 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) setHasOptionsMenu(true) - - if (savedState != null) { - selectedIndex = savedState.getInt(SELECTED_INDEX_KEY) - query = savedState.getString(QUERY_KEY) - } else { - selectedIndex = presenter.getLastUsedSourceIndex() - } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedState: Bundle?): View? { @@ -188,19 +170,15 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold val onItemSelected = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) { val source = spinnerAdapter.getItem(position) - if (selectedIndex != position || adapter.isEmpty) { - // Set previous selection if it's not a valid source and notify the user - if (!presenter.isValidSource(source)) { - spinner.setSelection(presenter.findFirstValidSource()) - context.toast(R.string.source_requires_login) - } else { - selectedIndex = position - presenter.setEnabledSource(selectedIndex) - showProgressBar() - glm.scrollToPositionWithOffset(0, 0) - llm.scrollToPositionWithOffset(0, 0) - presenter.startRequesting(source) - } + if (!presenter.isValidSource(source)) { + spinner.setSelection(selectedIndex) + context.toast(R.string.source_requires_login) + } else if (source != presenter.source) { + selectedIndex = position + showProgressBar() + glm.scrollToPositionWithOffset(0, 0) + llm.scrollToPositionWithOffset(0, 0) + presenter.setActiveSource(source) } } @@ -210,18 +188,15 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold spinner = Spinner(themedContext).apply { adapter = spinnerAdapter + selectedIndex = presenter.sources.indexOf(presenter.source) setSelection(selectedIndex) onItemSelectedListener = onItemSelected } setToolbarTitle("") toolbar.addView(spinner) - } - override fun onSaveInstanceState(outState: Bundle) { - outState.putInt(SELECTED_INDEX_KEY, selectedIndex) - outState.putString(QUERY_KEY, query) - super.onSaveInstanceState(outState) + showProgressBar() } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -268,14 +243,16 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold return true } - override fun onStart() { - super.onStart() - initializeSearchSubscription() + override fun onResume() { + super.onResume() + queryDebouncerSubscription = queryDebouncerSubject.debounce(SEARCH_TIMEOUT, MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { searchWithQuery(it) } } - override fun onStop() { - destroySearchSubscription() - super.onStop() + override fun onPause() { + queryDebouncerSubscription?.unsubscribe() + super.onPause() } override fun onDestroyView() { @@ -288,51 +265,34 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold } /** - * Listen for query events on the debouncer. - */ - private fun initializeSearchSubscription() { - queryDebouncerSubscription = queryDebouncerSubject.debounce(SEARCH_TIMEOUT, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { restartRequest(it) } - } - - /** - * Unsubscribe from the query debouncer. - */ - private fun destroySearchSubscription() { - queryDebouncerSubscription?.unsubscribe() - } - - /** - * Called when the input text changes or is submitted + * Called when the input text changes or is submitted. * * @param query the new query. * @param now whether to send the network call now or debounce it by [SEARCH_TIMEOUT]. */ private fun onSearchEvent(query: String, now: Boolean) { if (now) { - restartRequest(query) + searchWithQuery(query) } else { queryDebouncerSubject.onNext(query) } } /** - * Restarts the request. + * Restarts the request with a new query. * * @param newQuery the new query. */ - private fun restartRequest(newQuery: String) { + private fun searchWithQuery(newQuery: String) { // If text didn't change, do nothing if (query == newQuery) return - query = newQuery showProgressBar() catalogue_grid.layoutManager.scrollToPosition(0) catalogue_list.layoutManager.scrollToPosition(0) - presenter.restartRequest(query) + presenter.restartPager(newQuery) } /** @@ -373,7 +333,7 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold catalogue_view.snack(error.message ?: "") { setAction(R.string.action_retry) { showProgressBar() - presenter.retryRequest() + presenter.retryPage() } } } @@ -469,16 +429,16 @@ class CatalogueFragment : BaseRxFragment(), FlexibleViewHold * @param position the position of the element clicked. */ override fun onListItemLongClick(position: Int) { - val selectedManga = adapter.getItem(position) + val manga = adapter.getItem(position) ?: return - val textRes = if (selectedManga.favorite) R.string.remove_from_library else R.string.add_to_library + val textRes = if (manga.favorite) R.string.remove_from_library else R.string.add_to_library MaterialDialog.Builder(activity) .items(getString(textRes)) .itemsCallback { dialog, itemView, which, text -> when (which) { 0 -> { - presenter.changeMangaFavorite(selectedManga) + presenter.changeMangaFavorite(manga) adapter.notifyItemChanged(position) } } 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 e80ef2609..a3dbe9b5d 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 @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.data.source.EN import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.data.source.base.Source import eu.kanade.tachiyomi.data.source.model.MangasPage @@ -51,12 +52,13 @@ class CataloguePresenter : BasePresenter() { /** * Query from the view. */ - private var query: String? = null + var query: String? = null + private set /** * Pager containing a list of manga results. */ - private lateinit var pager: RxPager + private var pager = RxPager() /** * Last fetched page from network. @@ -76,45 +78,36 @@ class CataloguePresenter : BasePresenter() { companion object { /** - * Id of the restartable that delivers a list of manga from network. + * Id of the restartable that delivers a list of manga. */ - const val GET_MANGA_LIST = 1 + const val PAGER = 1 /** - * Id of the restartable that requests the list of manga from network. + * Id of the restartable that requests a page of manga from network. */ - const val GET_MANGA_PAGE = 2 + const val REQUEST_PAGE = 2 /** - * Id of the restartable that initializes the details of a manga. + * Id of the restartable that initializes the details of manga. */ - const val GET_MANGA_DETAIL = 3 + const val GET_MANGA_DETAILS = 3 /** - * Key to save and restore [source] from a [Bundle]. + * Key to save and restore [query] from a [Bundle]. */ - const val ACTIVE_SOURCE_KEY = "active_source" + const val QUERY_KEY = "query_key" } override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) + source = getLastUsedSource() + if (savedState != null) { - source = sourceManager.get(savedState.getInt(ACTIVE_SOURCE_KEY))!! + query = savedState.getString(QUERY_KEY) } - pager = RxPager() - - startableReplay(GET_MANGA_LIST, - { pager.results() }, - { view, pair -> view.onAddPage(pair.first, pair.second) }) - - startableFirst(GET_MANGA_PAGE, - { pager.request { page -> getMangasPageObservable(page + 1) } }, - { view, next -> }, - { view, error -> view.onAddPageError(error) }) - - startableLatestCache(GET_MANGA_DETAIL, + startableLatestCache(GET_MANGA_DETAILS, { mangaDetailSubject.observeOn(Schedulers.io()) .flatMap { Observable.from(it) } .filter { !it.initialized } @@ -126,10 +119,22 @@ class CataloguePresenter : BasePresenter() { add(prefs.catalogueAsList().asObservable() .subscribe { setDisplayMode(it) }) + + startableReplay(PAGER, + { pager.results() }, + { view, pair -> view.onAddPage(pair.first, pair.second) }) + + startableFirst(REQUEST_PAGE, + { pager.request { page -> getMangasPageObservable(page + 1) } }, + { view, next -> }, + { view, error -> view.onAddPageError(error) }) + + start(PAGER) + start(REQUEST_PAGE) } override fun onSave(state: Bundle) { - state.putInt(ACTIVE_SOURCE_KEY, source.id) + state.putString(QUERY_KEY, query) super.onSave(state) } @@ -141,37 +146,38 @@ class CataloguePresenter : BasePresenter() { private fun setDisplayMode(asList: Boolean) { isListMode = asList if (asList) { - stop(GET_MANGA_DETAIL) + stop(GET_MANGA_DETAILS) } else { - start(GET_MANGA_DETAIL) + start(GET_MANGA_DETAILS) } } /** - * Starts the request with the given source. + * Sets the active source and restarts the pager. * - * @param source the active source. + * @param source the new active source. */ - fun startRequesting(source: Source) { + fun setActiveSource(source: Source) { + prefs.lastUsedCatalogueSource().set(source.id) this.source = source - restartRequest(null) + restartPager(null) } /** - * Restarts the request for the active source with a query. + * Restarts the request for the active source. * - * @param query a query, or null if searching popular manga. + * @param query the query, or null if searching popular manga. */ - fun restartRequest(query: String?) { + fun restartPager(query: String?) { this.query = query - stop(GET_MANGA_PAGE) + stop(REQUEST_PAGE) lastMangasPage = null if (!isListMode) { - start(GET_MANGA_DETAIL) + start(GET_MANGA_DETAILS) } - start(GET_MANGA_LIST) - start(GET_MANGA_PAGE) + start(PAGER) + start(REQUEST_PAGE) } /** @@ -179,15 +185,22 @@ class CataloguePresenter : BasePresenter() { */ fun requestNext() { if (hasNextPage()) { - start(GET_MANGA_PAGE) + start(REQUEST_PAGE) } } /** - * Retry a failed request. + * Returns true if the last fetched page has a next page. */ - fun retryRequest() { - start(GET_MANGA_PAGE) + fun hasNextPage(): Boolean { + return lastMangasPage?.nextPageUrl != null + } + + /** + * Retries the current request that failed. + */ + fun retryPage() { + start(REQUEST_PAGE) } /** @@ -202,12 +215,12 @@ class CataloguePresenter : BasePresenter() { nextMangasPage.url = lastMangasPage!!.nextPageUrl } - val obs = if (query.isNullOrEmpty()) + val observable = if (query.isNullOrEmpty()) source.pullPopularMangasFromNetwork(nextMangasPage) else source.searchMangasFromNetwork(nextMangasPage, query!!) - return obs.subscribeOn(Schedulers.io()) + return observable.subscribeOn(Schedulers.io()) .doOnNext { lastMangasPage = it } .flatMap { Observable.from(it.mangas) } .map { networkToLocalManga(it) } @@ -259,23 +272,17 @@ class CataloguePresenter : BasePresenter() { } /** - * Returns true if the last fetched page has a next page. - */ - fun hasNextPage(): Boolean { - return lastMangasPage?.nextPageUrl != null - } - - /** - * Gets the last used source from preferences, or the first valid source. + * Returns the last used source from preferences or the first valid source. * - * @return the index of the last used source. + * @return a source. */ - fun getLastUsedSourceIndex(): Int { - val index = prefs.lastUsedCatalogueSource().get() ?: -1 - if (index < 0 || index >= sources.size || !isValidSource(sources[index])) { + fun getLastUsedSource(): Source { + val id = prefs.lastUsedCatalogueSource().get() ?: -1 + val source = sourceManager.get(id) + if (!isValidSource(source)) { return findFirstValidSource() } - return index + return source!! } /** @@ -284,11 +291,16 @@ class CataloguePresenter : BasePresenter() { * @param source the source to check. * @return true if the source is valid, false otherwise. */ - fun isValidSource(source: Source): Boolean = with(source) { - if (!isLoginRequired || isLogged) - return true + fun isValidSource(source: Source?): Boolean { + if (source == null) return false - prefs.sourceUsername(this) != "" && prefs.sourcePassword(this) != "" + return with(source) { + if (!isLoginRequired || isLogged) { + true + } else { + prefs.sourceUsername(this) != "" && prefs.sourcePassword(this) != "" + } + } } /** @@ -296,17 +308,8 @@ class CataloguePresenter : BasePresenter() { * * @return the index of the first valid source. */ - fun findFirstValidSource(): Int { - return sources.indexOfFirst { isValidSource(it) } - } - - /** - * Sets the enabled source. - * - * @param index the index of the source in [sources]. - */ - fun setEnabledSource(index: Int) { - prefs.lastUsedCatalogueSource().set(index) + fun findFirstValidSource(): Source { + return sources.find { isValidSource(it) }!! } /** @@ -317,7 +320,7 @@ class CataloguePresenter : BasePresenter() { // Ensure at least one language if (languages.isEmpty()) { - languages.add("EN") + languages.add(EN.code) } return sourceManager.getSources() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt index 5222e3275..15a27b0a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoFragment.kt @@ -98,38 +98,40 @@ class MangaInfoFragment : BaseRxFragment() { val coverCache = presenter.coverCache val headers = presenter.source.glideHeaders - // Check if thumbnail_url is given. - manga.thumbnail_url?.let { url -> - if (manga.favorite) { - coverCache.saveOrLoadFromCache(url, headers) { - if (isResumed) { - Glide.with(context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .signature(StringSignature(it.lastModified().toString())) - .into(manga_cover) + // Set cover if it wasn't already. + if (manga_cover.drawable == null) { + manga.thumbnail_url?.let { url -> + if (manga.favorite) { + coverCache.saveOrLoadFromCache(url, headers) { + if (isResumed) { + Glide.with(context) + .load(it) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .signature(StringSignature(it.lastModified().toString())) + .into(manga_cover) - Glide.with(context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .centerCrop() - .signature(StringSignature(it.lastModified().toString())) - .into(backdrop) + Glide.with(context) + .load(it) + .diskCacheStrategy(DiskCacheStrategy.RESULT) + .centerCrop() + .signature(StringSignature(it.lastModified().toString())) + .into(backdrop) + } } - } - } else { - Glide.with(context) - .load(if (headers != null) GlideUrl(url, headers) else url) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .centerCrop() - .into(manga_cover) + } else { + Glide.with(context) + .load(if (headers != null) GlideUrl(url, headers) else url) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .centerCrop() + .into(manga_cover) - Glide.with(context) - .load(if (headers != null) GlideUrl(url, headers) else url) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .centerCrop() - .into(backdrop) + Glide.with(context) + .load(if (headers != null) GlideUrl(url, headers) else url) + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .centerCrop() + .into(backdrop) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt index 5ad5d423f..956f0bae7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/base/BaseReader.kt @@ -78,7 +78,7 @@ abstract class BaseReader : BaseFragment() { * Returns the active page. */ fun getActivePage(): Page { - return pages[currentPage] + return pages.getOrElse(currentPage) { pages[0] } } /**