mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Add a new screen to help migrating manga from sources
This commit is contained in:
		@@ -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.Manga
 | 
			
		||||
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.MangaLastUpdatedPutResolver
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
 | 
			
		||||
@@ -74,6 +75,11 @@ interface MangaQueries : DbProvider {
 | 
			
		||||
            .withPutResolver(MangaLastUpdatedPutResolver())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun updateMangaFavorite(manga: Manga) = db.put()
 | 
			
		||||
            .`object`(manga)
 | 
			
		||||
            .withPutResolver(MangaFavoritePutResolver())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun deleteManga(manga: Manga) = db.delete().`object`(manga).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 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) {
 | 
			
		||||
        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 &&
 | 
			
		||||
            val holder = parent.getChildViewHolder(child)
 | 
			
		||||
            if (holder 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
 | 
			
		||||
                val left = parent.paddingLeft + holder.margin
 | 
			
		||||
                val right = parent.paddingRight + holder.margin
 | 
			
		||||
 | 
			
		||||
                divider.setBounds(left, top, right, bottom)
 | 
			
		||||
                divider.draw(c)
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ import android.view.ViewGroup
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.LoginSource
 | 
			
		||||
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.getRound
 | 
			
		||||
import eu.kanade.tachiyomi.util.gone
 | 
			
		||||
@@ -13,12 +14,17 @@ import eu.kanade.tachiyomi.util.visible
 | 
			
		||||
import io.github.mthli.slice.Slice
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val viewToSlice: View
 | 
			
		||||
        get() = card
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        source_browse.setOnClickListener {
 | 
			
		||||
            adapter.browseClickListener.onBrowseClick(adapterPosition)
 | 
			
		||||
@@ -50,56 +56,4 @@ class SourceHolder(view: View, adapter: CatalogueAdapter) : BaseFlexibleViewHold
 | 
			
		||||
            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]
 | 
			
		||||
 * [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>(),
 | 
			
		||||
        CatalogueSearchCardAdapter.OnMangaClickListener {
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ import uy.kohesive.injekt.api.get
 | 
			
		||||
 * @param db manages the database calls.
 | 
			
		||||
 * @param preferencesHelper manages the preference calls.
 | 
			
		||||
 */
 | 
			
		||||
class CatalogueSearchPresenter(
 | 
			
		||||
open class CatalogueSearchPresenter(
 | 
			
		||||
        val initialQuery: String? = "",
 | 
			
		||||
        val sourceManager: SourceManager = Injekt.get(),
 | 
			
		||||
        val db: DatabaseHelper = Injekt.get(),
 | 
			
		||||
@@ -86,7 +86,7 @@ class CatalogueSearchPresenter(
 | 
			
		||||
     *
 | 
			
		||||
     * @return list containing enabled sources.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
    protected open fun getEnabledSources(): List<CatalogueSource> {
 | 
			
		||||
        val languages = preferencesHelper.enabledLanguages().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.main.MainActivity
 | 
			
		||||
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.toast
 | 
			
		||||
import eu.kanade.tachiyomi.widget.DrawerSwipeCloseListener
 | 
			
		||||
@@ -360,6 +361,9 @@ class LibraryController(
 | 
			
		||||
            R.id.action_edit_categories -> {
 | 
			
		||||
                router.pushController(CategoryController().withFadeTransaction())
 | 
			
		||||
            }
 | 
			
		||||
            R.id.action_source_migration -> {
 | 
			
		||||
                router.pushController(MigrationController().withFadeTransaction())
 | 
			
		||||
            }
 | 
			
		||||
            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
 | 
			
		||||
)
 | 
			
		||||
		Reference in New Issue
	
	Block a user