diff --git a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt index 0707ac2bc..bdce7cfeb 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/DuplicateMangaDialog.kt @@ -1,6 +1,7 @@ package eu.kanade.presentation.manga -import androidx.compose.foundation.clickable +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -8,37 +9,82 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Brush +import androidx.compose.material.icons.filled.PersonOutline +import androidx.compose.material.icons.filled.Warning import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.Book -import androidx.compose.material.icons.outlined.SwapVert +import androidx.compose.material.icons.outlined.AttachMoney +import androidx.compose.material.icons.outlined.Block +import androidx.compose.material.icons.outlined.Close +import androidx.compose.material.icons.outlined.Done +import androidx.compose.material.icons.outlined.DoneAll +import androidx.compose.material.icons.outlined.Pause +import androidx.compose.material.icons.outlined.Schedule import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text +import androidx.compose.material3.Typography import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextMeasurer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastMaxOfOrNull +import coil3.request.ImageRequest +import coil3.request.crossfade import eu.kanade.presentation.components.AdaptiveSheet import eu.kanade.presentation.components.TabbedDialogPaddings +import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.model.SManga +import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.source.model.StubSource +import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR +import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.util.secondaryItemAlpha +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get @Composable fun DuplicateMangaDialog( + duplicates: List, onDismissRequest: () -> Unit, onConfirm: () -> Unit, - onOpenManga: () -> Unit, - onMigrate: () -> Unit, + onOpenManga: (manga: Manga) -> Unit, + onMigrate: (manga: Manga) -> Unit, modifier: Modifier = Modifier, ) { + val sourceManager = remember { Injekt.get() } val minHeight = LocalPreferenceMinHeight.current + val horizontalPadding = PaddingValues(horizontal = TabbedDialogPaddings.Horizontal) + val horizontalPaddingModifier = Modifier.padding(horizontalPadding) AdaptiveSheet( modifier = modifier, @@ -46,81 +92,292 @@ fun DuplicateMangaDialog( ) { Column( modifier = Modifier - .padding( - vertical = TabbedDialogPaddings.Vertical, - horizontal = TabbedDialogPaddings.Horizontal, - ) + .padding(vertical = TabbedDialogPaddings.Vertical) + .verticalScroll(rememberScrollState()) .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium), ) { Text( - modifier = Modifier.padding(TitlePadding), - text = stringResource(MR.strings.are_you_sure), + text = stringResource(MR.strings.possible_duplicates_title), style = MaterialTheme.typography.headlineMedium, + modifier = Modifier + .then(horizontalPaddingModifier) + .padding(top = MaterialTheme.padding.small), ) Text( - text = stringResource(MR.strings.confirm_add_duplicate_manga), + text = stringResource(MR.strings.possible_duplicates_summary), style = MaterialTheme.typography.bodyMedium, + modifier = Modifier.then(horizontalPaddingModifier), ) - Spacer(Modifier.height(PaddingSize)) - - TextPreferenceWidget( - title = stringResource(MR.strings.action_show_manga), - icon = Icons.Outlined.Book, - onPreferenceClick = { - onDismissRequest() - onOpenManga() - }, - ) - - HorizontalDivider() - - TextPreferenceWidget( - title = stringResource(MR.strings.action_migrate_duplicate), - icon = Icons.Outlined.SwapVert, - onPreferenceClick = { - onDismissRequest() - onMigrate() - }, - ) - - HorizontalDivider() - - TextPreferenceWidget( - title = stringResource(MR.strings.action_add_anyway), - icon = Icons.Outlined.Add, - onPreferenceClick = { - onDismissRequest() - onConfirm() - }, - ) - - Row( - modifier = Modifier - .sizeIn(minHeight = minHeight) - .clickable { onDismissRequest.invoke() } - .padding(ButtonPadding) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, + LazyRow( + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), + modifier = Modifier.height(getMaximumMangaCardHeight(duplicates)), + contentPadding = horizontalPadding, ) { - OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) { - Text( - modifier = Modifier - .padding(vertical = 8.dp), - text = stringResource(MR.strings.action_cancel), - color = MaterialTheme.colorScheme.primary, - style = MaterialTheme.typography.titleLarge, - fontSize = 16.sp, + items( + items = duplicates, + key = { it.id }, + ) { + DuplicateMangaListItem( + manga = it, + getSource = { sourceManager.getOrStub(it.source) }, + onMigrate = { onMigrate(it) }, + onDismissRequest = onDismissRequest, + onOpenManga = { onOpenManga(it) }, ) } } + + Column(modifier = horizontalPaddingModifier) { + HorizontalDivider() + + TextPreferenceWidget( + title = stringResource(MR.strings.action_add_anyway), + icon = Icons.Outlined.Add, + onPreferenceClick = { + onDismissRequest() + onConfirm() + }, + modifier = Modifier.clip(CircleShape), + ) + } + + OutlinedButton( + onClick = onDismissRequest, + modifier = Modifier + .then(horizontalPaddingModifier) + .padding(bottom = MaterialTheme.padding.medium) + .heightIn(min = minHeight) + .fillMaxWidth(), + ) { + Text( + modifier = Modifier.padding(vertical = MaterialTheme.padding.extraSmall), + text = stringResource(MR.strings.action_cancel), + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.bodyLarge, + ) + } } } } -private val PaddingSize = 16.dp +@Composable +private fun DuplicateMangaListItem( + manga: Manga, + getSource: () -> Source, + onDismissRequest: () -> Unit, + onOpenManga: () -> Unit, + onMigrate: () -> Unit, +) { + val source = getSource() + Column( + modifier = Modifier + .width(MangaCardWidth) + .clip(MaterialTheme.shapes.medium) + .background(MaterialTheme.colorScheme.surface) + .combinedClickable( + onLongClick = { onOpenManga() }, + onClick = { + onDismissRequest() + onMigrate() + }, + ) + .padding(MaterialTheme.padding.small), + ) { + MangaCover.Book( + data = ImageRequest.Builder(LocalContext.current) + .data(manga) + .crossfade(true) + .build(), + modifier = Modifier.fillMaxWidth(), + ) -private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp) -private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp) + Spacer(modifier = Modifier.height(MaterialTheme.padding.extraSmall)) + + Text( + text = manga.title, + style = MaterialTheme.typography.titleSmall, + overflow = TextOverflow.Ellipsis, + maxLines = 2, + ) + + if (!manga.author.isNullOrBlank()) { + MangaDetailRow( + text = manga.author!!, + iconImageVector = Icons.Filled.PersonOutline, + maxLines = 2, + ) + } + + if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) { + MangaDetailRow( + text = manga.artist!!, + iconImageVector = Icons.Filled.Brush, + maxLines = 2, + ) + } + + MangaDetailRow( + text = when (manga.status) { + SManga.ONGOING.toLong() -> stringResource(MR.strings.ongoing) + SManga.COMPLETED.toLong() -> stringResource(MR.strings.completed) + SManga.LICENSED.toLong() -> stringResource(MR.strings.licensed) + SManga.PUBLISHING_FINISHED.toLong() -> stringResource(MR.strings.publishing_finished) + SManga.CANCELLED.toLong() -> stringResource(MR.strings.cancelled) + SManga.ON_HIATUS.toLong() -> stringResource(MR.strings.on_hiatus) + else -> stringResource(MR.strings.unknown) + }, + iconImageVector = when (manga.status) { + SManga.ONGOING.toLong() -> Icons.Outlined.Schedule + SManga.COMPLETED.toLong() -> Icons.Outlined.DoneAll + SManga.LICENSED.toLong() -> Icons.Outlined.AttachMoney + SManga.PUBLISHING_FINISHED.toLong() -> Icons.Outlined.Done + SManga.CANCELLED.toLong() -> Icons.Outlined.Close + SManga.ON_HIATUS.toLong() -> Icons.Outlined.Pause + else -> Icons.Outlined.Block + }, + ) + + Spacer(modifier = Modifier.weight(1f)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + ) { + if (source is StubSource) { + Icon( + imageVector = Icons.Filled.Warning, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.error, + ) + } + Text( + text = source.name, + style = MaterialTheme.typography.labelSmall, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + } + } +} + +@Composable +private fun MangaDetailRow( + text: String, + iconImageVector: ImageVector, + maxLines: Int = 1, +) { + Row( + modifier = Modifier + .secondaryItemAlpha() + .padding(top = MaterialTheme.padding.extraSmall), + horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + imageVector = iconImageVector, + contentDescription = null, + modifier = Modifier.size(MangaDetailsIconWidth), + ) + Text( + text = text, + style = MaterialTheme.typography.bodySmall, + overflow = TextOverflow.Ellipsis, + maxLines = maxLines, + ) + } +} + +@Composable +private fun getMaximumMangaCardHeight(duplicates: List): Dp { + val density = LocalDensity.current + val typography = MaterialTheme.typography + val textMeasurer = rememberTextMeasurer() + + val smallPadding = with(density) { MaterialTheme.padding.small.roundToPx() } + val extraSmallPadding = with(density) { MaterialTheme.padding.extraSmall.roundToPx() } + + val width = with(density) { MangaCardWidth.roundToPx() - (2 * smallPadding) } + val iconWidth = with(density) { MangaDetailsIconWidth.roundToPx() } + + val coverHeight = width / MangaCover.Book.ratio + val constraints = Constraints(maxWidth = width) + val detailsConstraints = Constraints(maxWidth = width - iconWidth - extraSmallPadding) + + return remember( + duplicates, + density, + typography, + textMeasurer, + smallPadding, + extraSmallPadding, + coverHeight, + constraints, + detailsConstraints, + ) { + duplicates.fastMaxOfOrNull { + calculateMangaCardHeight( + manga = it, + density = density, + typography = typography, + textMeasurer = textMeasurer, + smallPadding = smallPadding, + extraSmallPadding = extraSmallPadding, + coverHeight = coverHeight, + constraints = constraints, + detailsConstraints = detailsConstraints, + ) + } + ?: 0.dp + } +} + +private fun calculateMangaCardHeight( + manga: Manga, + density: Density, + typography: Typography, + textMeasurer: TextMeasurer, + smallPadding: Int, + extraSmallPadding: Int, + coverHeight: Float, + constraints: Constraints, + detailsConstraints: Constraints, +): Dp { + val titleHeight = textMeasurer.measureHeight(manga.title, typography.titleSmall, 2, constraints) + val authorHeight = if (!manga.author.isNullOrBlank()) { + textMeasurer.measureHeight(manga.author!!, typography.bodySmall, 2, detailsConstraints) + } else { + 0 + } + val artistHeight = if (!manga.artist.isNullOrBlank() && manga.author != manga.artist) { + textMeasurer.measureHeight(manga.artist!!, typography.bodySmall, 2, detailsConstraints) + } else { + 0 + } + val statusHeight = textMeasurer.measureHeight("", typography.bodySmall, 2, detailsConstraints) + val sourceHeight = textMeasurer.measureHeight("", typography.labelSmall, 1, constraints) + + val totalHeight = coverHeight + titleHeight + authorHeight + artistHeight + statusHeight + sourceHeight + return with(density) { ((2 * smallPadding) + totalHeight + (5 * extraSmallPadding)).toDp() } +} + +private fun TextMeasurer.measureHeight( + text: String, + style: TextStyle, + maxLines: Int, + constraints: Constraints, +): Int = measure( + text = text, + style = style, + overflow = TextOverflow.Ellipsis, + maxLines = maxLines, + constraints = constraints, +) + .size + .height + +private val MangaCardWidth = 150.dp +private val MangaDetailsIconWidth = 16.dp diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index 523b23a57..9bb0bf500 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -256,14 +256,11 @@ data class BrowseSourceScreen( onMangaClick = { navigator.push(MangaScreen(it.id, true, smartSearchConfig)) }, onMangaLongClick = { manga -> scope.launchIO { - val duplicateManga = screenModel.getDuplicateLibraryManga(manga) + val duplicates = screenModel.getDuplicateLibraryManga(manga) when { manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga)) - duplicateManga != null -> screenModel.setDialog( - BrowseSourceScreenModel.Dialog.AddDuplicateManga( - manga, - duplicateManga, - ), + duplicates.isNotEmpty() -> screenModel.setDialog( + BrowseSourceScreenModel.Dialog.AddDuplicateManga(manga, duplicates), ) else -> screenModel.addFavorite(manga) } @@ -318,15 +315,16 @@ data class BrowseSourceScreen( } is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> { DuplicateMangaDialog( + duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, - onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(it.id)) }, onMigrate = { // SY --> PreMigrationScreen.navigateToMigration( Injekt.get().skipPreMigration().get(), navigator, - dialog.duplicate.id, + it.id, dialog.manga.id, ) // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index df052612b..3c3f4d486 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -393,8 +393,8 @@ open class BrowseSourceScreenModel( .orEmpty() } - suspend fun getDuplicateLibraryManga(manga: Manga): Manga? { - return getDuplicateLibraryManga.await(manga).getOrNull(0) + suspend fun getDuplicateLibraryManga(manga: Manga): List { + return getDuplicateLibraryManga.invoke(manga) } private fun moveMangaToCategories(manga: Manga, vararg categories: Category) { @@ -444,7 +444,7 @@ open class BrowseSourceScreenModel( sealed interface Dialog { data object Filter : Dialog data class RemoveManga(val manga: Manga) : Dialog - data class AddDuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog + data class AddDuplicateManga(val manga: Manga, val duplicates: List) : Dialog data class ChangeMangaCategory( val manga: Manga, val initialSelection: ImmutableList>, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt index 36e63f83b..b1aeb978c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryScreenModel.kt @@ -174,9 +174,9 @@ class HistoryScreenModel( screenModelScope.launchIO { val manga = getManga.await(mangaId) ?: return@launchIO - val duplicate = getDuplicateLibraryManga.await(manga).getOrNull(0) - if (duplicate != null) { - mutableState.update { it.copy(dialog = Dialog.DuplicateManga(manga, duplicate)) } + val duplicates = getDuplicateLibraryManga(manga) + if (duplicates.isNotEmpty()) { + mutableState.update { it.copy(dialog = Dialog.DuplicateManga(manga, duplicates)) } return@launchIO } @@ -246,7 +246,7 @@ class HistoryScreenModel( sealed interface Dialog { data object DeleteAll : Dialog data class Delete(val history: HistoryWithRelations) : Dialog - data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog + data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog data class ChangeCategory( val manga: Manga, val initialSelection: ImmutableList>, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt index 5bbad20c9..9d4081b52 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/history/HistoryTab.kt @@ -114,17 +114,18 @@ data object HistoryTab : Tab { } is HistoryScreenModel.Dialog.DuplicateManga -> { DuplicateMangaDialog( + duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, - onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(it.id)) }, onMigrate = { // SY --> PreMigrationScreen.navigateToMigration( Injekt.get().skipPreMigration().get(), navigator, - dialog.duplicate.id, + it.id, dialog.manga.id, ) // SY <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 1a8c76be8..57eed5bc4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -261,12 +261,13 @@ class MangaScreen( is MangaScreenModel.Dialog.DuplicateManga -> { DuplicateMangaDialog( + duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, onConfirm = { screenModel.toggleFavorite(onRemoved = {}, checkDuplicate = false) }, - onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(it.id)) }, onMigrate = { // SY --> - migrateManga(navigator, dialog.duplicate, screenModel.manga!!.id) + migrateManga(navigator, it, screenModel.manga!!.id) // SY <-- }, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 76570dfc4..832c0ae09 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -783,10 +783,10 @@ class MangaScreenModel( // Add to library // First, check if duplicate exists if callback is provided if (checkDuplicate) { - val duplicate = getDuplicateLibraryManga.await(manga).getOrNull(0) + val duplicates = getDuplicateLibraryManga(manga) - if (duplicate != null) { - updateSuccessState { it.copy(dialog = Dialog.DuplicateManga(manga, duplicate)) } + if (duplicates.isNotEmpty()) { + updateSuccessState { it.copy(dialog = Dialog.DuplicateManga(manga, duplicates)) } return@launchIO } } @@ -1653,7 +1653,7 @@ class MangaScreenModel( val initialSelection: ImmutableList>, ) : Dialog data class DeleteChapters(val chapters: List) : Dialog - data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog + data class DuplicateManga(val manga: Manga, val duplicates: List) : Dialog /* SY --> data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index a07aeb3b7..f829eef76 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -103,14 +103,15 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { is BrowseSourceScreenModel.Dialog.Migrate -> {} is BrowseSourceScreenModel.Dialog.AddDuplicateManga -> { DuplicateMangaDialog( + duplicates = dialog.duplicates, onDismissRequest = onDismissRequest, onConfirm = { screenModel.addFavorite(dialog.manga) }, - onOpenManga = { navigator.push(MangaScreen(dialog.duplicate.id)) }, + onOpenManga = { navigator.push(MangaScreen(it.id)) }, onMigrate = { PreMigrationScreen.navigateToMigration( Injekt.get().skipPreMigration().get(), navigator, - dialog.duplicate.id, + it.id, dialog.manga.id, ) }, diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index dd2a4a3c9..cf0de16ce 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -120,7 +120,7 @@ getDuplicateLibraryManga: SELECT * FROM mangas WHERE favorite = 1 -AND LOWER(title) = :title +AND lower(title) LIKE '%' || lower(:title) || '%' AND _id != :id; getUpcomingManga: diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt index df5cec44a..b81d73b6c 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt @@ -7,7 +7,7 @@ class GetDuplicateLibraryManga( private val mangaRepository: MangaRepository, ) { - suspend fun await(manga: Manga): List { + suspend operator fun invoke(manga: Manga): List { return mangaRepository.getDuplicateLibraryManga(manga.id, manga.title.lowercase()) } } diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index c9a045f53..f0caa34fe 100755 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -166,7 +166,6 @@ Start downloading now Not now Add anyway - Migrate existing entry Loading… @@ -708,7 +707,8 @@ Remove from library Unknown title - You have an entry in your library with the same name.\n\nDo you still wish to continue? + Possible duplicates + You have entries in your library with a similar name.\n\nSelect an entry to migrate or add anyway. Added to library Removed from library More