Add a new screen to help migrating manga from sources
This commit is contained in:
parent
b0482003bd
commit
a75457ad88
@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.DbProvider
|
|||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
@ -74,6 +75,11 @@ interface MangaQueries : DbProvider {
|
|||||||
.withPutResolver(MangaLastUpdatedPutResolver())
|
.withPutResolver(MangaLastUpdatedPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
|
fun updateMangaFavorite(manga: Manga) = db.put()
|
||||||
|
.`object`(manga)
|
||||||
|
.withPutResolver(MangaFavoritePutResolver())
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
fun deleteManga(manga: Manga) = db.delete().`object`(manga).prepare()
|
||||||
|
|
||||||
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
fun deleteMangas(mangas: List<Manga>) = db.delete().objects(mangas).prepare()
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.database.resolvers
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import com.pushtorefresh.storio.sqlite.StorIOSQLite
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
|
||||||
|
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
|
||||||
|
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
|
||||||
|
import eu.kanade.tachiyomi.data.database.inTransactionReturn
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||||
|
|
||||||
|
class MangaFavoritePutResolver : PutResolver<Manga>() {
|
||||||
|
|
||||||
|
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
|
||||||
|
val updateQuery = mapToUpdateQuery(manga)
|
||||||
|
val contentValues = mapToContentValues(manga)
|
||||||
|
|
||||||
|
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
|
||||||
|
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
|
.table(MangaTable.TABLE)
|
||||||
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
|
.whereArgs(manga.id)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
|
put(MangaTable.COL_FAVORITE, manga.favorite)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -165,4 +165,10 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
|
fun defaultCategory() = prefs.getInt(Keys.defaultCategory, -1)
|
||||||
|
|
||||||
|
fun migrateChapters() = rxPrefs.getBoolean("migrate_chapters", true)
|
||||||
|
|
||||||
|
fun migrateTracks() = rxPrefs.getBoolean("migrate_tracks", true)
|
||||||
|
|
||||||
|
fun migrateCategories() = rxPrefs.getBoolean("migrate_categories", true)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.base.holder
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.davidea.flexibleadapter.items.ISectionable
|
||||||
|
import eu.kanade.tachiyomi.util.dpToPx
|
||||||
|
import io.github.mthli.slice.Slice
|
||||||
|
|
||||||
|
interface SlicedHolder {
|
||||||
|
|
||||||
|
val slice: Slice
|
||||||
|
|
||||||
|
val adapter: FlexibleAdapter<IFlexible<*>>
|
||||||
|
|
||||||
|
val viewToSlice: View
|
||||||
|
|
||||||
|
fun setCardEdges(item: ISectionable<*, *>) {
|
||||||
|
// 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 = adapter.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) {
|
||||||
|
val margin = margin
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (viewToSlice.layoutParams is ViewGroup.MarginLayoutParams) {
|
||||||
|
val p = viewToSlice.layoutParams as ViewGroup.MarginLayoutParams
|
||||||
|
p.setMargins(left, top, right, bottom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val margin
|
||||||
|
get() = 8.dpToPx
|
||||||
|
|
||||||
|
}
|
@ -18,17 +18,17 @@ class SourceDividerItemDecoration(context: Context) : RecyclerView.ItemDecoratio
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
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
|
val childCount = parent.childCount
|
||||||
for (i in 0 until childCount - 1) {
|
for (i in 0 until childCount - 1) {
|
||||||
val child = parent.getChildAt(i)
|
val child = parent.getChildAt(i)
|
||||||
if (parent.getChildViewHolder(child) is SourceHolder &&
|
val holder = parent.getChildViewHolder(child)
|
||||||
|
if (holder is SourceHolder &&
|
||||||
parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder) {
|
parent.getChildViewHolder(parent.getChildAt(i + 1)) is SourceHolder) {
|
||||||
val params = child.layoutParams as RecyclerView.LayoutParams
|
val params = child.layoutParams as RecyclerView.LayoutParams
|
||||||
val top = child.bottom + params.bottomMargin
|
val top = child.bottom + params.bottomMargin
|
||||||
val bottom = top + divider.intrinsicHeight
|
val bottom = top + divider.intrinsicHeight
|
||||||
|
val left = parent.paddingLeft + holder.margin
|
||||||
|
val right = parent.paddingRight + holder.margin
|
||||||
|
|
||||||
divider.setBounds(left, top, right, bottom)
|
divider.setBounds(left, top, right, bottom)
|
||||||
divider.draw(c)
|
divider.draw(c)
|
||||||
|
@ -6,6 +6,7 @@ import android.view.ViewGroup
|
|||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.online.LoginSource
|
import eu.kanade.tachiyomi.source.online.LoginSource
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
import eu.kanade.tachiyomi.util.dpToPx
|
import eu.kanade.tachiyomi.util.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.getRound
|
import eu.kanade.tachiyomi.util.getRound
|
||||||
import eu.kanade.tachiyomi.util.gone
|
import eu.kanade.tachiyomi.util.gone
|
||||||
@ -13,12 +14,17 @@ import eu.kanade.tachiyomi.util.visible
|
|||||||
import io.github.mthli.slice.Slice
|
import io.github.mthli.slice.Slice
|
||||||
import kotlinx.android.synthetic.main.catalogue_main_controller_card_item.*
|
import kotlinx.android.synthetic.main.catalogue_main_controller_card_item.*
|
||||||
|
|
||||||
class SourceHolder(view: View, adapter: CatalogueAdapter) : BaseFlexibleViewHolder(view, adapter) {
|
class SourceHolder(view: View, override val adapter: CatalogueAdapter) :
|
||||||
|
BaseFlexibleViewHolder(view, adapter),
|
||||||
|
SlicedHolder {
|
||||||
|
|
||||||
private val slice = Slice(card).apply {
|
override val slice = Slice(card).apply {
|
||||||
setColor(adapter.cardBackground)
|
setColor(adapter.cardBackground)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val viewToSlice: View
|
||||||
|
get() = card
|
||||||
|
|
||||||
init {
|
init {
|
||||||
source_browse.setOnClickListener {
|
source_browse.setOnClickListener {
|
||||||
adapter.browseClickListener.onBrowseClick(adapterPosition)
|
adapter.browseClickListener.onBrowseClick(adapterPosition)
|
||||||
@ -50,56 +56,4 @@ class SourceHolder(view: View, adapter: CatalogueAdapter) : BaseFlexibleViewHold
|
|||||||
source_latest.visible()
|
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 = 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
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -18,7 +18,7 @@ import kotlinx.android.synthetic.main.catalogue_global_search_controller.*
|
|||||||
* This controller should only handle UI actions, IO actions should be done by [CatalogueSearchPresenter]
|
* This controller should only handle UI actions, IO actions should be done by [CatalogueSearchPresenter]
|
||||||
* [CatalogueSearchCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
* [CatalogueSearchCardAdapter.OnMangaClickListener] called when manga is clicked in global search
|
||||||
*/
|
*/
|
||||||
class CatalogueSearchController(private val initialQuery: String? = null) :
|
open class CatalogueSearchController(protected val initialQuery: String? = null) :
|
||||||
NucleusController<CatalogueSearchPresenter>(),
|
NucleusController<CatalogueSearchPresenter>(),
|
||||||
CatalogueSearchCardAdapter.OnMangaClickListener {
|
CatalogueSearchCardAdapter.OnMangaClickListener {
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
|
|||||||
* @param db manages the database calls.
|
* @param db manages the database calls.
|
||||||
* @param preferencesHelper manages the preference calls.
|
* @param preferencesHelper manages the preference calls.
|
||||||
*/
|
*/
|
||||||
class CatalogueSearchPresenter(
|
open class CatalogueSearchPresenter(
|
||||||
val initialQuery: String? = "",
|
val initialQuery: String? = "",
|
||||||
val sourceManager: SourceManager = Injekt.get(),
|
val sourceManager: SourceManager = Injekt.get(),
|
||||||
val db: DatabaseHelper = Injekt.get(),
|
val db: DatabaseHelper = Injekt.get(),
|
||||||
@ -86,7 +86,7 @@ class CatalogueSearchPresenter(
|
|||||||
*
|
*
|
||||||
* @return list containing enabled sources.
|
* @return list containing enabled sources.
|
||||||
*/
|
*/
|
||||||
private fun getEnabledSources(): List<CatalogueSource> {
|
protected open fun getEnabledSources(): List<CatalogueSource> {
|
||||||
val languages = preferencesHelper.enabledLanguages().getOrDefault()
|
val languages = preferencesHelper.enabledLanguages().getOrDefault()
|
||||||
val hiddenCatalogues = preferencesHelper.hiddenCatalogues().getOrDefault()
|
val hiddenCatalogues = preferencesHelper.hiddenCatalogues().getOrDefault()
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
|||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
import eu.kanade.tachiyomi.ui.category.CategoryController
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.ui.manga.MangaController
|
import eu.kanade.tachiyomi.ui.manga.MangaController
|
||||||
|
import eu.kanade.tachiyomi.ui.migration.MigrationController
|
||||||
import eu.kanade.tachiyomi.util.inflate
|
import eu.kanade.tachiyomi.util.inflate
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import eu.kanade.tachiyomi.widget.DrawerSwipeCloseListener
|
import eu.kanade.tachiyomi.widget.DrawerSwipeCloseListener
|
||||||
@ -360,6 +361,9 @@ class LibraryController(
|
|||||||
R.id.action_edit_categories -> {
|
R.id.action_edit_categories -> {
|
||||||
router.pushController(CategoryController().withFadeTransaction())
|
router.pushController(CategoryController().withFadeTransaction())
|
||||||
}
|
}
|
||||||
|
R.id.action_source_migration -> {
|
||||||
|
router.pushController(MigrationController().withFadeTransaction())
|
||||||
|
}
|
||||||
else -> return super.onOptionsItemSelected(item)
|
else -> return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.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,36 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
|
import kotlinx.android.synthetic.main.catalogue_list_item.*
|
||||||
|
|
||||||
|
class MangaHolder(
|
||||||
|
private val view: View,
|
||||||
|
private val 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)
|
||||||
|
GlideApp.with(itemView.context)
|
||||||
|
.load(item.manga)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
|
.centerCrop()
|
||||||
|
.circleCrop()
|
||||||
|
.dontAnimate()
|
||||||
|
.into(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||||
|
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.catalogue_list_item
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): MangaHolder {
|
||||||
|
return MangaHolder(view, adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>,
|
||||||
|
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,135 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v7.widget.LinearLayoutManager
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
|
||||||
|
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
|
||||||
|
import kotlinx.android.synthetic.main.migration_controller.*
|
||||||
|
|
||||||
|
class MigrationController : NucleusController<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 {
|
||||||
|
return inflater.inflate(R.layout.migration_controller, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View) {
|
||||||
|
super.onViewCreated(view)
|
||||||
|
|
||||||
|
adapter = FlexibleAdapter(null, this)
|
||||||
|
migration_recycler.layoutManager = LinearLayoutManager(view.context)
|
||||||
|
migration_recycler.adapter = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
migration_recycler.adapter = adapter
|
||||||
|
}
|
||||||
|
adapter?.updateDataSet(state.sourcesWithManga)
|
||||||
|
} else {
|
||||||
|
title = state.selectedSource.toString()
|
||||||
|
if (adapter !is MangaAdapter) {
|
||||||
|
adapter = MangaAdapter(this)
|
||||||
|
migration_recycler.adapter = adapter
|
||||||
|
}
|
||||||
|
adapter?.updateDataSet(state.mangaForSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun renderIsReplacingManga(state: ViewState) {
|
||||||
|
if (state.isReplacingManga) {
|
||||||
|
if (router.getControllerWithTag(LOADING_DIALOG_TAG) == null) {
|
||||||
|
LoadingController().showDialog(router, LOADING_DIALOG_TAG)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
router.popControllerWithTag(LOADING_DIALOG_TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(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(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun migrateManga(prevManga: Manga, manga: Manga) {
|
||||||
|
presenter.migrateManga(prevManga, manga, replace = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyManga(prevManga: Manga, manga: Manga) {
|
||||||
|
presenter.migrateManga(prevManga, manga, replace = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadingController : DialogController() {
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
return MaterialDialog.Builder(activity!!)
|
||||||
|
.progress(true, 0)
|
||||||
|
.content(R.string.migrating)
|
||||||
|
.cancelable(false)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val LOADING_DIALOG_TAG = "LoadingDialog"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.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.data.database.models.MangaCategory
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.combineLatest
|
||||||
|
import eu.kanade.tachiyomi.util.syncChaptersWithSource
|
||||||
|
import rx.Observable
|
||||||
|
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(),
|
||||||
|
private val preferences: PreferencesHelper = 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.getLibraryMangas()
|
||||||
|
.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()
|
||||||
|
|
||||||
|
stateRelay
|
||||||
|
// Render the view when any field other than isReplacingManga changes
|
||||||
|
.distinctUntilChanged { t1, t2 -> t1.isReplacingManga != t2.isReplacingManga }
|
||||||
|
.subscribeLatestCache(MigrationController::render)
|
||||||
|
|
||||||
|
stateRelay.distinctUntilChanged { state -> state.isReplacingManga }
|
||||||
|
.subscribeLatestCache(MigrationController::renderIsReplacingManga)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.get(it) else null }
|
||||||
|
.map { SourceItem(it, header) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
|
||||||
|
return library.filter { it.source == sourceId }.map(::MangaItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun migrateManga(prevManga: Manga, manga: Manga, replace: Boolean) {
|
||||||
|
val source = sourceManager.get(manga.source) ?: return
|
||||||
|
|
||||||
|
state = state.copy(isReplacingManga = true)
|
||||||
|
|
||||||
|
Observable.defer { source.fetchChapterList(manga) }
|
||||||
|
.doOnNext { migrateMangaInternal(source, it, prevManga, manga, replace) }
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.doOnUnsubscribe { state = state.copy(isReplacingManga = false) }
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun migrateMangaInternal(source: Source, sourceChapters: List<SChapter>,
|
||||||
|
prevManga: Manga, manga: Manga, replace: Boolean) {
|
||||||
|
|
||||||
|
db.inTransaction {
|
||||||
|
// Update chapters read
|
||||||
|
if (preferences.migrateChapters().getOrDefault()) {
|
||||||
|
syncChaptersWithSource(db, sourceChapters, manga, source)
|
||||||
|
|
||||||
|
val prevMangaChapters = db.getChapters(prevManga).executeAsBlocking()
|
||||||
|
val maxChapterRead = prevMangaChapters.filter { it.read }
|
||||||
|
.maxBy { it.chapter_number }?.chapter_number
|
||||||
|
if (maxChapterRead != null) {
|
||||||
|
val dbChapters = db.getChapters(manga).executeAsBlocking()
|
||||||
|
for (chapter in dbChapters) {
|
||||||
|
if (chapter.isRecognizedNumber && chapter.chapter_number <= maxChapterRead) {
|
||||||
|
chapter.read = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.insertChapters(dbChapters).executeAsBlocking()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update categories
|
||||||
|
if (preferences.migrateCategories().getOrDefault()) {
|
||||||
|
val categories = db.getCategoriesForManga(prevManga).executeAsBlocking()
|
||||||
|
val mangaCategories = categories.map { MangaCategory.create(manga, it) }
|
||||||
|
db.setMangaCategories(mangaCategories, listOf(manga))
|
||||||
|
}
|
||||||
|
// Update track
|
||||||
|
if (preferences.migrateTracks().getOrDefault()) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.os.Bundle
|
||||||
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
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.ui.base.controller.DialogController
|
||||||
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController
|
||||||
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
class SearchController(
|
||||||
|
private var manga: Manga? = null
|
||||||
|
) : CatalogueSearchController(manga?.title) {
|
||||||
|
|
||||||
|
private var newManga: Manga? = null
|
||||||
|
|
||||||
|
override fun createPresenter(): CatalogueSearchPresenter {
|
||||||
|
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 target = targetController as? MigrationController ?: return
|
||||||
|
val manga = manga ?: return
|
||||||
|
val newManga = newManga ?: return
|
||||||
|
|
||||||
|
router.popController(this)
|
||||||
|
target.migrateManga(manga, newManga)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copyManga() {
|
||||||
|
val target = targetController as? MigrationController ?: return
|
||||||
|
val manga = manga ?: return
|
||||||
|
val newManga = newManga ?: return
|
||||||
|
|
||||||
|
router.popController(this)
|
||||||
|
target.copyManga(manga, newManga)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMangaClick(manga: Manga) {
|
||||||
|
newManga = manga
|
||||||
|
val dialog = MigrationDialog()
|
||||||
|
dialog.targetController = this
|
||||||
|
dialog.showDialog(router)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MigrationDialog : DialogController() {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
|
val optionTitles = arrayOf(
|
||||||
|
R.string.chapters,
|
||||||
|
R.string.categories,
|
||||||
|
R.string.track
|
||||||
|
)
|
||||||
|
|
||||||
|
val optionPrefs = arrayOf(
|
||||||
|
preferences.migrateChapters(),
|
||||||
|
preferences.migrateCategories(),
|
||||||
|
preferences.migrateTracks()
|
||||||
|
)
|
||||||
|
|
||||||
|
val preselected = optionPrefs.mapIndexedNotNull { index, preference ->
|
||||||
|
if (preference.getOrDefault()) index else null
|
||||||
|
}
|
||||||
|
|
||||||
|
return MaterialDialog.Builder(activity!!)
|
||||||
|
.content(R.string.migration_dialog_what_to_include)
|
||||||
|
.items(optionTitles.map { resources?.getString(it) })
|
||||||
|
.alwaysCallMultiChoiceCallback()
|
||||||
|
.itemsCallbackMultiChoice(preselected.toTypedArray(), { _, positions, _ ->
|
||||||
|
// Save current settings for the next time
|
||||||
|
optionPrefs.forEachIndexed { index, preference ->
|
||||||
|
preference.set(index in positions)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
})
|
||||||
|
.positiveText(R.string.migrate)
|
||||||
|
.negativeText(R.string.copy)
|
||||||
|
.neutralText(android.R.string.cancel)
|
||||||
|
.onPositive { _, _ ->
|
||||||
|
(targetController as? SearchController)?.migrateManga()
|
||||||
|
}
|
||||||
|
.onNegative { _, _ ->
|
||||||
|
(targetController as? SearchController)?.copyManga()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||||
|
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchPresenter
|
||||||
|
|
||||||
|
class SearchPresenter(
|
||||||
|
initialQuery: String? = "",
|
||||||
|
private val manga: Manga
|
||||||
|
) : CatalogueSearchPresenter(initialQuery) {
|
||||||
|
|
||||||
|
override fun getEnabledSources(): List<CatalogueSource> {
|
||||||
|
// Filter out the source of the selected manga
|
||||||
|
return super.getEnabledSources()
|
||||||
|
.filterNot { it.id == manga.source }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractHeaderItem
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
|
import kotlinx.android.synthetic.main.catalogue_main_controller_card.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.catalogue_main_controller_card
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new view holder for this item.
|
||||||
|
*/
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): Holder {
|
||||||
|
return SelectionHeader.Holder(view, adapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds this item to the given view holder.
|
||||||
|
*/
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: Holder,
|
||||||
|
position: Int, payloads: List<Any?>?) {
|
||||||
|
// Intentionally empty
|
||||||
|
}
|
||||||
|
|
||||||
|
class Holder(view: View, adapter: FlexibleAdapter<*>) : BaseFlexibleViewHolder(view, adapter) {
|
||||||
|
init {
|
||||||
|
title.text = "Please select a source to migrate from"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.migration
|
||||||
|
|
||||||
|
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 [MigrationController].
|
||||||
|
*/
|
||||||
|
class SourceAdapter(val controller: MigrationController) :
|
||||||
|
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
|
||||||
|
|
||||||
|
val cardBackground = controller.activity!!.getResourceColor(R.attr.background_card)
|
||||||
|
|
||||||
|
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,43 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.ui.base.holder.SlicedHolder
|
||||||
|
import eu.kanade.tachiyomi.util.getRound
|
||||||
|
import eu.kanade.tachiyomi.util.gone
|
||||||
|
import io.github.mthli.slice.Slice
|
||||||
|
import kotlinx.android.synthetic.main.catalogue_main_controller_card_item.*
|
||||||
|
|
||||||
|
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(adapterPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(item: SourceItem) {
|
||||||
|
val source = item.source
|
||||||
|
setCardEdges(item)
|
||||||
|
|
||||||
|
// Set source name
|
||||||
|
title.text = source.name
|
||||||
|
|
||||||
|
// Set circle letter image.
|
||||||
|
itemView.post {
|
||||||
|
image.setImageDrawable(image.getRound(source.name.take(1).toUpperCase(),false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.davidea.flexibleadapter.items.AbstractSectionableItem
|
||||||
|
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.catalogue_main_controller_card_item
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new view holder for this item.
|
||||||
|
*/
|
||||||
|
override fun createViewHolder(view: View, adapter: FlexibleAdapter<*>): SourceHolder {
|
||||||
|
return SourceHolder(view, adapter as SourceAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds this item to the given view holder.
|
||||||
|
*/
|
||||||
|
override fun bindViewHolder(adapter: FlexibleAdapter<*>, holder: SourceHolder,
|
||||||
|
position: Int, payloads: List<Any?>?) {
|
||||||
|
|
||||||
|
holder.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.ui.migration
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
|
||||||
|
data class ViewState(
|
||||||
|
val selectedSource: Source? = null,
|
||||||
|
val mangaForSource: List<MangaItem> = emptyList(),
|
||||||
|
val sourcesWithManga: List<SourceItem> = emptyList(),
|
||||||
|
val isReplacingManga: Boolean = false
|
||||||
|
)
|
6
app/src/main/res/layout/migration_controller.xml
Normal file
6
app/src/main/res/layout/migration_controller.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.v7.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/migration_recycler"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"/>
|
@ -27,4 +27,9 @@
|
|||||||
android:title="@string/action_edit_categories"
|
android:title="@string/action_edit_categories"
|
||||||
app:showAsAction="never"/>
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_source_migration"
|
||||||
|
android:title="Source migration"
|
||||||
|
app:showAsAction="never"/>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
||||||
|
11
app/src/main/res/menu/migration.xml
Normal file
11
app/src/main/res/menu/migration.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_change_source"
|
||||||
|
android:icon="@drawable/ic_filter_list_white_24dp"
|
||||||
|
android:title="@string/action_filter"
|
||||||
|
app:showAsAction="always"/>
|
||||||
|
|
||||||
|
</menu>
|
@ -20,7 +20,7 @@
|
|||||||
<string name="label_categories">Categories</string>
|
<string name="label_categories">Categories</string>
|
||||||
<string name="label_selected">Selected: %1$d</string>
|
<string name="label_selected">Selected: %1$d</string>
|
||||||
<string name="label_backup">Backup</string>
|
<string name="label_backup">Backup</string>
|
||||||
|
<string name="label_migration">Source migration</string>
|
||||||
|
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<string name="action_settings">Settings</string>
|
<string name="action_settings">Settings</string>
|
||||||
@ -390,6 +390,14 @@
|
|||||||
<!-- Recent manga fragment -->
|
<!-- Recent manga fragment -->
|
||||||
<string name="recent_manga_source">%1$s - Ch.%2$s</string>
|
<string name="recent_manga_source">%1$s - Ch.%2$s</string>
|
||||||
|
|
||||||
|
<!-- Source migration screen -->
|
||||||
|
<string name="migration_info">Tap to select the source to migrate from</string>
|
||||||
|
<string name="migration_dialog_what_to_include">Select data to include</string>
|
||||||
|
<string name="select">Select</string>
|
||||||
|
<string name="migrate">Migrate</string>
|
||||||
|
<string name="copy">Copy</string>
|
||||||
|
<string name="migrating">Migrating…</string>
|
||||||
|
|
||||||
<!-- Downloads activity and service -->
|
<!-- Downloads activity and service -->
|
||||||
<string name="download_queue_error">An error occurred while downloading chapters. You can try again in the downloads section</string>
|
<string name="download_queue_error">An error occurred while downloading chapters. You can try again in the downloads section</string>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user