Added Source Migration to browse section

Don't ask me how I did it.
This commit is contained in:
Jays2Kings 2021-03-23 12:58:57 -04:00
parent 90a6543334
commit d6b07b7f40
11 changed files with 477 additions and 128 deletions

View File

@ -1,19 +1,33 @@
package eu.kanade.tachiyomi.ui.extension package eu.kanade.tachiyomi.ui.extension
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.ExtensionsChangedListener import eu.kanade.tachiyomi.extension.ExtensionsChangedListener
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.migration.MangaItem
import eu.kanade.tachiyomi.ui.migration.SelectionHeader
import eu.kanade.tachiyomi.ui.migration.SourceItem
import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.executeOnIO
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -32,23 +46,64 @@ class ExtensionBottomPresenter(
private var extensions = emptyList<ExtensionItem>() private var extensions = emptyList<ExtensionItem>()
var sourceItems = emptyList<SourceItem>()
private set
var mangaItems = hashMapOf<Long, List<MangaItem>>()
private set
private var currentDownloads = hashMapOf<String, InstallStep>() private var currentDownloads = hashMapOf<String, InstallStep>()
private val sourceManager: SourceManager = Injekt.get()
private var selectedSource: Long? = null
private val db: DatabaseHelper = Injekt.get()
fun onCreate() { fun onCreate() {
scope.launch { scope.launch {
extensionManager.findAvailableExtensionsAsync() val extensionJob = async {
extensions = toItems( extensionManager.findAvailableExtensionsAsync()
Triple( extensions = toItems(
extensionManager.installedExtensions, Triple(
extensionManager.untrustedExtensions, extensionManager.installedExtensions,
extensionManager.availableExtensions extensionManager.untrustedExtensions,
extensionManager.availableExtensions
)
) )
) withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions) }
withContext(Dispatchers.Main) { bottomSheet.setExtensions(extensions) } extensionManager.setListener(this@ExtensionBottomPresenter)
extensionManager.setListener(this@ExtensionBottomPresenter) }
val migrationJob = async {
val favs = db.getFavoriteMangas().executeOnIO()
sourceItems = findSourcesWithManga(favs)
mangaItems = HashMap(sourceItems.associate {
it.source.id to this@ExtensionBottomPresenter.libraryToMigrationItem(favs, it.source.id)
})
withContext(Dispatchers.Main) {
if (selectedSource != null) {
bottomSheet.setMigrationManga(mangaItems[selectedSource])
}
else {
bottomSheet.setMigrationSources(sourceItems)
}
}
}
listOf(migrationJob, extensionJob).awaitAll()
} }
} }
private fun findSourcesWithManga(library: List<Manga>): List<SourceItem> {
val header = SelectionHeader()
return library.map { it.source }.toSet()
.mapNotNull { if (it != LocalSource.ID) sourceManager.getOrStub(it) else null }
.sortedBy { it.name }
.map { SourceItem(it, header) }
}
private fun libraryToMigrationItem(library: List<Manga>, sourceId: Long): List<MangaItem> {
return library.filter { it.source == sourceId }.map(::MangaItem)
}
fun onDestroy() { fun onDestroy() {
extensionManager.removeListener(this) extensionManager.removeListener(this)
} }
@ -66,6 +121,24 @@ class ExtensionBottomPresenter(
} }
} }
fun refreshMigrations() {
scope.launch {
val favs = db.getFavoriteMangas().executeOnIO()
sourceItems = findSourcesWithManga(favs)
mangaItems = HashMap(sourceItems.associate {
it.source.id to this@ExtensionBottomPresenter.libraryToMigrationItem(favs, it.source.id)
})
withContext(Dispatchers.Main) {
if (selectedSource != null) {
bottomSheet.setMigrationManga(mangaItems[selectedSource])
}
else {
bottomSheet.setMigrationSources(sourceItems)
}
}
}
}
override fun extensionsUpdated() { override fun extensionsUpdated() {
refreshExtensions() refreshExtensions()
} }
@ -166,4 +239,18 @@ class ExtensionBottomPresenter(
fun trustSignature(signatureHash: String) { fun trustSignature(signatureHash: String) {
extensionManager.trustSignature(signatureHash) extensionManager.trustSignature(signatureHash)
} }
fun setSelectedSource(source: Source) {
selectedSource = source.id
scope.launch {
withContext(Dispatchers.Main) { bottomSheet.setMigrationManga(mangaItems[source.id]) }
}
}
fun deselectSource() {
selectedSource = null
scope.launch {
withContext(Dispatchers.Main) { bottomSheet.setMigrationSources(sourceItems) }
}
}
} }

