diff --git a/app/build.gradle b/app/build.gradle index 3164356b7..ab2804d35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,7 +72,7 @@ android { } lint { abortOnError true - disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled' + disable 'MissingTranslation', 'PrivateResource', 'SetJavaScriptEnabled', 'SimpleDateFormat' } testOptions { unitTests.includeAndroidResources true diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt index 9fa018811..716e77fdf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.core.backup -import android.annotation.SuppressLint import android.content.Context import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible @@ -34,7 +33,6 @@ class BackupZipOutput(val file: File) : Closeable { companion object { const val DIR_BACKUPS = "backups" - @SuppressLint("SimpleDateFormat") private val dateTimeFormat = SimpleDateFormat("yyyyMMdd-HHmm") fun generateFileName(context: Context) = buildString { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt index 3ffcf5098..059f4a824 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/PageSaveHelper.kt @@ -36,10 +36,11 @@ import org.koitharu.kotatsu.core.util.ext.writeAllCancellable import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaPage +import org.koitharu.kotatsu.parsers.util.toFileNameSafe import org.koitharu.kotatsu.reader.domain.PageLoader import java.io.File -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter +import java.text.SimpleDateFormat +import java.util.Date import javax.inject.Provider import kotlin.coroutines.resume @@ -66,20 +67,20 @@ class PageSaveHelper @AssistedInject constructor( } } - suspend fun save(manga: Manga, chapter: MangaChapter, pageNumber: Int, pages: Set): Uri? = when (pages.size) { + suspend fun save(tasks: Set): Uri? = when (tasks.size) { 0 -> null - 1 -> saveImpl(manga, chapter, pageNumber, pages.first()) + 1 -> saveImpl(tasks.first()) else -> { - saveImpl(manga, chapter, pageNumber, pages) + saveImpl(tasks) null } } - private suspend fun saveImpl(manga: Manga, chapter: MangaChapter, pageNumber: Int, page: MangaPage): Uri { + private suspend fun saveImpl(task: Task): Uri { val pageLoader = pageLoaderProvider.get() - val pageUrl = pageLoader.getPageUrl(page).toUri() - val pageUri = pageLoader.loadPage(page, force = false) - val proposedName = getProposedFileName(manga, chapter, pageNumber, pageUrl, pageUri) + val pageUrl = pageLoader.getPageUrl(task.page).toUri() + val pageUri = pageLoader.loadPage(task.page, force = false) + val proposedName = task.getFileBaseName() + "." + getPageExtension(pageUrl, pageUri) val destination = getDefaultFileUri(proposedName)?.uri ?: run { val defaultUri = settings.getPagesSaveDir(context)?.uri?.buildUpon()?.appendPath(proposedName)?.toString() savePageRequest.launchAndAwait(defaultUri ?: proposedName) @@ -88,49 +89,28 @@ class PageSaveHelper @AssistedInject constructor( return destination } - private suspend fun saveImpl(manga: Manga, chapter: MangaChapter, pageNumber: Int, pages: Collection) { + private suspend fun saveImpl(tasks: Collection) { val pageLoader = pageLoaderProvider.get() val destinationDir = getDefaultFileUri(null) ?: run { val defaultUri = settings.getPagesSaveDir(context)?.uri DocumentFile.fromTreeUri(context, pickDirectoryRequest.launchAndAwait(defaultUri)) } ?: throw IOException("Cannot get destination directory") - var count = 0 - for (page in pages) { - val pageUrl = pageLoader.getPageUrl(page).toUri() - val pageUri = pageLoader.loadPage(page, force = false) - val proposedName = getProposedFileName(manga, chapter, pageNumber.plus(count), pageUrl, pageUri) - val ext = proposedName.substringAfterLast('.', "") + for (task in tasks) { + val pageUrl = pageLoader.getPageUrl(task.page).toUri() + val pageUri = pageLoader.loadPage(task.page, force = false) + val proposedName = task.getFileBaseName() + val ext = getPageExtension(pageUrl, pageUri) val mime = requireNotNull(MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)) { "Unknown type of $proposedName" } val destination = destinationDir.createFile(mime, proposedName.substringBeforeLast('.')) copyImpl(pageUri, destination?.uri ?: throw IOException("Cannot create destination file")) - - count++ } } - private suspend fun getProposedFileName(manga: Manga, chapter: MangaChapter, pageNumber: Number, pageUrl: Uri, pageUri: Uri): String { - var mangaInfos = getNameFromMangaChapterPage(manga, chapter, pageNumber) - var currentTime = getCurrentTime() - var extension = getPageExtension(pageUrl, pageUri) - - return mangaInfos + "_" + currentTime + extension - } - - private fun getNameFromMangaChapterPage(manga: Manga, chapter: MangaChapter, pageNumber: Number): String { - return manga.title + "-" + chapter.number + "-" + pageNumber - } - - private fun getCurrentTime(): String { - val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HHmm") - val current = LocalDateTime.now().format(formatter) - return current - } - private suspend fun getPageExtension(url: Uri, fileUri: Uri): String { - var name = requireNotNull( + val name = requireNotNull( if (url.isZipUri()) { url.fragment?.substringAfterLast(File.separatorChar) } else { @@ -146,7 +126,7 @@ class PageSaveHelper @AssistedInject constructor( EXTENSION_FALLBACK } } - return ".$extension" + return extension } private suspend fun ActivityResultLauncher.launchAndAwait(input: I): Uri { @@ -203,6 +183,24 @@ class PageSaveHelper @AssistedInject constructor( options.outMimeType } + data class Task( + val manga: Manga, + val chapter: MangaChapter, + val pageNumber: Int, + val page: MangaPage, + ) { + + fun getFileBaseName() = buildString { + append(manga.title.toFileNameSafe().take(MAX_BASENAME_LENGTH)) + append('-') + append(chapter.number) + append('-') + append(pageNumber) + append('_') + append(SimpleDateFormat("yyyy-MM-dd_HHmm").format(Date())) + } + } + @AssistedFactory interface Factory { @@ -211,7 +209,7 @@ class PageSaveHelper @AssistedInject constructor( private companion object { - private const val MAX_FILENAME_LENGTH = 16 + private const val MAX_BASENAME_LENGTH = 12 private const val EXTENSION_FALLBACK = "png" } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index a17c89fe3..c1a5c70f3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -57,7 +57,6 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.reader.domain.ChaptersLoader import org.koitharu.kotatsu.reader.domain.DetectReaderModeUseCase @@ -260,25 +259,17 @@ class ReaderViewModel @Inject constructor( prevJob?.cancelAndJoin() val state = checkNotNull(getCurrentState()) val currentManga = manga.requireValue() - val currentChapter = checkNotNull(currentManga.findChapter(state.chapterId)) - val currentPageNumber = state.page - val currentPage = checkNotNull(getCurrentPage()) { "Cannot find current page" } - val dest = pageSaveHelper.save(currentManga, currentChapter, currentPageNumber, setOf(currentPage)) + val task = PageSaveHelper.Task( + manga = currentManga, + chapter = checkNotNull(currentManga.findChapter(state.chapterId)), + pageNumber = state.page, + page = checkNotNull(getCurrentPage()) { "Cannot find current page" }, + ) + val dest = pageSaveHelper.save(setOf(task)) onPageSaved.call(dest) } } - fun getCurrentManga(): Manga? { - return manga.value - } - - fun getCurrentChapter(): MangaChapter? { - val state = readingState.value?: return null - return manga.value?.chapters?.find { - it.id == state.chapterId - } - } - fun getCurrentPage(): MangaPage? { val state = readingState.value ?: return null return content.value.pages.find { @@ -286,11 +277,6 @@ class ReaderViewModel @Inject constructor( }?.toMangaPage() } - fun getPageNumber(): Int? { - val state = readingState.value?: return null - return state.page - } - fun switchChapter(id: Long, page: Int) { val prevJob = loadingJob loadingJob = launchLoadingJob(Dispatchers.Default) { diff --git a/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml index 7353dbd1f..d8a3b79e5 100644 --- a/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/nightly/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - + - \ No newline at end of file + +