refactor(mass-migration): replace MigrationBottomSheetDialog with MigrationConfigScreenSheet (#1401)

- Replaces the legacy `MigrationBottomSheetDialog` (which used View binding) with the newer Compose-based `MigrationConfigScreenSheet`.
- Deletes `MigrationBottomSheetDialog.kt` and its associated layout XML `migration_bottom_sheet.xml`.
- Adds a `fullSettings` flag to `MigrationConfigScreenSheet` to support a simplified UI mode by hiding advanced search and priority options.
- Updates the primary button text in the sheet based on the `fullSettings` state.
- Exposes `preferences` in `MigrationListScreenModel` to facilitate the sheet migration.
This commit is contained in:
Cuong-Tran 2025-12-04 12:00:00 +07:00
parent 42a97bb918
commit d8bef417f1
5 changed files with 53 additions and 467 deletions

View file

@ -1,201 +0,0 @@
package eu.kanade.tachiyomi.ui.browse.migration.advanced.design
import android.view.LayoutInflater
import android.widget.CompoundButton
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
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 eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.theme.colorscheme.AndroidViewColorScheme
import eu.kanade.tachiyomi.databinding.MigrationBottomSheetBinding
import mihon.domain.migration.models.MigrationFlag
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.preference.Preference
import tachiyomi.core.common.util.lang.toLong
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
@Composable
fun MigrationBottomSheetDialog(
onDismissRequest: () -> Unit,
onStartMigration: (extraParam: String?) -> Unit,
// KMK -->
fullSettings: Boolean = true,
// KMK <--
) {
val startMigration = rememberUpdatedState(onStartMigration)
val state = remember {
MigrationBottomSheetDialogState(
startMigration,
// KMK -->
fullSettings,
// KMK <--
)
}
// KMK -->
val colorScheme = AndroidViewColorScheme(MaterialTheme.colorScheme)
// KMK <--
AdaptiveSheet(onDismissRequest = onDismissRequest) {
// Wrap AndroidView in a scrollable Column using verticalScroll
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth(),
) {
AndroidView(
factory = { factoryContext ->
val binding = MigrationBottomSheetBinding.inflate(LayoutInflater.from(factoryContext))
state.initPreferences(binding)
// KMK -->
with(binding) {
migrateBtn.setBackgroundColor(colorScheme.primary)
dataLabel.setTextColor(colorScheme.primary)
optionsLabel.setTextColor(colorScheme.primary)
listOf(
migChapters,
migCategories,
migTracking,
migCustomCover,
migExtra,
migDeleteDownloaded,
radioButton,
radioButton2,
).forEach {
it.buttonTintList = colorScheme.checkboxTintList
}
listOf(
useSmartSearch,
extraSearchParam,
hideNotFoundManga,
onlyShowUpdates,
).forEach {
it.trackTintList = colorScheme.trackTintList
it.thumbTintList = colorScheme.thumbTintList
}
colorScheme.setTextInputLayoutColor(extraSearchParamInputLayout)
colorScheme.setEditTextColor(extraSearchParamText)
}
// KMK <--
binding.root
},
)
}
}
}
class MigrationBottomSheetDialogState(
private val onStartMigration: State<(extraParam: String?) -> Unit>,
// KMK -->
private val fullSettings: Boolean = true,
// KMK <--
) {
private val preferences: SourcePreferences by injectLazy()
/**
* Init general reader preferences.
*/
fun initPreferences(binding: MigrationBottomSheetBinding) {
val flags = preferences.migrationFlags().get()
with(binding) {
migChapters.isChecked = MigrationFlag.CHAPTER in flags
migCategories.isChecked = MigrationFlag.CATEGORY in flags
migTracking.isChecked = MigrationFlag.TRACK in flags
migCustomCover.isChecked = MigrationFlag.CUSTOM_COVER in flags
migExtra.isChecked = MigrationFlag.EXTRA in flags
migDeleteDownloaded.isChecked = MigrationFlag.REMOVE_DOWNLOAD in flags
listOf(
migChapters,
migCategories,
migTracking,
migCustomCover,
migExtra,
migDeleteDownloaded,
).forEach { checkBox ->
checkBox.setOnCheckedChangeListener { _, _ -> setFlags(binding) }
}
useSmartSearch.bindToPreference(preferences.migrationDeepSearchMode())
extraSearchParamInputLayout.isVisible = false
extraSearchParam.setOnCheckedChangeListener { _, isChecked ->
extraSearchParamInputLayout.isVisible = isChecked
}
sourceGroup.bindToPreference(preferences.migrationPrioritizeByChapters())
hideNotFoundManga.isChecked = preferences.migrationHideUnmatched().get()
onlyShowUpdates.isChecked = preferences.migrationHideWithoutUpdates().get()
migrateBtn.setOnClickListener {
preferences.migrationHideUnmatched().set(hideNotFoundManga.isChecked)
preferences.migrationHideWithoutUpdates().set(onlyShowUpdates.isChecked)
onStartMigration.value(
if (useSmartSearch.isChecked && !extraSearchParamText.text.isNullOrBlank()) {
extraSearchParamText.text.toString()
} else {
null
},
)
}
// KMK -->
if (!fullSettings) {
useSmartSearch.isVisible = false
extraSearchParam.isVisible = false
extraSearchParamInputLayout.isVisible = false
sourceGroup.isVisible = false
migrateBtn.text = root.context.stringResource(MR.strings.action_save)
}
// KMK <--
}
}
private fun setFlags(binding: MigrationBottomSheetBinding) {
val flags = mutableSetOf<MigrationFlag>()
with(binding) {
if (migChapters.isChecked) flags.add(MigrationFlag.CHAPTER)
if (migCategories.isChecked) flags.add(MigrationFlag.CATEGORY)
if (migTracking.isChecked) flags.add(MigrationFlag.TRACK)
if (migCustomCover.isChecked) flags.add(MigrationFlag.CUSTOM_COVER)
if (migExtra.isChecked) flags.add(MigrationFlag.EXTRA)
if (migDeleteDownloaded.isChecked) flags.add(MigrationFlag.REMOVE_DOWNLOAD)
}
preferences.migrationFlags().set(flags)
}
/**
* Binds a checkbox or switch view with a boolean preference.
*/
private fun CompoundButton.bindToPreference(pref: Preference<Boolean>) {
isChecked = pref.get()
setOnCheckedChangeListener { _, isChecked -> pref.set(isChecked) }
}
/**
* Binds a radio group with a boolean preference.
*/
private fun RadioGroup.bindToPreference(pref: Preference<Boolean>) {
(getChildAt(pref.get().toLong().toInt()) as RadioButton).isChecked = true
setOnCheckedChangeListener { _, value ->
val index = indexOfChild(findViewById(value))
pref.set(index == 1)
}
}
}

