Use a compose Adaptive Sheet for pre-migration sheet

This commit is contained in:
Jobobby04 2023-07-15 19:53:14 -04:00
parent c8c9e79a3e
commit cd4c217a7f
3 changed files with 83 additions and 135 deletions

View File

@ -1,109 +1,56 @@
package eu.kanade.tachiyomi.ui.browse.migration.advanced.design package eu.kanade.tachiyomi.ui.browse.migration.advanced.design
import android.content.Context
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.RadioGroup import android.widget.RadioGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.content.getSystemService import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.material.bottomsheet.BottomSheetBehavior import eu.kanade.presentation.components.AdaptiveSheet
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.getElevation
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding
import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isNightMode
import eu.kanade.tachiyomi.util.system.isTabletUi
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
import tachiyomi.core.preference.Preference import tachiyomi.core.preference.Preference
import tachiyomi.core.util.lang.toLong import tachiyomi.core.util.lang.toLong
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class MigrationBottomSheetDialog(private val baseContext: Context, private val listener: StartMigrationListener) : BottomSheetDialog(baseContext) { @Composable
fun MigrationBottomSheetDialog(
onDismissRequest: () -> Unit,
onStartMigration: (extraParam: String?) -> Unit,
) {
val startMigration = rememberUpdatedState(onStartMigration)
val state = remember {
MigrationBottomSheetDialogState(startMigration)
}
AdaptiveSheet(onDismissRequest = onDismissRequest) {
AndroidView(
factory = { factoryContext ->
val binding = MigrationBottomSheetBinding.inflate(LayoutInflater.from(factoryContext))
state.initPreferences(binding)
binding.root
},
modifier = Modifier.fillMaxWidth(),
)
}
}
class MigrationBottomSheetDialogState(private val onStartMigration: State<(extraParam: String?) -> Unit>) {
private val preferences: UnsortedPreferences by injectLazy() private val preferences: UnsortedPreferences by injectLazy()
lateinit var binding: MigrationBottomSheetBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootView = createView()
setContentView(rootView)
// Enforce max width for tablets
if (context.isTabletUi()) {
behavior.maxWidth = 480.dpToPx
} else {
behavior.maxWidth = 0.dpToPx
}
// Set peek height to 50% display height
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display
} else {
@Suppress("DEPRECATION")
context.getSystemService<WindowManager>()?.defaultDisplay
}?.let {
val metrics = DisplayMetrics()
@Suppress("DEPRECATION")
it.getRealMetrics(metrics)
behavior.peekHeight = metrics.heightPixels / 2
}
// Set navbar color to transparent for edge-to-edge bottom sheet if we can use light navigation bar
// TODO Replace deprecated systemUiVisibility when material-components uses new API to modify status bar icons
@Suppress("DEPRECATION")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window?.setNavigationBarTransparentCompat(context, behavior.getElevation())
val bottomSheet = rootView.parent as ViewGroup
var flags = bottomSheet.systemUiVisibility
flags = if (context.isNightMode()) {
flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
} else {
flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
}
bottomSheet.systemUiVisibility = flags
}
initPreferences()
binding.migrateBtn.setOnClickListener {
preferences.skipPreMigration().set(binding.skipStep.isChecked)
preferences.hideNotFoundMigration().set(binding.HideNotFoundManga.isChecked)
listener.startMigration(
if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) {
binding.extraSearchParamText.toString()
} else {
null
},
)
dismiss()
}
behavior.peekHeight = Resources.getSystem().displayMetrics.heightPixels
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
fun createView(): View {
binding = MigrationBottomSheetBinding.inflate(LayoutInflater.from(baseContext))
return binding.root
}
/** /**
* Init general reader preferences. * Init general reader preferences.
*/ */
private fun initPreferences() { fun initPreferences(binding: MigrationBottomSheetBinding) {
val flags = preferences.migrateFlags().get() val flags = preferences.migrateFlags().get()
binding.migChapters.isChecked = MigrationFlags.hasChapters(flags) binding.migChapters.isChecked = MigrationFlags.hasChapters(flags)
@ -112,11 +59,11 @@ class MigrationBottomSheetDialog(private val baseContext: Context, private val l
binding.migCustomCover.isChecked = MigrationFlags.hasCustomCover(flags) binding.migCustomCover.isChecked = MigrationFlags.hasCustomCover(flags)
binding.migExtra.isChecked = MigrationFlags.hasExtra(flags) binding.migExtra.isChecked = MigrationFlags.hasExtra(flags)
binding.migChapters.setOnCheckedChangeListener { _, _ -> setFlags() } binding.migChapters.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
binding.migCategories.setOnCheckedChangeListener { _, _ -> setFlags() } binding.migCategories.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
binding.migTracking.setOnCheckedChangeListener { _, _ -> setFlags() } binding.migTracking.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
binding.migCustomCover.setOnCheckedChangeListener { _, _ -> setFlags() } binding.migCustomCover.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
binding.migExtra.setOnCheckedChangeListener { _, _ -> setFlags() } binding.migExtra.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
binding.useSmartSearch.bindToPreference(preferences.smartMigration()) binding.useSmartSearch.bindToPreference(preferences.smartMigration())
binding.extraSearchParamText.isVisible = false binding.extraSearchParamText.isVisible = false
@ -129,15 +76,27 @@ class MigrationBottomSheetDialog(private val baseContext: Context, private val l
binding.HideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get() binding.HideNotFoundManga.isChecked = preferences.hideNotFoundMigration().get()
binding.skipStep.setOnCheckedChangeListener { _, isChecked -> binding.skipStep.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) { if (isChecked) {
context.toast( binding.root.context.toast(
R.string.pre_migration_skip_toast, R.string.pre_migration_skip_toast,
Toast.LENGTH_LONG, Toast.LENGTH_LONG,
) )
} }
} }
binding.migrateBtn.setOnClickListener {
preferences.skipPreMigration().set(binding.skipStep.isChecked)
preferences.hideNotFoundMigration().set(binding.HideNotFoundManga.isChecked)
onStartMigration.value(
if (binding.useSmartSearch.isChecked && binding.extraSearchParamText.text.isNotBlank()) {
binding.extraSearchParamText.toString()
} else {
null
},
)
}
} }
private fun setFlags() { private fun setFlags(binding: MigrationBottomSheetBinding) {
var flags = 0 var flags = 0
if (binding.migChapters.isChecked) flags = flags or MigrationFlags.CHAPTERS if (binding.migChapters.isChecked) flags = flags or MigrationFlags.CHAPTERS
if (binding.migCategories.isChecked) flags = flags or MigrationFlags.CATEGORIES if (binding.migCategories.isChecked) flags = flags or MigrationFlags.CATEGORIES
@ -167,7 +126,3 @@ class MigrationBottomSheetDialog(private val baseContext: Context, private val l
} }
} }
} }
interface StartMigrationListener {
fun startMigration(extraParam: String?)
}

