PreMigrationController: Partial Compose Conversion

This commit is contained in:
Jobobby04 2022-09-10 13:13:38 -04:00
parent d0518515e9
commit fc44ffa5af
4 changed files with 262 additions and 123 deletions

View File

@ -7,10 +7,9 @@ import eu.kanade.tachiyomi.source.SourceManager
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationSourceAdapter( class MigrationSourceAdapter(
var items: List<MigrationSourceItem>,
controllerPre: PreMigrationController, controllerPre: PreMigrationController,
) : FlexibleAdapter<MigrationSourceItem>( ) : FlexibleAdapter<MigrationSourceItem>(
items, null,
controllerPre, controllerPre,
true, true,
) { ) {
@ -41,10 +40,6 @@ class MigrationSourceAdapter(
super.onRestoreInstanceState(savedInstanceState) super.onRestoreInstanceState(savedInstanceState)
} }
fun updateItems() {
items = currentItems
}
companion object { companion object {
private const val SELECTED_SOURCES_KEY = "selected_sources" private const val SELECTED_SOURCES_KEY = "selected_sources"
} }

View File

@ -39,16 +39,6 @@ class MigrationSourceHolder(view: View, val adapter: MigrationSourceAdapter) :
} }
} }
/**
* Called when an item is released.
*
* @param position The position of the released item.
*/
override fun onItemReleased(position: Int) {
super.onItemReleased(position)
adapter.updateItems()
}
companion object { companion object {
private const val DISABLED_ALPHA = 0.3f private const val DISABLED_ALPHA = 0.3f
} }

View File

