diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt index f0643d81c..7bd6e8b07 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/MigrationFlags.kt @@ -11,6 +11,22 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +data class MigrationFlag( + val flag: Int, + val isDefaultSelected: Boolean, + val titleId: Int, +) { + companion object { + fun create(flag: Int, defaultSelectionMap: Int, titleId: Int): MigrationFlag { + return MigrationFlag( + flag = flag, + isDefaultSelected = defaultSelectionMap and flag != 0, + titleId = titleId, + ) + } + } +} + object MigrationFlags { private const val CHAPTERS = 0b00001 @@ -23,9 +39,6 @@ object MigrationFlags { private val getTracks: GetTracks = Injekt.get() private val downloadCache: DownloadCache by injectLazy() - val flags get() = arrayOf(CHAPTERS, CATEGORIES, TRACK, CUSTOM_COVER, DELETE_DOWNLOADED) - private var enableFlags = emptyList().toMutableList() - fun hasChapters(value: Int): Boolean { return value and CHAPTERS != 0 } @@ -46,34 +59,35 @@ object MigrationFlags { return value and DELETE_DOWNLOADED != 0 } - fun getEnabledFlagsPositions(value: Int): List { - return flags.mapIndexedNotNull { index, flag -> if (value and flag != 0) index else null } - } + /** Returns information about applicable flags with default selections. */ + fun getFlags(manga: Manga?, defaultSelectedBitMap: Int): List { + val flags = mutableListOf() + flags += MigrationFlag.create(CHAPTERS, defaultSelectedBitMap, R.string.chapters) + flags += MigrationFlag.create(CATEGORIES, defaultSelectedBitMap, R.string.categories) - fun getFlagsFromPositions(positions: Array): Int { - val fold = positions.fold(0) { accumulated, position -> accumulated or enableFlags[position] } - enableFlags.clear() - return fold - } - - fun titles(manga: Manga?): Array { - enableFlags.add(CHAPTERS) - enableFlags.add(CATEGORIES) - val titles = arrayOf(R.string.chapters, R.string.categories).toMutableList() if (manga != null) { if (runBlocking { getTracks.await(manga.id) }.isNotEmpty()) { - titles.add(R.string.track) - enableFlags.add(TRACK) + flags += MigrationFlag.create(TRACK, defaultSelectedBitMap, R.string.track) } if (manga.hasCustomCover(coverCache)) { - titles.add(R.string.custom_cover) - enableFlags.add(CUSTOM_COVER) + flags += MigrationFlag.create(CUSTOM_COVER, defaultSelectedBitMap, R.string.custom_cover) } if (downloadCache.getDownloadCount(manga) > 0) { - titles.add(R.string.delete_downloaded) - enableFlags.add(DELETE_DOWNLOADED) + flags += MigrationFlag.create(DELETE_DOWNLOADED, defaultSelectedBitMap, R.string.delete_downloaded) } } - return titles.toTypedArray() + return flags + } + + /** Returns a bit map of selected flags. */ + fun getSelectedFlagsBitMap( + selectedFlags: List, + flags: List, + ): Int { + return selectedFlags + .zip(flags) + .filter { (isSelected, _) -> isSelected } + .map { (_, flag) -> flag.flag } + .reduceOrNull { acc, mask -> acc or mask } ?: 0 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt index 71e40cee7..fba1fb25d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateDialog.kt @@ -19,15 +19,14 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEachIndexed import cafe.adriel.voyager.core.model.StateScreenModel import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.manga.interactor.UpdateManga @@ -74,15 +73,8 @@ internal fun MigrateDialog( val scope = rememberCoroutineScope() val state by screenModel.state.collectAsState() - val activeFlags = remember { MigrationFlags.getEnabledFlagsPositions(screenModel.migrateFlags.get()) } - val items = remember { - MigrationFlags.titles(oldManga) - .map { context.getString(it) } - .toList() - } - val selected = remember { - mutableStateListOf(*List(items.size) { i -> activeFlags.contains(i) }.toTypedArray()) - } + val flags = remember { MigrationFlags.getFlags(oldManga, screenModel.migrateFlags.get()) } + val selectedFlags = remember { flags.map { it.isDefaultSelected }.toMutableStateList() } if (state.isMigrating) { LoadingScreen( @@ -99,18 +91,16 @@ internal fun MigrateDialog( Column( modifier = Modifier.verticalScroll(rememberScrollState()), ) { - items.forEachIndexed { index, title -> - val onChange: () -> Unit = { - selected[index] = !selected[index] - } + flags.forEachIndexed { index, flag -> + val onChange = { selectedFlags[index] = !selectedFlags[index] } Row( modifier = Modifier .fillMaxWidth() .clickable(onClick = onChange), verticalAlignment = Alignment.CenterVertically, ) { - Checkbox(checked = selected[index], onCheckedChange = { onChange() }) - Text(text = title) + Checkbox(checked = selectedFlags[index], onCheckedChange = { onChange() }) + Text(text = context.getString(flag.titleId)) } } } @@ -133,7 +123,12 @@ internal fun MigrateDialog( TextButton( onClick = { scope.launchIO { - screenModel.migrateManga(oldManga, newManga, false) + screenModel.migrateManga( + oldManga, + newManga, + false, + MigrationFlags.getSelectedFlagsBitMap(selectedFlags, flags), + ) withUIContext { onPopScreen() } } }, @@ -143,12 +138,13 @@ internal fun MigrateDialog( TextButton( onClick = { scope.launchIO { - val selectedIndices = mutableListOf() - selected.fastForEachIndexed { i, b -> if (b) selectedIndices.add(i) } - val newValue = - MigrationFlags.getFlagsFromPositions(selectedIndices.toTypedArray()) - screenModel.migrateFlags.set(newValue) - screenModel.migrateManga(oldManga, newManga, true) + screenModel.migrateManga( + oldManga, + newManga, + true, + MigrationFlags.getSelectedFlagsBitMap(selectedFlags, flags), + ) + withUIContext { onPopScreen() } } }, @@ -184,7 +180,13 @@ internal class MigrateDialogScreenModel( Injekt.get().services.filterIsInstance() } - suspend fun migrateManga(oldManga: Manga, newManga: Manga, replace: Boolean) { + suspend fun migrateManga( + oldManga: Manga, + newManga: Manga, + replace: Boolean, + flags: Int, + ) { + migrateFlags.set(flags) val source = sourceManager.get(newManga.source) ?: return val prevSource = sourceManager.get(oldManga.source) @@ -200,6 +202,7 @@ internal class MigrateDialogScreenModel( newManga = newManga, sourceChapters = chapters, replace = replace, + flags = flags, ) } catch (_: Throwable) { // Explicitly stop if an error occurred; the dialog normally gets popped at the end @@ -215,9 +218,8 @@ internal class MigrateDialogScreenModel( newManga: Manga, sourceChapters: List, replace: Boolean, + flags: Int, ) { - val flags = migrateFlags.get() - val migrateChapters = MigrationFlags.hasChapters(flags) val migrateCategories = MigrationFlags.hasCategories(flags) val migrateTracks = MigrationFlags.hasTracks(flags)