feat(local): Allow delete whole manga's downloaded folder & clear chapters list (#1016)
* feat(local): Allow delete local files * Refresh chapters list after deleting * Allow delete whole entry's downloaded folder & clear episodes/chapters list Also hide `Open folder` for merged entries * Don't delete local folder & refresh local's chapters list state after clear * use forEachIndexed instead of forEach * adding comment * add empty line
This commit is contained in:
parent
32d448d6ab
commit
3ec6aeaba2
9 changed files with 192 additions and 12 deletions
|
|
@ -57,17 +57,14 @@ fun DeleteLibraryMangaDialog(
|
|||
},
|
||||
text = {
|
||||
Column {
|
||||
list.forEach { state ->
|
||||
list.forEachIndexed { index, state ->
|
||||
LabeledCheckbox(
|
||||
label = stringResource(state.value),
|
||||
checked = state.isChecked,
|
||||
onCheckedChange = {
|
||||
val index = list.indexOf(state)
|
||||
if (index != -1) {
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<StringResource>
|
||||
list = mutableList.toList()
|
||||
}
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<StringResource>
|
||||
list = mutableList.toList()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ fun MangaScreen(
|
|||
// KMK -->
|
||||
getMangaState: @Composable (Manga) -> State<Manga>,
|
||||
onClickSourceSettingsClicked: (() -> Unit)?,
|
||||
onClearManga: () -> Unit,
|
||||
onOpenMangaFolder: (() -> Unit)?,
|
||||
onRelatedMangasScreenClick: () -> Unit,
|
||||
onRelatedMangaClick: (Manga) -> Unit,
|
||||
|
|
@ -269,6 +270,7 @@ fun MangaScreen(
|
|||
// KMK -->
|
||||
getMangaState = getMangaState,
|
||||
onClickSourceSettingsClicked = onClickSourceSettingsClicked,
|
||||
onClearManga = onClearManga,
|
||||
onOpenMangaFolder = onOpenMangaFolder,
|
||||
onRelatedMangasScreenClick = onRelatedMangasScreenClick,
|
||||
onRelatedMangaClick = onRelatedMangaClick,
|
||||
|
|
@ -330,6 +332,7 @@ fun MangaScreen(
|
|||
// KMK -->
|
||||
getMangaState = getMangaState,
|
||||
onClickSourceSettingsClicked = onClickSourceSettingsClicked,
|
||||
onClearManga = onClearManga,
|
||||
onOpenMangaFolder = onOpenMangaFolder,
|
||||
onRelatedMangasScreenClick = onRelatedMangasScreenClick,
|
||||
onRelatedMangaClick = onRelatedMangaClick,
|
||||
|
|
@ -408,6 +411,7 @@ private fun MangaScreenSmallImpl(
|
|||
// KMK -->
|
||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
||||
onClickSourceSettingsClicked: (() -> Unit)?,
|
||||
onClearManga: () -> Unit,
|
||||
onOpenMangaFolder: (() -> Unit)?,
|
||||
onRelatedMangasScreenClick: () -> Unit,
|
||||
onRelatedMangaClick: (Manga) -> Unit,
|
||||
|
|
@ -491,6 +495,7 @@ private fun MangaScreenSmallImpl(
|
|||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||
// KMK -->
|
||||
onClickSourceSettings = onClickSourceSettingsClicked,
|
||||
onClearManga = onClearManga,
|
||||
onOpenMangaFolder = onOpenMangaFolder,
|
||||
onClickRelatedMangas = onRelatedMangasScreenClick.takeIf {
|
||||
!expandRelatedMangas &&
|
||||
|
|
@ -875,6 +880,7 @@ private fun MangaScreenLargeImpl(
|
|||
// KMK -->
|
||||
getMangaState: @Composable ((Manga) -> State<Manga>),
|
||||
onClickSourceSettingsClicked: (() -> Unit)?,
|
||||
onClearManga: () -> Unit,
|
||||
onOpenMangaFolder: (() -> Unit)?,
|
||||
onRelatedMangasScreenClick: () -> Unit,
|
||||
onRelatedMangaClick: (Manga) -> Unit,
|
||||
|
|
@ -950,6 +956,7 @@ private fun MangaScreenLargeImpl(
|
|||
onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite },
|
||||
// KMK -->
|
||||
onClickSourceSettings = onClickSourceSettingsClicked,
|
||||
onClearManga = onClearManga,
|
||||
onOpenMangaFolder = onOpenMangaFolder,
|
||||
onClickRelatedMangas = onRelatedMangasScreenClick.takeIf {
|
||||
!expandRelatedMangas &&
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import androidx.compose.material3.TextButton
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -19,10 +20,14 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.i18n.kmk.KMR
|
||||
import tachiyomi.presentation.core.components.LabeledCheckbox
|
||||
import tachiyomi.presentation.core.components.WheelTextPicker
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||
|
|
@ -62,6 +67,65 @@ fun DeleteChaptersDialog(
|
|||
)
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
@Composable
|
||||
fun ClearMangaDialog(
|
||||
isMergedSource: Boolean,
|
||||
onDismissRequest: () -> Unit,
|
||||
onConfirm: (Boolean, Boolean) -> Unit,
|
||||
) {
|
||||
var list by remember {
|
||||
mutableStateOf(
|
||||
buildList<CheckboxState.State<StringResource>> {
|
||||
add(CheckboxState.State.None(KMR.strings.downloaded_data))
|
||||
add(CheckboxState.State.None(KMR.strings.chapters_from_database))
|
||||
},
|
||||
)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = list.any { it.isChecked },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(
|
||||
list[0].isChecked,
|
||||
list[1].isChecked,
|
||||
)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_remove))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
list.forEachIndexed { index, state ->
|
||||
LabeledCheckbox(
|
||||
label = stringResource(state.value),
|
||||
checked = state.isChecked,
|
||||
enabled = index != 0 || !isMergedSource,
|
||||
onCheckedChange = {
|
||||
val mutableList = list.toMutableList()
|
||||
mutableList[index] = state.next() as CheckboxState.State<StringResource>
|
||||
list = mutableList.toList()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
@Composable
|
||||
fun SetIntervalDialog(
|
||||
interval: Int,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ fun MangaToolbar(
|
|||
// KMK -->
|
||||
onClickRelatedMangas: (() -> Unit)?,
|
||||
onClickSourceSettings: (() -> Unit)?,
|
||||
onClearManga: () -> Unit,
|
||||
onOpenMangaFolder: (() -> Unit)?,
|
||||
// KMK <--
|
||||
onClickRecommend: (() -> Unit)?,
|
||||
|
|
@ -243,6 +244,12 @@ fun MangaToolbar(
|
|||
),
|
||||
)
|
||||
}
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(KMR.strings.action_clear_manga),
|
||||
onClick = onClearManga,
|
||||
),
|
||||
)
|
||||
if (onClickSourceSettings != null) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import tachiyomi.domain.download.service.DownloadPreferences
|
|||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
|
|
@ -277,6 +278,10 @@ class DownloadManager(
|
|||
provider.findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source)?.delete()
|
||||
cache.removeManga(manga)
|
||||
|
||||
// KMK -->
|
||||
if (source.isLocal()) return@launchIO
|
||||
// KMK <--
|
||||
|
||||
// Delete source directory if empty
|
||||
val sourceDir = provider.findSourceDir(source)
|
||||
if (sourceDir?.listFiles()?.isEmpty() == true) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import tachiyomi.domain.chapter.model.Chapter
|
|||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.storage.service.StorageManager
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
|
|
@ -74,7 +75,13 @@ class DownloadProvider(
|
|||
* @param source the source to query.
|
||||
*/
|
||||
fun findSourceDir(source: Source): UniFile? {
|
||||
return downloadsDir?.findFile(getSourceDirName(source))
|
||||
// KMK -->
|
||||
return if (source.isLocal()) {
|
||||
storageManager.getLocalSourceDirectory()
|
||||
} else {
|
||||
// KMK <--
|
||||
downloadsDir?.findFile(getSourceDirName(source))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -113,9 +120,22 @@ class DownloadProvider(
|
|||
fun findChapterDirs(chapters: List<Chapter>, manga: Manga, source: Source): Pair<UniFile?, List<UniFile>> {
|
||||
val mangaDir = findMangaDir(/* SY --> */ manga.ogTitle /* SY <-- */, source) ?: return null to emptyList()
|
||||
return mangaDir to chapters.mapNotNull { chapter ->
|
||||
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
// KMK -->
|
||||
if (source.isLocal()) {
|
||||
val splitUrl = chapter.url.split('/', limit = 2)
|
||||
if (splitUrl.size < 2) {
|
||||
null
|
||||
} else {
|
||||
val (mangaDirName, chapterDirName) = splitUrl
|
||||
mangaDir.findFile(chapterDirName)
|
||||
?: storageManager.getLocalSourceDirectory()?.findFile(mangaDirName)?.findFile(chapterDirName)
|
||||
}
|
||||
} else {
|
||||
// KMK <--
|
||||
getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence()
|
||||
.mapNotNull { mangaDir.findFile(it) }
|
||||
.firstOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import eu.kanade.presentation.manga.ChapterSettingsDialog
|
|||
import eu.kanade.presentation.manga.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.manga.EditCoverAction
|
||||
import eu.kanade.presentation.manga.MangaScreen
|
||||
import eu.kanade.presentation.manga.components.ClearMangaDialog
|
||||
import eu.kanade.presentation.manga.components.DeleteChaptersDialog
|
||||
import eu.kanade.presentation.manga.components.MangaCoverDialog
|
||||
import eu.kanade.presentation.manga.components.ScanlatorFilterDialog
|
||||
|
|
@ -65,6 +66,7 @@ import eu.kanade.tachiyomi.source.ConfigurableSource
|
|||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.isLocalOrStub
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.ExtensionsScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
||||
|
|
@ -418,8 +420,9 @@ class MangaScreen(
|
|||
else -> {}
|
||||
}
|
||||
}.takeIf { isConfigurableSource },
|
||||
onClearManga = { screenModel.showClearMangaDialog(successState.source is MergedSource) },
|
||||
onOpenMangaFolder = { screenModel.openMangaFolder() }
|
||||
.takeIf { successState.source !is StubSource },
|
||||
.takeIf { successState.source !is StubSource && successState.source !is MergedSource },
|
||||
onRelatedMangasScreenClick = {
|
||||
if (successState.isRelatedMangasFetched == null) {
|
||||
scope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) }
|
||||
|
|
@ -634,6 +637,15 @@ class MangaScreen(
|
|||
)
|
||||
}
|
||||
// SY <--
|
||||
// KMK -->
|
||||
is MangaScreenModel.Dialog.ClearManga -> {
|
||||
ClearMangaDialog(
|
||||
isMergedSource = dialog.isMergedSource,
|
||||
onDismissRequest = onDismissRequest,
|
||||
onConfirm = screenModel::clearManga,
|
||||
)
|
||||
}
|
||||
// KMK <--
|
||||
}
|
||||
|
||||
if (showScanlatorsDialog) {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ import tachiyomi.core.common.util.system.logcat
|
|||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.chapter.interactor.DeleteChapters
|
||||
import tachiyomi.domain.chapter.interactor.GetMergedChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.chapter.interactor.UpdateChapter
|
||||
|
|
@ -231,6 +232,7 @@ class MangaScreenModel(
|
|||
private val deleteLibraryUpdateErrors: DeleteLibraryUpdateErrors = Injekt.get(),
|
||||
private val insertLibraryUpdateErrors: InsertLibraryUpdateErrors = Injekt.get(),
|
||||
private val insertLibraryUpdateErrorMessages: InsertLibraryUpdateErrorMessages = Injekt.get(),
|
||||
private val deleteChaptersFromDb: DeleteChapters = Injekt.get(),
|
||||
// KMK <--
|
||||
) : StateScreenModel<MangaScreenModel.State>(State.Loading) {
|
||||
|
||||
|
|
@ -1524,6 +1526,12 @@ class MangaScreenModel(
|
|||
ignoreCategoryExclusion = true,
|
||||
// KMK <--
|
||||
)
|
||||
// KMK -->
|
||||
if (source?.isLocal() == true) {
|
||||
// Refresh chapters state for Local source
|
||||
fetchChaptersFromSource()
|
||||
}
|
||||
// KMK <--
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
|
|
@ -1531,6 +1539,51 @@ class MangaScreenModel(
|
|||
}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
fun clearManga(
|
||||
deleteDownload: Boolean,
|
||||
removeChapters: Boolean,
|
||||
) {
|
||||
if (deleteDownload) {
|
||||
deleteDownloadedData()
|
||||
}
|
||||
if (removeChapters) {
|
||||
removeChaptersDatabase()
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteDownloadedData() {
|
||||
screenModelScope.launchNonCancellable {
|
||||
try {
|
||||
successState?.let { state ->
|
||||
downloadManager.deleteManga(
|
||||
manga = state.manga,
|
||||
source = state.source,
|
||||
removeQueued = true,
|
||||
)
|
||||
if (source?.isLocal() == true) {
|
||||
// Refresh chapters state for Local source
|
||||
fetchChaptersFromSource()
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeChaptersDatabase() {
|
||||
screenModelScope.launchNonCancellable {
|
||||
try {
|
||||
successState?.chapters?.map { it.id }
|
||||
?.let { deleteChaptersFromDb.await(it) }
|
||||
} catch (e: Throwable) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
private fun downloadNewChapters(chapters: List<Chapter>) {
|
||||
screenModelScope.launchNonCancellable {
|
||||
val manga = successState?.manga ?: return@launchNonCancellable
|
||||
|
|
@ -1822,6 +1875,10 @@ class MangaScreenModel(
|
|||
data class EditMergedSettings(val mergedData: MergedMangaData) : Dialog
|
||||
// SY <--
|
||||
|
||||
// KMK -->
|
||||
data class ClearManga(val isMergedSource: Boolean) : Dialog
|
||||
// KMK <--
|
||||
|
||||
data object SettingsSheet : Dialog
|
||||
data object TrackSheet : Dialog
|
||||
data object FullCover : Dialog
|
||||
|
|
@ -1883,6 +1940,12 @@ class MangaScreenModel(
|
|||
}
|
||||
// SY <--
|
||||
|
||||
// KMK -->
|
||||
fun showClearMangaDialog(isMergedSource: Boolean) {
|
||||
updateSuccessState { it.copy(dialog = Dialog.ClearManga(isMergedSource)) }
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
sealed interface State {
|
||||
@Immutable
|
||||
data object Loading : State
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
<string name="action_show_hidden_categories">Show hidden categories</string>
|
||||
<string name="action_panorama_cover">Panorama cover</string>
|
||||
<string name="action_open_folder">Open folder</string>
|
||||
<string name="action_clear_manga">Clear manga</string>
|
||||
<string name="action_update">Update</string>
|
||||
|
||||
<!-- Reader -->
|
||||
<!-- Reader Actions -->
|
||||
<string name="action_copy_to_clipboard_first_page">Copy first page to clipboard</string>
|
||||
|
|
@ -121,6 +123,9 @@
|
|||
<string name="action_skip_duplicate_manga">Skip it</string>
|
||||
<string name="action_allow_all_duplicate_manga">Allow all</string>
|
||||
<string name="action_skip_all_duplicate_manga">Skip all</string>
|
||||
<string name="downloaded_data">Downloaded data</string>
|
||||
<string name="chapters_from_database">Chapters & History from Database</string>
|
||||
|
||||
<!--UpdateCheck-->
|
||||
<string name="update_check_try_now">Try now</string>
|
||||
<string name="update_completed">Update completed</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue