mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Global Search (#849)
* Global Search * Cards are now independent of design by use of recycler. * Added local * Some attribute fixes + moved onclick to controller. * Lots of improvements to code * Reversed some stuff. Thanks API 16 * Code fixes * Performance improvements * Moved adapter creation to constructor * Small changes * Removed sources settings from settings menu. Added OnChangeListener in catalogue. Made setting icon visible if room. * bug fix * Code review part uno * Code review part uno-2 * Single recycler approach * Add last source used * Fix scroll state and some layout issues * Fix wrong item binding * Use data class for items * Calculate item position and count while binding * Fix background color with slices * Reuse slices. Fix card background. Flatten constraint layout * Fix global_search scroll issue * Store last state with global search * Minor changes * Remove catalogue toolbar spinner. Persist catalogue across process restarts * Save view state of recycler views. Set toolbar title with current query
This commit is contained in:
		
				
					committed by
					
						
						inorichi
					
				
			
			
				
	
			
			
			
						parent
						
							56bde40035
						
					
				
				
					commit
					54c8b3ef29
				
			@@ -191,6 +191,7 @@ dependencies {
 | 
			
		||||
    compile 'com.afollestad.material-dialogs:core:0.9.4.5'
 | 
			
		||||
    compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
 | 
			
		||||
    compile 'com.nightlynexus.viewstatepageradapter:viewstatepageradapter:1.0.4'
 | 
			
		||||
    compile 'com.github.mthli:Slice:v1.2'
 | 
			
		||||
 | 
			
		||||
    // Conductor
 | 
			
		||||
    compile "com.bluelinelabs:conductor:2.1.4"
 | 
			
		||||
@@ -275,4 +276,4 @@ afterEvaluate {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ abstract class BaseController(bundle: Bundle? = null) : RestoreViewOnCreateContr
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setTitle() {
 | 
			
		||||
    fun setTitle() {
 | 
			
		||||
        var parentController = parentController
 | 
			
		||||
        while (parentController != null) {
 | 
			
		||||
            if (parentController is BaseController && parentController.getTitle() != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ import nucleus.factory.PresenterFactory
 | 
			
		||||
import nucleus.presenter.Presenter
 | 
			
		||||
 | 
			
		||||
@Suppress("LeakingThis")
 | 
			
		||||
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) : RxController(),
 | 
			
		||||
abstract class NucleusController<P : Presenter<*>>(val bundle: Bundle? = null) : RxController(bundle),
 | 
			
		||||
        PresenterFactory<P> {
 | 
			
		||||
 | 
			
		||||
    private val delegate = NucleusConductorDelegate(this)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,24 +4,20 @@ 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.app.AppCompatActivity
 | 
			
		||||
import android.support.v7.widget.*
 | 
			
		||||
import android.view.*
 | 
			
		||||
import android.widget.AdapterView
 | 
			
		||||
import android.widget.ArrayAdapter
 | 
			
		||||
import android.widget.Spinner
 | 
			
		||||
import com.afollestad.materialdialogs.MaterialDialog
 | 
			
		||||
import com.bluelinelabs.conductor.RouterTransaction
 | 
			
		||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
 | 
			
		||||
import com.f2prateek.rx.preferences.Preference
 | 
			
		||||
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
 | 
			
		||||
import com.jakewharton.rxbinding.widget.itemSelections
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.IFlexible
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
 | 
			
		||||
@@ -43,7 +39,7 @@ import java.util.concurrent.TimeUnit
 | 
			
		||||
/**
 | 
			
		||||
 * Controller to manage the catalogues available in the app.
 | 
			
		||||
 */
 | 
			
		||||
open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
open class CatalogueController(bundle: Bundle) :
 | 
			
		||||
        NucleusController<CataloguePresenter>(bundle),
 | 
			
		||||
        SecondaryDrawerController,
 | 
			
		||||
        FlexibleAdapter.OnItemClickListener,
 | 
			
		||||
@@ -51,6 +47,10 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
        FlexibleAdapter.EndlessScrollListener<ProgressItem>,
 | 
			
		||||
        ChangeMangaCategoriesDialog.Listener {
 | 
			
		||||
 | 
			
		||||
    constructor(source: CatalogueSource) : this(Bundle().apply {
 | 
			
		||||
        putLong(SOURCE_ID_KEY, source.id)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Preferences helper.
 | 
			
		||||
     */
 | 
			
		||||
@@ -61,11 +61,6 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
     */
 | 
			
		||||
    private var adapter: FlexibleAdapter<IFlexible<*>>? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Spinner shown in the toolbar to change the selected source.
 | 
			
		||||
     */
 | 
			
		||||
    private var spinner: Spinner? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Snackbar containing an error message when a request fails.
 | 
			
		||||
     */
 | 
			
		||||
@@ -81,26 +76,24 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
     */
 | 
			
		||||
    private var recycler: RecyclerView? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Drawer listener to allow swipe only for closing the drawer.
 | 
			
		||||
     */
 | 
			
		||||
    private var drawerListener: DrawerLayout.DrawerListener? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Query of the search box.
 | 
			
		||||
     */
 | 
			
		||||
    private val query: String
 | 
			
		||||
        get() = presenter.query
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Selected index of the spinner (selected source).
 | 
			
		||||
     */
 | 
			
		||||
    private var selectedIndex: Int = 0
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription for the search view.
 | 
			
		||||
     */
 | 
			
		||||
    private var searchViewSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription for the number of manga per row.
 | 
			
		||||
     */
 | 
			
		||||
    private var numColumnsSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Endless loading item.
 | 
			
		||||
     */
 | 
			
		||||
    private var progressItem: ProgressItem? = null
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
@@ -108,11 +101,11 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getTitle(): String? {
 | 
			
		||||
        return ""
 | 
			
		||||
        return presenter.source.toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun createPresenter(): CataloguePresenter {
 | 
			
		||||
        return CataloguePresenter()
 | 
			
		||||
        return CataloguePresenter(args.getLong(SOURCE_ID_KEY))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
 | 
			
		||||
@@ -126,54 +119,18 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
        adapter = FlexibleAdapter(null, this)
 | 
			
		||||
        setupRecycler(view)
 | 
			
		||||
 | 
			
		||||
        // Create toolbar spinner
 | 
			
		||||
        val themedContext = (activity as AppCompatActivity).supportActionBar?.themedContext
 | 
			
		||||
                ?: activity
 | 
			
		||||
 | 
			
		||||
        val spinnerAdapter = ArrayAdapter(themedContext,
 | 
			
		||||
                android.R.layout.simple_spinner_item, presenter.sources)
 | 
			
		||||
        spinnerAdapter.setDropDownViewResource(R.layout.common_spinner_item)
 | 
			
		||||
 | 
			
		||||
        val onItemSelected: (Int) -> Unit = { position ->
 | 
			
		||||
            val source = spinnerAdapter.getItem(position)
 | 
			
		||||
            if (!presenter.isValidSource(source)) {
 | 
			
		||||
                spinner?.setSelection(selectedIndex)
 | 
			
		||||
                activity?.toast(R.string.source_requires_login)
 | 
			
		||||
            } else if (source != presenter.source) {
 | 
			
		||||
                selectedIndex = position
 | 
			
		||||
                showProgressBar()
 | 
			
		||||
                adapter?.clear()
 | 
			
		||||
                presenter.setActiveSource(source)
 | 
			
		||||
                navView?.setFilters(presenter.filterItems)
 | 
			
		||||
                activity?.invalidateOptionsMenu()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        selectedIndex = presenter.sources.indexOf(presenter.source)
 | 
			
		||||
 | 
			
		||||
        spinner = Spinner(themedContext).apply {
 | 
			
		||||
            adapter = spinnerAdapter
 | 
			
		||||
            setSelection(selectedIndex)
 | 
			
		||||
            itemSelections()
 | 
			
		||||
                    .skip(1)
 | 
			
		||||
                    .filter { it != AdapterView.INVALID_POSITION }
 | 
			
		||||
                    .subscribeUntilDestroy { onItemSelected(it) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        activity?.toolbar?.addView(spinner)
 | 
			
		||||
        navView?.setFilters(presenter.filterItems)
 | 
			
		||||
 | 
			
		||||
        view.progress?.visible()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView(view: View) {
 | 
			
		||||
        super.onDestroyView(view)
 | 
			
		||||
        activity?.toolbar?.removeView(spinner)
 | 
			
		||||
        numColumnsSubscription?.unsubscribe()
 | 
			
		||||
        numColumnsSubscription = null
 | 
			
		||||
        searchViewSubscription?.unsubscribe()
 | 
			
		||||
        searchViewSubscription = null
 | 
			
		||||
        adapter = null
 | 
			
		||||
        spinner = null
 | 
			
		||||
        snack = null
 | 
			
		||||
        recycler = null
 | 
			
		||||
    }
 | 
			
		||||
@@ -265,6 +222,7 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
        menu.findItem(R.id.action_search).apply {
 | 
			
		||||
            val searchView = actionView as SearchView
 | 
			
		||||
 | 
			
		||||
            val query = presenter.query
 | 
			
		||||
            if (!query.isBlank()) {
 | 
			
		||||
                expandActionView()
 | 
			
		||||
                searchView.setQuery(query, true)
 | 
			
		||||
@@ -328,7 +286,7 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
     */
 | 
			
		||||
    private fun searchWithQuery(newQuery: String) {
 | 
			
		||||
        // If text didn't change, do nothing
 | 
			
		||||
        if (query == newQuery)
 | 
			
		||||
        if (presenter.query == newQuery)
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        // FIXME dirty fix to restore the toolbar buttons after closing search mode.
 | 
			
		||||
@@ -447,9 +405,9 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
     */
 | 
			
		||||
    fun getColumnsPreferenceForCurrentOrientation(): Preference<Int> {
 | 
			
		||||
        return if (resources?.configuration?.orientation == Configuration.ORIENTATION_PORTRAIT)
 | 
			
		||||
            presenter.prefs.portraitColumns()
 | 
			
		||||
            preferences.portraitColumns()
 | 
			
		||||
        else
 | 
			
		||||
            presenter.prefs.landscapeColumns()
 | 
			
		||||
            preferences.landscapeColumns()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -558,4 +516,8 @@ open class CatalogueController(bundle: Bundle? = null) :
 | 
			
		||||
        presenter.updateMangaCategories(manga, categories)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected companion object {
 | 
			
		||||
        const val SOURCE_ID_KEY = "sourceId"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,15 +9,11 @@ import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.LocalSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Filter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LoginSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.filter.*
 | 
			
		||||
import rx.Observable
 | 
			
		||||
@@ -33,22 +29,17 @@ import uy.kohesive.injekt.api.get
 | 
			
		||||
 * Presenter of [CatalogueController].
 | 
			
		||||
 */
 | 
			
		||||
open class CataloguePresenter(
 | 
			
		||||
        val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
        val db: DatabaseHelper = Injekt.get(),
 | 
			
		||||
        val prefs: PreferencesHelper = Injekt.get(),
 | 
			
		||||
        val coverCache: CoverCache = Injekt.get()
 | 
			
		||||
        sourceId: Long,
 | 
			
		||||
        sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
        private val db: DatabaseHelper = Injekt.get(),
 | 
			
		||||
        private val prefs: PreferencesHelper = Injekt.get(),
 | 
			
		||||
        private val coverCache: CoverCache = Injekt.get()
 | 
			
		||||
) : BasePresenter<CatalogueController>() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enabled sources.
 | 
			
		||||
     * Selected source.
 | 
			
		||||
     */
 | 
			
		||||
    val sources by lazy { getEnabledSources() }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Active source.
 | 
			
		||||
     */
 | 
			
		||||
    lateinit var source: CatalogueSource
 | 
			
		||||
        private set
 | 
			
		||||
    val source = sourceManager.get(sourceId) as CatalogueSource
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Query from the view.
 | 
			
		||||
@@ -106,7 +97,6 @@ open class CataloguePresenter(
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        source = getLastUsedSource()
 | 
			
		||||
        sourceFilters = source.getFilterList()
 | 
			
		||||
 | 
			
		||||
        if (savedState != null) {
 | 
			
		||||
@@ -149,9 +139,9 @@ open class CataloguePresenter(
 | 
			
		||||
                .doOnNext { initializeMangas(it.second) }
 | 
			
		||||
                .map { it.first to it.second.map(::CatalogueItem) }
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribeReplay({ view, pair ->
 | 
			
		||||
                    view.onAddPage(pair.first, pair.second)
 | 
			
		||||
                }, { view, error ->
 | 
			
		||||
                .subscribeReplay({ view, (page, mangas) ->
 | 
			
		||||
                    view.onAddPage(page, mangas)
 | 
			
		||||
                }, { _, error ->
 | 
			
		||||
                    Timber.e(error)
 | 
			
		||||
                })
 | 
			
		||||
 | 
			
		||||
@@ -167,7 +157,7 @@ open class CataloguePresenter(
 | 
			
		||||
 | 
			
		||||
        pageSubscription?.let { remove(it) }
 | 
			
		||||
        pageSubscription = Observable.defer { pager.requestNext() }
 | 
			
		||||
                .subscribeFirst({ view, page ->
 | 
			
		||||
                .subscribeFirst({ _, _ ->
 | 
			
		||||
                    // Nothing to do when onNext is emitted.
 | 
			
		||||
                }, CatalogueController::onAddPageError)
 | 
			
		||||
    }
 | 
			
		||||
@@ -179,19 +169,6 @@ open class CataloguePresenter(
 | 
			
		||||
        return pager.hasNextPage
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the active source and restarts the pager.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source the new active source.
 | 
			
		||||
     */
 | 
			
		||||
    fun setActiveSource(source: CatalogueSource) {
 | 
			
		||||
        prefs.lastUsedCatalogueSource().set(source.id)
 | 
			
		||||
        this.source = source
 | 
			
		||||
        sourceFilters = source.getFilterList()
 | 
			
		||||
 | 
			
		||||
        restartPager(query = "", filters = FilterList())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the display mode.
 | 
			
		||||
     *
 | 
			
		||||
@@ -267,50 +244,6 @@ open class CataloguePresenter(
 | 
			
		||||
                .onErrorResumeNext { Observable.just(manga) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the last used source from preferences or the first valid source.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a source.
 | 
			
		||||
     */
 | 
			
		||||
    fun getLastUsedSource(): CatalogueSource {
 | 
			
		||||
        val id = prefs.lastUsedCatalogueSource().get() ?: -1
 | 
			
		||||
        val source = sourceManager.get(id)
 | 
			
		||||
        if (!isValidSource(source) || source !in sources) {
 | 
			
		||||
            return sources.first { isValidSource(it) }
 | 
			
		||||
        }
 | 
			
		||||
        return source as CatalogueSource
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the given source is valid.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source the source to check.
 | 
			
		||||
     * @return true if the source is valid, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    open fun isValidSource(source: Source?): Boolean {
 | 
			
		||||
        if (source == null) return false
 | 
			
		||||
 | 
			
		||||
        if (source is LoginSource) {
 | 
			
		||||
            return source.isLogged() ||
 | 
			
		||||
                    (prefs.sourceUsername(source) != "" && prefs.sourcePassword(source) != "")
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of enabled sources ordered by language and name.
 | 
			
		||||
     */
 | 
			
		||||
    open protected fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
        val languages = prefs.enabledLanguages().getOrDefault()
 | 
			
		||||
        val hiddenCatalogues = prefs.hiddenCatalogues().getOrDefault()
 | 
			
		||||
 | 
			
		||||
        return sourceManager.getCatalogueSources()
 | 
			
		||||
                .filter { it.lang in languages }
 | 
			
		||||
                .filterNot { it.id.toString() in hiddenCatalogues }
 | 
			
		||||
                .sortedBy { "(${it.lang}) ${it.name}" } +
 | 
			
		||||
                sourceManager.get(LocalSource.ID) as LocalSource
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds or removes a manga from the library.
 | 
			
		||||
     *
 | 
			
		||||
@@ -370,13 +303,12 @@ open class CataloguePresenter(
 | 
			
		||||
                }
 | 
			
		||||
                is Filter.Sort -> {
 | 
			
		||||
                    val group = SortGroup(it)
 | 
			
		||||
                    val subItems = it.values.mapNotNull {
 | 
			
		||||
                    val subItems = it.values.map {
 | 
			
		||||
                        SortItem(it, group)
 | 
			
		||||
                    }
 | 
			
		||||
                    group.subItems = subItems
 | 
			
		||||
                    group
 | 
			
		||||
                }
 | 
			
		||||
                else -> null
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -407,7 +339,7 @@ open class CataloguePresenter(
 | 
			
		||||
     * @param categories the selected categories.
 | 
			
		||||
     * @param manga the manga to move.
 | 
			
		||||
     */
 | 
			
		||||
    fun moveMangaToCategories(manga: Manga, categories: List<Category>) {
 | 
			
		||||
    private fun moveMangaToCategories(manga: Manga, categories: List<Category>) {
 | 
			
		||||
        val mc = categories.filter { it.id != 0 }.map { MangaCategory.create(manga, it) }
 | 
			
		||||
        db.setMangaCategories(mc, listOf(manga))
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,74 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.os.Parcelable
 | 
			
		||||
import android.support.v7.widget.RecyclerView
 | 
			
		||||
import android.util.SparseArray
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adapter that holds the search cards.
 | 
			
		||||
 *
 | 
			
		||||
 * @param controller instance of [CatalogueSearchController].
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchAdapter(val controller: CatalogueSearchController) :
 | 
			
		||||
        FlexibleAdapter<CatalogueSearchItem>(null, controller, true) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Bundle where the view state of the holders is saved.
 | 
			
		||||
     */
 | 
			
		||||
    private var bundle = Bundle()
 | 
			
		||||
 | 
			
		||||
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any?>?) {
 | 
			
		||||
        super.onBindViewHolder(holder, position, payloads)
 | 
			
		||||
        restoreHolderState(holder)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
 | 
			
		||||
        super.onViewRecycled(holder)
 | 
			
		||||
        saveHolderState(holder, bundle)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onSaveInstanceState(outState: Bundle) {
 | 
			
		||||
        val holdersBundle = Bundle()
 | 
			
		||||
        allBoundViewHolders.forEach { saveHolderState(it, holdersBundle) }
 | 
			
		||||
        outState.putBundle(HOLDER_BUNDLE_KEY, holdersBundle)
 | 
			
		||||
        super.onSaveInstanceState(outState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
 | 
			
		||||
        super.onRestoreInstanceState(savedInstanceState)
 | 
			
		||||
        bundle = savedInstanceState.getBundle(HOLDER_BUNDLE_KEY)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the view state of the given holder.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holder The holder to save.
 | 
			
		||||
     * @param outState The bundle where the state is saved.
 | 
			
		||||
     */
 | 
			
		||||
    private fun saveHolderState(holder: RecyclerView.ViewHolder, outState: Bundle) {
 | 
			
		||||
        val key = "holder_${holder.adapterPosition}"
 | 
			
		||||
        val holderState = SparseArray<Parcelable>()
 | 
			
		||||
        holder.itemView.saveHierarchyState(holderState)
 | 
			
		||||
        outState.putSparseParcelableArray(key, holderState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Restores the view state of the given holder.
 | 
			
		||||
     *
 | 
			
		||||
     * @param holder The holder to restore.
 | 
			
		||||
     */
 | 
			
		||||
    private fun restoreHolderState(holder: RecyclerView.ViewHolder) {
 | 
			
		||||
        val key = "holder_${holder.adapterPosition}"
 | 
			
		||||
        val holderState = bundle.getSparseParcelableArray<Parcelable>(key)
 | 
			
		||||
        if (holderState != null) {
 | 
			
		||||
            holder.itemView.restoreHierarchyState(holderState)
 | 
			
		||||
            bundle.remove(key)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private companion object {
 | 
			
		||||
        const val HOLDER_BUNDLE_KEY = "holder_bundle"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adapter that holds the manga items from search results.
 | 
			
		||||
 *
 | 
			
		||||
 * @param controller instance of [CatalogueSearchController].
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchCardAdapter(controller: CatalogueSearchController) :
 | 
			
		||||
        FlexibleAdapter<CatalogueSearchCardItem>(null, controller, true) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listen for browse item clicks.
 | 
			
		||||
     */
 | 
			
		||||
    val mangaClickListener: OnMangaClickListener = controller
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener which should be called when user clicks browse.
 | 
			
		||||
     * Note: Should only be handled by [CatalogueSearchController]
 | 
			
		||||
     */
 | 
			
		||||
    interface OnMangaClickListener {
 | 
			
		||||
        fun onMangaClick(manga: Manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,43 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.view.View
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
 | 
			
		||||
import eu.davidea.viewholders.FlexibleViewHolder
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_global_search_controller_card_item.view.*
 | 
			
		||||
 | 
			
		||||
class CatalogueSearchCardHolder(view: View, adapter: CatalogueSearchCardAdapter)
 | 
			
		||||
    : FlexibleViewHolder(view, adapter) {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        // Call onMangaClickListener when item is pressed.
 | 
			
		||||
        itemView.setOnClickListener {
 | 
			
		||||
            val item = adapter.getItem(adapterPosition)
 | 
			
		||||
            if (item != null) {
 | 
			
		||||
                adapter.mangaClickListener.onMangaClick(item.manga)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun bind(manga: Manga) {
 | 
			
		||||
        itemView.tvTitle.text = manga.title
 | 
			
		||||
 | 
			
		||||
        setImage(manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setImage(manga: Manga) {
 | 
			
		||||
        Glide.clear(itemView.itemImage)
 | 
			
		||||
        if (!manga.thumbnail_url.isNullOrEmpty()) {
 | 
			
		||||
            Glide.with(itemView.context)
 | 
			
		||||
                    .load(manga)
 | 
			
		||||
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE)
 | 
			
		||||
                    .centerCrop()
 | 
			
		||||
                    .skipMemoryCache(true)
 | 
			
		||||
                    .placeholder(android.R.color.transparent)
 | 
			
		||||
                    .into(StateImageViewTarget(itemView.itemImage, itemView.progress))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,38 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.util.inflate
 | 
			
		||||
 | 
			
		||||
class CatalogueSearchCardItem(val manga: Manga) : AbstractFlexibleItem<CatalogueSearchCardHolder>() {
 | 
			
		||||
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.catalogue_global_search_controller_card_item
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater,
 | 
			
		||||
                                  parent: ViewGroup): CatalogueSearchCardHolder {
 | 
			
		||||
        return CatalogueSearchCardHolder(parent.inflate(layoutRes), adapter as CatalogueSearchCardAdapter)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: CatalogueSearchCardHolder,
 | 
			
		||||
                                position: Int, payloads: List<Any?>?) {
 | 
			
		||||
        holder.bind(manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun equals(other: Any?): Boolean {
 | 
			
		||||
        if (other is CatalogueSearchCardItem) {
 | 
			
		||||
            return manga.id == other.manga.id
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        return manga.id?.toInt() ?: 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,171 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.support.v7.widget.LinearLayoutManager
 | 
			
		||||
import android.support.v7.widget.SearchView
 | 
			
		||||
import android.view.*
 | 
			
		||||
import com.bluelinelabs.conductor.RouterTransaction
 | 
			
		||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
 | 
			
		||||
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_global_search_controller.view.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This controller shows and manages the different search result in global search.
 | 
			
		||||
 * This controller should only handle UI actions, IO actions should be done by [CatalogueSearchPresenter]
 | 
			
		||||
 * [CatalogueSearchCardAdapter.OnMangaClickListener] called when manga is clicked in global search
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchController(private val initialQuery: String? = null) :
 | 
			
		||||
        NucleusController<CatalogueSearchPresenter>(),
 | 
			
		||||
        CatalogueSearchCardAdapter.OnMangaClickListener {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adapter containing search results grouped by lang.
 | 
			
		||||
     */
 | 
			
		||||
    private var adapter: CatalogueSearchAdapter? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when controller is initialized.
 | 
			
		||||
     */
 | 
			
		||||
    init {
 | 
			
		||||
        setHasOptionsMenu(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiate the view with [R.layout.catalogue_global_search_controller].
 | 
			
		||||
     *
 | 
			
		||||
     * @param inflater used to load the layout xml.
 | 
			
		||||
     * @param container containing parent views.
 | 
			
		||||
     * @return inflated view
 | 
			
		||||
     */
 | 
			
		||||
    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): android.view.View {
 | 
			
		||||
        return inflater.inflate(R.layout.catalogue_global_search_controller, container, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the title of controller.
 | 
			
		||||
     *
 | 
			
		||||
     * @return title.
 | 
			
		||||
     */
 | 
			
		||||
    override fun getTitle(): String? {
 | 
			
		||||
        return presenter.query
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create the [CatalogueSearchPresenter] used in controller.
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of [CatalogueSearchPresenter]
 | 
			
		||||
     */
 | 
			
		||||
    override fun createPresenter(): CatalogueSearchPresenter {
 | 
			
		||||
        return CatalogueSearchPresenter(initialQuery)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when manga in global search is clicked, opens manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga clicked item containing manga information.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onMangaClick(manga: Manga) {
 | 
			
		||||
        // Open MangaController.
 | 
			
		||||
        router.pushController(RouterTransaction.with(MangaController(manga, true))
 | 
			
		||||
                .pushChangeHandler(FadeChangeHandler())
 | 
			
		||||
                .popChangeHandler(FadeChangeHandler()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds items to the options menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param menu menu containing options.
 | 
			
		||||
     * @param inflater used to load the menu xml.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        // Inflate menu.
 | 
			
		||||
        inflater.inflate(R.menu.catalogue_new_list, menu)
 | 
			
		||||
 | 
			
		||||
        // Initialize search menu
 | 
			
		||||
        val searchItem = menu.findItem(R.id.action_search)
 | 
			
		||||
        val searchView = searchItem.actionView as SearchView
 | 
			
		||||
        searchView.queryTextChangeEvents()
 | 
			
		||||
                .filter { it.isSubmitted }
 | 
			
		||||
                .subscribeUntilDestroy {
 | 
			
		||||
                    presenter.search(it.queryText().toString())
 | 
			
		||||
                    searchItem.collapseActionView()
 | 
			
		||||
                    setTitle() // Update toolbar title
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the view is created
 | 
			
		||||
     *
 | 
			
		||||
     * @param view view of controller
 | 
			
		||||
     * @param savedViewState information from previous state.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onViewCreated(view: View, savedViewState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedViewState)
 | 
			
		||||
 | 
			
		||||
        adapter = CatalogueSearchAdapter(this)
 | 
			
		||||
 | 
			
		||||
        with(view) {
 | 
			
		||||
            // Create recycler and set adapter.
 | 
			
		||||
            recycler.layoutManager = LinearLayoutManager(context)
 | 
			
		||||
            recycler.adapter = adapter
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView(view: View) {
 | 
			
		||||
        adapter = null
 | 
			
		||||
        super.onDestroyView(view)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onSaveViewState(view: View, outState: Bundle) {
 | 
			
		||||
        super.onSaveViewState(view, outState)
 | 
			
		||||
        adapter?.onSaveInstanceState(outState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRestoreViewState(view: View, savedViewState: Bundle) {
 | 
			
		||||
        super.onRestoreViewState(view, savedViewState)
 | 
			
		||||
        adapter?.onRestoreInstanceState(savedViewState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the view holder for the given manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source used to find holder containing source
 | 
			
		||||
     * @return the holder of the manga or null if it's not bound.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getHolder(source: CatalogueSource): CatalogueSearchHolder? {
 | 
			
		||||
        val adapter = adapter ?: return null
 | 
			
		||||
 | 
			
		||||
        adapter.allBoundViewHolders.forEach { holder ->
 | 
			
		||||
            val item = adapter.getItem(holder.adapterPosition)
 | 
			
		||||
            if (item != null && source.id == item.source.id) {
 | 
			
		||||
                return holder as CatalogueSearchHolder
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add search result to adapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param searchResult result of search.
 | 
			
		||||
     */
 | 
			
		||||
    fun setItems(searchResult: List<CatalogueSearchItem>) {
 | 
			
		||||
        adapter?.updateDataSet(searchResult)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called from the presenter when a manga is initialized.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the initialized manga.
 | 
			
		||||
     */
 | 
			
		||||
    fun onMangaInitialized(source: CatalogueSource, manga: Manga) {
 | 
			
		||||
        getHolder(source)?.setImage(manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,100 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.support.v7.widget.LinearLayoutManager
 | 
			
		||||
import android.view.View
 | 
			
		||||
import eu.davidea.viewholders.FlexibleViewHolder
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Manga
 | 
			
		||||
import eu.kanade.tachiyomi.util.getResourceColor
 | 
			
		||||
import eu.kanade.tachiyomi.util.gone
 | 
			
		||||
import eu.kanade.tachiyomi.util.setVectorCompat
 | 
			
		||||
import eu.kanade.tachiyomi.util.visible
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_global_search_controller_card.view.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Holder that binds the [CatalogueSearchItem] containing catalogue cards.
 | 
			
		||||
 *
 | 
			
		||||
 * @param view view of [CatalogueSearchItem]
 | 
			
		||||
 * @param adapter instance of [CatalogueSearchAdapter]
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchHolder(view: View, val adapter: CatalogueSearchAdapter) : FlexibleViewHolder(view, adapter) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adapter containing manga from search results.
 | 
			
		||||
     */
 | 
			
		||||
    private val mangaAdapter = CatalogueSearchCardAdapter(adapter.controller)
 | 
			
		||||
 | 
			
		||||
    private var lastBoundResults: List<CatalogueSearchCardItem>? = null
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        with(itemView) {
 | 
			
		||||
            // Set layout horizontal.
 | 
			
		||||
            recycler.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
 | 
			
		||||
            recycler.adapter = mangaAdapter
 | 
			
		||||
 | 
			
		||||
            nothing_found_icon.setVectorCompat(R.drawable.ic_search_black_112dp,
 | 
			
		||||
                    context.getResourceColor(android.R.attr.textColorHint))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show the loading of source search result.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item item of card.
 | 
			
		||||
     */
 | 
			
		||||
    fun bind(item: CatalogueSearchItem) {
 | 
			
		||||
        val source = item.source
 | 
			
		||||
        val results = item.results
 | 
			
		||||
 | 
			
		||||
        with(itemView) {
 | 
			
		||||
            // Set Title witch country code if available.
 | 
			
		||||
            title.text = if (!source.lang.isEmpty()) "${source.name} (${source.lang})" else source.name
 | 
			
		||||
 | 
			
		||||
            when {
 | 
			
		||||
                results == null -> {
 | 
			
		||||
                    progress.visible()
 | 
			
		||||
                    nothing_found.gone()
 | 
			
		||||
                }
 | 
			
		||||
                results.isEmpty() -> {
 | 
			
		||||
                    progress.gone()
 | 
			
		||||
                    nothing_found.visible()
 | 
			
		||||
                }
 | 
			
		||||
                else -> {
 | 
			
		||||
                    progress.gone()
 | 
			
		||||
                    nothing_found.gone()
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (results !== lastBoundResults) {
 | 
			
		||||
                mangaAdapter.updateDataSet(results)
 | 
			
		||||
                lastBoundResults = results
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called from the presenter when a manga is initialized.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the initialized manga.
 | 
			
		||||
     */
 | 
			
		||||
    fun setImage(manga: Manga) {
 | 
			
		||||
        getHolder(manga)?.setImage(manga)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the view holder for the given manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the manga to find.
 | 
			
		||||
     * @return the holder of the manga or null if it's not bound.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getHolder(manga: Manga): CatalogueSearchCardHolder? {
 | 
			
		||||
        mangaAdapter.allBoundViewHolders.forEach { holder ->
 | 
			
		||||
            val item = mangaAdapter.getItem(holder.adapterPosition)
 | 
			
		||||
            if (item != null && item.manga.id!! == manga.id!!) {
 | 
			
		||||
                return holder as CatalogueSearchCardHolder
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.inflate
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Item that contains search result information.
 | 
			
		||||
 *
 | 
			
		||||
 * @param source contains information about search result.
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchItem(val source: CatalogueSource, val results: List<CatalogueSearchCardItem>?)
 | 
			
		||||
    : AbstractFlexibleItem<CatalogueSearchHolder>() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return id of view
 | 
			
		||||
     */
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.catalogue_global_search_controller_card
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create view holder (see [CatalogueSearchAdapter].
 | 
			
		||||
     *
 | 
			
		||||
     * @return holder of view.
 | 
			
		||||
     */
 | 
			
		||||
    override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater,
 | 
			
		||||
                                  parent: ViewGroup): CatalogueSearchHolder {
 | 
			
		||||
        return CatalogueSearchHolder(parent.inflate(layoutRes), adapter as CatalogueSearchAdapter)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Bind item to view.
 | 
			
		||||
     */
 | 
			
		||||
    override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: CatalogueSearchHolder,
 | 
			
		||||
                                position: Int, payloads: List<Any?>?) {
 | 
			
		||||
        holder.bind(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Used to check if two items are equal.
 | 
			
		||||
     *
 | 
			
		||||
     * @return items are equal?
 | 
			
		||||
     */
 | 
			
		||||
    override fun equals(other: Any?): Boolean {
 | 
			
		||||
        if (other is CatalogueSearchItem) {
 | 
			
		||||
            return source.id == other.source.id
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return hash code of item.
 | 
			
		||||
     *
 | 
			
		||||
     * @return hashcode
 | 
			
		||||
     */
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        return source.id.toInt()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,215 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
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.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LoginSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import rx.Subscription
 | 
			
		||||
import rx.android.schedulers.AndroidSchedulers
 | 
			
		||||
import rx.schedulers.Schedulers
 | 
			
		||||
import rx.subjects.PublishSubject
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Presenter of [CatalogueSearchController]
 | 
			
		||||
 * Function calls should be done from here. UI calls should be done from the controller.
 | 
			
		||||
 *
 | 
			
		||||
 * @param sourceManager manages the different sources.
 | 
			
		||||
 * @param db manages the database calls.
 | 
			
		||||
 * @param preferencesHelper manages the preference calls.
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchPresenter(
 | 
			
		||||
        val initialQuery: String? = "",
 | 
			
		||||
        val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
        val db: DatabaseHelper = Injekt.get(),
 | 
			
		||||
        val preferencesHelper: PreferencesHelper = Injekt.get()
 | 
			
		||||
) : BasePresenter<CatalogueSearchController>() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    val sources by lazy { getEnabledSources() }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Query from the view.
 | 
			
		||||
     */
 | 
			
		||||
    var query = ""
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetches the different sources by user settings.
 | 
			
		||||
     */
 | 
			
		||||
    private var fetchSourcesSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subject which fetches image of given manga.
 | 
			
		||||
     */
 | 
			
		||||
    private val fetchImageSubject = PublishSubject.create<Pair<List<Manga>, Source>>()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription for fetching images of manga.
 | 
			
		||||
     */
 | 
			
		||||
    private var fetchImageSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        // Perform a search with previous or initial state
 | 
			
		||||
        search(savedState?.getString(CataloguePresenter::query.name) ?: initialQuery.orEmpty())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroy() {
 | 
			
		||||
        fetchSourcesSubscription?.unsubscribe()
 | 
			
		||||
        fetchImageSubscription?.unsubscribe()
 | 
			
		||||
        super.onDestroy()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onSave(state: Bundle) {
 | 
			
		||||
        state.putString(CataloguePresenter::query.name, query)
 | 
			
		||||
        super.onSave(state)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of enabled sources ordered by language and name.
 | 
			
		||||
     *
 | 
			
		||||
     * @return list containing enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
        val languages = preferencesHelper.enabledLanguages().getOrDefault()
 | 
			
		||||
        val hiddenCatalogues = preferencesHelper.hiddenCatalogues().getOrDefault()
 | 
			
		||||
 | 
			
		||||
        return sourceManager.getCatalogueSources()
 | 
			
		||||
                .filter { it.lang in languages }
 | 
			
		||||
                .filterNot { it is LoginSource && !it.isLogged() }
 | 
			
		||||
                .filterNot { it.id.toString() in hiddenCatalogues }
 | 
			
		||||
                .sortedBy { "(${it.lang}) ${it.name}" }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiates a search for mnaga per catalogue.
 | 
			
		||||
     *
 | 
			
		||||
     * @param query query on which to search.
 | 
			
		||||
     */
 | 
			
		||||
    fun search(query: String) {
 | 
			
		||||
        // Return if there's nothing to do
 | 
			
		||||
        if (this.query == query) return
 | 
			
		||||
 | 
			
		||||
        // Update query
 | 
			
		||||
        this.query = query
 | 
			
		||||
 | 
			
		||||
        // Create image fetch subscription
 | 
			
		||||
        initializeFetchImageSubscription()
 | 
			
		||||
 | 
			
		||||
        // Create items with the initial state
 | 
			
		||||
        val initialItems = sources.map { CatalogueSearchItem(it, null) }
 | 
			
		||||
        var items = initialItems
 | 
			
		||||
 | 
			
		||||
        fetchSourcesSubscription?.unsubscribe()
 | 
			
		||||
        fetchSourcesSubscription = Observable.from(sources)
 | 
			
		||||
                .observeOn(Schedulers.io())
 | 
			
		||||
                .flatMap { source ->
 | 
			
		||||
                    source.fetchSearchManga(1, query, FilterList())
 | 
			
		||||
                            .onExceptionResumeNext(Observable.empty()) // Ignore timeouts.
 | 
			
		||||
                            .map { it.mangas.take(10) } // Get at most 10 manga from search result.
 | 
			
		||||
                            .map { it.map { networkToLocalManga(it, source.id) } } // Convert to local manga.
 | 
			
		||||
                            .doOnNext { fetchImage(it, source) } // Load manga covers.
 | 
			
		||||
                            .map { CatalogueSearchItem(source, it.map { CatalogueSearchCardItem(it) }) }
 | 
			
		||||
                }
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                // Update matching source with the obtained results
 | 
			
		||||
                .map { result ->
 | 
			
		||||
                    items.map { item -> if (item.source == result.source) result else item }
 | 
			
		||||
                }
 | 
			
		||||
                // Update current state
 | 
			
		||||
                .doOnNext { items = it }
 | 
			
		||||
                // Deliver initial state
 | 
			
		||||
                .startWith(initialItems)
 | 
			
		||||
                .subscribeLatestCache({ view, manga ->
 | 
			
		||||
                    view.setItems(manga)
 | 
			
		||||
                }, { _, error ->
 | 
			
		||||
                    Timber.e(error)
 | 
			
		||||
                })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize a list of manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the list of manga to initialize.
 | 
			
		||||
     */
 | 
			
		||||
    private fun fetchImage(manga: List<Manga>, source: Source) {
 | 
			
		||||
        fetchImageSubject.onNext(Pair(manga, source))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscribes to the initializer of manga details and updates the view if needed.
 | 
			
		||||
     */
 | 
			
		||||
    private fun initializeFetchImageSubscription() {
 | 
			
		||||
        fetchImageSubscription?.unsubscribe()
 | 
			
		||||
        fetchImageSubscription = fetchImageSubject.observeOn(Schedulers.io())
 | 
			
		||||
                .flatMap {
 | 
			
		||||
                    val source = it.second
 | 
			
		||||
                    Observable.from(it.first).filter { it.thumbnail_url == null && !it.initialized }
 | 
			
		||||
                            .map { Pair(it, source) }
 | 
			
		||||
                            .concatMap { getMangaDetailsObservable(it.first, it.second) }
 | 
			
		||||
                            .map { Pair(source as CatalogueSource, it) }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                .onBackpressureBuffer()
 | 
			
		||||
                .observeOn(AndroidSchedulers.mainThread())
 | 
			
		||||
                .subscribe({ (source, manga) ->
 | 
			
		||||
                    @Suppress("DEPRECATION")
 | 
			
		||||
                    view?.onMangaInitialized(source, manga)
 | 
			
		||||
                }, { error ->
 | 
			
		||||
                    Timber.e(error)
 | 
			
		||||
                })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an observable of manga that initializes the given manga.
 | 
			
		||||
     *
 | 
			
		||||
     * @param manga the manga to initialize.
 | 
			
		||||
     * @return an observable of the manga to initialize
 | 
			
		||||
     */
 | 
			
		||||
    private fun getMangaDetailsObservable(manga: Manga, source: Source): Observable<Manga> {
 | 
			
		||||
        return source.fetchMangaDetails(manga)
 | 
			
		||||
                .flatMap { networkManga ->
 | 
			
		||||
                    manga.copyFrom(networkManga)
 | 
			
		||||
                    manga.initialized = true
 | 
			
		||||
                    db.insertManga(manga).executeAsBlocking()
 | 
			
		||||
                    Observable.just(manga)
 | 
			
		||||
                }
 | 
			
		||||
                .onErrorResumeNext { Observable.just(manga) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a manga from the database for the given manga from network. It creates a new entry
 | 
			
		||||
     * if the manga is not yet in the database.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sManga the manga from the source.
 | 
			
		||||
     * @return a manga from the database.
 | 
			
		||||
     */
 | 
			
		||||
    private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
 | 
			
		||||
        var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
 | 
			
		||||
        if (localManga == null) {
 | 
			
		||||
            val newManga = Manga.create(sManga.url, sManga.title, sourceId)
 | 
			
		||||
            newManga.copyFrom(sManga)
 | 
			
		||||
            val result = db.insertManga(newManga).executeAsBlocking()
 | 
			
		||||
            newManga.id = result.insertedId()
 | 
			
		||||
            localManga = newManga
 | 
			
		||||
        }
 | 
			
		||||
        return localManga
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.IFlexible
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.util.getResourceColor
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adapter that holds the catalogue cards.
 | 
			
		||||
 *
 | 
			
		||||
 * @param controller instance of [CatalogueMainController].
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueMainAdapter(val controller: CatalogueMainController) :
 | 
			
		||||
        FlexibleAdapter<IFlexible<*>>(null, controller, true) {
 | 
			
		||||
 | 
			
		||||
    val cardBackground = controller.activity!!.getResourceColor(R.attr.background_card)
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        setDisplayHeadersAtStartUp(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener for browse item clicks.
 | 
			
		||||
     */
 | 
			
		||||
    val browseClickListener: OnBrowseClickListener = controller
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener for latest item clicks.
 | 
			
		||||
     */
 | 
			
		||||
    val latestClickListener: OnLatestClickListener = controller
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener which should be called when user clicks browse.
 | 
			
		||||
     * Note: Should only be handled by [CatalogueMainController]
 | 
			
		||||
     */
 | 
			
		||||
    interface OnBrowseClickListener {
 | 
			
		||||
        fun onBrowseClick(position: Int)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener which should be called when user clicks latest.
 | 
			
		||||
     * Note: Should only be handled by [CatalogueMainController]
 | 
			
		||||
     */
 | 
			
		||||
    interface OnLatestClickListener {
 | 
			
		||||
        fun onLatestClick(position: Int)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,238 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.support.v7.widget.LinearLayoutManager
 | 
			
		||||
import android.support.v7.widget.SearchView
 | 
			
		||||
import android.view.*
 | 
			
		||||
import com.bluelinelabs.conductor.ControllerChangeHandler
 | 
			
		||||
import com.bluelinelabs.conductor.ControllerChangeType
 | 
			
		||||
import com.bluelinelabs.conductor.RouterTransaction
 | 
			
		||||
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
 | 
			
		||||
import com.jakewharton.rxbinding.support.v7.widget.queryTextChangeEvents
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.IFlexible
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LoginSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.latest_updates.LatestUpdatesController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
 | 
			
		||||
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_main_controller.view.*
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This controller shows and manages the different catalogues enabled by the user.
 | 
			
		||||
 * This controller should only handle UI actions, IO actions should be done by [CatalogueMainPresenter]
 | 
			
		||||
 * [SourceLoginDialog.Listener] refreshes the adapter on successful login of catalogues.
 | 
			
		||||
 * [CatalogueMainAdapter.OnBrowseClickListener] call function data on browse item click.
 | 
			
		||||
 * [CatalogueMainAdapter.OnLatestClickListener] call function data on latest item click
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueMainController : NucleusController<CatalogueMainPresenter>(),
 | 
			
		||||
        SourceLoginDialog.Listener,
 | 
			
		||||
        FlexibleAdapter.OnItemClickListener,
 | 
			
		||||
        CatalogueMainAdapter.OnBrowseClickListener,
 | 
			
		||||
        CatalogueMainAdapter.OnLatestClickListener {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Application preferences.
 | 
			
		||||
     */
 | 
			
		||||
    private val preferences: PreferencesHelper = Injekt.get()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adapter containing sources.
 | 
			
		||||
     */
 | 
			
		||||
    private var adapter : CatalogueMainAdapter? = null
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when controller is initialized.
 | 
			
		||||
     */
 | 
			
		||||
    init {
 | 
			
		||||
        // Enable the option menu
 | 
			
		||||
        setHasOptionsMenu(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the title of controller.
 | 
			
		||||
     *
 | 
			
		||||
     * @return title.
 | 
			
		||||
     */
 | 
			
		||||
    override fun getTitle(): String? {
 | 
			
		||||
        return applicationContext?.getString(R.string.label_catalogues)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create the [CatalogueMainPresenter] used in controller.
 | 
			
		||||
     *
 | 
			
		||||
     * @return instance of [CatalogueMainPresenter]
 | 
			
		||||
     */
 | 
			
		||||
    override fun createPresenter(): CatalogueMainPresenter {
 | 
			
		||||
        return CatalogueMainPresenter()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initiate the view with [R.layout.catalogue_main_controller].
 | 
			
		||||
     *
 | 
			
		||||
     * @param inflater used to load the layout xml.
 | 
			
		||||
     * @param container containing parent views.
 | 
			
		||||
     * @return inflated view.
 | 
			
		||||
     */
 | 
			
		||||
    override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
 | 
			
		||||
        return inflater.inflate(R.layout.catalogue_main_controller, container, false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the view is created
 | 
			
		||||
     *
 | 
			
		||||
     * @param view view of controller
 | 
			
		||||
     * @param savedViewState information from previous state.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onViewCreated(view: View, savedViewState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedViewState)
 | 
			
		||||
 | 
			
		||||
        adapter = CatalogueMainAdapter(this)
 | 
			
		||||
 | 
			
		||||
        with(view) {
 | 
			
		||||
            // Create recycler and set adapter.
 | 
			
		||||
            recycler.layoutManager = LinearLayoutManager(context)
 | 
			
		||||
            recycler.adapter = adapter
 | 
			
		||||
            recycler.addItemDecoration(SourceDividerItemDecoration(context))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDestroyView(view: View) {
 | 
			
		||||
        adapter = null
 | 
			
		||||
        super.onDestroyView(view)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
 | 
			
		||||
        super.onChangeStarted(handler, type)
 | 
			
		||||
        if (!type.isPush && handler is SettingsSourcesFadeChangeHandler) {
 | 
			
		||||
            presenter.updateSources()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when login dialog is closed, refreshes the adapter.
 | 
			
		||||
     *
 | 
			
		||||
     * @param source clicked item containing source information.
 | 
			
		||||
     */
 | 
			
		||||
    override fun loginDialogClosed(source: LoginSource) {
 | 
			
		||||
        if (source.isLogged()) {
 | 
			
		||||
            adapter?.clear()
 | 
			
		||||
            presenter.loadSources()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when item is clicked
 | 
			
		||||
     */
 | 
			
		||||
    override fun onItemClick(position: Int): Boolean {
 | 
			
		||||
        val item = adapter?.getItem(position) as? SourceItem ?: return false
 | 
			
		||||
        val source = item.source
 | 
			
		||||
        if (source is LoginSource && !source.isLogged()) {
 | 
			
		||||
            val dialog = SourceLoginDialog(source)
 | 
			
		||||
            dialog.targetController = this
 | 
			
		||||
            dialog.showDialog(router)
 | 
			
		||||
        } else {
 | 
			
		||||
            // Open the catalogue view.
 | 
			
		||||
            openCatalogue(source, CatalogueController(source))
 | 
			
		||||
        }
 | 
			
		||||
        return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when browse is clicked in [CatalogueMainAdapter]
 | 
			
		||||
     */
 | 
			
		||||
    override fun onBrowseClick(position: Int) {
 | 
			
		||||
        onItemClick(position)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when latest is clicked in [CatalogueMainAdapter]
 | 
			
		||||
     */
 | 
			
		||||
    override fun onLatestClick(position: Int) {
 | 
			
		||||
        val item = adapter?.getItem(position) as? SourceItem ?: return
 | 
			
		||||
        openCatalogue(item.source, LatestUpdatesController(item.source))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens a catalogue with the given controller.
 | 
			
		||||
     */
 | 
			
		||||
    private fun openCatalogue(source: CatalogueSource, controller: CatalogueController) {
 | 
			
		||||
        preferences.lastUsedCatalogueSource().set(source.id)
 | 
			
		||||
        router.pushController(RouterTransaction.with(controller)
 | 
			
		||||
                .popChangeHandler(FadeChangeHandler())
 | 
			
		||||
                .pushChangeHandler(FadeChangeHandler()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds items to the options menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param menu menu containing options.
 | 
			
		||||
     * @param inflater used to load the menu xml.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
 | 
			
		||||
        // Inflate menu
 | 
			
		||||
        inflater.inflate(R.menu.catalogue_main, menu)
 | 
			
		||||
 | 
			
		||||
        // Initialize search option.
 | 
			
		||||
        val searchItem = menu.findItem(R.id.action_search)
 | 
			
		||||
        val searchView = searchItem.actionView as SearchView
 | 
			
		||||
 | 
			
		||||
        // Change hint to show global search.
 | 
			
		||||
        searchView.queryHint = applicationContext?.getString(R.string.action_global_search_hint)
 | 
			
		||||
 | 
			
		||||
        // Create query listener which opens the global search view.
 | 
			
		||||
        searchView.queryTextChangeEvents()
 | 
			
		||||
                .filter { it.isSubmitted }
 | 
			
		||||
                .subscribeUntilDestroy {
 | 
			
		||||
                    val query = it.queryText().toString()
 | 
			
		||||
                    router.pushController((RouterTransaction.with(CatalogueSearchController(query)))
 | 
			
		||||
                            .popChangeHandler(FadeChangeHandler())
 | 
			
		||||
                            .pushChangeHandler(FadeChangeHandler()))
 | 
			
		||||
                }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when an option menu item has been selected by the user.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item The selected item.
 | 
			
		||||
     * @return True if this event has been consumed, false if it has not.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
 | 
			
		||||
        when (item.itemId) {
 | 
			
		||||
            // Initialize option to open catalogue settings.
 | 
			
		||||
            R.id.action_settings -> {
 | 
			
		||||
                router.pushController((RouterTransaction.with(SettingsSourcesController()))
 | 
			
		||||
                        .popChangeHandler(SettingsSourcesFadeChangeHandler())
 | 
			
		||||
                        .pushChangeHandler(FadeChangeHandler()))
 | 
			
		||||
            }
 | 
			
		||||
            else -> return super.onOptionsItemSelected(item)
 | 
			
		||||
        }
 | 
			
		||||
        return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called to update adapter containing sources.
 | 
			
		||||
     */
 | 
			
		||||
    fun setSources(sources: List<IFlexible<*>>) {
 | 
			
		||||
        adapter?.updateDataSet(sources.toMutableList())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called to set the last used catalogue at the top of the view.
 | 
			
		||||
     */
 | 
			
		||||
    fun setLastUsedSource(item: SourceItem?) {
 | 
			
		||||
        adapter?.removeAllScrollableHeaders()
 | 
			
		||||
        if (item != null) {
 | 
			
		||||
            adapter?.addScrollableHeader(item)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class SettingsSourcesFadeChangeHandler : FadeChangeHandler()
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,97 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.LocalSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import rx.Subscription
 | 
			
		||||
import rx.android.schedulers.AndroidSchedulers
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Presenter of [CatalogueMainController]
 | 
			
		||||
 * Function calls should be done from here. UI calls should be done from the controller.
 | 
			
		||||
 *
 | 
			
		||||
 * @param sourceManager manages the different sources.
 | 
			
		||||
 * @param preferences application preferences.
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueMainPresenter(
 | 
			
		||||
        val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
        private val preferences: PreferencesHelper = Injekt.get()
 | 
			
		||||
) : BasePresenter<CatalogueMainController>() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    var sources = getEnabledSources()
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Subscription for retrieving enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    private var sourceSubscription: Subscription? = null
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedState)
 | 
			
		||||
 | 
			
		||||
        // Load enabled and last used sources
 | 
			
		||||
        loadSources()
 | 
			
		||||
        loadLastUsedSource()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unsubscribe and create a new subscription to fetch enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    fun loadSources() {
 | 
			
		||||
        sourceSubscription?.unsubscribe()
 | 
			
		||||
 | 
			
		||||
        val map = TreeMap<String, MutableList<CatalogueSource>> { d1, d2 -> d1.compareTo(d2) }
 | 
			
		||||
        val byLang = sources.groupByTo(map, { it.lang })
 | 
			
		||||
        val sourceItems = byLang.flatMap {
 | 
			
		||||
            val langItem = LangItem(it.key)
 | 
			
		||||
            it.value.map { source -> SourceItem(source, langItem) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        sourceSubscription = Observable.just(sourceItems)
 | 
			
		||||
                .subscribeLatestCache(CatalogueMainController::setSources)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadLastUsedSource() {
 | 
			
		||||
        val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share()
 | 
			
		||||
 | 
			
		||||
        // Emit the first item immediately but delay subsequent emissions by 500ms.
 | 
			
		||||
        Observable.merge(
 | 
			
		||||
                sharedObs.take(1),
 | 
			
		||||
                sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()))
 | 
			
		||||
                .distinctUntilChanged()
 | 
			
		||||
                .map { (sourceManager.get(it) as? CatalogueSource)?.let { SourceItem(it) } }
 | 
			
		||||
                .subscribeLatestCache(CatalogueMainController::setLastUsedSource)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateSources() {
 | 
			
		||||
        sources = getEnabledSources()
 | 
			
		||||
        loadSources()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of enabled sources ordered by language and name.
 | 
			
		||||
     *
 | 
			
		||||
     * @return list containing enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
        val languages = preferences.enabledLanguages().getOrDefault()
 | 
			
		||||
        val hiddenCatalogues = preferences.hiddenCatalogues().getOrDefault()
 | 
			
		||||
 | 
			
		||||
        return sourceManager.getCatalogueSources()
 | 
			
		||||
                .filter { it.lang in languages }
 | 
			
		||||
                .filterNot { it.id.toString() in hiddenCatalogues }
 | 
			
		||||
                .sortedBy { "(${it.lang}) ${it.name}" } +
 | 
			
		||||
                sourceManager.get(LocalSource.ID) as LocalSource
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,21 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.view.View
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.viewholders.FlexibleViewHolder
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_main_controller_card.view.*
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class LangHolder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter, true) {
 | 
			
		||||
 | 
			
		||||
    fun bind(item: LangItem) {
 | 
			
		||||
        itemView.title.text = when {
 | 
			
		||||
            item.code == "" -> itemView.context.getString(R.string.other_source)
 | 
			
		||||
            else -> {
 | 
			
		||||
                val locale = Locale(item.code)
 | 
			
		||||
                locale.getDisplayName(locale).capitalize()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Item that contains the language header.
 | 
			
		||||
 *
 | 
			
		||||
 * @param code The lang code.
 | 
			
		||||
 */
 | 
			
		||||
data class LangItem(val code: String) : AbstractHeaderItem<LangHolder>() {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the layout resource of this item.
 | 
			
		||||
     */
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.catalogue_main_controller_card
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new view holder for this item.
 | 
			
		||||
     */
 | 
			
		||||
    override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater,
 | 
			
		||||
                                  parent: ViewGroup): LangHolder {
 | 
			
		||||
 | 
			
		||||
        return LangHolder(inflater.inflate(layoutRes, parent, false), adapter)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Binds this item to the given view holder.
 | 
			
		||||
     */
 | 
			
		||||
    override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: LangHolder,
 | 
			
		||||
                                position: Int, payloads: List<Any?>?) {
 | 
			
		||||
 | 
			
		||||
        holder.bind(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,47 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.graphics.Canvas
 | 
			
		||||
import android.graphics.Rect
 | 
			
		||||
import android.graphics.drawable.Drawable
 | 
			
		||||
import android.support.v7.widget.RecyclerView
 | 
			
		||||
import android.view.View
 | 
			
		||||
 | 
			
		||||
class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoration() {
 | 
			
		||||
 | 
			
		||||
    private val divider: Drawable
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        val a = context.obtainStyledAttributes(ATTRS)
 | 
			
		||||
        divider = a.getDrawable(0)
 | 
			
		||||
        a.recycle()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
 | 
			
		||||
        val left = parent.paddingLeft + SourceHolder.margin
 | 
			
		||||
        val right = parent.width - parent.paddingRight - SourceHolder.margin
 | 
			
		||||
 | 
			
		||||
        val childCount = parent.childCount
 | 
			
		||||
        for (i in 0 until childCount - 1) {
 | 
			
		||||
            val child = parent.getChildAt(i)
 | 
			
		||||
            if (parent.getChildViewHolder(child) is SourceHolder &&
 | 
			
		||||
                    parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder) {
 | 
			
		||||
                val params = child.layoutParams as RecyclerView.LayoutParams
 | 
			
		||||
                val top = child.bottom + params.bottomMargin
 | 
			
		||||
                val bottom = top + divider.intrinsicHeight
 | 
			
		||||
 | 
			
		||||
                divider.setBounds(left, top, right, bottom)
 | 
			
		||||
                divider.draw(c)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView,
 | 
			
		||||
                                state: RecyclerView.State) {
 | 
			
		||||
        outRect.set(0, 0, 0, divider.intrinsicHeight)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private val ATTRS = intArrayOf(android.R.attr.listDivider)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,107 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.davidea.viewholders.FlexibleViewHolder
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LoginSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.dpToPx
 | 
			
		||||
import eu.kanade.tachiyomi.util.getRound
 | 
			
		||||
import eu.kanade.tachiyomi.util.gone
 | 
			
		||||
import eu.kanade.tachiyomi.util.visible
 | 
			
		||||
import io.github.mthli.slice.Slice
 | 
			
		||||
import kotlinx.android.synthetic.main.catalogue_main_controller_card_item.view.*
 | 
			
		||||
 | 
			
		||||
class SourceHolder(view: View, adapter: CatalogueMainAdapter) : FlexibleViewHolder(view, adapter) {
 | 
			
		||||
 | 
			
		||||
    private val slice = Slice(itemView.card).apply {
 | 
			
		||||
        setColor(adapter.cardBackground)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        itemView.source_browse.setOnClickListener {
 | 
			
		||||
            adapter.browseClickListener.onBrowseClick(adapterPosition)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        itemView.source_latest.setOnClickListener {
 | 
			
		||||
            adapter.latestClickListener.onLatestClick(adapterPosition)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun bind(item: SourceItem) {
 | 
			
		||||
        val source = item.source
 | 
			
		||||
        with(itemView) {
 | 
			
		||||
            setCardEdges(item)
 | 
			
		||||
 | 
			
		||||
            // Set source name
 | 
			
		||||
            title.text = source.name
 | 
			
		||||
 | 
			
		||||
            // Set circle letter image.
 | 
			
		||||
            post {
 | 
			
		||||
                image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // If source is login, show only login option
 | 
			
		||||
            if (source is LoginSource && !source.isLogged()) {
 | 
			
		||||
                source_browse.setText(R.string.login)
 | 
			
		||||
                source_latest.gone()
 | 
			
		||||
            } else {
 | 
			
		||||
                source_browse.setText(R.string.browse)
 | 
			
		||||
                source_latest.visible()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setCardEdges(item: SourceItem) {
 | 
			
		||||
        // Position of this item in its header. Defaults to 0 when header is null.
 | 
			
		||||
        var position = 0
 | 
			
		||||
 | 
			
		||||
        // Number of items in the header of this item. Defaults to 1 when header is null.
 | 
			
		||||
        var count = 1
 | 
			
		||||
 | 
			
		||||
        if (item.header != null) {
 | 
			
		||||
            val sectionItems = mAdapter.getSectionItems(item.header)
 | 
			
		||||
            position = sectionItems.indexOf(item)
 | 
			
		||||
            count = sectionItems.size
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        when {
 | 
			
		||||
            // Only one item in the card
 | 
			
		||||
            count == 1 -> applySlice(2f, false, false, true, true)
 | 
			
		||||
            // First item of the card
 | 
			
		||||
            position == 0 -> applySlice(2f, false, true, true, false)
 | 
			
		||||
            // Last item of the card
 | 
			
		||||
            position == count - 1 -> applySlice(2f, true, false, false, true)
 | 
			
		||||
            // Middle item
 | 
			
		||||
            else -> applySlice(0f, false, false, false, false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun applySlice(radius: Float, topRect: Boolean, bottomRect: Boolean,
 | 
			
		||||
                           topShadow: Boolean, bottomShadow: Boolean) {
 | 
			
		||||
 | 
			
		||||
        slice.setRadius(radius)
 | 
			
		||||
        slice.showLeftTopRect(topRect)
 | 
			
		||||
        slice.showRightTopRect(topRect)
 | 
			
		||||
        slice.showLeftBottomRect(bottomRect)
 | 
			
		||||
        slice.showRightBottomRect(bottomRect)
 | 
			
		||||
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
 | 
			
		||||
            slice.showTopEdgeShadow(topShadow)
 | 
			
		||||
            slice.showBottomEdgeShadow(bottomShadow)
 | 
			
		||||
        }
 | 
			
		||||
        setMargins(margin, if (topShadow) margin else 0, margin, if (bottomShadow) margin else 0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setMargins(left: Int, top: Int, right: Int, bottom: Int) {
 | 
			
		||||
        val v = itemView.card
 | 
			
		||||
        if (v.layoutParams is ViewGroup.MarginLayoutParams) {
 | 
			
		||||
            val p = v.layoutParams as ViewGroup.MarginLayoutParams
 | 
			
		||||
            p.setMargins(left, top, right, bottom)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val margin = 8.dpToPx
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.catalogue.main
 | 
			
		||||
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.davidea.flexibleadapter.FlexibleAdapter
 | 
			
		||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Item that contains source information.
 | 
			
		||||
 *
 | 
			
		||||
 * @param source Instance of [CatalogueSource] containing source information.
 | 
			
		||||
 * @param header The header for this item.
 | 
			
		||||
 */
 | 
			
		||||
data class SourceItem(val source: CatalogueSource, val header: LangItem? = null) :
 | 
			
		||||
        AbstractSectionableItem<SourceHolder, LangItem>(header) {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the layout resource of this item.
 | 
			
		||||
     */
 | 
			
		||||
    override fun getLayoutRes(): Int {
 | 
			
		||||
        return R.layout.catalogue_main_controller_card_item
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new view holder for this item.
 | 
			
		||||
     */
 | 
			
		||||
    override fun createViewHolder(adapter: FlexibleAdapter<*>, inflater: LayoutInflater,
 | 
			
		||||
                                  parent: ViewGroup): SourceHolder {
 | 
			
		||||
 | 
			
		||||
        val view = inflater.inflate(layoutRes, parent, false)
 | 
			
		||||
        return SourceHolder(view, adapter as CatalogueMainAdapter)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Binds this item to the given view holder.
 | 
			
		||||
     */
 | 
			
		||||
    override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: SourceHolder,
 | 
			
		||||
                                position: Int, payloads: List<Any?>?) {
 | 
			
		||||
 | 
			
		||||
        holder.bind(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@ import com.amulyakhare.textdrawable.TextDrawable
 | 
			
		||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
			
		||||
import eu.davidea.viewholders.FlexibleViewHolder
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.util.getRound
 | 
			
		||||
import kotlinx.android.synthetic.main.categories_item.view.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -38,27 +39,10 @@ class CategoryHolder(view: View, val adapter: CategoryAdapter) : FlexibleViewHol
 | 
			
		||||
 | 
			
		||||
        // Update circle letter image.
 | 
			
		||||
        itemView.post {
 | 
			
		||||
            itemView.image.setImageDrawable(getRound(category.name.take(1).toUpperCase()))
 | 
			
		||||
            itemView.image.setImageDrawable(itemView.image.getRound(category.name.take(1).toUpperCase(),false))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns circle letter image.
 | 
			
		||||
     *
 | 
			
		||||
     * @param text The first letter of string.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getRound(text: String): TextDrawable {
 | 
			
		||||
        val size = Math.min(itemView.image.width, itemView.image.height)
 | 
			
		||||
        return TextDrawable.builder()
 | 
			
		||||
                .beginConfig()
 | 
			
		||||
                .width(size)
 | 
			
		||||
                .height(size)
 | 
			
		||||
                .textColor(Color.WHITE)
 | 
			
		||||
                .useFont(Typeface.DEFAULT)
 | 
			
		||||
                .endConfig()
 | 
			
		||||
                .buildRound(text, ColorGenerator.MATERIAL.getColor(text))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when an item is released.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,25 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.latest_updates
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.support.v4.widget.DrawerLayout
 | 
			
		||||
import android.view.Menu
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Fragment that shows the manga from the catalogue. Inherit CatalogueFragment.
 | 
			
		||||
 * Controller that shows the latest manga from the catalogue. Inherit [CatalogueController].
 | 
			
		||||
 */
 | 
			
		||||
class LatestUpdatesController : CatalogueController() {
 | 
			
		||||
class LatestUpdatesController(bundle: Bundle) : CatalogueController(bundle) {
 | 
			
		||||
 | 
			
		||||
    constructor(source: CatalogueSource) : this(Bundle().apply {
 | 
			
		||||
        putLong(SOURCE_ID_KEY, source.id)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    override fun createPresenter(): CataloguePresenter {
 | 
			
		||||
        return LatestUpdatesPresenter()
 | 
			
		||||
        return LatestUpdatesPresenter(args.getLong(SOURCE_ID_KEY))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onPrepareOptionsMenu(menu: Menu) {
 | 
			
		||||
@@ -30,4 +36,4 @@ class LatestUpdatesController : CatalogueController() {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.latest_updates
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.source.CatalogueSource
 | 
			
		||||
import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.Pager
 | 
			
		||||
@@ -9,18 +7,10 @@ import eu.kanade.tachiyomi.ui.catalogue.Pager
 | 
			
		||||
/**
 | 
			
		||||
 * Presenter of [LatestUpdatesController]. Inherit CataloguePresenter.
 | 
			
		||||
 */
 | 
			
		||||
class LatestUpdatesPresenter : CataloguePresenter() {
 | 
			
		||||
class LatestUpdatesPresenter(sourceId: Long) : CataloguePresenter(sourceId) {
 | 
			
		||||
 | 
			
		||||
    override fun createPager(query: String, filters: FilterList): Pager {
 | 
			
		||||
        return LatestUpdatesPager(source)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
        return super.getEnabledSources().filter { it.supportsLatest }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun isValidSource(source: Source?): Boolean {
 | 
			
		||||
        return super.isValidSource(source) && (source as CatalogueSource).supportsLatest
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -18,9 +18,8 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.SecondaryDrawerController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.main.CatalogueMainController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.download.DownloadController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.latest_updates.LatestUpdatesController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.library.LibraryController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
 | 
			
		||||
@@ -84,8 +83,7 @@ class MainActivity : BaseActivity() {
 | 
			
		||||
                    R.id.nav_drawer_library -> setRoot(LibraryController(), id)
 | 
			
		||||
                    R.id.nav_drawer_recent_updates -> setRoot(RecentChaptersController(), id)
 | 
			
		||||
                    R.id.nav_drawer_recently_read -> setRoot(RecentlyReadController(), id)
 | 
			
		||||
                    R.id.nav_drawer_catalogues -> setRoot(CatalogueController(), id)
 | 
			
		||||
                    R.id.nav_drawer_latest_updates -> setRoot(LatestUpdatesController(), id)
 | 
			
		||||
                    R.id.nav_drawer_catalogues -> setRoot(CatalogueMainController(), id)
 | 
			
		||||
                    R.id.nav_drawer_downloads -> {
 | 
			
		||||
                        router.pushController(RouterTransaction.with(DownloadController())
 | 
			
		||||
                                .pushChangeHandler(FadeChangeHandler())
 | 
			
		||||
@@ -250,4 +248,4 @@ class MainActivity : BaseActivity() {
 | 
			
		||||
        const val SHORTCUT_MANGA = "eu.kanade.tachiyomi.SHOW_MANGA"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,12 +30,6 @@ class SettingsMainController : SettingsController() {
 | 
			
		||||
            titleRes = R.string.pref_category_downloads
 | 
			
		||||
            onClick { navigateTo(SettingsDownloadController()) }
 | 
			
		||||
        }
 | 
			
		||||
        preference {
 | 
			
		||||
            iconRes = R.drawable.ic_language_black_24dp
 | 
			
		||||
            iconTint = tintColor
 | 
			
		||||
            titleRes = R.string.pref_category_sources
 | 
			
		||||
            onClick { navigateTo(SettingsSourcesController()) }
 | 
			
		||||
        }
 | 
			
		||||
        preference {
 | 
			
		||||
            iconRes = R.drawable.ic_sync_black_24dp
 | 
			
		||||
            iconTint = tintColor
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.ui.setting
 | 
			
		||||
import android.graphics.drawable.Drawable
 | 
			
		||||
import android.support.v7.preference.PreferenceGroup
 | 
			
		||||
import android.support.v7.preference.PreferenceScreen
 | 
			
		||||
import com.bluelinelabs.conductor.ControllerChangeHandler
 | 
			
		||||
import com.bluelinelabs.conductor.ControllerChangeType
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ val Context.powerManager: PowerManager
 | 
			
		||||
 *
 | 
			
		||||
 * @param intent intent that contains broadcast information
 | 
			
		||||
 */
 | 
			
		||||
fun Context.sendLocalBroadcast(intent:Intent){
 | 
			
		||||
fun Context.sendLocalBroadcast(intent: Intent) {
 | 
			
		||||
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,9 +4,12 @@ package eu.kanade.tachiyomi.util
 | 
			
		||||
 | 
			
		||||
import android.graphics.Color
 | 
			
		||||
import android.graphics.Point
 | 
			
		||||
import android.graphics.Typeface
 | 
			
		||||
import android.support.design.widget.Snackbar
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import com.amulyakhare.textdrawable.TextDrawable
 | 
			
		||||
import com.amulyakhare.textdrawable.util.ColorGenerator
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns coordinates of view.
 | 
			
		||||
@@ -43,3 +46,21 @@ inline fun View.invisible() {
 | 
			
		||||
inline fun View.gone() {
 | 
			
		||||
    visibility = View.GONE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns a TextDrawable determined by input
 | 
			
		||||
 *
 | 
			
		||||
 * @param text text of [TextDrawable]
 | 
			
		||||
 * @param random random color
 | 
			
		||||
 */
 | 
			
		||||
fun View.getRound(text: String, random : Boolean = true): TextDrawable {
 | 
			
		||||
    val size = Math.min(this.width, this.height)
 | 
			
		||||
    return TextDrawable.builder()
 | 
			
		||||
            .beginConfig()
 | 
			
		||||
            .width(size)
 | 
			
		||||
            .height(size)
 | 
			
		||||
            .textColor(Color.WHITE)
 | 
			
		||||
            .useFont(Typeface.DEFAULT)
 | 
			
		||||
            .endConfig()
 | 
			
		||||
            .buildRound(text, if (random) ColorGenerator.MATERIAL.randomColor else ColorGenerator.MATERIAL.getColor(text))
 | 
			
		||||
}
 | 
			
		||||
@@ -18,6 +18,4 @@
 | 
			
		||||
            </item>
 | 
			
		||||
        </selector>
 | 
			
		||||
    </item>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</ripple>
 | 
			
		||||
</ripple>
 | 
			
		||||
@@ -18,6 +18,4 @@
 | 
			
		||||
            </item>
 | 
			
		||||
        </selector>
 | 
			
		||||
    </item>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</ripple>
 | 
			
		||||
</ripple>
 | 
			
		||||
@@ -18,6 +18,4 @@
 | 
			
		||||
            </item>
 | 
			
		||||
        </selector>
 | 
			
		||||
    </item>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</ripple>
 | 
			
		||||
</ripple>
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:color="@color/rippleColorDark">
 | 
			
		||||
    android:color="@color/rippleColorDark">
 | 
			
		||||
    <item>
 | 
			
		||||
        <selector>
 | 
			
		||||
            <item android:state_selected="true">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:color="@color/rippleColorDark">
 | 
			
		||||
    android:color="@color/rippleColorDark">
 | 
			
		||||
    <item>
 | 
			
		||||
        <selector>
 | 
			
		||||
            <item android:state_selected="true">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
        android:color="@color/rippleColorLight">
 | 
			
		||||
    android:color="@color/rippleColorLight">
 | 
			
		||||
    <item>
 | 
			
		||||
        <selector>
 | 
			
		||||
            <item android:state_selected="true">
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,6 @@
 | 
			
		||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:color="?android:colorControlHighlight">
 | 
			
		||||
    <item android:id="@android:id/mask">
 | 
			
		||||
        <color android:color="@android:color/white" />
 | 
			
		||||
    </item>
 | 
			
		||||
</ripple>
 | 
			
		||||
							
								
								
									
										9
									
								
								app/src/main/res/drawable/ic_search_black_112dp.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/drawable/ic_search_black_112dp.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:width="112dp"
 | 
			
		||||
    android:height="112dp"
 | 
			
		||||
    android:viewportHeight="24.0"
 | 
			
		||||
    android:viewportWidth="24.0">
 | 
			
		||||
    <path
 | 
			
		||||
        android:fillColor="#FF000000"
 | 
			
		||||
        android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
 | 
			
		||||
</vector>
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
 | 
			
		||||
          xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
 | 
			
		||||
    <item android:state_focused="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:state_pressed="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:state_activated="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:drawable="@color/md_black_1000"/>
 | 
			
		||||
 | 
			
		||||
</selector>
 | 
			
		||||
</selector>
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
 | 
			
		||||
          xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
 | 
			
		||||
    <item android:state_focused="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:state_pressed="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:state_activated="true" android:drawable="@color/selectorColorDark"/>
 | 
			
		||||
    <item android:drawable="@color/backgroundDark"/>
 | 
			
		||||
 | 
			
		||||
</selector>
 | 
			
		||||
</selector>
 | 
			
		||||
@@ -1,19 +1,10 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!--<selector android:exitFadeDuration="@android:integer/config_longAnimTime"-->
 | 
			
		||||
<!--xmlns:android="http://schemas.android.com/apk/res/android">-->
 | 
			
		||||
 | 
			
		||||
<!--<item android:state_focused="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
<!--<item android:state_pressed="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
<!--<item android:state_activated="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
<!--<item android:drawable="?android:attr/colorBackground"/>-->
 | 
			
		||||
<!--</selector>-->
 | 
			
		||||
 | 
			
		||||
<selector android:exitFadeDuration="@android:integer/config_longAnimTime"
 | 
			
		||||
          xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
 | 
			
		||||
    <item android:state_focused="true" android:drawable="@color/selectorColorLight"/>
 | 
			
		||||
    <item android:state_pressed="true" android:drawable="@color/selectorColorLight"/>
 | 
			
		||||
    <item android:state_activated="true" android:drawable="@color/selectorColorLight"/>
 | 
			
		||||
    <item android:drawable="@color/backgroundLight"/>
 | 
			
		||||
 | 
			
		||||
</selector>
 | 
			
		||||
</selector>
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
          android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
    android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
 | 
			
		||||
    <item android:drawable="@color/rippleColorDark" android:state_focused="true"/>
 | 
			
		||||
    <item android:drawable="@color/rippleColorDark" android:state_pressed="true"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
          android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
    android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
 | 
			
		||||
    <item android:drawable="@color/rippleColorDark" android:state_focused="true"/>
 | 
			
		||||
    <item android:drawable="@color/rippleColorDark" android:state_pressed="true"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!--<selector android:exitFadeDuration="@android:integer/config_longAnimTime"-->
 | 
			
		||||
          <!--xmlns:android="http://schemas.android.com/apk/res/android">-->
 | 
			
		||||
 | 
			
		||||
    <!--<item android:state_focused="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
    <!--<item android:state_pressed="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
    <!--<item android:state_activated="true" android:drawable="?attr/colorAccent"/>-->
 | 
			
		||||
    <!--<item android:drawable="?android:attr/colorBackground"/>-->
 | 
			
		||||
<!--</selector>-->
 | 
			
		||||
 | 
			
		||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
          android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
    android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
 | 
			
		||||
    <item android:drawable="@color/rippleColorLight" android:state_focused="true"/>
 | 
			
		||||
    <item android:drawable="@color/rippleColorLight" android:state_pressed="true"/>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								app/src/main/res/drawable/list_item_selector_trans.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/src/main/res/drawable/list_item_selector_trans.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:exitFadeDuration="@android:integer/config_longAnimTime">
 | 
			
		||||
 | 
			
		||||
    <item android:drawable="@color/rippleColorLight" android:state_focused="true"/>
 | 
			
		||||
    <item android:drawable="@color/rippleColorLight" android:state_pressed="true"/>
 | 
			
		||||
    <item android:drawable="@color/rippleColorLight" android:state_activated="true"/>
 | 
			
		||||
    <item android:drawable="@android:color/transparent"/>
 | 
			
		||||
 | 
			
		||||
</selector>
 | 
			
		||||
							
								
								
									
										15
									
								
								app/src/main/res/drawable/text_button.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/src/main/res/drawable/text_button.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
 | 
			
		||||
    <stroke
 | 
			
		||||
        android:width="1dp"
 | 
			
		||||
        android:color="?attr/colorAccent" />
 | 
			
		||||
 | 
			
		||||
    <solid android:color="?attr/cardBackgroundColor" />
 | 
			
		||||
 | 
			
		||||
    <padding
 | 
			
		||||
        android:left="1dp"
 | 
			
		||||
        android:right="1dp"
 | 
			
		||||
        android:top="1dp" />
 | 
			
		||||
 | 
			
		||||
    <corners android:radius="5dp" />
 | 
			
		||||
</shape>
 | 
			
		||||
@@ -6,12 +6,12 @@
 | 
			
		||||
    android:layout_height="match_parent">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
          android:layout_width="match_parent"
 | 
			
		||||
          android:layout_height="match_parent"
 | 
			
		||||
          android:fitsSystemWindows="true"
 | 
			
		||||
          android:orientation="vertical"
 | 
			
		||||
          android:id="@+id/catalogue_view"
 | 
			
		||||
          tools:context="eu.kanade.tachiyomi.ui.catalogue.CatalogueController">
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:fitsSystemWindows="true"
 | 
			
		||||
        android:orientation="vertical"
 | 
			
		||||
        android:id="@+id/catalogue_view"
 | 
			
		||||
        tools:context="eu.kanade.tachiyomi.ui.catalogue.CatalogueController">
 | 
			
		||||
 | 
			
		||||
        <ProgressBar
 | 
			
		||||
            android:id="@+id/progress"
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
android:layout_width="match_parent"
 | 
			
		||||
android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
<android.support.v7.widget.RecyclerView
 | 
			
		||||
    android:id="@+id/recycler"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:paddingBottom="4dp"
 | 
			
		||||
    android:paddingTop="4dp"
 | 
			
		||||
    tools:listitem="@layout/catalogue_global_search_controller_card" />
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
@@ -0,0 +1,83 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<android.support.constraint.ConstraintLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/title"
 | 
			
		||||
        style="@style/TextAppearance.Regular.SubHeading"
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:padding="@dimen/material_component_text_fields_padding_above_and_below_label"
 | 
			
		||||
        app:layout_constraintBottom_toTopOf="@+id/source_card"
 | 
			
		||||
        app:layout_constraintHeight_default="wrap"
 | 
			
		||||
        app:layout_constraintLeft_toLeftOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        tools:text="Title" />
 | 
			
		||||
 | 
			
		||||
    <android.support.v7.widget.CardView
 | 
			
		||||
        android:id="@+id/source_card"
 | 
			
		||||
        style="@style/Theme.Widget.CardView.Item"
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:minHeight="144dp"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintHeight_default="wrap"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent">
 | 
			
		||||
 | 
			
		||||
        <ProgressBar
 | 
			
		||||
            android:id="@+id/progress"
 | 
			
		||||
            style="?android:attr/progressBarStyleSmall"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center" />
 | 
			
		||||
 | 
			
		||||
        <android.support.constraint.ConstraintLayout
 | 
			
		||||
            android:id="@+id/nothing_found"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_gravity="center"
 | 
			
		||||
            android:visibility="gone">
 | 
			
		||||
 | 
			
		||||
            <ImageView
 | 
			
		||||
                android:id="@+id/nothing_found_icon"
 | 
			
		||||
                android:layout_width="112dp"
 | 
			
		||||
                android:layout_height="112dp"
 | 
			
		||||
                android:scaleType="fitCenter"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
                tools:ignore="ContentDescription"
 | 
			
		||||
                tools:src="@mipmap/ic_launcher" />
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/nothing_found_text"
 | 
			
		||||
                style="@style/TextAppearance.Regular.Caption.Hint"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginTop="0dp"
 | 
			
		||||
                android:ellipsize="end"
 | 
			
		||||
                android:gravity="center"
 | 
			
		||||
                android:maxLines="1"
 | 
			
		||||
                android:paddingBottom="8dp"
 | 
			
		||||
                android:text="@string/no_results"
 | 
			
		||||
                app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
                app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
                app:layout_constraintTop_toBottomOf="@+id/nothing_found_icon" />
 | 
			
		||||
 | 
			
		||||
        </android.support.constraint.ConstraintLayout>
 | 
			
		||||
 | 
			
		||||
        <android.support.v7.widget.RecyclerView
 | 
			
		||||
            android:id="@+id/recycler"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:orientation="horizontal"
 | 
			
		||||
            android:paddingEnd="4dp"
 | 
			
		||||
            android:paddingStart="4dp"
 | 
			
		||||
            tools:listitem="@layout/catalogue_global_search_controller_card_item" />
 | 
			
		||||
    </android.support.v7.widget.CardView>
 | 
			
		||||
</android.support.constraint.ConstraintLayout>
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<android.support.constraint.ConstraintLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="wrap_content"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:background="?attr/selectable_list_drawable"
 | 
			
		||||
    android:orientation="vertical"
 | 
			
		||||
    android:paddingBottom="8dp"
 | 
			
		||||
    android:paddingEnd="4dp"
 | 
			
		||||
    android:paddingStart="4dp"
 | 
			
		||||
    android:paddingTop="8dp">
 | 
			
		||||
 | 
			
		||||
    <ProgressBar
 | 
			
		||||
        android:id="@+id/progress"
 | 
			
		||||
        style="?android:attr/progressBarStyleSmall"
 | 
			
		||||
        android:layout_width="0dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:visibility="gone"
 | 
			
		||||
        app:layout_constraintHeight_default="wrap"
 | 
			
		||||
        app:layout_constraintWidth_default="wrap"
 | 
			
		||||
        app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent" />
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
        android:id="@+id/itemImage"
 | 
			
		||||
        android:layout_width="112dp"
 | 
			
		||||
        android:layout_height="112dp"
 | 
			
		||||
        android:paddingBottom="8dp"
 | 
			
		||||
        android:scaleType="fitCenter"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
        tools:ignore="ContentDescription"
 | 
			
		||||
        tools:src="@mipmap/ic_launcher" />
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/tvTitle"
 | 
			
		||||
        style="@style/TextAppearance.Regular.Caption"
 | 
			
		||||
        android:layout_width="104dp"
 | 
			
		||||
        android:layout_height="0dp"
 | 
			
		||||
        android:layout_marginTop="0dp"
 | 
			
		||||
        android:ellipsize="end"
 | 
			
		||||
        android:gravity="center"
 | 
			
		||||
        android:maxLines="1"
 | 
			
		||||
        app:layout_constraintHeight_default="wrap"
 | 
			
		||||
        app:layout_constraintEnd_toEndOf="parent"
 | 
			
		||||
        app:layout_constraintStart_toStartOf="parent"
 | 
			
		||||
        app:layout_constraintTop_toBottomOf="@+id/itemImage"
 | 
			
		||||
        tools:text="Sample title" />
 | 
			
		||||
 | 
			
		||||
</android.support.constraint.ConstraintLayout>
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content"
 | 
			
		||||
    android:background="?attr/selectable_library_drawable">
 | 
			
		||||
    android:background="?selectable_library_drawable">
 | 
			
		||||
 | 
			
		||||
    <FrameLayout
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,7 @@
 | 
			
		||||
        android:paddingEnd="0dp"
 | 
			
		||||
        android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
 | 
			
		||||
        android:paddingRight="0dp"
 | 
			
		||||
        android:paddingStart="@dimen/material_component_lists_icon_left_padding"
 | 
			
		||||
        tools:src="@drawable/icon"/>
 | 
			
		||||
        android:paddingStart="@dimen/material_component_lists_icon_left_padding"/>
 | 
			
		||||
 | 
			
		||||
    <RelativeLayout
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								app/src/main/res/layout/catalogue_main_controller.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/src/main/res/layout/catalogue_main_controller.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <android.support.v7.widget.RecyclerView
 | 
			
		||||
        android:id="@+id/recycler"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        tools:listitem="@layout/catalogue_main_controller_card" />
 | 
			
		||||
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
							
								
								
									
										18
									
								
								app/src/main/res/layout/catalogue_main_controller_card.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/src/main/res/layout/catalogue_main_controller_card.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/title"
 | 
			
		||||
        style="@style/TextAppearance.Regular.SubHeading"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:paddingTop="8dp"
 | 
			
		||||
        android:paddingBottom="8dp"
 | 
			
		||||
        android:paddingLeft="@dimen/material_component_text_fields_padding_above_and_below_label"
 | 
			
		||||
        tools:text="Title" />
 | 
			
		||||
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
@@ -0,0 +1,72 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<FrameLayout
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="wrap_content">
 | 
			
		||||
 | 
			
		||||
    <android.support.constraint.ConstraintLayout
 | 
			
		||||
        android:id="@+id/card"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="@dimen/material_component_lists_two_line_height"
 | 
			
		||||
        android:background="?attr/selectable_list_drawable">
 | 
			
		||||
 | 
			
		||||
        <ImageView
 | 
			
		||||
            android:id="@+id/image"
 | 
			
		||||
            android:layout_width="48dp"
 | 
			
		||||
            android:layout_height="56dp"
 | 
			
		||||
            android:clickable="true"
 | 
			
		||||
            android:paddingLeft="8dp"
 | 
			
		||||
            android:paddingStart="8dp"
 | 
			
		||||
            android:paddingRight="0dp"
 | 
			
		||||
            android:paddingEnd="0dp"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintLeft_toLeftOf="parent"
 | 
			
		||||
            tools:src="@mipmap/ic_launcher_round"/>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/title"
 | 
			
		||||
            android:layout_width="0dp"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:maxLines="1"
 | 
			
		||||
            android:paddingLeft="16dp"
 | 
			
		||||
            android:paddingStart="16dp"
 | 
			
		||||
            android:paddingRight="8dp"
 | 
			
		||||
            android:paddingEnd="8dp"
 | 
			
		||||
            android:ellipsize="end"
 | 
			
		||||
            android:textAppearance="@style/TextAppearance.Regular.SubHeading"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintLeft_toRightOf="@+id/image"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"
 | 
			
		||||
            app:layout_constraintRight_toLeftOf="@+id/source_latest"
 | 
			
		||||
            tools:text="Source title"/>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/source_latest"
 | 
			
		||||
            style="@style/TextAppearance.Medium.Button"
 | 
			
		||||
            android:background="@drawable/list_item_selector_trans"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="@string/latest"
 | 
			
		||||
            android:padding="@dimen/material_component_dialogs_padding_around_buttons"
 | 
			
		||||
            app:layout_constraintRight_toLeftOf="@+id/source_browse"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"/>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:id="@+id/source_browse"
 | 
			
		||||
            style="@style/TextAppearance.Medium.Button"
 | 
			
		||||
            android:background="@drawable/list_item_selector_trans"
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:text="@string/browse"
 | 
			
		||||
            android:padding="@dimen/material_component_dialogs_padding_around_buttons"
 | 
			
		||||
            app:layout_constraintRight_toRightOf="parent"
 | 
			
		||||
            app:layout_constraintBottom_toBottomOf="parent"
 | 
			
		||||
            app:layout_constraintTop_toTopOf="parent"/>
 | 
			
		||||
 | 
			
		||||
    </android.support.constraint.ConstraintLayout>
 | 
			
		||||
 | 
			
		||||
</FrameLayout>
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/catalogue_grid"
 | 
			
		||||
    style="@style/Theme.Widget.GridView"
 | 
			
		||||
    style="@style/Theme.Widget.GridView.Catalogue"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:columnWidth="140dp"
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,8 @@
 | 
			
		||||
        android:paddingLeft="@dimen/material_component_lists_icon_left_padding"
 | 
			
		||||
        android:paddingStart="@dimen/material_component_lists_icon_left_padding"
 | 
			
		||||
        android:paddingRight="0dp"
 | 
			
		||||
        android:paddingEnd="0dp"/>
 | 
			
		||||
        android:paddingEnd="0dp"
 | 
			
		||||
        tools:src="@mipmap/ic_launcher_round"/>
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:id="@+id/title"
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools"
 | 
			
		||||
    android:id="@+id/library_grid"
 | 
			
		||||
    style="@style/Theme.Widget.GridView"
 | 
			
		||||
    style="@style/Theme.Widget.GridView.Catalogue"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:columnWidth="140dp"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								app/src/main/res/menu/catalogue_main.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/src/main/res/menu/catalogue_main.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools" tools:context=".CatalogueListActivity">
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_search"
 | 
			
		||||
        android:title="@string/action_search"
 | 
			
		||||
        android:icon="@drawable/ic_search_white_24dp"
 | 
			
		||||
        app:showAsAction="collapseActionView|ifRoom"
 | 
			
		||||
        app:actionViewClass="android.support.v7.widget.SearchView"/>
 | 
			
		||||
 | 
			
		||||
    <item android:id="@+id/action_settings"
 | 
			
		||||
        android:title="@string/pref_category_sources"
 | 
			
		||||
        android:icon="@drawable/ic_settings_white_24dp"
 | 
			
		||||
        app:showAsAction="ifRoom"/>
 | 
			
		||||
</menu>
 | 
			
		||||
							
								
								
									
										11
									
								
								app/src/main/res/menu/catalogue_new_list.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/src/main/res/menu/catalogue_new_list.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools" tools:context=".CatalogueListActivity">
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/action_search"
 | 
			
		||||
        android:title="@string/action_search"
 | 
			
		||||
        android:icon="@drawable/ic_search_white_24dp"
 | 
			
		||||
        app:showAsAction="collapseActionView|ifRoom"
 | 
			
		||||
        app:actionViewClass="android.support.v7.widget.SearchView"/>
 | 
			
		||||
</menu>
 | 
			
		||||
@@ -34,6 +34,7 @@
 | 
			
		||||
    <string name="action_sort_last_read">Last read</string>
 | 
			
		||||
    <string name="action_sort_last_updated">Last updated</string>
 | 
			
		||||
    <string name="action_search">Search</string>
 | 
			
		||||
    <string name="action_global_search">Global search</string>
 | 
			
		||||
    <string name="action_select_all">Select all</string>
 | 
			
		||||
    <string name="action_mark_as_read">Mark as read</string>
 | 
			
		||||
    <string name="action_mark_as_unread">Mark as unread</string>
 | 
			
		||||
@@ -85,6 +86,8 @@
 | 
			
		||||
    <string name="action_open_log">Open log</string>
 | 
			
		||||
    <string name="action_create">Create</string>
 | 
			
		||||
    <string name="action_restore">Restore</string>
 | 
			
		||||
    <string name="action_open">Open</string>
 | 
			
		||||
    <string name="action_login">Login</string>
 | 
			
		||||
 | 
			
		||||
    <!-- Operations -->
 | 
			
		||||
    <string name="deleting">Deleting…</string>
 | 
			
		||||
@@ -276,8 +279,13 @@
 | 
			
		||||
    <string name="no_valid_sources">Please enable at least one valid source</string>
 | 
			
		||||
    <string name="no_more_results">No more results</string>
 | 
			
		||||
    <string name="local_source">Local manga</string>
 | 
			
		||||
    <string name="other_source">Other</string>
 | 
			
		||||
    <string name="invalid_combination">Default can\'t be selected with other categories</string>
 | 
			
		||||
    <string name="added_to_library">The manga has been added to your library</string>
 | 
			
		||||
    <string name="action_global_search_hint">Global search…</string>
 | 
			
		||||
    <string name="no_results">No results found!</string>
 | 
			
		||||
    <string name="latest">Latest</string>
 | 
			
		||||
    <string name="browse">Browse</string>
 | 
			
		||||
 | 
			
		||||
    <!-- Manga activity -->
 | 
			
		||||
    <string name="manga_not_in_db">This manga was removed from the database!</string>
 | 
			
		||||
@@ -430,5 +438,4 @@
 | 
			
		||||
    <string name="download_notifier_text_only_wifi">No wifi connection available</string>
 | 
			
		||||
    <string name="download_notifier_no_network">No network connection available</string>
 | 
			
		||||
    <string name="download_notifier_download_paused">Download paused</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
    <!--========-->
 | 
			
		||||
    <!--Toolbars-->
 | 
			
		||||
    <!--========-->
 | 
			
		||||
    <style name="Theme.ActionBar" parent="@style/ThemeOverlay.AppCompat.ActionBar"/>
 | 
			
		||||
    <style name="Theme.ActionBar" parent="@style/ThemeOverlay.AppCompat.ActionBar" />
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.ActionBar.Light" parent="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
 | 
			
		||||
        <item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
 | 
			
		||||
@@ -13,12 +13,12 @@
 | 
			
		||||
    <!--====-->
 | 
			
		||||
    <!--Tabs-->
 | 
			
		||||
    <!--====-->
 | 
			
		||||
    <style name="Theme.ActionBar.Tab" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
 | 
			
		||||
    <style name="Theme.ActionBar.Tab" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
 | 
			
		||||
 | 
			
		||||
    <!--===========-->
 | 
			
		||||
    <!--AlertDialog-->
 | 
			
		||||
    <!--===========-->
 | 
			
		||||
    <style name="Theme.AlertDialog"/>
 | 
			
		||||
    <style name="Theme.AlertDialog" />
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.AlertDialog.Light" parent="Theme.AppCompat.Light.Dialog.Alert">
 | 
			
		||||
        <item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
 | 
			
		||||
@@ -35,7 +35,7 @@
 | 
			
		||||
    <!--==============-->
 | 
			
		||||
    <!--NavigationView-->
 | 
			
		||||
    <!--==============-->
 | 
			
		||||
    <style name="Theme.Widget.NavigationView"/>
 | 
			
		||||
    <style name="Theme.Widget.NavigationView" />
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.NavigationView.Dark">
 | 
			
		||||
        <item name="colorControlHighlight">@color/md_grey_900</item>
 | 
			
		||||
@@ -85,6 +85,10 @@
 | 
			
		||||
        <item name="android:textSize">16sp</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="TextAppearance.Regular.SubHeading.Upper">
 | 
			
		||||
        <item name="android:textAllCaps">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="TextAppearance.Regular.SubHeading.Secondary">
 | 
			
		||||
        <item name="android:textColor">?android:attr/textColorSecondary</item>
 | 
			
		||||
    </style>
 | 
			
		||||
@@ -105,6 +109,10 @@
 | 
			
		||||
        <item name="android:textSize">20sp</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="TextAppearance.Medium.Title.Upper">
 | 
			
		||||
        <item name="android:textAllCaps">true</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="TextAppearance.Medium.Title.Secondary">
 | 
			
		||||
        <item name="android:textColor">?android:attr/textColorSecondary</item>
 | 
			
		||||
    </style>
 | 
			
		||||
@@ -130,7 +138,7 @@
 | 
			
		||||
    <!--=======-->
 | 
			
		||||
    <!--Widgets-->
 | 
			
		||||
    <!--=======-->
 | 
			
		||||
    <style name="Theme.Widget"/>
 | 
			
		||||
    <style name="Theme.Widget" />
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.FAB">
 | 
			
		||||
        <item name="android:layout_height">@dimen/fab_size</item>
 | 
			
		||||
@@ -147,10 +155,16 @@
 | 
			
		||||
        <item name="android:layout_width">match_parent</item>
 | 
			
		||||
        <item name="android:layout_height">wrap_content</item>
 | 
			
		||||
        <item name="android:padding">@dimen/material_component_cards_top_and_bottom_padding</item>
 | 
			
		||||
        <item name="android:layout_marginTop">@dimen/material_component_cards_space_between_cards</item>
 | 
			
		||||
        <item name="android:layout_marginBottom">@dimen/material_component_cards_space_between_cards</item>
 | 
			
		||||
        <item name="android:layout_marginStart">@dimen/material_component_cards_space_between_cards</item>
 | 
			
		||||
        <item name="android:layout_marginEnd">@dimen/material_component_cards_space_between_cards</item>
 | 
			
		||||
        <item name="android:layout_marginTop">@dimen/material_component_cards_space_between_cards
 | 
			
		||||
        </item>
 | 
			
		||||
        <item name="android:layout_marginBottom">
 | 
			
		||||
            @dimen/material_component_cards_space_between_cards
 | 
			
		||||
        </item>
 | 
			
		||||
        <item name="android:layout_marginStart">
 | 
			
		||||
            @dimen/material_component_cards_space_between_cards
 | 
			
		||||
        </item>
 | 
			
		||||
        <item name="android:layout_marginEnd">@dimen/material_component_cards_space_between_cards
 | 
			
		||||
        </item>
 | 
			
		||||
        <item name="cardBackgroundColor">?attr/background_card</item>
 | 
			
		||||
        <item name="cardElevation">2dp</item>
 | 
			
		||||
    </style>
 | 
			
		||||
@@ -161,21 +175,24 @@
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.GridView">
 | 
			
		||||
        <item name="android:smoothScrollbar">true</item>
 | 
			
		||||
        <item name="android:numColumns">auto_fit</item>
 | 
			
		||||
        <item name="android:stretchMode">columnWidth</item>
 | 
			
		||||
        <item name="android:scrollbarStyle">outsideOverlay</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.GridView.Catalogue">
 | 
			
		||||
        <item name="android:padding">5dp</item>
 | 
			
		||||
        <item name="android:clipToPadding">false</item>
 | 
			
		||||
        <item name="android:gravity">top|left</item>
 | 
			
		||||
        <item name="android:smoothScrollbar">true</item>
 | 
			
		||||
        <item name="android:cacheColorHint">?android:attr/textColorHint</item>
 | 
			
		||||
        <item name="android:fastScrollEnabled">true</item>
 | 
			
		||||
        <item name="android:horizontalSpacing">0dp</item>
 | 
			
		||||
        <item name="android:verticalSpacing">0dp</item>
 | 
			
		||||
        <item name="android:numColumns">auto_fit</item>
 | 
			
		||||
        <item name="android:stretchMode">columnWidth</item>
 | 
			
		||||
        <item name="android:scrollbarStyle">outsideOverlay</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.CheckBox"/>
 | 
			
		||||
    <style name="Theme.Widget.CheckBox" />
 | 
			
		||||
 | 
			
		||||
    <style name="Theme.Widget.CheckBox.Light" parent="TextAppearance.Regular.Body1.Light">
 | 
			
		||||
        <item name="buttonTint">@color/md_white_1000</item>
 | 
			
		||||
@@ -212,8 +229,7 @@
 | 
			
		||||
        <item name="nnf_toolbarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
    <style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Light.Dialog.Alert">
 | 
			
		||||
    </style>
 | 
			
		||||
    <style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Light.Dialog.Alert"></style>
 | 
			
		||||
 | 
			
		||||
    <style name="reader_settings_popup_animation">
 | 
			
		||||
        <item name="android:windowEnterAnimation">@anim/enter_from_right</item>
 | 
			
		||||
@@ -226,5 +242,4 @@
 | 
			
		||||
    </style>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user