Support mass migration for selected library items (#2336)
(cherry picked from commit 982ebcf777215c90584ad28fae79e9ca8a22a951) # Conflicts: # CHANGELOG.md # app/src/main/java/eu/kanade/presentation/manga/components/MangaBottomActionMenu.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryTab.kt
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
package eu.kanade.presentation.components
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
@@ -15,7 +17,41 @@ fun DownloadDropdownMenu(
|
||||
expanded: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
offset: DpOffset? = null,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (offset != null) {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
offset = offset,
|
||||
content = {
|
||||
DownloadDropdownMenuItems(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
content = {
|
||||
DownloadDropdownMenuItems(
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.DownloadDropdownMenuItems(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDownloadClicked: (DownloadAction) -> Unit,
|
||||
) {
|
||||
val options = persistentListOf(
|
||||
DownloadAction.NEXT_1_CHAPTER to pluralStringResource(MR.plurals.download_amount, 1, 1),
|
||||
@@ -25,19 +61,13 @@ fun DownloadDropdownMenu(
|
||||
DownloadAction.UNREAD_CHAPTERS to stringResource(MR.strings.download_unread),
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
modifier = modifier,
|
||||
) {
|
||||
options.map { (downloadAction, string) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = string) },
|
||||
onClick = {
|
||||
onDownloadClicked(downloadAction)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
options.map { (downloadAction, string) ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(text = string) },
|
||||
onClick = {
|
||||
onDownloadClicked(downloadAction)
|
||||
onDismissRequest()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-35
@@ -9,6 +9,7 @@ import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
@@ -30,7 +31,6 @@ import androidx.compose.material.icons.outlined.DoneAll
|
||||
import androidx.compose.material.icons.outlined.Download
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
import androidx.compose.material.icons.outlined.RemoveDone
|
||||
import androidx.compose.material.icons.outlined.SwapCalls
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -52,6 +52,7 @@ import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.DownloadDropdownMenu
|
||||
import eu.kanade.presentation.components.DropdownMenu
|
||||
@@ -192,7 +193,7 @@ private fun RowScope.Button(
|
||||
targetValue = if (toConfirm) 2f else 1f,
|
||||
label = "weight",
|
||||
)
|
||||
Column(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.weight(animatedWeight)
|
||||
@@ -202,24 +203,28 @@ private fun RowScope.Button(
|
||||
onLongClick = onLongClick,
|
||||
onClick = onClick,
|
||||
),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = toConfirm,
|
||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = toConfirm,
|
||||
enter = expandVertically(expandFrom = Alignment.Top) + fadeIn(),
|
||||
exit = shrinkVertically(shrinkTowards = Alignment.Top) + fadeOut(),
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
overflow = TextOverflow.Visible,
|
||||
maxLines = 1,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
}
|
||||
}
|
||||
content?.invoke()
|
||||
}
|
||||
@@ -233,9 +238,9 @@ fun LibraryBottomActionMenu(
|
||||
onMarkAsUnreadClicked: () -> Unit,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onDeleteClicked: () -> Unit,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
// SY -->
|
||||
onClickCleanTitles: (() -> Unit)?,
|
||||
onClickMigrate: (() -> Unit)?,
|
||||
onClickCollectRecommendations: (() -> Unit)?,
|
||||
onClickAddToMangaDex: (() -> Unit)?,
|
||||
onClickResetInfo: (() -> Unit)?,
|
||||
@@ -254,12 +259,11 @@ fun LibraryBottomActionMenu(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
) {
|
||||
val haptic = LocalHapticFeedback.current
|
||||
val confirm =
|
||||
remember { mutableStateListOf(false, false, false, false, false /* SY --> */, false /* SY <-- */) }
|
||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false) }
|
||||
var resetJob: Job? = remember { null }
|
||||
val onLongClickItem: (Int) -> Unit = { toConfirmIndex ->
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
(0..<5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
(0..5).forEach { i -> confirm[i] = i == toConfirmIndex }
|
||||
resetJob?.cancel()
|
||||
resetJob = scope.launch {
|
||||
delay(1.seconds)
|
||||
@@ -270,7 +274,8 @@ fun LibraryBottomActionMenu(
|
||||
val showOverflow = onClickCleanTitles != null ||
|
||||
onClickAddToMangaDex != null ||
|
||||
onClickResetInfo != null ||
|
||||
onClickCollectRecommendations != null
|
||||
onClickCollectRecommendations != null ||
|
||||
onMigrateClicked != null
|
||||
val configuration = LocalConfiguration.current
|
||||
val moveMarkPrev = remember { !configuration.isTabletUi() }
|
||||
var overFlowOpen by remember { mutableStateOf(false) }
|
||||
@@ -299,11 +304,11 @@ fun LibraryBottomActionMenu(
|
||||
onLongClick = { onLongClickItem(3) },
|
||||
onClick = { downloadExpanded = !downloadExpanded },
|
||||
) {
|
||||
val onDismissRequest = { downloadExpanded = false }
|
||||
DownloadDropdownMenu(
|
||||
expanded = downloadExpanded,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onDismissRequest = { downloadExpanded = false },
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
offset = BottomBarMenuDpOffset,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -355,10 +360,10 @@ fun LibraryBottomActionMenu(
|
||||
onClick = onClickCleanTitles,
|
||||
)
|
||||
}
|
||||
if (onClickMigrate != null) {
|
||||
if (onMigrateClicked != null) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(MR.strings.migrate)) },
|
||||
onClick = onClickMigrate,
|
||||
onClick = onMigrateClicked,
|
||||
)
|
||||
}
|
||||
if (onClickCollectRecommendations != null) {
|
||||
@@ -388,18 +393,11 @@ fun LibraryBottomActionMenu(
|
||||
onLongClick = { onLongClickItem(2) },
|
||||
onClick = onMarkAsUnreadClicked,
|
||||
)
|
||||
if (onClickMigrate != null) {
|
||||
Button(
|
||||
title = stringResource(MR.strings.migrate),
|
||||
icon = Icons.Outlined.SwapCalls,
|
||||
toConfirm = confirm[5],
|
||||
onLongClick = { onLongClickItem(5) },
|
||||
onClick = onClickMigrate,
|
||||
)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val BottomBarMenuDpOffset = DpOffset(0.dp, 0.dp)
|
||||
|
||||
@@ -28,7 +28,6 @@ import cafe.adriel.voyager.navigator.Navigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.library.DeleteLibraryMangaDialog
|
||||
import eu.kanade.presentation.library.LibrarySettingsDialog
|
||||
@@ -43,7 +42,6 @@ import eu.kanade.presentation.util.Tab
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
import eu.kanade.tachiyomi.data.sync.SyncDataJob
|
||||
import eu.kanade.tachiyomi.ui.browse.migration.advanced.design.PreMigrationScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
||||
import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
@@ -62,6 +60,7 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import mihon.feature.migration.config.MigrationConfigScreen
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.domain.category.model.Category
|
||||
@@ -76,8 +75,6 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.screens.EmptyScreenAction
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
data object LibraryTab : Tab {
|
||||
|
||||
@@ -190,23 +187,23 @@ data object LibraryTab : Tab {
|
||||
onDownloadClicked = screenModel::performDownloadAction
|
||||
.takeIf { state.selectedManga.fastAll { !it.isLocal() } },
|
||||
onDeleteClicked = screenModel::openDeleteMangaDialog,
|
||||
// SY -->
|
||||
onClickCleanTitles = screenModel::cleanTitles.takeIf { state.showCleanTitles },
|
||||
onClickMigrate = {
|
||||
val selectedMangaIds = state.selectedManga
|
||||
onMigrateClicked = {
|
||||
val selection = state.selectedManga
|
||||
// SY -->
|
||||
.filterNot { it.source == MERGED_SOURCE_ID }
|
||||
.map { it.id }
|
||||
// <-- SY
|
||||
screenModel.clearSelection()
|
||||
if (selectedMangaIds.isNotEmpty()) {
|
||||
PreMigrationScreen.navigateToMigration(
|
||||
Injekt.get<SourcePreferences>().skipPreMigration().get(),
|
||||
navigator,
|
||||
selectedMangaIds,
|
||||
)
|
||||
} else {
|
||||
context.toast(SYMR.strings.no_valid_entry)
|
||||
}
|
||||
/* SY --> */if (selection.isNotEmpty()) { /* <-- SY */
|
||||
navigator.push(MigrationConfigScreen(selection))
|
||||
// SY ->>
|
||||
} else {
|
||||
context.toast(SYMR.strings.no_valid_entry)
|
||||
}
|
||||
// <-- SY
|
||||
},
|
||||
// SY -->
|
||||
onClickCleanTitles = screenModel::cleanTitles.takeIf { state.showCleanTitles },
|
||||
onClickCollectRecommendations = screenModel::showRecommendationSearchDialog.takeIf { state.selection.size > 1 },
|
||||
onClickAddToMangaDex = screenModel::syncMangaToDex.takeIf { state.showAddToMangadex },
|
||||
onClickResetInfo = screenModel::resetInfo.takeIf { state.showResetInfo },
|
||||
|
||||
@@ -70,7 +70,7 @@ import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MigrationConfigScreen(private val mangaIds: List<Long>) : Screen() {
|
||||
class MigrationConfigScreen(private val mangaIds: Collection<Long>) : Screen() {
|
||||
|
||||
constructor(mangaId: Long) : this(listOf(mangaId))
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import mihon.feature.migration.list.components.MigrationMangaDialog
|
||||
import mihon.feature.migration.list.components.MigrationProgressDialog
|
||||
import tachiyomi.i18n.MR
|
||||
|
||||
class MigrationListScreen(private val mangaIds: List<Long>, private val extraSearchQuery: String?) : Screen() {
|
||||
class MigrationListScreen(private val mangaIds: Collection<Long>, private val extraSearchQuery: String?) : Screen() {
|
||||
|
||||
private var matchOverride: Pair<Long, Long>? = null
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MigrationListScreenModel(
|
||||
mangaIds: List<Long>,
|
||||
mangaIds: Collection<Long>,
|
||||
extraSearchQuery: String?,
|
||||
private val preferences: SourcePreferences = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
|
||||
Reference in New Issue
Block a user