diff --git a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt index 300794b4c1..9e2ea85718 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt +++ b/app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithSource.kt @@ -63,7 +63,7 @@ class SyncChaptersWithSource( .mapIndexed { i, sChapter -> Chapter.create() .copyFromSChapter(sChapter) - .copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.title) }) + .copy(name = with(ChapterSanitizer) { sChapter.name.sanitize(manga.ogTitle) }) .copy(mangaId = manga.id, sourceOrder = i.toLong()) } @@ -92,7 +92,12 @@ class SyncChaptersWithSource( } // Recognize chapter number for the chapter. - val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber) + val chapterNumber = ChapterRecognition.parseChapterNumber( + manga.ogTitle, + chapter.name, + chapter.chapterNumber + ) + chapter = chapter.copy(chapterNumber = chapterNumber) val dbChapter = dbChapters.find { it.url == chapter.url } @@ -110,7 +115,7 @@ class SyncChaptersWithSource( if (shouldUpdateDbChapter.await(dbChapter, chapter)) { val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) && downloadManager.isChapterDownloaded( - dbChapter.name, dbChapter.scanlator, manga.title, manga.source, + dbChapter.name, dbChapter.scanlator, manga.ogTitle, manga.source, ) if (shouldRenameChapter) { diff --git a/app/src/main/java/eu/kanade/domain/chapter/model/ChapterFilter.kt b/app/src/main/java/eu/kanade/domain/chapter/model/ChapterFilter.kt index ad476418f0..d358d64015 100644 --- a/app/src/main/java/eu/kanade/domain/chapter/model/ChapterFilter.kt +++ b/app/src/main/java/eu/kanade/domain/chapter/model/ChapterFilter.kt @@ -26,7 +26,7 @@ fun List.applyFilters(manga: Manga, downloadManager: DownloadManager): val downloaded = downloadManager.isChapterDownloaded( chapter.name, chapter.scanlator, - manga.title, + manga.ogTitle, manga.source, ) downloaded || isLocalManga diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt index d5cfc248dd..368a5d2ac1 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt @@ -103,4 +103,8 @@ class UpdateManga( MangaUpdate(id = mangaId, favorite = favorite, dateAdded = dateAdded), ) } + + suspend fun awaitUpdateEditInfo(mangaUpdate: MangaUpdate): Boolean { + return mangaRepository.updateEditedInfo(mangaUpdate) + } } diff --git a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt index 1b9dd62c8f..cbcc187732 100644 --- a/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/model/Manga.kt @@ -51,9 +51,9 @@ fun Manga.toSManga(): SManga = SManga.create().also { } fun Manga.copyFrom(other: SManga): Manga { - val author = other.author ?: author - val artist = other.artist ?: artist - val description = other.description ?: description + val author = other.author ?: ogAuthor + val artist = other.artist ?: ogArtist + val description = other.description ?: ogDescription val genres = if (other.genre != null) { other.getGenres() } else { @@ -61,9 +61,9 @@ fun Manga.copyFrom(other: SManga): Manga { } val thumbnailUrl = other.thumbnail_url ?: thumbnailUrl return this.copy( - author = author, - artist = artist, - description = description, + ogAuthor = author, + ogArtist = artist, + ogDescription = description, genre = genres, thumbnailUrl = thumbnailUrl, status = other.status.toLong(), @@ -75,10 +75,10 @@ fun Manga.copyFrom(other: SManga): Manga { fun SManga.toDomainManga(sourceId: Long): Manga { return Manga.create().copy( url = url, - title = title, - artist = artist, - author = author, - description = description, + ogTitle = title, + ogArtist = artist, + ogAuthor = author, + ogDescription = description, genre = getGenres(), status = status.toLong(), thumbnailUrl = thumbnail_url, diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index 9a6e3f6fda..d97b0fe0a5 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -113,6 +113,7 @@ fun MangaScreen( onEditCategoryClicked: (() -> Unit)?, onEditFetchIntervalClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?, + onEditInfoClicked: (() -> Unit)?, // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, @@ -160,6 +161,7 @@ fun MangaScreen( onDownloadActionClicked = onDownloadActionClicked, onEditCategoryClicked = onEditCategoryClicked, onEditIntervalClicked = onEditFetchIntervalClicked, + onEditInfoClicked = onEditInfoClicked, onMigrateClicked = onMigrateClicked, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, @@ -195,6 +197,7 @@ fun MangaScreen( onDownloadActionClicked = onDownloadActionClicked, onEditCategoryClicked = onEditCategoryClicked, onEditIntervalClicked = onEditFetchIntervalClicked, + onEditInfoClicked = onEditInfoClicked, onMigrateClicked = onMigrateClicked, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, @@ -241,6 +244,7 @@ private fun MangaScreenSmallImpl( onEditCategoryClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?, + onEditInfoClicked: (() -> Unit)?, // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, @@ -309,6 +313,7 @@ private fun MangaScreenSmallImpl( actionModeCounter = selectedChapterCount, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, + onClickEditInfo = onEditInfoClicked, ) }, bottomBar = { @@ -489,6 +494,7 @@ fun MangaScreenLargeImpl( onEditCategoryClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?, onMigrateClicked: (() -> Unit)?, + onEditInfoClicked: (() -> Unit)?, // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, @@ -550,6 +556,7 @@ fun MangaScreenLargeImpl( actionModeCounter = selectedChapterCount, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, + onClickEditInfo = onEditInfoClicked, ) }, bottomBar = { diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt index a6ef3f9a56..f2258ee970 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt @@ -5,13 +5,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text 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 @@ -23,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.isDevFlavor import eu.kanade.tachiyomi.util.system.isPreviewBuildType import kotlinx.collections.immutable.toImmutableList import tachiyomi.domain.manga.interactor.FetchInterval +import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.material.padding @@ -148,3 +153,80 @@ fun SetIntervalDialog( }, ) } + +@Composable +fun EditInfoDialog( + manga: Manga, + onDismissRequest: () -> Unit, + onConfirm: (manga: Manga) -> Unit, +) { + var editedManga by remember { + mutableStateOf( + manga.copy( + customTitle = manga.customTitle ?: manga.title, + customAuthor = manga.customAuthor ?: manga.author, + customArtist = manga.customArtist ?: manga.artist, + customDescription = manga.customDescription ?: manga.description, + ) + ) + } + + AlertDialog( + onDismissRequest = onDismissRequest, + title = { + Text(text = stringResource(MR.strings.action_edit_info)) + }, + text = { + Column( + Modifier + .fillMaxWidth() + .verticalScroll(rememberScrollState()), + ) { + OutlinedTextField( + value = editedManga.customTitle.orEmpty(), + onValueChange = { + editedManga = editedManga.copy(customTitle = it) + }, + label = { Text(text = stringResource(MR.strings.title)) }, + ) + + OutlinedTextField( + value = editedManga.customAuthor.orEmpty(), + onValueChange = { + editedManga = editedManga.copy(customAuthor = it) + }, + label = { Text(text = stringResource(MR.strings.author)) }, + ) + + OutlinedTextField( + value = editedManga.customArtist.orEmpty(), + onValueChange = { + editedManga = editedManga.copy(customArtist = it) + }, + label = { Text(text = stringResource(MR.strings.artist)) }, + ) + + OutlinedTextField( + value = editedManga.customDescription.orEmpty(), + onValueChange = { + editedManga = editedManga.copy(customDescription = it) + }, + label = { Text(text = stringResource(MR.strings.description)) }, + ) + } + }, + dismissButton = { + TextButton(onClick = onDismissRequest) { + Text(text = stringResource(MR.strings.action_cancel)) + } + }, + confirmButton = { + TextButton(onClick = { + onConfirm(editedManga) + onDismissRequest() + }) { + Text(text = stringResource(MR.strings.action_ok)) + } + }, + ) +} diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt index 4415bbf278..7c468701fa 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaToolbar.kt @@ -44,6 +44,7 @@ fun MangaToolbar( onClickEditCategory: (() -> Unit)?, onClickRefresh: () -> Unit, onClickMigrate: (() -> Unit)?, + onClickEditInfo: (() -> Unit)?, // For action mode actionModeCounter: Int, @@ -149,6 +150,14 @@ fun MangaToolbar( ), ) } + if (onClickEditInfo != null) { + add( + AppBar.OverflowAction( + title = stringResource(MR.strings.action_edit_info), + onClick = onClickEditInfo, + ) + ) + } } .build(), ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 9f51a0d557..663e77c711 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -82,10 +82,10 @@ class MangaBackupCreator( private fun Manga.toBackupManga() = BackupManga( url = this.url, - title = this.title, - artist = this.artist, - author = this.author, - description = this.description, + title = this.ogTitle, + artist = this.ogArtist, + author = this.ogAuthor, + description = this.ogDescription, genre = this.genre.orEmpty(), status = this.status.toInt(), thumbnailUrl = this.thumbnailUrl, @@ -98,4 +98,8 @@ private fun Manga.toBackupManga() = updateStrategy = this.updateStrategy, lastModifiedAt = this.lastModifiedAt, favoriteModifiedAt = this.favoriteModifiedAt, + customArtist = this.customArtist, + customAuthor = this.customAuthor, + customTitle = this.customTitle, + customDescription = this.customDescription, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index da202ae557..c953c76ec5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -39,14 +39,20 @@ data class BackupManga( @ProtoNumber(106) var lastModifiedAt: Long = 0, @ProtoNumber(107) var favoriteModifiedAt: Long? = null, @ProtoNumber(108) var excludedScanlators: List = emptyList(), + // Numbers to keep compatibility with fork edited manga fields + // https://github.com/jobobby04/TachiyomiSY/blob/7e151ddb83d5d7e0ea553eca686a8c4aa3a1fa8c/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt#L49 + @ProtoNumber(800) var customTitle: String? = null, + @ProtoNumber(801) var customArtist: String? = null, + @ProtoNumber(802) var customAuthor: String? = null, + @ProtoNumber(804) var customDescription: String? = null, ) { fun getMangaImpl(): Manga { return Manga.create().copy( url = this@BackupManga.url, - title = this@BackupManga.title, - artist = this@BackupManga.artist, - author = this@BackupManga.author, - description = this@BackupManga.description, + ogTitle = this@BackupManga.title, + ogArtist = this@BackupManga.artist, + ogAuthor = this@BackupManga.author, + ogDescription = this@BackupManga.description, genre = this@BackupManga.genre, status = this@BackupManga.status.toLong(), thumbnailUrl = this@BackupManga.thumbnailUrl, @@ -58,6 +64,10 @@ data class BackupManga( updateStrategy = this@BackupManga.updateStrategy, lastModifiedAt = this@BackupManga.lastModifiedAt, favoriteModifiedAt = this@BackupManga.favoriteModifiedAt, + customTitle = this@BackupManga.customTitle, + customArtist = this@BackupManga.customArtist, + customAuthor = this@BackupManga.customAuthor, + customDescription = this@BackupManga.customDescription, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index 30dc443685..e6e8cff514 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -93,9 +93,9 @@ class MangaRestorer( private fun Manga.copyFrom(newer: Manga): Manga { return this.copy( favorite = this.favorite || newer.favorite, - author = newer.author, - artist = newer.artist, - description = newer.description, + ogAuthor = newer.ogAuthor, + ogArtist = newer.ogArtist, + ogDescription = newer.ogDescription, genre = newer.genre, thumbnailUrl = newer.thumbnailUrl, status = newer.status, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index fb1c8e1767..baeefc89ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -181,7 +181,7 @@ class DownloadCache( val sourceDir = rootDownloadsDir.sourceDirs[manga.source] if (sourceDir != null) { - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.ogTitle)] if (mangaDir != null) { return mangaDir.chapterDirs.size } @@ -208,7 +208,7 @@ class DownloadCache( } // Retrieve the cached manga directory or cache a new one - val mangaDirName = provider.getMangaDirName(manga.title) + val mangaDirName = provider.getMangaDirName(manga.ogTitle) var mangaDir = sourceDir.mangaDirs[mangaDirName] if (mangaDir == null) { mangaDir = MangaDirectory(mangaUniFile) @@ -231,7 +231,7 @@ class DownloadCache( suspend fun removeChapter(chapter: Chapter, manga: Manga) { rootDownloadsDirLock.withLock { val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.ogTitle)] ?: return provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { if (it in mangaDir.chapterDirs) { mangaDir.chapterDirs -= it @@ -251,7 +251,7 @@ class DownloadCache( suspend fun removeChapters(chapters: List, manga: Manga) { rootDownloadsDirLock.withLock { val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.title)] ?: return + val mangaDir = sourceDir.mangaDirs[provider.getMangaDirName(manga.ogTitle)] ?: return chapters.forEach { chapter -> provider.getValidChapterDirNames(chapter.name, chapter.scanlator).forEach { if (it in mangaDir.chapterDirs) { @@ -272,7 +272,7 @@ class DownloadCache( suspend fun removeManga(manga: Manga) { rootDownloadsDirLock.withLock { val sourceDir = rootDownloadsDir.sourceDirs[manga.source] ?: return - val mangaDirName = provider.getMangaDirName(manga.title) + val mangaDirName = provider.getMangaDirName(manga.ogTitle) if (sourceDir.mangaDirs.containsKey(mangaDirName)) { sourceDir.mangaDirs -= mangaDirName } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 01a342859d..648af1b89e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -158,7 +158,7 @@ class DownloadManager( * @return the list of pages from the chapter. */ fun buildPageList(source: Source, manga: Manga, chapter: Chapter): List { - val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.title, source) + val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.ogTitle, source) val files = chapterDir?.listFiles().orEmpty() .filter { "image" in it.type.orEmpty() } @@ -250,7 +250,7 @@ class DownloadManager( if (removeQueued) { downloader.removeFromQueue(manga) } - provider.findMangaDir(manga.title, source)?.delete() + provider.findMangaDir(manga.ogTitle, source)?.delete() cache.removeManga(manga) // Delete source directory if empty @@ -336,7 +336,7 @@ class DownloadManager( */ suspend fun renameChapter(source: Source, manga: Manga, oldChapter: Chapter, newChapter: Chapter) { val oldNames = provider.getValidChapterDirNames(oldChapter.name, oldChapter.scanlator) - val mangaDir = provider.getMangaDir(manga.title, source) + val mangaDir = provider.getMangaDir(manga.ogTitle, source) // Assume there's only 1 version of the chapter name formats present val oldDownload = oldNames.asSequence() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt index 4acd8322e9..ffc28daded 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadNotifier.kt @@ -95,7 +95,7 @@ internal class DownloadNotifier(private val context: Context) { setContentTitle(downloadingProgressText) setContentText(null) } else { - val title = download.manga.title.chop(15) + val title = download.manga.ogTitle.chop(15) val quotedTitle = Pattern.quote(title) val chapter = download.chapter.name.replaceFirst( "$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt index 1bee154b9c..83d088e8d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadPendingDeleter.kt @@ -124,7 +124,7 @@ class DownloadPendingDeleter( /** * Returns a manga entry from a manga model. */ - private fun Manga.toEntry() = MangaEntry(id, url, title, source) + private fun Manga.toEntry() = MangaEntry(id, url, ogTitle, source) /** * Returns a chapter entry from a chapter model. @@ -136,7 +136,7 @@ class DownloadPendingDeleter( */ private fun MangaEntry.toModel() = Manga.create().copy( url = url, - title = title, + ogTitle = title, source = source, id = id, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt index 001395af10..f054d03f53 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt @@ -94,7 +94,7 @@ class DownloadProvider( * @param source the source of the chapter. */ fun findChapterDirs(chapters: List, manga: Manga, source: Source): Pair> { - val mangaDir = findMangaDir(manga.title, source) ?: return null to emptyList() + val mangaDir = findMangaDir(manga.ogTitle, source) ?: return null to emptyList() return mangaDir to chapters.mapNotNull { chapter -> getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence() .mapNotNull { mangaDir.findFile(it, true) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index facbe605a9..3759b0c86b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -272,7 +272,7 @@ class Downloader( val wasEmpty = queueState.value.isEmpty() val chaptersToQueue = chapters.asSequence() // Filter out those already downloaded. - .filter { provider.findChapterDir(it.name, it.scanlator, manga.title, source) == null } + .filter { provider.findChapterDir(it.name, it.scanlator, manga.ogTitle, source) == null } // Add chapters to queue from the start. .sortedByDescending { it.sourceOrder } // Filter out those already enqueued. @@ -313,7 +313,7 @@ class Downloader( * @param download the chapter to be downloaded. */ private suspend fun downloadChapter(download: Download) { - val mangaDir = provider.getMangaDir(download.manga.title, download.source) + val mangaDir = provider.getMangaDir(download.manga.ogTitle, download.source) val availSpace = DiskUtil.getAvailableStorageSpace(mangaDir) if (availSpace != -1L && availSpace < MIN_DISK_SPACE) { @@ -321,7 +321,7 @@ class Downloader( notifier.onError( context.stringResource(MR.strings.download_insufficient_space), download.chapter.name, - download.manga.title, + download.manga.ogTitle, ) return } @@ -405,7 +405,7 @@ class Downloader( // If the page list threw, it will resume here logcat(LogPriority.ERROR, error) download.status = Download.State.ERROR - notifier.onError(error.message, download.chapter.name, download.manga.title) + notifier.onError(error.message, download.chapter.name, download.manga.ogTitle) } } @@ -455,7 +455,7 @@ class Downloader( // Mark this page as error and allow to download the remaining page.progress = 0 page.status = Page.State.ERROR - notifier.onError(e.message, download.chapter.name, download.manga.title) + notifier.onError(e.message, download.chapter.name, download.manga.ogTitle) } } 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 f7dad1d8ce..969920c6da 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 @@ -31,6 +31,7 @@ import eu.kanade.presentation.manga.DuplicateMangaDialog import eu.kanade.presentation.manga.EditCoverAction import eu.kanade.presentation.manga.MangaScreen import eu.kanade.presentation.manga.components.DeleteChaptersDialog +import eu.kanade.presentation.manga.components.EditInfoDialog import eu.kanade.presentation.manga.components.MangaCoverDialog import eu.kanade.presentation.manga.components.ScanlatorFilterDialog import eu.kanade.presentation.manga.components.SetIntervalDialog @@ -61,6 +62,7 @@ import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.MR import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.source.local.LocalSource class MangaScreen( private val mangaId: Long, @@ -148,6 +150,9 @@ class MangaScreen( onEditFetchIntervalClicked = screenModel::showSetFetchIntervalDialog.takeIf { successState.manga.favorite }, + onEditInfoClicked = screenModel::showEditInfoDialog.takeIf { + successState.manga.favorite && successState.manga.source != LocalSource.ID + }, onMigrateClicked = { navigator.push(MigrateSearchScreen(successState.manga.id)) }.takeIf { successState.manga.favorite }, @@ -249,6 +254,13 @@ class MangaScreen( .takeIf { screenModel.isUpdateIntervalEnabled }, ) } + is MangaScreenModel.Dialog.EditInfo -> { + EditInfoDialog( + manga = dialog.manga, + onDismissRequest = onDismissRequest, + onConfirm = screenModel::updateMangaInfo, + ) + } } if (showScanlatorsDialog) { 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 26ad3a7fc2..1d74fee908 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 @@ -75,6 +75,7 @@ import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetMangaWithChapters import tachiyomi.domain.manga.interactor.SetMangaChapterFlags import tachiyomi.domain.manga.model.Manga +import tachiyomi.domain.manga.model.MangaUpdate import tachiyomi.domain.manga.model.applyFilter import tachiyomi.domain.manga.repository.MangaRepository import tachiyomi.domain.source.service.SourceManager @@ -369,6 +370,51 @@ class MangaScreenModel( } } + fun showEditInfoDialog() { + val manga = successState?.manga ?: return + screenModelScope.launch { + updateSuccessState { successState -> + successState.copy( + dialog = Dialog.EditInfo( + manga = manga, + ), + ) + } + } + } + + fun updateMangaInfo( + manga: Manga, + ) { + val newTitle = if (manga.customTitle.isNullOrBlank()) null else manga.customTitle + val newAuthor = if (manga.customAuthor.isNullOrBlank()) null else manga.customAuthor + val newArtist = if (manga.customArtist.isNullOrBlank()) null else manga.customArtist + val newDescription = if (manga.customDescription.isNullOrBlank()) null else manga.customDescription + + screenModelScope.launchNonCancellable { + updateManga.awaitUpdateEditInfo( + MangaUpdate( + id = manga.id, + title = newTitle, + artist = newArtist, + author = newAuthor, + description = newDescription, + ) + ) + } + + updateSuccessState { successState -> + successState.copy( + manga = manga.copy( + customTitle = newTitle, + customAuthor = newAuthor, + customArtist = newArtist, + customDescription = newDescription, + ) + ) + } + } + fun showSetFetchIntervalDialog() { val manga = successState?.manga ?: return updateSuccessState { @@ -513,7 +559,7 @@ class MangaScreenModel( val downloaded = if (isLocal) { true } else { - downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source) + downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.ogTitle, manga.source) } val downloadState = when { activeDownload != null -> activeDownload.status @@ -1004,6 +1050,7 @@ class MangaScreenModel( data class DeleteChapters(val chapters: List) : Dialog data class DuplicateManga(val manga: Manga, val duplicate: Manga) : Dialog data class SetFetchInterval(val manga: Manga) : Dialog + data class EditInfo(val manga: Manga) : Dialog data object SettingsSheet : Dialog data object TrackSheet : Dialog data object FullCover : Dialog diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index a59f0500b0..a3d1554800 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -164,13 +164,13 @@ class ReaderViewModel @JvmOverloads constructor( ( manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_DOWNLOADED && !downloadManager.isChapterDownloaded( - it.name, it.scanlator, manga.title, manga.source, + it.name, it.scanlator, manga.ogTitle, manga.source, ) ) || ( manga.downloadedFilterRaw == Manga.CHAPTER_SHOW_NOT_DOWNLOADED && downloadManager.isChapterDownloaded( - it.name, it.scanlator, manga.title, manga.source, + it.name, it.scanlator, manga.ogTitle, manga.source, ) ) || (manga.bookmarkedFilterRaw == Manga.CHAPTER_SHOW_BOOKMARKED && !it.bookmark) || @@ -381,7 +381,7 @@ class ReaderViewModel @JvmOverloads constructor( val isDownloaded = downloadManager.isChapterDownloaded( dbChapter.name, dbChapter.scanlator, - manga.title, + manga.ogTitle, manga.source, skipCache = true, ) @@ -457,7 +457,7 @@ class ReaderViewModel @JvmOverloads constructor( val isNextChapterDownloaded = downloadManager.isChapterDownloaded( nextChapter.name, nextChapter.scanlator, - manga.title, + manga.ogTitle, manga.source, ) if (!isNextChapterDownloaded) return@launchIO @@ -719,7 +719,7 @@ class ReaderViewModel @JvmOverloads constructor( val chapter = page.chapter.chapter val filenameSuffix = " - ${page.number}" return DiskUtil.buildValidFilename( - "${manga.title} - ${chapter.name}".takeBytes(DiskUtil.MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()), + "${manga.ogTitle} - ${chapter.name}".takeBytes(DiskUtil.MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()), ) + filenameSuffix } @@ -773,7 +773,7 @@ class ReaderViewModel @JvmOverloads constructor( // Pictures directory. val relativePath = if (readerPreferences.folderPerManga().get()) { DiskUtil.buildValidFilename( - manga.title, + manga.ogTitle, ) } else { "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 1cd18bcebe..7ec6a13be4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -80,7 +80,7 @@ class ChapterLoader( val isDownloaded = downloadManager.isChapterDownloaded( dbChapter.name, dbChapter.scanlator, - manga.title, + manga.ogTitle, manga.source, skipCache = true, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index abef28540c..4437c5e1bb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -33,7 +33,7 @@ internal class DownloadPageLoader( override suspend fun getPages(): List { val dbChapter = chapter.chapter - val chapterPath = downloadProvider.findChapterDir(dbChapter.name, dbChapter.scanlator, manga.title, source) + val chapterPath = downloadProvider.findChapterDir(dbChapter.name, dbChapter.scanlator, manga.ogTitle, source) return if (chapterPath?.isFile == true) { getPagesFromArchive(chapterPath) } else { diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilterDownloaded.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilterDownloaded.kt index 0f489f2f5a..f5debc01cb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilterDownloaded.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilterDownloaded.kt @@ -15,5 +15,5 @@ fun List.filterDownloaded(manga: Manga): List { val downloadCache: DownloadCache = Injekt.get() - return filter { downloadCache.isChapterDownloaded(it.name, it.scanlator, manga.title, manga.source, false) } + return filter { downloadCache.isChapterDownloaded(it.name, it.scanlator, manga.ogTitle, manga.source, false) } } diff --git a/config/detekt/baseline.xml b/config/detekt/baseline.xml index 1845cae53c..69a0debcbb 100644 --- a/config/detekt/baseline.xml +++ b/config/detekt/baseline.xml @@ -410,6 +410,10 @@ MagicNumber:BackupManga.kt$BackupManga$7 MagicNumber:BackupManga.kt$BackupManga$8 MagicNumber:BackupManga.kt$BackupManga$9 + MagicNumber:BackupManga.kt$BackupManga$800 + MagicNumber:BackupManga.kt$BackupManga$801 + MagicNumber:BackupManga.kt$BackupManga$802 + MagicNumber:BackupManga.kt$BackupManga$804 MagicNumber:BackupPreferences.kt$BackupPreferences$12 MagicNumber:BackupTracking.kt$BackupTracking$10 MagicNumber:BackupTracking.kt$BackupTracking$100 diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index d9937b2858..b2ffb1b5b5 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -5,6 +5,7 @@ import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga object MangaMapper { + @Suppress("LongParameterList") fun mapManga( id: Long, source: Long, @@ -28,6 +29,10 @@ object MangaMapper { calculateInterval: Long, lastModifiedAt: Long, favoriteModifiedAt: Long?, + customArtist: String?, + customAuthor: String?, + customDescription: String?, + customTitle: String?, ): Manga = Manga( id = id, source = source, @@ -40,10 +45,10 @@ object MangaMapper { chapterFlags = chapterFlags, coverLastModified = coverLastModified, url = url, - title = title, - artist = artist, - author = author, - description = description, + ogTitle = title, + ogArtist = artist, + ogAuthor = author, + ogDescription = description, genre = genre, status = status, thumbnailUrl = thumbnailUrl, @@ -51,8 +56,13 @@ object MangaMapper { initialized = initialized, lastModifiedAt = lastModifiedAt, favoriteModifiedAt = favoriteModifiedAt, + customArtist = customArtist, + customAuthor = customAuthor, + customDescription = customDescription, + customTitle = customTitle, ) + @Suppress("LongParameterList") fun mapLibraryManga( id: Long, source: Long, @@ -76,6 +86,10 @@ object MangaMapper { calculateInterval: Long, lastModifiedAt: Long, favoriteModifiedAt: Long?, + customArtist: String?, + customAuthor: String?, + customDescription: String?, + customTitle: String?, totalCount: Long, readCount: Double, latestUpload: Long, @@ -107,6 +121,10 @@ object MangaMapper { calculateInterval, lastModifiedAt, favoriteModifiedAt, + customArtist, + customAuthor, + customDescription, + customTitle, ), category = category, totalChapters = totalCount, diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index 88ef9b3b71..82ca86be33 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -159,4 +159,17 @@ class MangaRepositoryImpl( } } } + + override suspend fun updateEditedInfo(update: MangaUpdate): Boolean { + handler.await { + mangasQueries.updateMangaEdit( + artist = update.artist, + author = update.author, + description = update.description, + title = update.title, + mangaId = update.id + ) + } + return true + } } diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index 07ef12eba1..7931dcb472 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -25,7 +25,11 @@ CREATE TABLE mangas( update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, calculate_interval INTEGER DEFAULT 0 NOT NULL, last_modified_at INTEGER NOT NULL DEFAULT 0, - favorite_modified_at INTEGER + favorite_modified_at INTEGER, + custom_artist TEXT, + custom_author TEXT, + custom_description TEXT, + custom_title TEXT ); CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; @@ -144,3 +148,11 @@ WHERE _id = :mangaId; selectLastInsertedRowId: SELECT last_insert_rowid(); + +updateMangaEdit: +UPDATE mangas SET + custom_artist = :artist, + custom_author = :author, + custom_description = :description, + custom_title = :title +WHERE _id = :mangaId; diff --git a/data/src/main/sqldelight/tachiyomi/migrations/2.sqm b/data/src/main/sqldelight/tachiyomi/migrations/2.sqm new file mode 100644 index 0000000000..4a9db401f6 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/2.sqm @@ -0,0 +1,6 @@ +-- Add the manga edited info and the new function -- + +ALTER TABLE mangas ADD COLUMN custom_artist TEXT; +ALTER TABLE mangas ADD COLUMN custom_author TEXT; +ALTER TABLE mangas ADD COLUMN custom_description TEXT; +ALTER TABLE mangas ADD COLUMN custom_title TEXT; 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 df5cec44a7..bc11657668 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/GetDuplicateLibraryManga.kt @@ -8,6 +8,6 @@ class GetDuplicateLibraryManga( ) { suspend fun await(manga: Manga): List { - return mangaRepository.getDuplicateLibraryManga(manga.id, manga.title.lowercase()) + return mangaRepository.getDuplicateLibraryManga(manga.id, manga.ogTitle.lowercase()) } } diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt index 5ca3fb647b..7a423a4932 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt @@ -17,7 +17,7 @@ class NetworkToLocalManga( !localManga.favorite -> { // if the manga isn't a favorite, set its display title from source // if it later becomes a favorite, updated title will go to db - localManga.copy(title = manga.title) + localManga.copy(ogTitle = manga.ogTitle) } else -> { localManga diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt index 0ffe9856a2..fa8342a82a 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt @@ -18,10 +18,10 @@ data class Manga( val chapterFlags: Long, val coverLastModified: Long, val url: String, - val title: String, - val artist: String?, - val author: String?, - val description: String?, + val ogTitle: String, + val ogArtist: String?, + val ogAuthor: String?, + val ogDescription: String?, val genre: List?, val status: Long, val thumbnailUrl: String?, @@ -29,7 +29,22 @@ data class Manga( val initialized: Boolean, val lastModifiedAt: Long, val favoriteModifiedAt: Long?, + val customArtist: String?, + val customAuthor: String?, + val customDescription: String?, + val customTitle: String?, ) : Serializable { + val title: String + get() = customTitle ?: ogTitle + + val author: String? + get() = customAuthor ?: ogAuthor + + val artist: String? + get() = customArtist ?: ogArtist + + val description: String? + get() = customDescription ?: ogDescription val expectedNextUpdate: Instant? get() = nextUpdate @@ -102,7 +117,8 @@ data class Manga( fun create() = Manga( id = -1L, url = "", - title = "", + ogTitle = "", + customTitle = null, source = -1L, favorite = false, lastUpdate = 0L, @@ -112,9 +128,12 @@ data class Manga( viewerFlags = 0L, chapterFlags = 0L, coverLastModified = 0L, - artist = null, - author = null, - description = null, + ogArtist = null, + customArtist = null, + ogAuthor = null, + customAuthor = null, + ogDescription = null, + customDescription = null, genre = null, status = 0L, thumbnailUrl = null, diff --git a/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt b/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt index c460038cd8..87843ae5df 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/repository/MangaRepository.kt @@ -34,4 +34,6 @@ interface MangaRepository { suspend fun update(update: MangaUpdate): Boolean suspend fun updateAll(mangaUpdates: List): Boolean + + suspend fun updateEditedInfo(update: MangaUpdate): Boolean } diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 6c6e1681ac..00d55b1f36 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -19,6 +19,10 @@ Delete downloaded History Scanlator + Author + Artist + Description + More @@ -149,6 +153,7 @@ Save Reset Revert to default + Edit info Undo Close