mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Display all similarly named duplicates in duplicate manga dialogue (#1861)
Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
		| @@ -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<Manga>, | ||||
|     onDismissRequest: () -> Unit, | ||||
|     onConfirm: () -> Unit, | ||||
|     onOpenManga: () -> Unit, | ||||
|     onMigrate: () -> Unit, | ||||
|     onOpenManga: (manga: Manga) -> Unit, | ||||
|     onMigrate: (manga: Manga) -> Unit, | ||||
|     modifier: Modifier = Modifier, | ||||
| ) { | ||||
|     val sourceManager = remember { Injekt.get<SourceManager>() } | ||||
|     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<Manga>): 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 | ||||
|   | ||||
| @@ -219,14 +219,11 @@ data class BrowseSourceScreen( | ||||
|                 onMangaClick = { navigator.push((MangaScreen(it.id, true))) }, | ||||
|                 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) | ||||
|                         } | ||||
| @@ -249,12 +246,11 @@ 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)) }, | ||||
|                     onMigrate = { | ||||
|                         screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, dialog.duplicate)) | ||||
|                     }, | ||||
|                     onOpenManga = { navigator.push(MangaScreen(it.id)) }, | ||||
|                     onMigrate = { screenModel.setDialog(BrowseSourceScreenModel.Dialog.Migrate(dialog.manga, it)) }, | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -284,8 +284,8 @@ class BrowseSourceScreenModel( | ||||
|             .orEmpty() | ||||
|     } | ||||
|  | ||||
|     suspend fun getDuplicateLibraryManga(manga: Manga): Manga? { | ||||
|         return getDuplicateLibraryManga.await(manga).getOrNull(0) | ||||
|     suspend fun getDuplicateLibraryManga(manga: Manga): List<Manga> { | ||||
|         return getDuplicateLibraryManga.invoke(manga) | ||||
|     } | ||||
|  | ||||
|     private fun moveMangaToCategories(manga: Manga, vararg categories: Category) { | ||||
| @@ -335,7 +335,7 @@ 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<Manga>) : Dialog | ||||
|         data class ChangeMangaCategory( | ||||
|             val manga: Manga, | ||||
|             val initialSelection: ImmutableList<CheckboxState.State<Category>>, | ||||
|   | ||||
| @@ -43,7 +43,6 @@ import tachiyomi.domain.manga.model.Manga | ||||
| import tachiyomi.domain.source.service.SourceManager | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import kotlin.collections.map | ||||
|  | ||||
| class HistoryScreenModel( | ||||
|     private val addTracks: AddTracks = Injekt.get(), | ||||
| @@ -175,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 | ||||
|             } | ||||
|  | ||||
| @@ -247,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<Manga>) : Dialog | ||||
|         data class ChangeCategory( | ||||
|             val manga: Manga, | ||||
|             val initialSelection: ImmutableList<CheckboxState<Category>>, | ||||
|   | ||||
| @@ -98,14 +98,13 @@ 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)) }, | ||||
|                     onMigrate = { | ||||
|                         screenModel.showMigrateDialog(dialog.manga, dialog.duplicate) | ||||
|                     }, | ||||
|                     onOpenManga = { navigator.push(MangaScreen(it.id)) }, | ||||
|                     onMigrate = { screenModel.showMigrateDialog(dialog.manga, it) }, | ||||
|                 ) | ||||
|             } | ||||
|             is HistoryScreenModel.Dialog.ChangeCategory -> { | ||||
|   | ||||
| @@ -203,12 +203,11 @@ 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)) }, | ||||
|                     onMigrate = { | ||||
|                         screenModel.showMigrateDialog(dialog.duplicate) | ||||
|                     }, | ||||
|                     onOpenManga = { navigator.push(MangaScreen(it.id)) }, | ||||
|                     onMigrate = { screenModel.showMigrateDialog(it) }, | ||||
|                 ) | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -88,8 +88,6 @@ import tachiyomi.i18n.MR | ||||
| import tachiyomi.source.local.isLocal | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import kotlin.collections.filter | ||||
| import kotlin.collections.forEach | ||||
| import kotlin.math.floor | ||||
|  | ||||
| class MangaScreenModel( | ||||
| @@ -328,10 +326,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 | ||||
|                     } | ||||
|                 } | ||||
| @@ -1071,7 +1069,7 @@ class MangaScreenModel( | ||||
|             val initialSelection: ImmutableList<CheckboxState<Category>>, | ||||
|         ) : Dialog | ||||
|         data class DeleteChapters(val chapters: List<Chapter>) : Dialog | ||||
|         data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog | ||||
|         data class DuplicateManga(val manga: Manga, val duplicates: List<Manga>) : Dialog | ||||
|         data class Migrate(val newManga: Manga, val oldManga: Manga) : Dialog | ||||
|         data class SetFetchInterval(val manga: Manga) : Dialog | ||||
|         data object SettingsSheet : Dialog | ||||
|   | ||||
		Reference in New Issue
	
	Block a user