View file

@ -14,7 +14,6 @@ import eu.kanade.presentation.browse.components.MigrationExitDialog
import eu.kanade.presentation.browse.components.MigrationMangaDialog
import eu.kanade.presentation.browse.components.MigrationProgressDialog
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.MigrationBottomSheetDialog
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.util.system.toast
@ -22,6 +21,7 @@ import exh.util.overEq
import exh.util.underEq
import kotlinx.collections.immutable.persistentListOf
import mihon.feature.migration.config.MigrationConfigScreen
import mihon.feature.migration.config.MigrationConfigScreenSheet
import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.i18n.sy.SYMR
@ -142,7 +142,8 @@ class MigrationListScreen(private val config: MigrationProcedureConfig) : Screen
}
// KMK -->
MigrationListScreenModel.Dialog.MigrationOptionsDialog -> {
MigrationBottomSheetDialog(
MigrationConfigScreenSheet(
preferences = screenModel.preferences,
onDismissRequest = onDismissRequest,
onStartMigration = { _ ->
onDismissRequest()

View file

@ -49,7 +49,7 @@ import java.util.concurrent.atomic.AtomicInteger
class MigrationListScreenModel(
private val config: MigrationProcedureConfig,
private val preferences: SourcePreferences = Injekt.get(),
val preferences: SourcePreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),

View file

@ -51,6 +51,9 @@ fun MigrationConfigScreenSheet(
preferences: SourcePreferences,
onDismissRequest: () -> Unit,
onStartMigration: (extraSearchQuery: String?) -> Unit,
// KMK -->
fullSettings: Boolean = true,
// KMK <--
) {
var extraSearchQuery by rememberSaveable { mutableStateOf("") }
val migrationFlags by preferences.migrationFlags().collectAsState()
@ -124,6 +127,9 @@ fun MigrationConfigScreenSheet(
},
)
MigrationSheetDividerItem()
// KMK -->
if (fullSettings) {
// KMK <--
OutlinedTextField(
value = extraSearchQuery,
onValueChange = { extraSearchQuery = it },
@ -139,6 +145,7 @@ fun MigrationConfigScreenSheet(
vertical = MaterialTheme.padding.extraSmall,
),
)
}
MigrationSheetSwitchItem(
title = stringResource(MR.strings.migrationConfigScreen_hideUnmatchedTitle),
subtitle = null,
@ -149,6 +156,9 @@ fun MigrationConfigScreenSheet(
subtitle = stringResource(MR.strings.migrationConfigScreen_hideWithoutUpdatesSubtitle),
preference = preferences.migrationHideWithoutUpdates(),
)
// KMK -->
if (fullSettings) {
// KMK <--
MigrationSheetDividerItem()
MigrationSheetWarningItem(stringResource(MR.strings.migrationConfigScreen_enhancedOptionsWarning))
MigrationSheetSwitchItem(
@ -162,6 +172,7 @@ fun MigrationConfigScreenSheet(
preference = preferences.migrationPrioritizeByChapters(),
)
}
}
HorizontalDivider()
Button(
onClick = {
@ -175,7 +186,17 @@ fun MigrationConfigScreenSheet(
vertical = MaterialTheme.padding.small,
),
) {
Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText))
Text(
text = stringResource(
// KMK -->
if (!fullSettings) {
MR.strings.action_save
} else {
// KMK <--
MR.strings.migrationConfigScreen_continueButtonText
},
),
)
}
}
}

View file

@ -1,235 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/constraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingVertical="8dp"
android:orientation="vertical">
<TextView
android:id="@+id/data_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:layout_marginStart="16dp"
android:text="@string/data_to_include_in_migration"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorPrimary"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/migration_data_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/data_label">
<androidx.constraintlayout.helper.widget.Flow
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:constraint_referenced_ids="mig_chapters,mig_categories,mig_tracking,mig_custom_cover,mig_extra,mig_delete_downloaded"
app:flow_horizontalBias="0"
app:flow_horizontalGap="8dp"
app:flow_horizontalStyle="packed"
app:flow_verticalGap="2dp"
app:flow_wrapMode="chain"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<CheckBox
android:id="@+id/mig_chapters"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/chapters" />
<CheckBox
android:id="@+id/mig_categories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/categories" />
<CheckBox
android:id="@+id/mig_tracking"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/track" />
<CheckBox
android:id="@+id/mig_custom_cover"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/custom_cover" />
<CheckBox
android:id="@+id/mig_extra"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/log_extra" />
<CheckBox
android:id="@+id/mig_delete_downloaded"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/delete_downloaded" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/migration_data_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:background="?android:attr/divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/migration_data_group"/>
<TextView
android:id="@+id/options_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
android:text="@string/action_settings"
android:textAppearance="?attr/textAppearanceTitleMedium"
android:textColor="?attr/colorPrimary"
app:layout_constraintStart_toStartOf="@id/migration_data_group"
app:layout_constraintTop_toBottomOf="@id/migration_data_divider" />
<RadioGroup
android:id="@+id/sourceGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/options_label"
app:layout_constraintTop_toBottomOf="@id/options_label">
<RadioButton
android:id="@+id/radioButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="8dp"
android:text="@string/use_first_source" />
<RadioButton
android:id="@+id/radioButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/use_most_chapters" />
</RadioGroup>
<View
android:id="@+id/sourceGroup_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:background="?android:attr/divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sourceGroup"/>
<LinearLayout
android:id="@+id/switches"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sourceGroup_divider">
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/use_smart_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/use_intelligent_search" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/extra_search_param"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/include_extra_search_parameter" />
<com.google.android.material.textfield.TextInputLayout
app:boxBackgroundMode="none"
android:id="@+id/extra_search_param_input_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/extra_search_param_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/search_parameter"
android:importantForAutofill="no"
android:inputType="textPersonName"
android:textSize="14sp"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/hide_not_found_manga"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/hide_not_found_entries" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/only_show_updates"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:text="@string/only_show_updated_entries" />
</LinearLayout>
<View
android:id="@+id/migrate_btn_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/divider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/switches" />
<Button
android:id="@+id/migrate_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:text="@string/action_migrate"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/migrate_btn_divider"/>
</androidx.constraintlayout.widget.ConstraintLayout>