View File

@ -2,16 +2,33 @@ package eu.kanade.tachiyomi.ui.extension
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.core.view.get
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.migration.MangaAdapter
import eu.kanade.tachiyomi.ui.migration.MangaItem
import eu.kanade.tachiyomi.ui.migration.SourceAdapter
import eu.kanade.tachiyomi.ui.migration.SourceItem
import eu.kanade.tachiyomi.ui.migration.manga.design.PreMigrationController
import eu.kanade.tachiyomi.ui.recents.RecentMangaHolder
import eu.kanade.tachiyomi.ui.source.SourceController import eu.kanade.tachiyomi.ui.source.SourceController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.await
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.expand
@ -19,15 +36,25 @@ import eu.kanade.tachiyomi.util.view.isExpanded
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import kotlinx.android.synthetic.main.extensions_bottom_sheet.view.* import kotlinx.android.synthetic.main.extensions_bottom_sheet.view.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.migration_controller.*
import kotlinx.android.synthetic.main.recents_controller.*
import kotlinx.android.synthetic.main.recycler_with_scroller.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import rx.schedulers.Schedulers
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
LinearLayout(context, attrs), LinearLayout(context, attrs),
ExtensionAdapter.OnButtonClickListener, ExtensionAdapter.OnButtonClickListener,
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener, FlexibleAdapter.OnItemLongClickListener,
ExtensionTrustDialog.Listener { ExtensionTrustDialog.Listener,
SourceAdapter.OnAllClickListener {
var sheetBehavior: BottomSheetBehavior<*>? = null var sheetBehavior: BottomSheetBehavior<*>? = null
@ -37,28 +64,79 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
* Adapter containing the list of extensions * Adapter containing the list of extensions
*/ */
private var adapter: FlexibleAdapter<IFlexible<*>>? = null private var adapter: FlexibleAdapter<IFlexible<*>>? = null
private var migAdapter: FlexibleAdapter<IFlexible<*>>? = null
val presenter = ExtensionBottomPresenter(this) val presenter = ExtensionBottomPresenter(this)
private var extensions: List<ExtensionItem> = emptyList() private var extensions: List<ExtensionItem> = emptyList()
var canExpand = false
lateinit var controller: SourceController lateinit var controller: SourceController
private val pAdapter = FlexibleAdapter<IFlexible<*>>(null, this, true)
val extensionFrameLayout =
inflate(context, R.layout.recycler_with_scroller, null) as FrameLayout
val migrationFrameLayout =
inflate(context, R.layout.recycler_with_scroller, null) as FrameLayout
fun onCreate(controller: SourceController) { fun onCreate(controller: SourceController) {
// Initialize adapter, scroll listener and recycler views // Initialize adapter, scroll listener and recycler views
adapter = ExtensionAdapter(this) adapter = ExtensionAdapter(this)
migAdapter = ExtensionAdapter(this)
sheetBehavior = BottomSheetBehavior.from(this) sheetBehavior = BottomSheetBehavior.from(this)
// Create recycler and set adapter. // Create recycler and set adapter.
ext_recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context) val extRecyler = extensionFrameLayout.recycler
ext_recycler.adapter = adapter val migRecyler = migrationFrameLayout.recycler
ext_recycler.setHasFixedSize(true)
ext_recycler.addItemDecoration(ExtensionDividerItemDecoration(context)) extRecyler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
adapter?.fastScroller = fast_scroller extRecyler.adapter = adapter
extRecyler.setHasFixedSize(true)
extRecyler.addItemDecoration(ExtensionDividerItemDecoration(context))
migRecyler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
migRecyler.setHasFixedSize(true)
adapter?.fastScroller = extensionFrameLayout.fast_scroller
this.controller = controller this.controller = controller
ext_recycler.doOnApplyWindowInsets { view, _, _ -> pager.doOnApplyWindowInsets { _, _, _ ->
val bottomBar = (this@ExtensionBottomSheet.controller.activity as? MainActivity)?.bottom_nav val bottomBar =
view.updatePaddingRelative(bottom = bottomBar?.height ?: 0) (this@ExtensionBottomSheet.controller.activity as? MainActivity)?.bottom_nav
extRecyler.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
migRecyler.updatePaddingRelative(bottom = bottomBar?.height ?: 0)
} }
pager.adapter = TabbedSheetAdapter()
tabs.setupWithViewPager(pager)
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (canExpand) {
this@ExtensionBottomSheet.sheetBehavior?.expand()
}
this@ExtensionBottomSheet.controller.updateTitleAndMenu()
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler?.isNestedScrollingEnabled = true
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler?.requestLayout()
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler?.isNestedScrollingEnabled = false
}
override fun onTabReselected(tab: TabLayout.Tab?) {
this@ExtensionBottomSheet.sheetBehavior?.expand()
when (tab?.position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}.recycler?.isNestedScrollingEnabled = true
}
})
presenter.onCreate() presenter.onCreate()
updateExtTitle() updateExtTitle()
@ -66,12 +144,6 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
val array = context.obtainStyledAttributes(attrsArray) val array = context.obtainStyledAttributes(attrsArray)
val headerHeight = array.getDimensionPixelSize(0, 0) val headerHeight = array.getDimensionPixelSize(0, 0)
array.recycle() array.recycle()
ext_recycler_layout.doOnApplyWindowInsets { v, windowInsets, _ ->
v.updateLayoutParams<MarginLayoutParams> {
topMargin = windowInsets.systemWindowInsetTop + headerHeight -
(sheet_layout.height)
}
}
sheet_layout.setOnClickListener { sheet_layout.setOnClickListener {
if (!sheetBehavior.isExpanded()) { if (!sheetBehavior.isExpanded()) {
sheetBehavior?.expand() sheetBehavior?.expand()
@ -92,18 +164,8 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
fun updateExtTitle() { fun updateExtTitle() {
val extCount = presenter.getExtensionUpdateCount() val extCount = presenter.getExtensionUpdateCount()
title_text.text = if (extCount == 0) context.getString(R.string.extensions) if (extCount > 0) tabs.getTabAt(0)?.orCreateBadge?.number = extCount
else resources.getQuantityString( else tabs.getTabAt(0)?.removeBadge()
R.plurals.extension_updates_available,
extCount,
extCount
)
title_text.setTextColor(
context.getResourceColor(
if (extCount == 0) R.attr.actionBarTintColor else R.attr.colorAccent
)
)
} }
override fun onButtonClick(position: Int) { override fun onButtonClick(position: Int) {
@ -126,23 +188,55 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
} }
override fun onItemClick(view: View?, position: Int): Boolean { override fun onItemClick(view: View?, position: Int): Boolean {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false when (tabs.selectedTabPosition) {
if (extension is Extension.Installed) { 0 -> {
openDetails(extension) val extension =
} else if (extension is Extension.Untrusted) { (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return false
openTrustDialog(extension) if (extension is Extension.Installed) {
} openDetails(extension)
} else if (extension is Extension.Untrusted) {
openTrustDialog(extension)
}
}
else -> {
val item = migAdapter?.getItem(position) ?: return false
if (item is MangaItem) {
PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().getOrDefault(),
controller.router,
listOf(item.manga.id!!)
)
} else if (item is SourceItem) {
presenter.setSelectedSource(item.source)
}
}
}
return false return false
} }
override fun onItemLongClick(position: Int) { override fun onItemLongClick(position: Int) {
val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return if (tabs.selectedTabPosition == 0) {
if (extension is Extension.Installed || extension is Extension.Untrusted) { val extension = (adapter?.getItem(position) as? ExtensionItem)?.extension ?: return
uninstallExtension(extension.pkgName) if (extension is Extension.Installed || extension is Extension.Untrusted) {
uninstallExtension(extension.pkgName)
}
} }
} }
override fun onAllClick(position: Int) {
val item = migAdapter?.getItem(position) as? SourceItem ?: return
val sourceMangas =
presenter.mangaItems[item.source.id]?.mapNotNull { it.manga.id }?.toList()
?: emptyList()
PreMigrationController.navigateToMigration(
Injekt.get<PreferencesHelper>().skipPreMigration().getOrDefault(),
controller.router,
sourceMangas
)
}
private fun openDetails(extension: Extension.Installed) { private fun openDetails(extension: Extension.Installed) {
val controller = ExtensionDetailsController(extension.pkgName) val controller = ExtensionDetailsController(extension.pkgName)
this.controller.router.pushController(controller.withFadeTransaction()) this.controller.router.pushController(controller.withFadeTransaction())
@ -159,8 +253,28 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
drawExtensions() drawExtensions()
} }
fun setMigrationSources(sources: List<SourceItem>) {
val migRecyler = migrationFrameLayout.recycler
if (migAdapter !is SourceAdapter) {
migAdapter = SourceAdapter(this)
migRecyler.adapter = migAdapter
migAdapter?.fastScroller = migrationFrameLayout.fast_scroller
}
migAdapter?.updateDataSet(sources)
}
fun setMigrationManga(manga: List<MangaItem>?) {
val migRecyler = migrationFrameLayout.recycler
if (migAdapter !is MangaAdapter) {
migAdapter = MangaAdapter(this)
migRecyler.adapter = migAdapter
migAdapter?.fastScroller = migrationFrameLayout.fast_scroller
}
migAdapter?.updateDataSet(manga)
}
fun drawExtensions() { fun drawExtensions() {
if (!controller.extQuery.isBlank()) { if (controller.extQuery.isNotBlank()) {
adapter?.updateDataSet( adapter?.updateDataSet(
extensions.filter { extensions.filter {
it.extension.name.contains(controller.extQuery, ignoreCase = true) it.extension.name.contains(controller.extQuery, ignoreCase = true)
@ -172,6 +286,14 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
updateExtTitle() updateExtTitle()
} }
fun canGoBack(): Boolean {
if (tabs.selectedTabPosition == 1 && migAdapter is MangaAdapter) {
presenter.deselectSource()
return false
}
return true
}
fun downloadUpdate(item: ExtensionItem) { fun downloadUpdate(item: ExtensionItem) {
adapter?.updateItem(item, item.installStep) adapter?.updateItem(item, item.installStep)
} }
@ -183,4 +305,25 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At
override fun uninstallExtension(pkgName: String) { override fun uninstallExtension(pkgName: String) {
presenter.uninstallExtension(pkgName) presenter.uninstallExtension(pkgName)
} }
private inner class TabbedSheetAdapter : ViewPagerAdapter() {
override fun createView(container: ViewGroup, position: Int): View {
return when (position) {
0 -> extensionFrameLayout
else -> migrationFrameLayout
}
}
override fun getCount(): Int {
return 2
}
override fun getPageTitle(position: Int): CharSequence {
return context.getString(when (position) {
0 -> R.string.extensions
else -> R.string.migration
})
}
}
} }

View File

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.ui.migration
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
class MangaAdapter(controller: MigrationController) : class MangaAdapter(listener: Any) :
FlexibleAdapter<IFlexible<*>>(null, controller) { FlexibleAdapter<IFlexible<*>>(null, listener) {
private var items: List<IFlexible<*>>? = null private var items: List<IFlexible<*>>? = null

View File

@ -8,8 +8,8 @@ import eu.davidea.flexibleadapter.items.IFlexible
* *
* @param controller instance of [MigrationController]. * @param controller instance of [MigrationController].
*/ */
class SourceAdapter(val controller: MigrationController) : class SourceAdapter(val allClickListener: OnAllClickListener) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) { FlexibleAdapter<IFlexible<*>>(null, allClickListener, true) {
private var items: List<IFlexible<*>>? = null private var items: List<IFlexible<*>>? = null
@ -17,11 +17,6 @@ class SourceAdapter(val controller: MigrationController) :
setDisplayHeadersAtStartUp(true) setDisplayHeadersAtStartUp(true)
} }
/**
* Listener for auto item clicks.
*/
val allClickListener: OnAllClickListener? = controller
/** /**
* Listener which should be called when user clicks select. * Listener which should be called when user clicks select.
*/ */

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.source
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity import android.app.Activity
import android.content.res.ColorStateList
import android.os.Parcelable import android.os.Parcelable
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -10,6 +11,8 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
@ -29,12 +32,15 @@ import eu.kanade.tachiyomi.ui.extension.SettingsExtensionsController
import eu.kanade.tachiyomi.ui.main.BottomSheetController import eu.kanade.tachiyomi.ui.main.BottomSheetController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.RootSearchInterface import eu.kanade.tachiyomi.ui.main.RootSearchInterface
import eu.kanade.tachiyomi.ui.setting.SettingsBrowseController
import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController import eu.kanade.tachiyomi.ui.setting.SettingsSourcesController
import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController import eu.kanade.tachiyomi.ui.source.global_search.GlobalSearchController
import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getBottomGestureInsets import eu.kanade.tachiyomi.util.system.getBottomGestureInsets
import eu.kanade.tachiyomi.util.system.spToPx import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.openInBrowser
import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.expand import eu.kanade.tachiyomi.util.view.expand
import eu.kanade.tachiyomi.util.view.isCollapsed import eu.kanade.tachiyomi.util.view.isCollapsed
@ -48,17 +54,20 @@ import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import eu.kanade.tachiyomi.util.view.withFadeTransaction import eu.kanade.tachiyomi.util.view.withFadeTransaction
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.extensions_bottom_sheet.* import kotlinx.android.synthetic.main.extensions_bottom_sheet.*
import kotlinx.android.synthetic.main.extensions_bottom_sheet.ext_bottom_sheet
import kotlinx.android.synthetic.main.extensions_bottom_sheet.sheet_layout import kotlinx.android.synthetic.main.extensions_bottom_sheet.sheet_layout
import kotlinx.android.synthetic.main.extensions_bottom_sheet.view.* import kotlinx.android.synthetic.main.extensions_bottom_sheet.view.*
import kotlinx.android.synthetic.main.filter_bottom_sheet.* import kotlinx.android.synthetic.main.filter_bottom_sheet.*
import kotlinx.android.synthetic.main.library_list_controller.* import kotlinx.android.synthetic.main.library_list_controller.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.recycler_with_scroller.view.*
import kotlinx.android.synthetic.main.rounded_category_hopper.* import kotlinx.android.synthetic.main.rounded_category_hopper.*
import kotlinx.android.synthetic.main.source_controller.* import kotlinx.android.synthetic.main.source_controller.*
import kotlinx.android.synthetic.main.source_controller.shadow2 import kotlinx.android.synthetic.main.source_controller.shadow2
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.math.max import kotlin.math.max
import kotlin.math.min
/** /**
* This controller shows and manages the different catalogues enabled by the user. * This controller shows and manages the different catalogues enabled by the user.
@ -101,7 +110,12 @@ class SourceController :
override fun getTitle(): String? { override fun getTitle(): String? {
return if (showingExtensions) return if (showingExtensions)
view?.context?.getString(R.string.extensions) view?.context?.getString(
when (ext_bottom_sheet.tabs.selectedTabPosition) {
0 -> R.string.extensions
else -> R.string.source_migration
}
)
else view?.context?.getString(R.string.sources) else view?.context?.getString(R.string.sources)
} }
@ -144,6 +158,7 @@ class SourceController :
recycler?.post { recycler?.post {
setBottomPadding() setBottomPadding()
setBottomSheetTabs(if (ext_bottom_sheet?.sheetBehavior.isCollapsed()) 0f else 1f)
} }
recycler.addOnScrollListener( recycler.addOnScrollListener(
object : RecyclerView.OnScrollListener() { object : RecyclerView.OnScrollListener() {
@ -163,12 +178,10 @@ class SourceController :
override fun onSlide(bottomSheet: View, progress: Float) { override fun onSlide(bottomSheet: View, progress: Float) {
val recycler = recycler ?: return val recycler = recycler ?: return
shadow2?.alpha = (1 - max(0f, progress)) * 0.25f shadow2?.alpha = (1 - max(0f, progress)) * 0.25f
activity?.appbar?.elevation = max( activity?.appbar?.elevation = min(
progress * 15f, (1f - progress) * 15f,
if (recycler.canScrollVertically(-1)) 15f else 0f if (recycler.canScrollVertically(-1)) 15f else 0f
) )
sheet_layout?.alpha = 1 - progress
activity?.appbar?.y = max(activity!!.appbar.y, -headerHeight * (1 - progress)) activity?.appbar?.y = max(activity!!.appbar.y, -headerHeight * (1 - progress))
val oldShow = showingExtensions val oldShow = showingExtensions
showingExtensions = progress > 0.92f showingExtensions = progress > 0.92f
@ -176,9 +189,7 @@ class SourceController :
setTitle() setTitle()
activity?.invalidateOptionsMenu() activity?.invalidateOptionsMenu()
} }
val bottomBar = activity?.bottom_nav ?: return setBottomSheetTabs(max(0f, progress))
val pad = bottomBar.translationY - bottomBar.height
ext_bottom_sheet.updatePaddingRelative(bottom = (pad * (1 - max(progress, 0f))).toInt())
} }
override fun onStateChanged(p0: View, state: Int) { override fun onStateChanged(p0: View, state: Int) {
@ -189,8 +200,6 @@ class SourceController :
if (state == BottomSheetBehavior.STATE_EXPANDED || if (state == BottomSheetBehavior.STATE_EXPANDED ||
state == BottomSheetBehavior.STATE_COLLAPSED state == BottomSheetBehavior.STATE_COLLAPSED
) { ) {
sheet_layout?.alpha =
if (state == BottomSheetBehavior.STATE_COLLAPSED) 1f else 0f
showingExtensions = state == BottomSheetBehavior.STATE_EXPANDED showingExtensions = state == BottomSheetBehavior.STATE_EXPANDED
setTitle() setTitle()
if (state == BottomSheetBehavior.STATE_EXPANDED) if (state == BottomSheetBehavior.STATE_EXPANDED)
@ -203,6 +212,7 @@ class SourceController :
RetainViewMode.RETAIN_DETACH else RetainViewMode.RELEASE_DETACH RetainViewMode.RETAIN_DETACH else RetainViewMode.RELEASE_DETACH
sheet_layout.isClickable = state == BottomSheetBehavior.STATE_COLLAPSED sheet_layout.isClickable = state == BottomSheetBehavior.STATE_COLLAPSED
sheet_layout.isFocusable = state == BottomSheetBehavior.STATE_COLLAPSED sheet_layout.isFocusable = state == BottomSheetBehavior.STATE_COLLAPSED
setBottomSheetTabs(if (state == BottomSheetBehavior.STATE_COLLAPSED) 0f else 1f)
} }
} }
) )
@ -212,21 +222,73 @@ class SourceController :
} }
} }
fun setBottomPadding() { fun updateTitleAndMenu() {
setTitle()
activity?.invalidateOptionsMenu()
}
fun setBottomSheetTabs(progress: Float) {
val bottomSheet = ext_bottom_sheet ?: return
val bottomBar = activity?.bottom_nav ?: return val bottomBar = activity?.bottom_nav ?: return
ext_bottom_sheet.updatePaddingRelative( ext_bottom_sheet.tabs.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottom = topMargin = ((activity?.appbar?.height?.minus(9f.dpToPx) ?: 0f) * progress).toInt()
if (ext_bottom_sheet.sheetBehavior.isExpanded()) 0 else }
max( val selectedColor = ColorUtils.setAlphaComponent(
(-bottomBar.translationY + bottomBar.height).toInt(), ContextCompat.getColor(ext_bottom_sheet.tabs.context, R.color.colorAccent),
this@SourceController.view?.rootWindowInsets?.getBottomGestureInsets() (progress * 255).toInt()
?: 0 )
) val unselectedColor = ColorUtils.setAlphaComponent(
bottomSheet.context.getResourceColor(R.attr.colorOnBackground),
153
)
ext_bottom_sheet.sheet_layout.elevation = progress * 5
ext_bottom_sheet.pager.alpha = progress * 10
ext_bottom_sheet.tabs.setSelectedTabIndicatorColor(selectedColor)
ext_bottom_sheet.tabs.setTabTextColors(
ColorUtils.blendARGB(
bottomSheet.context.getResourceColor(R.attr.actionBarTintColor),
unselectedColor,
progress),
ColorUtils.blendARGB(
bottomSheet.context.getResourceColor(R.attr.actionBarTintColor),
selectedColor,
progress)
) )
val pad = bottomBar.translationY - bottomBar.height val pad = bottomBar.translationY - bottomBar.height
val padding = (max(
(-pad).toInt(),
this@SourceController.view?.rootWindowInsets?.getBottomGestureInsets() ?: 0
) * (1f - progress)).toInt()
ext_bottom_sheet.updatePaddingRelative(
bottom = padding
)
ext_bottom_sheet.sheet_layout.backgroundTintList = ColorStateList.valueOf(
ColorUtils.blendARGB(
bottomSheet.context.getResourceColor(R.attr.colorPrimaryVariant),
bottomSheet.context.getResourceColor(R.attr.colorSecondary),
progress)
)
}
fun setBottomPadding() {
val bottomBar = activity?.bottom_nav ?: return
ext_bottom_sheet ?: return
val pad = bottomBar.translationY - bottomBar.height
val padding = max(
(-pad).toInt(),
if (ext_bottom_sheet.sheetBehavior.isExpanded()) 0 else
this@SourceController.view?.rootWindowInsets?.getBottomGestureInsets()
?: 0
)
ext_bottom_sheet.updatePaddingRelative(
bottom = padding
)
shadow2.translationY = pad shadow2.translationY = pad
ext_bottom_sheet.sheetBehavior?.peekHeight = 48.spToPx + ext_bottom_sheet.paddingBottom ext_bottom_sheet.sheetBehavior?.peekHeight = 60.dpToPx + padding
ext_bottom_sheet.fast_scroller.updateLayoutParams<ViewGroup.MarginLayoutParams> { ext_bottom_sheet.extensionFrameLayout.fast_scroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = -pad.toInt()
}
ext_bottom_sheet.migrationFrameLayout.fast_scroller.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = -pad.toInt() bottomMargin = -pad.toInt()
} }
} }
@ -247,7 +309,9 @@ class SourceController :
override fun handleSheetBack(): Boolean { override fun handleSheetBack(): Boolean {
if (!ext_bottom_sheet.sheetBehavior.isCollapsed()) { if (!ext_bottom_sheet.sheetBehavior.isCollapsed()) {
ext_bottom_sheet.sheetBehavior?.collapse() if (ext_bottom_sheet.canGoBack()) {
ext_bottom_sheet.sheetBehavior?.collapse()
}
return true return true
} }
return false return false
@ -265,6 +329,23 @@ class SourceController :
ext_bottom_sheet.presenter.refreshExtensions() ext_bottom_sheet.presenter.refreshExtensions()
} }
if (!type.isEnter) { if (!type.isEnter) {
ext_bottom_sheet.canExpand = false
activity?.appbar?.elevation =
when {
ext_bottom_sheet.sheetBehavior.isExpanded() -> 0f
recycler.canScrollVertically(-1) -> 15f
else -> 0f
}
} else {
ext_bottom_sheet.presenter.refreshMigrations()
}
setBottomPadding()
}
override fun onChangeEnded(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeEnded(handler, type)
if (type.isEnter) {
ext_bottom_sheet.canExpand = true
setBottomPadding() setBottomPadding()
} }
} }
@ -272,6 +353,8 @@ class SourceController :
override fun onActivityResumed(activity: Activity) { override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity) super.onActivityResumed(activity)
ext_bottom_sheet?.presenter?.refreshExtensions() ext_bottom_sheet?.presenter?.refreshExtensions()
ext_bottom_sheet?.presenter?.refreshMigrations()
setBottomPadding()
} }
override fun onItemClick(view: View, position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {
@ -351,21 +434,25 @@ class SourceController :
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingExtensions) if (onRoot) (activity as? MainActivity)?.setDismissIcon(showingExtensions)
if (showingExtensions) { if (showingExtensions) {
// Inflate menu if (ext_bottom_sheet.tabs.selectedTabPosition == 0) {
inflater.inflate(R.menu.extension_main, menu) // Inflate menu
inflater.inflate(R.menu.extension_main, menu)
// Initialize search option. // Initialize search option.
val searchItem = menu.findItem(R.id.action_search) val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.actionView as SearchView val searchView = searchItem.actionView as SearchView
// Change hint to show global search. // Change hint to show global search.
searchView.queryHint = view?.context?.getString(R.string.search_extensions) searchView.queryHint = view?.context?.getString(R.string.search_extensions)
// Create query listener which opens the global search view. // Create query listener which opens the global search view.
setOnQueryTextChangeListener(searchView) { setOnQueryTextChangeListener(searchView) {
extQuery = it ?: "" extQuery = it ?: ""
ext_bottom_sheet.drawExtensions() ext_bottom_sheet.drawExtensions()
true true
}
} else {
inflater.inflate(R.menu.migration_main, menu)
} }
} else { } else {
// Inflate menu // Inflate menu
@ -410,6 +497,12 @@ class SourceController :
).pushChangeHandler(FadeChangeHandler()) ).pushChangeHandler(FadeChangeHandler())
) )
} }
R.id.action_migration_guide -> {
activity?.openInBrowser(HELP_URL)
}
R.id.action_sources_settings -> {
router.pushController(SettingsBrowseController().withFadeTransaction())
}
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
return true return true
@ -437,4 +530,8 @@ class SourceController :
@Parcelize @Parcelize
data class SmartSearchConfig(val origTitle: String, val origMangaId: Long) : Parcelable data class SmartSearchConfig(val origTitle: String, val origMangaId: Long) : Parcelable
companion object {
const val HELP_URL = "https://tachiyomi.org/help/guides/source-migration/"
}
} }

View File

@ -1,8 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:viewportWidth="24.0" android:tint="?attr/actionBarTintColor"
android:viewportHeight="24.0"> android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path <path
android:fillColor="#FF000000" android:fillColor="#FF000000"
android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/> android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/>

View File

@ -1,6 +1,7 @@
<!-- drawable/pin.xml --> <!-- drawable/pin.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" android:height="24dp"
android:tint="?attr/actionBarTintColor"
android:width="24dp" android:width="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">

View File

@ -9,7 +9,7 @@
android:background="@drawable/bottom_sheet_rounded_background" android:background="@drawable/bottom_sheet_rounded_background"
android:backgroundTint="?android:attr/colorBackground" android:backgroundTint="?android:attr/colorBackground"
android:orientation="vertical" android:orientation="vertical"
app:behavior_peekHeight="48sp" app:behavior_peekHeight="60sp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior"> app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout <LinearLayout
@ -19,6 +19,7 @@
android:background="@drawable/bottom_sheet_rounded_background" android:background="@drawable/bottom_sheet_rounded_background"
android:backgroundTint="?attr/colorPrimaryVariant" android:backgroundTint="?attr/colorPrimaryVariant"
android:orientation="vertical" android:orientation="vertical"
android:elevation="5dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -37,41 +38,25 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.tabs.TabLayout
android:id="@+id/title_text" android:id="@+id/tabs"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title" style="@style/Theme.Widget.Tabs"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:background="@android:color/transparent"
android:layout_marginTop="6dp" app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="10dp" app:layout_constraintEnd_toStartOf="@+id/menu"
android:ellipsize="end" app:layout_constraintStart_toStartOf="parent"
android:gravity="center" app:layout_constraintTop_toTopOf="parent"
android:maxLines="1" app:tabTextColor="@color/tabs_selector_background"
android:textAlignment="center" app:tabIndicatorColor="?attr/colorAccent"
android:textColor="?actionBarTintColor" app:tabRippleColor="@color/rippleColor"
android:textSize="18sp" app:tabGravity="fill"
tools:text="Extensions" /> app:tabMode="fixed" />
</LinearLayout> </LinearLayout>
<FrameLayout <androidx.viewpager.widget.ViewPager
android:id="@+id/ext_recycler_layout" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/ext_recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:clipToPadding="false"
tools:listitem="@layout/extension_card_header" />
<eu.kanade.tachiyomi.ui.base.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastScrollerBubbleEnabled="false" />
</FrameLayout>
</eu.kanade.tachiyomi.ui.extension.ExtensionBottomSheet> </eu.kanade.tachiyomi.ui.extension.ExtensionBottomSheet>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/recycler_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
tools:listitem="@layout/extension_card_header" />
<eu.kanade.tachiyomi.ui.base.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:fastScrollerBubbleEnabled="false" />
</FrameLayout>

View File

@ -0,0 +1,15 @@
<?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_sources_settings"
android:icon="@drawable/ic_tune_24dp"
android:title="@string/options"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_migration_guide"
android:title="@string/help"
android:icon="@drawable/ic_help_24dp"
app:showAsAction="ifRoom"/>
</menu>

View File

@ -620,6 +620,7 @@
<string name="migration_sources_changed">Migration sources changed</string> <string name="migration_sources_changed">Migration sources changed</string>
<string name="you_can_migrate_in_library">You can also migrate by selecting manga in your <string name="you_can_migrate_in_library">You can also migrate by selecting manga in your
library</string> library</string>
<string name="source_migration_guide">Source migration guide</string>
<!-- About section --> <!-- About section -->
<string name="version">Version</string> <string name="version">Version</string>