View File

@ -12,8 +12,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -24,7 +22,6 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -57,17 +54,6 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
var fabExpanded by remember { mutableStateOf(true) } var fabExpanded by remember { mutableStateOf(true) }
val items by screenModel.state.collectAsState() val items by screenModel.state.collectAsState()
val context = LocalContext.current
DisposableEffect(screenModel) {
screenModel.dialog = MigrationBottomSheetDialog(context, screenModel.listener)
onDispose {}
}
LaunchedEffect(screenModel) {
screenModel.startMigration.collect { extraParam ->
navigator replace MigrationListScreen(MigrationProcedureConfig(mangaIds, extraParam))
}
}
val nestedScrollConnection = remember { val nestedScrollConnection = remember {
// All this lines just for fab state :/ // All this lines just for fab state :/
@ -132,9 +118,7 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen() {
) )
}, },
onClick = { onClick = {
if (!screenModel.dialog.isShowing) { screenModel.onMigrationSheet(true)
screenModel.dialog.show()
}
}, },
expanded = fabExpanded, expanded = fabExpanded,
) )
@ -182,6 +166,19 @@ class PreMigrationScreen(val mangaIds: List<Long>) : Screen() {
) )
} }
} }
val migrationSheetOpen by screenModel.migrationSheetOpen.collectAsState()
if (migrationSheetOpen) {
MigrationBottomSheetDialog(
onDismissRequest = { screenModel.onMigrationSheet(false) },
onStartMigration = { extraParam ->
screenModel.onMigrationSheet(false)
screenModel.saveEnabledSources()
navigator replace MigrationListScreen(MigrationProcedureConfig(mangaIds, extraParam))
},
)
}
} }
companion object { companion object {

View File

@ -6,11 +6,9 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.databinding.PreMigrationListBinding import eu.kanade.tachiyomi.databinding.PreMigrationListBinding
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
@ -26,28 +24,12 @@ class PreMigrationScreenModel(
private val _state = MutableStateFlow(emptyList<MigrationSourceItem>()) private val _state = MutableStateFlow(emptyList<MigrationSourceItem>())
val state = _state.asStateFlow() val state = _state.asStateFlow()
private val _migrationSheetOpen = MutableStateFlow(false)
val migrationSheetOpen = _migrationSheetOpen.asStateFlow()
lateinit var controllerBinding: PreMigrationListBinding lateinit var controllerBinding: PreMigrationListBinding
var adapter: MigrationSourceAdapter? = null var adapter: MigrationSourceAdapter? = null
val startMigration = MutableSharedFlow<String?>()
val listener = object : StartMigrationListener {
override fun startMigration(extraParam: String?) {
val listOfSources = adapter?.currentItems
?.filterIsInstance<MigrationSourceItem>()
?.filter {
it.sourceEnabled
}
?.joinToString("/") { it.source.id.toString() }
.orEmpty()
prefs.migrationSources().set(listOfSources)
coroutineScope.launch {
startMigration.emit(extraParam)
}
}
}
val clickListener = FlexibleAdapter.OnItemClickListener { _, position -> val clickListener = FlexibleAdapter.OnItemClickListener { _, position ->
val adapter = adapter ?: return@OnItemClickListener false val adapter = adapter ?: return@OnItemClickListener false
adapter.getItem(position)?.let { adapter.getItem(position)?.let {
@ -57,8 +39,6 @@ class PreMigrationScreenModel(
false false
} }
lateinit var dialog: MigrationBottomSheetDialog
init { init {
coroutineScope.launchIO { coroutineScope.launchIO {
val enabledSources = getEnabledSources() val enabledSources = getEnabledSources()
@ -140,4 +120,20 @@ class PreMigrationScreenModel(
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)
} }
fun onMigrationSheet(isOpen: Boolean) {
_migrationSheetOpen.value = isOpen
}
fun saveEnabledSources() {
val listOfSources = adapter?.currentItems
?.filterIsInstance<MigrationSourceItem>()
?.filter {
it.sourceEnabled
}
?.joinToString("/") { it.source.id.toString() }
.orEmpty()
prefs.migrationSources().set(listOfSources)
}
} }