@ -2,34 +2,63 @@ package eu.kanade.tachiyomi.ui.browse.migration.advanced.design
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.Deselect
import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.Router import com.bluelinelabs.conductor.Router
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import dev.chrisbanes.insetter.applyInsetter
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.ExtendedFloatingActionButton
import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.PreMigrationControllerBinding import eu.kanade.tachiyomi.databinding.PreMigrationControllerBinding
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController import eu.kanade.tachiyomi.ui.base.controller.BasicFullComposeController
import eu.kanade.tachiyomi.ui.base.controller.FabController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListController
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationProcedureConfig
import eu.kanade.tachiyomi.util.view.shrinkOnScroll import eu.kanade.tachiyomi.util.lang.launchIO
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import kotlin.math.roundToInt
class PreMigrationController(bundle: Bundle? = null) : class PreMigrationController(bundle: Bundle? = null) :
BaseController<PreMigrationControllerBinding>(bundle), BasicFullComposeController(bundle),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FabController,
StartMigrationListener { StartMigrationListener {
constructor(mangaIds: List<Long>) : this( constructor(mangaIds: List<Long>) : this(
@ -45,60 +74,163 @@ class PreMigrationController(bundle: Bundle? = null) :
private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0) private val config: LongArray = args.getLongArray(MANGA_IDS_EXTRA) ?: LongArray(0)
private var actionFab: ExtendedFloatingActionButton? = null
private var actionFabScrollListener: RecyclerView.OnScrollListener? = null
private lateinit var dialog: MigrationBottomSheetDialog private lateinit var dialog: MigrationBottomSheetDialog
override fun getTitle() = view?.context?.getString(R.string.select_sources) private lateinit var controllerBinding: PreMigrationControllerBinding
override fun createBinding(inflater: LayoutInflater) = PreMigrationControllerBinding.inflate(inflater) var items by mutableStateOf(emptyList<MigrationSourceItem>())
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
binding.recycler.applyInsetter {
type(navigationBars = true) {
padding()
}
}
val ourAdapter = adapter ?: MigrationSourceAdapter(
getEnabledSources().map { MigrationSourceItem(it, isEnabled(it.id.toString())) },
this,
)
adapter = ourAdapter
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.setHasFixedSize(true)
binding.recycler.adapter = ourAdapter
ourAdapter.itemTouchHelperCallback = null // Reset adapter touch adapter to fix drag after rotation
ourAdapter.isHandleDragEnabled = true
dialog = MigrationBottomSheetDialog(activity!!, this) dialog = MigrationBottomSheetDialog(activity!!, this)
actionFabScrollListener = actionFab?.shrinkOnScroll(binding.recycler) viewScope.launchIO {
items = getEnabledSources()
}
} }
override fun configureFab(fab: ExtendedFloatingActionButton) { @Composable
actionFab = fab override fun ComposeContent() {
fab.setText(R.string.action_migrate) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
fab.setIconResource(R.drawable.ic_arrow_forward_24dp) var fabExpanded by remember { mutableStateOf(true) }
fab.setOnClickListener { val nestedScrollConnection = remember {
// All this lines just for fab state :/
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
fabExpanded = available.y >= 0
return scrollBehavior.nestedScrollConnection.onPreScroll(available, source)
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
return scrollBehavior.nestedScrollConnection.onPostScroll(consumed, available, source)
}
override suspend fun onPreFling(available: Velocity): Velocity {
return scrollBehavior.nestedScrollConnection.onPreFling(available)
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
return scrollBehavior.nestedScrollConnection.onPostFling(consumed, available)
}
}
}
Scaffold(
topBar = {
AppBar(
title = stringResource(R.string.select_sources),
navigateUp = router::popCurrentController,
scrollBehavior = scrollBehavior,
actions = {
IconButton(onClick = { massSelect(false) }) {
Icon(
imageVector = Icons.Default.Deselect,
contentDescription = stringResource(R.string.select_none),
)
}
IconButton(onClick = { massSelect(true) }) {
Icon(
imageVector = Icons.Default.SelectAll,
contentDescription = stringResource(R.string.action_select_all),
)
}
val (expanded, onExpanded) = remember { mutableStateOf(false) }
Box {
IconButton(onClick = { onExpanded(!expanded) }) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = stringResource(R.string.label_more),
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { onExpanded(false) },
) {
DropdownMenuItem(
text = { Text(stringResource(R.string.match_enabled_sources)) },
onClick = { matchSelection(true) },
)
DropdownMenuItem(
text = { Text(stringResource(R.string.match_pinned_sources)) },
onClick = { matchSelection(false) },
)
}
}
},
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(text = stringResource(R.string.action_migrate)) },
icon = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = stringResource(R.string.action_migrate),
)
},
onClick = {
if (!dialog.isShowing) { if (!dialog.isShowing) {
dialog.show() dialog.show()
} }
} },
expanded = fabExpanded,
modifier = Modifier.navigationBarsPadding(),
)
},
) { contentPadding ->
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() }
val top = with(density) { contentPadding.calculateTopPadding().toPx().roundToInt() }
val right = with(density) { contentPadding.calculateRightPadding(layoutDirection).toPx().roundToInt() }
val bottom = with(density) { contentPadding.calculateBottomPadding().toPx().roundToInt() }
Box(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
AndroidView(
factory = { context ->
controllerBinding = PreMigrationControllerBinding.inflate(LayoutInflater.from(context))
adapter = MigrationSourceAdapter(this@PreMigrationController)
controllerBinding.recycler.adapter = adapter
adapter?.isHandleDragEnabled = true
adapter?.fastScroller = controllerBinding.fastScroller
controllerBinding.recycler.layoutManager = LinearLayoutManager(context)
ViewCompat.setNestedScrollingEnabled(controllerBinding.root, true)
controllerBinding.root
},
update = {
controllerBinding.recycler
.updatePadding(
left = left,
top = top,
right = right,
bottom = bottom,
)
controllerBinding.fastScroller
.updateLayoutParams<ViewGroup.MarginLayoutParams> {
leftMargin = left
topMargin = top
rightMargin = right
bottomMargin = bottom
} }
override fun cleanupFab(fab: ExtendedFloatingActionButton) { adapter?.updateDataSet(items)
fab.setOnClickListener(null) },
actionFabScrollListener?.let { binding.recycler.removeOnScrollListener(it) } )
actionFab = null }
}
} }
override fun startMigration(extraParam: String?) { override fun startMigration(extraParam: String?) {
val listOfSources = adapter?.items?.filter { val listOfSources = adapter?.currentItems
?.filterIsInstance<MigrationSourceItem>()
?.filter {
it.sourceEnabled it.sourceEnabled
}?.joinToString("/") { it.source.id.toString() }.orEmpty() }
?.joinToString("/") { it.source.id.toString() }
.orEmpty()
prefs.migrationSources().set(listOfSources) prefs.migrationSources().set(listOfSources)
router.replaceTopController( router.replaceTopController(
@ -111,6 +243,11 @@ class PreMigrationController(bundle: Bundle? = null) :
) )
} }
override fun onDestroyView(view: View) {
adapter = null
super.onDestroyView(view)
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
adapter?.onSaveInstanceState(outState) adapter?.onSaveInstanceState(outState)
@ -123,10 +260,11 @@ class PreMigrationController(bundle: Bundle? = null) :
} }
override fun onItemClick(view: View, position: Int): Boolean { override fun onItemClick(view: View, position: Int): Boolean {
adapter?.getItem(position)?.let { val adapter = adapter ?: return false
adapter.getItem(position)?.let {
it.sourceEnabled = !it.sourceEnabled it.sourceEnabled = !it.sourceEnabled
} }
adapter?.notifyItemChanged(position) adapter.notifyItemChanged(position)
return false return false
} }
@ -135,56 +273,70 @@ class PreMigrationController(bundle: Bundle? = null) :
* *
* @return list containing enabled sources. * @return list containing enabled sources.
*/ */
private fun getEnabledSources(): List<HttpSource> { private fun getEnabledSources(): List<MigrationSourceItem> {
val languages = prefs.enabledLanguages().get() val languages = prefs.enabledLanguages().get()
val sourcesSaved = prefs.migrationSources().get().split("/") val sourcesSaved = prefs.migrationSources().get().split("/")
.mapNotNull { it.toLongOrNull() }
val disabledSources = prefs.disabledSources().get()
.mapNotNull { it.toLongOrNull() }
val sources = sourceManager.getVisibleCatalogueSources() val sources = sourceManager.getVisibleCatalogueSources()
.filterIsInstance<HttpSource>() .filterIsInstance<HttpSource>()
.filter { it.lang in languages } .filter { it.lang in languages }
.sortedBy { "(${it.lang}) ${it.name}" } .sortedBy { "(${it.lang}) ${it.name}" }
.map {
return sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf(it.id.toString()) } + sources.filterNot { isEnabled(it.id.toString()) } MigrationSourceItem(
it,
isEnabled(
sourcesSaved,
disabledSources,
it.id,
),
)
} }
fun isEnabled(id: String): Boolean { return sources
val sourcesSaved = prefs.migrationSources().get() .filter { it.sourceEnabled }
val disabledSourceIds = prefs.disabledSources().get() .sortedBy { sourcesSaved.indexOf(it.source.id) } +
return if (sourcesSaved.isEmpty()) id !in disabledSourceIds sources.filterNot { it.sourceEnabled }
else sourcesSaved.split("/").contains(id)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { fun isEnabled(
inflater.inflate(R.menu.pre_migration, menu) sourcesSaved: List<Long>,
disabledSources: List<Long>,
id: Long,
): Boolean {
return if (sourcesSaved.isEmpty()) {
id !in disabledSources
} else {
id in sourcesSaved
}
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { private fun massSelect(selectAll: Boolean) {
when (item.itemId) { val adapter = adapter ?: return
R.id.action_select_all, R.id.action_select_none -> { adapter.currentItems.forEach {
adapter?.currentItems?.forEach { it.sourceEnabled = selectAll
it.sourceEnabled = item.itemId == R.id.action_select_all
} }
adapter?.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
R.id.action_match_enabled, R.id.action_match_pinned -> {
val enabledSources = if (item.itemId == R.id.action_match_enabled) { private fun matchSelection(matchEnabled: Boolean) {
val adapter = adapter ?: return
val enabledSources = if (matchEnabled) {
prefs.disabledSources().get().mapNotNull { it.toLongOrNull() } prefs.disabledSources().get().mapNotNull { it.toLongOrNull() }
} else { } else {
prefs.pinnedSources().get().mapNotNull { it.toLongOrNull() } prefs.pinnedSources().get().mapNotNull { it.toLongOrNull() }
} }
val items = adapter?.currentItems?.toList() ?: return true val items = adapter.currentItems.toList()
items.forEach { items.forEach {
it.sourceEnabled = if (item.itemId == R.id.action_match_enabled) { it.sourceEnabled = if (matchEnabled) {
it.source.id !in enabledSources it.source.id !in enabledSources
} else { } else {
it.source.id in enabledSources it.source.id in enabledSources
} }
} }
val sortedItems = items.sortedBy { it.source.name }.sortedBy { !it.sourceEnabled } val sortedItems = items.sortedBy { it.source.name }.sortedBy { !it.sourceEnabled }
adapter?.updateDataSet(sortedItems) adapter.updateDataSet(sortedItems)
}
else -> return super.onOptionsItemSelected(item)
}
return true
} }
companion object { companion object {

View File

@ -1,23 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout" android:id="@+id/frame_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:animateLayoutChanges="true">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler" android:id="@+id/recycler"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent" tools:listitem="@layout/migration_source_item"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/migration_source_item">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout> <eu.kanade.tachiyomi.widget.MaterialFastScroll
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
app:fastScrollerBubbleEnabled="false"
tools:visibility="visible" />
</FrameLayout>