Skip to content

Duplicate managment functionality #1997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Use Github markdown flavour for Github releases & fix bullet list alignment ([@Secozzi](https://github.com/Secozzi)) ([#2024](https://github.com/mihonapp/mihon/pull/2024))
- Add Nord Theme ([@Riztard](https://github.com/Riztard)) ([#1951](https://github.com/mihonapp/mihon/pull/1951))
- Option to keep read manga when clearing database ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#1979](https://github.com/mihonapp/mihon/pull/1979))
- Duplicates Management ([@NarwhalHorns](https://github.com/NarwhalHorns)) ([#1997](https://github.com/mihonapp/mihon/pull/1997))

### Improved
- Significantly improve browsing speed (near instantaneous) ([@AntsyLich](https://github.com/AntsyLich)) ([#1946](https://github.com/mihonapp/mihon/pull/1946))
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/java/eu/kanade/domain/DomainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import mihon.domain.extensionrepo.service.ExtensionRepoService
import mihon.domain.upcoming.interactor.GetUpcomingManga
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
import tachiyomi.data.hiddenDuplicates.HiddenDuplicateRepositoryImpl
import tachiyomi.data.history.HistoryRepositoryImpl
import tachiyomi.data.manga.MangaRepositoryImpl
import tachiyomi.data.release.ReleaseServiceImpl
Expand All @@ -63,6 +64,10 @@ import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
import tachiyomi.domain.chapter.interactor.ShouldUpdateDbChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.repository.ChapterRepository
import tachiyomi.domain.hiddenDuplicates.interactor.AddHiddenDuplicate
import tachiyomi.domain.hiddenDuplicates.interactor.GetAllHiddenDuplicates
import tachiyomi.domain.hiddenDuplicates.interactor.RemoveHiddenDuplicate
import tachiyomi.domain.hiddenDuplicates.repository.HiddenDuplicateRepository
import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.history.interactor.GetNextChapters
import tachiyomi.domain.history.interactor.GetTotalReadDuration
Expand All @@ -72,6 +77,7 @@ import tachiyomi.domain.history.repository.HistoryRepository
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.interactor.GetHiddenDuplicateManga
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaByUrlAndSourceId
Expand Down Expand Up @@ -116,6 +122,7 @@ class DomainModule : InjektModule {

addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetHiddenDuplicateManga(get()) }
addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
Expand All @@ -135,6 +142,11 @@ class DomainModule : InjektModule {
addFactory { GetExcludedScanlators(get()) }
addFactory { SetExcludedScanlators(get()) }

addSingletonFactory<HiddenDuplicateRepository> { HiddenDuplicateRepositoryImpl(get()) }
addFactory { AddHiddenDuplicate(get()) }
addFactory { RemoveHiddenDuplicate(get()) }
addFactory { GetAllHiddenDuplicates(get()) }

addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
addFactory { GetApplicationRelease(get(), get()) }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package eu.kanade.presentation.duplicates

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.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.outlined.Add
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.presentation.duplicates.components.DuplicateMangaListItem
import eu.kanade.presentation.duplicates.components.getMaximumMangaCardHeight
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

@Composable
fun DuplicateMangaDialog(
duplicates: List<MangaWithChapterCount>,
onDismissRequest: () -> Unit,
onConfirm: () -> 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,
onDismissRequest = onDismissRequest,
) {
Column(
modifier = Modifier
.padding(vertical = TabbedDialogPaddings.Vertical)
.verticalScroll(rememberScrollState())
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.medium),
) {
Text(
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.possible_duplicates_summary),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.then(horizontalPaddingModifier),
)

LazyRow(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
modifier = Modifier.height(getMaximumMangaCardHeight(duplicates, duplicateMangaDialogCardWidth)),
contentPadding = horizontalPadding,
) {
items(
items = duplicates,
key = { it.manga.id },
) {
DuplicateMangaListItem(
duplicate = it,
getSource = { sourceManager.getOrStub(it.manga.source) },
cardWidth = duplicateMangaDialogCardWidth,
onClick = { onMigrate(it.manga) },
onDismissRequest = onDismissRequest,
onLongClick = { onOpenManga(it.manga) },
)
}
}

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 duplicateMangaDialogCardWidth = 150.dp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package eu.kanade.presentation.duplicates

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Visibility
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.VerticalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.duplicates.components.DuplicateMangaListItem
import eu.kanade.presentation.duplicates.components.ManageDuplicateAction
import eu.kanade.presentation.duplicates.components.getMaximumMangaCardHeight
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaWithChapterCount
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.padding
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

@Composable
fun HiddenDuplicatesContent(
hiddenDuplicatesMap: Map<MangaWithChapterCount, List<MangaWithChapterCount>>,
paddingValues: PaddingValues,
lazyListState: LazyListState,
onOpenManga: (Manga) -> Unit,
onDismissRequest: () -> Unit,
onUnhideSingleClicked: (MangaWithChapterCount, MangaWithChapterCount) -> Unit,
onUnhideGroupClicked: (MangaWithChapterCount, List<MangaWithChapterCount>) -> Unit,
) {
val sourceManager = remember { Injekt.get<SourceManager>() }

ScrollbarLazyColumn(
modifier = Modifier.fillMaxSize(),
state = lazyListState,
contentPadding = paddingValues,
verticalArrangement = Arrangement.spacedBy(verticalListPadding),
) {
items(
items = hiddenDuplicatesMap.toList(),
) { duplicatePair ->
val height =
getMaximumMangaCardHeight(
duplicatePair.second + duplicatePair.first,
hiddenDuplicatesCardWidth,
actions = true,
)

Row(
modifier = Modifier
.height(height)
.padding(start = MaterialTheme.padding.small),
) {
Column {
DuplicateMangaListItem(
duplicate = duplicatePair.first,
getSource = { sourceManager.getOrStub(duplicatePair.first.manga.source) },
cardWidth = hiddenDuplicatesCardWidth,
onClick = { onOpenManga(duplicatePair.first.manga) },
onDismissRequest = onDismissRequest,
onLongClick = { onOpenManga(duplicatePair.first.manga) },
actions = listOf(
ManageDuplicateAction(
icon = Icons.Outlined.Visibility,
onClick = { onUnhideGroupClicked(duplicatePair.first, duplicatePair.second) },
),
),
)
}
VerticalDivider(
modifier = Modifier.padding(horizontalListPadding),
)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(horizontalListPadding),
) {
items(
items = duplicatePair.second,
) { duplicate ->
DuplicateMangaListItem(
duplicate = duplicate,
getSource = { sourceManager.getOrStub(duplicate.manga.source) },
cardWidth = hiddenDuplicatesCardWidth,
onClick = { onOpenManga(duplicate.manga) },
onDismissRequest = onDismissRequest,
onLongClick = { onOpenManga(duplicate.manga) },
actions = listOf(
ManageDuplicateAction(
icon = Icons.Outlined.Visibility,
onClick = { onUnhideSingleClicked(duplicatePair.first, duplicate) },
),
),
)
}
}
}
HorizontalDivider(
modifier = Modifier.padding(top = verticalListPadding),
)
}
}
}

private val hiddenDuplicatesCardWidth = 120.dp
private val horizontalListPadding = MaterialTheme.padding.extraSmall
private val verticalListPadding = MaterialTheme.padding.extraSmall
Loading