Split up MigrationController

This commit is contained in:
arkon 2020-05-23 20:49:20 -04:00
parent 3c4bc17065
commit af1935d2e4
13 changed files with 207 additions and 235 deletions

View file

@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import java.io.Serializable
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -12,7 +13,7 @@ import uy.kohesive.injekt.api.get
/**
* A basic interface for creating a source. It could be an online source, a local source, etc...
*/
interface Source {
interface Source : Serializable {
/**
* Id for the source. Must be unique.

View file

@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.ui.base.controller.RootController
import eu.kanade.tachiyomi.ui.base.controller.RxController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionController
import eu.kanade.tachiyomi.ui.browse.migration.MigrationController
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrationSourcesController
import eu.kanade.tachiyomi.ui.browse.source.SourceController
import kotlinx.android.synthetic.main.main_activity.tabs
import uy.kohesive.injekt.injectLazy
@ -126,7 +126,7 @@ class BrowseController :
val controller: Controller = when (position) {
SOURCES_CONTROLLER -> SourceController()
EXTENSIONS_CONTROLLER -> ExtensionController()
MIGRATION_CONTROLLER -> MigrationController()
MIGRATION_CONTROLLER -> MigrationSourcesController()
else -> error("Wrong position $position")
}
router.setRoot(RouterTransaction.with(controller))

View file

@ -1,110 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.manga.MangaAdapter
import eu.kanade.tachiyomi.ui.browse.migration.manga.MangaItem
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.browse.migration.sources.SourceAdapter
import eu.kanade.tachiyomi.ui.browse.migration.sources.SourceItem
import eu.kanade.tachiyomi.ui.browse.source.SourceDividerItemDecoration
class MigrationController :
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
FlexibleAdapter.OnItemClickListener {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
private var title: String? = null
set(value) {
field = value
setTitle()
}
override fun createPresenter(): MigrationPresenter {
return MigrationPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = FlexibleAdapter(null, this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
}
override fun onDestroyView(view: View) {
adapter = null
super.onDestroyView(view)
}
override fun getTitle(): String? {
return title
}
override fun handleBack(): Boolean {
return if (presenter.state.selectedSource != null) {
presenter.deselectSource()
true
} else {
super.handleBack()
}
}
fun render(state: ViewState) {
if (state.selectedSource == null) {
title = resources?.getString(R.string.label_migration)
if (adapter !is SourceAdapter) {
adapter =
SourceAdapter(
this
)
binding.recycler.adapter = adapter
adapter?.fastScroller = binding.fastScroller
}
adapter?.updateDataSet(state.sourcesWithManga)
} else {
title = state.selectedSource.toString()
if (adapter !is MangaAdapter) {
adapter =
MangaAdapter(
this
)
binding.recycler.adapter = adapter
adapter?.fastScroller = binding.fastScroller
}
adapter?.updateDataSet(state.mangaForSource)
}
}
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) ?: return false
if (item is MangaItem) {
val controller =
SearchController(
item.manga
)
controller.targetController = this
parentController!!.router.pushController(controller.withFadeTransaction())
} else if (item is SourceItem) {
presenter.setSelectedSource(item.source)
}
return false
}
}

View file

@ -1,80 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration
import android.os.Bundle
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.migration.manga.MangaItem
import eu.kanade.tachiyomi.ui.browse.migration.sources.SelectionHeader
import eu.kanade.tachiyomi.ui.browse.migration.sources.SourceItem
import eu.kanade.tachiyomi.util.lang.combineLatest
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MigrationPresenter(
private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get()
) : BasePresenter<MigrationController>() {
var state = ViewState()
private set(value) {
field = value
stateRelay.call(value)
}
private val stateRelay = BehaviorRelay.create(state)
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
db.getFavoriteMangas()
.asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(sourcesWithManga = findSourcesWithManga(it)) }
.combineLatest(
stateRelay.map { it.selectedSource }
.distinctUntilChanged()
) { library, source -> library to source }
.filter { (_, source) -> source != null }
.observeOn(Schedulers.io())
.map { (library, source) -> libraryToMigrationItem(library, source!!.id) }
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { state = state.copy(mangaForSource = it) }
.subscribe()
// Render the view when any field changes
stateRelay.subscribeLatestCache(MigrationController::render)
}
fun setSelectedSource(source: Source) {
state = state.copy(selectedSource = source, mangaForSource = emptyList())
}
fun deselectSource() {
state = state.copy(selectedSource = null, mangaForSource = emptyList())
}
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
val header =
SelectionHeader()
return library.map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.sortedBy { it.name }
.map {
SourceItem(
it,
header
)
}
}
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
return library.filter { it.source == sourceId }.map(::MangaItem)
}
}

View file

@ -1,11 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.migration.manga.MangaItem
import eu.kanade.tachiyomi.ui.browse.migration.sources.SourceItem
data class ViewState(
val selectedSource: Source? = null,
val mangaForSource: List<MangaItem> = emptyList(),
val sourcesWithManga: List<SourceItem> = emptyList()
)

View file

@ -1,18 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.ui.browse.migration.MigrationController
class MangaAdapter(controller: MigrationController) :
FlexibleAdapter<IFlexible<*>>(null, controller) {
private var items: List<IFlexible<*>>? = null
override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
if (this.items !== items) {
this.items = items
super.updateDataSet(items)
}
}
}

View file

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -7,8 +8,10 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import kotlinx.android.parcel.Parcelize
class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
@Parcelize
class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>(), Parcelable {
override fun getLayoutRes(): Int {
return R.layout.source_list_item

View file

@ -0,0 +1,76 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.browse.source.SourceDividerItemDecoration
class MigrationMangaController :
NucleusController<MigrationControllerBinding, MigrationMangaPresenter>,
FlexibleAdapter.OnItemClickListener {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
constructor(source: Source) : super(
Bundle().apply {
putSerializable(SOURCE_EXTRA, source)
}
)
@Suppress("unused")
constructor(bundle: Bundle) : this(bundle.getSerializable(SOURCE_EXTRA) as Source)
private val source: Source = args.getSerializable(SOURCE_EXTRA) as Source
override fun getTitle(): String? {
return source.name
}
override fun createPresenter(): MigrationMangaPresenter {
return MigrationMangaPresenter(source.id)
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = FlexibleAdapter<IFlexible<*>>(null, this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
adapter?.fastScroller = binding.fastScroller
}
override fun onDestroyView(view: View) {
adapter = null
super.onDestroyView(view)
}
fun setManga(manga: List<MangaItem>) {
adapter?.updateDataSet(manga)
}
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? MangaItem ?: return false
val controller = SearchController(item.manga)
router.pushController(controller.withFadeTransaction())
return false
}
companion object {
const val SOURCE_EXTRA = "source_id_extra"
}
}

View file

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MigrationMangaPresenter(
private val sourceId: Long,
private val db: DatabaseHelper = Injekt.get()
) : BasePresenter<MigrationMangaController>() {
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
db.getFavoriteMangas()
.asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
.map { libraryToMigrationItem(it) }
.subscribeLatestCache(MigrationMangaController::setManga)
}
private fun libraryToMigrationItem(library: List<Manga>): List<MangaItem> {
return library.filter { it.source == sourceId }
.sortedBy { it.title }
.map { MangaItem(it) }
}
}

View file

@ -0,0 +1,54 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.databinding.MigrationControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrationMangaController
import eu.kanade.tachiyomi.ui.browse.source.SourceDividerItemDecoration
class MigrationSourcesController :
NucleusController<MigrationControllerBinding, MigrationSourcesPresenter>(),
FlexibleAdapter.OnItemClickListener {
private var adapter: SourceAdapter? = null
override fun createPresenter(): MigrationSourcesPresenter {
return MigrationSourcesPresenter()
}
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
binding = MigrationControllerBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = SourceAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
binding.recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
adapter?.fastScroller = binding.fastScroller
}
override fun onDestroyView(view: View) {
adapter = null
super.onDestroyView(view)
}
fun setSources(sourcesWithManga: List<SourceItem>) {
adapter?.updateDataSet(sourcesWithManga)
}
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? SourceItem ?: return false
val controller = MigrationMangaController(item.source)
parentController!!.router.pushController(controller.withFadeTransaction())
return false
}
}

View file

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.android.schedulers.AndroidSchedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MigrationSourcesPresenter(
private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get()
) : BasePresenter<MigrationSourcesController>() {
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
db.getFavoriteMangas()
.asRxObservable()
.observeOn(AndroidSchedulers.mainThread())
.map { findSourcesWithManga(it) }
.subscribeLatestCache(MigrationSourcesController::setSources)
}
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
val header = SelectionHeader()
return library.map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.sortedBy { it.name }
.map { SourceItem(it, header) }
}
}

View file

@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import com.bluelinelabs.conductor.Controller
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.MigrationController
import eu.kanade.tachiyomi.util.system.getResourceColor
/**
@ -11,21 +11,12 @@ import eu.kanade.tachiyomi.util.system.getResourceColor
*
* @param controller instance of [MigrationController].
*/
class SourceAdapter(val controller: MigrationController) :
class SourceAdapter(val controller: Controller) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface)
private var items: List<IFlexible<*>>? = null
init {
setDisplayHeadersAtStartUp(true)
}
override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
if (this.items !== items) {
this.items = items
super.updateDataSet(items)
}
}
}

View file

@ -14,7 +14,7 @@ import eu.kanade.tachiyomi.source.Source
* @param source Instance of [Source] containing source information.
* @param header The header for this item.
*/
data class SourceItem(val source: Source, val header: SelectionHeader? = null) :
data class SourceItem(val source: Source, val header: SelectionHeader) :
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
/**