Move migration classes under browse
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.view.View
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import kotlinx.android.synthetic.main.source_list_item.thumbnail
|
||||
import kotlinx.android.synthetic.main.source_list_item.title
|
||||
|
||||
class MangaHolder(
|
||||
view: View,
|
||||
adapter: FlexibleAdapter<*>
|
||||
) : BaseFlexibleViewHolder(view, adapter) {
|
||||
|
||||
fun bind(item: MangaItem) {
|
||||
// Update the title of the manga.
|
||||
title.text = item.manga.title
|
||||
|
||||
// Create thumbnail onclick to simulate long click
|
||||
thumbnail.setOnClickListener {
|
||||
// Simulate long click on this view to enter selection mode
|
||||
onLongClick(itemView)
|
||||
}
|
||||
|
||||
// Update the cover.
|
||||
GlideApp.with(itemView.context).clear(thumbnail)
|
||||
|
||||
val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
||||
GlideApp.with(itemView.context)
|
||||
.load(item.manga.toMangaThumbnail())
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.apply(requestOptions)
|
||||
.dontAnimate()
|
||||
.into(thumbnail)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
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
|
||||
|
||||
class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>() {
|
||||
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.source_list_item
|
||||
}
|
||||
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaHolder {
|
||||
return MangaHolder(view, adapter)
|
||||
}
|
||||
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: MangaHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
holder.bind(this)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other is MangaItem) {
|
||||
return manga.id == other.manga.id
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return manga.id!!.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
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.source.SourceDividerItemDecoration
|
||||
|
||||
class MigrationController :
|
||||
NucleusController<MigrationControllerBinding, MigrationPresenter>(),
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
SourceAdapter.OnSelectClickListener {
|
||||
|
||||
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
|
||||
|
||||
router.pushController(controller.withFadeTransaction())
|
||||
} else if (item is SourceItem) {
|
||||
presenter.setSelectedSource(item.source)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onSelectClick(position: Int) {
|
||||
onItemClick(view!!, position)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import eu.kanade.tachiyomi.R
|
||||
|
||||
object MigrationFlags {
|
||||
|
||||
private const val CHAPTERS = 0b001
|
||||
private const val CATEGORIES = 0b010
|
||||
private const val TRACK = 0b100
|
||||
|
||||
private const val CHAPTERS2 = 0x1
|
||||
private const val CATEGORIES2 = 0x2
|
||||
private const val TRACK2 = 0x4
|
||||
|
||||
val titles get() = arrayOf(R.string.chapters, R.string.categories, R.string.track)
|
||||
|
||||
val flags get() = arrayOf(CHAPTERS, CATEGORIES, TRACK)
|
||||
|
||||
fun hasChapters(value: Int): Boolean {
|
||||
return value and CHAPTERS != 0
|
||||
}
|
||||
|
||||
fun hasCategories(value: Int): Boolean {
|
||||
return value and CATEGORIES != 0
|
||||
}
|
||||
|
||||
fun hasTracks(value: Int): Boolean {
|
||||
return value and TRACK != 0
|
||||
}
|
||||
|
||||
fun getEnabledFlagsPositions(value: Int): List<Int> {
|
||||
return flags.mapIndexedNotNull { index, flag -> if (value and flag != 0) index else null }
|
||||
}
|
||||
|
||||
fun getFlagsFromPositions(positions: Array<Int>): Int {
|
||||
return positions.fold(0, { accumulated, position -> accumulated or (1 shl position) })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
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.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.list.listItemsMultiChoice
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class SearchController(
|
||||
private var manga: Manga? = null
|
||||
) : GlobalSearchController(manga?.title) {
|
||||
|
||||
private var newManga: Manga? = null
|
||||
|
||||
override fun createPresenter(): GlobalSearchPresenter {
|
||||
return SearchPresenter(initialQuery, manga!!)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
outState.putSerializable(::manga.name, manga)
|
||||
outState.putSerializable(::newManga.name, newManga)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
super.onRestoreInstanceState(savedInstanceState)
|
||||
manga = savedInstanceState.getSerializable(::manga.name) as? Manga
|
||||
newManga = savedInstanceState.getSerializable(::newManga.name) as? Manga
|
||||
}
|
||||
|
||||
fun migrateManga() {
|
||||
val manga = manga ?: return
|
||||
val newManga = newManga ?: return
|
||||
|
||||
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, true)
|
||||
}
|
||||
|
||||
fun copyManga() {
|
||||
val manga = manga ?: return
|
||||
val newManga = newManga ?: return
|
||||
|
||||
(presenter as? SearchPresenter)?.migrateManga(manga, newManga, false)
|
||||
}
|
||||
|
||||
override fun onMangaClick(manga: Manga) {
|
||||
newManga = manga
|
||||
val dialog = MigrationDialog()
|
||||
dialog.targetController = this
|
||||
dialog.showDialog(router)
|
||||
}
|
||||
|
||||
override fun onMangaLongClick(manga: Manga) {
|
||||
// Call parent's default click listener
|
||||
super.onMangaClick(manga)
|
||||
}
|
||||
|
||||
fun renderIsReplacingManga(isReplacingManga: Boolean) {
|
||||
if (isReplacingManga) {
|
||||
binding.progress.visible()
|
||||
} else {
|
||||
binding.progress.gone()
|
||||
router.popController(this)
|
||||
}
|
||||
}
|
||||
|
||||
class MigrationDialog : DialogController() {
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val prefValue = preferences.migrateFlags().get()
|
||||
|
||||
val preselected = MigrationFlags.getEnabledFlagsPositions(prefValue)
|
||||
|
||||
return MaterialDialog(activity!!)
|
||||
.message(R.string.migration_dialog_what_to_include)
|
||||
.listItemsMultiChoice(
|
||||
items = MigrationFlags.titles.map { resources?.getString(it) as CharSequence },
|
||||
initialSelection = preselected.toIntArray()
|
||||
) { _, positions, _ ->
|
||||
// Save current settings for the next time
|
||||
val newValue = MigrationFlags.getFlagsFromPositions(positions.toTypedArray())
|
||||
preferences.migrateFlags().set(newValue)
|
||||
}
|
||||
.positiveButton(R.string.migrate) {
|
||||
(targetController as? SearchController)?.migrateManga()
|
||||
}
|
||||
.negativeButton(R.string.copy) {
|
||||
(targetController as? SearchController)?.copyManga()
|
||||
}
|
||||
.neutralButton(android.R.string.cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.os.Bundle
|
||||
import com.jakewharton.rxrelay.BehaviorRelay
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchCardItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItem
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
|
||||
class SearchPresenter(
|
||||
initialQuery: String? = "",
|
||||
private val manga: Manga
|
||||
) : GlobalSearchPresenter(initialQuery) {
|
||||
|
||||
private val replacingMangaRelay = BehaviorRelay.create<Boolean>()
|
||||
|
||||
override fun onCreate(savedState: Bundle?) {
|
||||
super.onCreate(savedState)
|
||||
|
||||
replacingMangaRelay.subscribeLatestCache({ controller, isReplacingManga -> (controller as? SearchController)?.renderIsReplacingManga(isReplacingManga) })
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
// Put the source of the selected manga at the top
|
||||
return super.getEnabledSources()
|
||||
.sortedByDescending { it.id == manga.source }
|
||||
}
|
||||
|
||||
override fun createCatalogueSearchItem(source: CatalogueSource, results: List<GlobalSearchCardItem>?): GlobalSearchItem {
|
||||
// Set the catalogue search item as highlighted if the source matches that of the selected manga
|
||||
return GlobalSearchItem(source, results, source.id == manga.source)
|
||||
}
|
||||
|
||||
override fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
|
||||
val localManga = super.networkToLocalManga(sManga, sourceId)
|
||||
// For migration, displayed title should always match source rather than local DB
|
||||
localManga.title = sManga.title
|
||||
return localManga
|
||||
}
|
||||
|
||||
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
|
||||
val source = sourceManager.get(manga.source) ?: return
|
||||
|
||||
replacingMangaRelay.call(true)
|
||||
|
||||
Observable.defer { source.fetchChapterList(manga) }
|
||||
.onErrorReturn { emptyList() }
|
||||
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
|
||||
.onErrorReturn { emptyList() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnUnsubscribe { replacingMangaRelay.call(false) }
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private fun migrateMangaInternal(
|
||||
source: Source,
|
||||
sourceChapters: List<SChapter>,
|
||||
prevManga: Manga,
|
||||
manga: Manga,
|
||||
replace: Boolean
|
||||
) {
|
||||
val flags = preferences.migrateFlags().get()
|
||||
val migrateChapters = MigrationFlags.hasChapters(flags)
|
||||
val migrateCategories = MigrationFlags.hasCategories(flags)
|
||||
val migrateTracks = MigrationFlags.hasTracks(flags)
|
||||
|
||||
db.inTransaction {
|
||||
// Update chapters read
|
||||
if (migrateChapters) {
|
||||
try {
|
||||
syncChaptersWithSource(db, sourceChapters, manga, source)
|
||||
} catch (e: Exception) {
|
||||
// Worst case, chapters won't be synced
|
||||
}
|
||||
|
||||
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||
val maxChapterRead = prevMangaChapters
|
||||
.filter { it.read }
|
||||
.maxBy { it.chapter_number }?.chapter_number
|
||||
val bookmarkedChapters = prevMangaChapters
|
||||
.filter { it.bookmark && it.isRecognizedNumber }
|
||||
.map { it.chapter_number }
|
||||
if (maxChapterRead != null) {
|
||||
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||
for (chapter in dbChapters) {
|
||||
if (chapter.isRecognizedNumber) {
|
||||
if (chapter.chapter_number <= maxChapterRead) {
|
||||
chapter.read = true
|
||||
}
|
||||
if (chapter.chapter_number in bookmarkedChapters) {
|
||||
chapter.bookmark = true
|
||||
}
|
||||
}
|
||||
}
|
||||
db.insertChapters(dbChapters).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
// Update categories
|
||||
if (migrateCategories) {
|
||||
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
|
||||
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
|
||||
db.setMangaCategories(mangaCategories, listOf(manga))
|
||||
}
|
||||
|
||||
// Update track
|
||||
if (migrateTracks) {
|
||||
val tracks = db.getTracks(prevManga).executeAsBlocking()
|
||||
for (track in tracks) {
|
||||
track.id = null
|
||||
track.manga_id = manga.id!!
|
||||
}
|
||||
db.insertTracks(tracks).executeAsBlocking()
|
||||
}
|
||||
|
||||
// Update favorite status
|
||||
if (replace) {
|
||||
prevManga.favorite = false
|
||||
db.updateMangaFavorite(prevManga).executeAsBlocking()
|
||||
}
|
||||
manga.favorite = true
|
||||
db.updateMangaFavorite(manga).executeAsBlocking()
|
||||
|
||||
// Update reading preferences
|
||||
manga.chapter_flags = prevManga.chapter_flags
|
||||
db.updateFlags(manga).executeAsBlocking()
|
||||
manga.viewer = prevManga.viewer
|
||||
db.updateMangaViewer(manga).executeAsBlocking()
|
||||
|
||||
// SearchPresenter#networkToLocalManga may have updated the manga title, so ensure db gets updated title
|
||||
db.updateMangaTitle(manga).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_header.title
|
||||
|
||||
/**
|
||||
* Item that contains the selection header.
|
||||
*/
|
||||
class SelectionHeader : AbstractHeaderItem<SelectionHeader.Holder>() {
|
||||
|
||||
/**
|
||||
* Returns the layout resource of this item.
|
||||
*/
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.source_main_controller_card_header
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new view holder for this item.
|
||||
*/
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
||||
return Holder(view, adapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this item to the given view holder.
|
||||
*/
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: Holder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) {
|
||||
init {
|
||||
title.text = view.context.getString(R.string.migration_selection_prompt)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is SelectionHeader
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
|
||||
/**
|
||||
* Adapter that holds the catalogue cards.
|
||||
*
|
||||
* @param controller instance of [MigrationController].
|
||||
*/
|
||||
class SourceAdapter(val controller: MigrationController) :
|
||||
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
||||
|
||||
val cardBackground = controller.activity!!.getResourceColor(R.attr.colorSurface)
|
||||
|
||||
private var items: List<IFlexible<*>>? = null
|
||||
|
||||
init {
|
||||
setDisplayHeadersAtStartUp(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener for browse item clicks.
|
||||
*/
|
||||
val selectClickListener: OnSelectClickListener? = controller
|
||||
|
||||
/**
|
||||
* Listener which should be called when user clicks select.
|
||||
*/
|
||||
interface OnSelectClickListener {
|
||||
fun onSelectClick(position: Int)
|
||||
}
|
||||
|
||||
override fun updateDataSet(items: MutableList<IFlexible<*>>?) {
|
||||
if (this.items !== items) {
|
||||
this.items = items
|
||||
super.updateDataSet(items)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.view.View
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.icon
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import io.github.mthli.slice.Slice
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.card
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.image
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.source_browse
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.source_latest
|
||||
import kotlinx.android.synthetic.main.source_main_controller_card_item.title
|
||||
|
||||
class SourceHolder(view: View, override val adapter: SourceAdapter) :
|
||||
BaseFlexibleViewHolder(view, adapter),
|
||||
SlicedHolder {
|
||||
|
||||
override val slice = Slice(card).apply {
|
||||
setColor(adapter.cardBackground)
|
||||
}
|
||||
|
||||
override val viewToSlice: View
|
||||
get() = card
|
||||
|
||||
init {
|
||||
source_latest.gone()
|
||||
source_browse.setText(R.string.select)
|
||||
source_browse.setOnClickListener {
|
||||
adapter.selectClickListener?.onSelectClick(bindingAdapterPosition)
|
||||
}
|
||||
}
|
||||
|
||||
fun bind(item: SourceItem) {
|
||||
val source = item.source
|
||||
setCardEdges(item)
|
||||
|
||||
// Set source name
|
||||
title.text = source.name
|
||||
|
||||
// Set source icon
|
||||
itemView.post {
|
||||
image.setImageDrawable(source.icon())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
|
||||
/**
|
||||
* Item that contains source information.
|
||||
*
|
||||
* @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) :
|
||||
AbstractSectionableItem<SourceHolder, SelectionHeader>(header) {
|
||||
|
||||
/**
|
||||
* Returns the layout resource of this item.
|
||||
*/
|
||||
override fun getLayoutRes(): Int {
|
||||
return R.layout.source_main_controller_card_item
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new view holder for this item.
|
||||
*/
|
||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SourceHolder {
|
||||
return SourceHolder(view, adapter as SourceAdapter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds this item to the given view holder.
|
||||
*/
|
||||
override fun bindViewHolder(
|
||||
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
|
||||
holder: SourceHolder,
|
||||
position: Int,
|
||||
payloads: List<Any?>?
|
||||
) {
|
||||
holder.bind(this)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.migration
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
|
||||
data class ViewState(
|
||||
val selectedSource: Source? = null,
|
||||
val mangaForSource: List<MangaItem> = emptyList(),
|
||||
val sourcesWithManga: List<SourceItem> = emptyList()
|
||||
)
|
||||
Reference in New Issue
Block a user