From 92f94389927d4b83cc821cec8773f85e5548f8f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 12 Jul 2021 12:59:34 +0000 Subject: [PATCH 01/19] App strings reworked --- app/src/main/res/values/strings.xml | 148 ++++++++++++++-------------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 615dd5eb8..e9dc3e30a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,8 +6,8 @@ Local storage Favourites History - An error has occurred - Network connection error + An error occurred + Could not connect to the Internet Details Chapters List @@ -22,12 +22,12 @@ Try again Clear history Nothing found - History is empty + No history yet Read Add bookmark - You have not favourites yet - Add to favourites - Add new category + No favourites yet + Favourite + New category Add Enter category name Save @@ -36,40 +36,42 @@ Share %s Search Search manga - Manga downloading… + Downloading… Processing… - Download complete + Downloaded Downloads - By name + Name Popular Updated Newest - By rating + Rating All - Sort order + Sorting order Genre Filter Theme Light Dark + Black + Uses less power on AMOLED screens Automatic Pages Clear - Do you really want to clear all your reading history? This action cannot be undone. + Clear all reading history permanently? Remove \"%s\" removed from history \"%s\" deleted from local storage - Wait for the load to finish + Waiting for loading to finish… Save page - Page saved successful + Saved Share image Import Delete - This operation is not supported - Invalid file. Only ZIP and CBZ are supported. + Unsupported operation + Either pick a ZIP or CBZ file. No description History and cache - Clear pages cache + Clear page cache Cache B|kB|MB|GB|TB Standard @@ -78,32 +80,32 @@ Grid size Search on %s Delete manga - Do you really want to delete \"%s\" from your phone\'s local storage? \nThis operation cannot be undone. + Delete \"%s\" from device storage permanently? Reader settings Switch pages - Taps on edges + Tap edges Volume buttons Continue Warning - This operation may consume a lot of network traffic - Don`t ask again + This may download a lot of data + Don't ask again Cancelling… Error - Clear thumbnails cache + Clear thumbnail cache Clear search history - Search history cleared + Cleared Gestures only Internal storage External storage Domain - Check for updates automatically - Application update is available - Show notification if update is available + Check for new versions of the app + A new version of the app is available + Show notification if a new version is available Open in browser - This manga has %s. Do you want to save all of it? - Save manga + This manga has %s. Save all of it? + Save Notifications - Enabled %1$d of %2$d + Turned on %1$d of %2$d New chapters Notify about updates of manga you are reading Download @@ -111,61 +113,61 @@ Restart Notifications settings Notification sound - Light indicator + LED indicator Vibration - Favourites categories + Favourite categories Categories… Rename - Do you really want to remove category \"%s\" from your favourites? \nAll containing manga will be lost. - Remove category + Remove the \"%s\" category from your favourites? \nAll manga in it will be lost. + Remove It\'s kind of empty here… - You can use categories to organize your favourite manga. Press «+» to create a category + Use categories to organize your favourites. Press «+» to create a category Try to reformulate the query. - Manga you are reading will be displayed here - You can find what to read in side menu. - You have not any saved manga yet - You can save it from online sources or import from file. - Manga shelf - Recent manga - Pages animation - Manga download location + What you read is displayed here + Find what to read in the side menu. + Save something first + Save it from online sources or import files. + Shelf + Recent + Page animation + Downloads Not available - Cannot find any available storage + No available storage Other storage - Use secure connection (HTTPS) + Use secure (HTTPS) connection Done All favourites - This category is empty + Empty category Read later Updates - Here you will see the new chapters of the manga you are reading + New chapters of what you are reading are shown here Search results Related New version: %s Size: %s Waiting for network… Clear updates feed - Updates feed cleared + Cleared Rotate screen Update Feed update will start soon - Check updates for manga - Don`t check + Look for updates + Don't check Enter password Wrong password - Protect application - Ask for password on application start + Protect the app + Ask for password when starting Kotatsu Repeat password - Passwords do not match + Mismatching passwords About Version %s Check for updates Checking for updates… - Update check failed + Could not look for updates No updates available - Right to left - Prefer Right to left reader - You can set up the reading mode for each manga separately + Right-to-left ← + Prefer right-to-left reader + Reading mode can be set up separately for each file New category Create issue on GitHub Scale mode @@ -173,51 +175,49 @@ Fit to height Fit to width Keep at start - Black dark theme - Useful for AMOLED screens - Restart required + App restart required Create data backup Restore from backup Data restored Preparing… File not found - All data restored successfully - The data restored, but there are errors - You can create backup of your history and favourites and restore it + All data restored + The data restored, but with errors + Create a backup of your history and favourites to be restored later Just now Yesterday Long ago Group Today Tap to try again - Chosen configuration will be remembered for this manga + Chosen configuration will be remembered for this file Silent - CAPTCHA is required + CAPTCHA required Solve Clear cookies - All cookies was removed + Removed Checking for new chapters: %1$d of %2$d Clear feed - All updates history will be cleared and this action cannot be undone. Are you sure? - New chapters checking + Clear all update history permanently? + New-chapter check Reverse Sign in - You should authorize to view this content + Authorise viewing this content Default: %s …and %1$d more Next - Enter password that will be required when the application starts + Enter password required to start the app Confirm - Password must be at least 4 characters + Use four characters or more Hide toolbar when scrolling Search only on %s - Do you really want to remove all recent search queries? This action cannot be undone. + Remove all recent search queries permanently? Other Languages Welcome Description - Backup saved successfully - Some manufacturers can change the system behavior, which may breaks background tasks. + Saved + Some devices have different system behavior, which may break background tasks. Read more - \ No newline at end of file + From e6b6a6bb37af1bc187a91411784c1aa92265e511 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 12 Jul 2021 20:29:14 +0300 Subject: [PATCH 02/19] Update app/src/main/res/values/strings.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Allan Nordhøy --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e9dc3e30a..83a84c363 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -167,7 +167,7 @@ No updates available Right-to-left ← Prefer right-to-left reader - Reading mode can be set up separately for each file + Reading mode can be set up separately for each series New category Create issue on GitHub Scale mode From 550dfa9c9e26333d436721e303206893afa8ed96 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 12 Jul 2021 20:29:30 +0300 Subject: [PATCH 03/19] Update app/src/main/res/values/strings.xml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Allan Nordhøy --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 83a84c363..b7b7ccbd2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,7 +191,7 @@ Group Today Tap to try again - Chosen configuration will be remembered for this file + Chosen configuration will be remembered for this series Silent CAPTCHA required Solve From 22ac13c140dbc0d0d3c1f1378d1cdfa2fffb0428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Fri, 7 Jan 2022 20:28:22 +0000 Subject: [PATCH 04/19] Suggested changes made --- app/src/main/res/values/strings.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b7b7ccbd2..67f078e7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,7 +26,7 @@ Read Add bookmark No favourites yet - Favourite + Favourite this New category Add Enter category name @@ -61,7 +61,6 @@ Remove \"%s\" removed from history \"%s\" deleted from local storage - Waiting for loading to finish… Save page Saved Share image @@ -101,7 +100,7 @@ Check for new versions of the app A new version of the app is available Show notification if a new version is available - Open in browser + Open in web browser This manga has %s. Save all of it? Save Notifications From 7cb51f552a3320c026a1cf5aec4256d42a11e7e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Mon, 10 Jan 2022 13:32:29 +0100 Subject: [PATCH 05/19] Suggested changes made Co-authored-by: Koitharu --- app/src/main/res/values/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b8154845d..f47c6547d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -53,7 +53,7 @@ Theme Light Dark - System + Follow system Pages Clear Clear all reading history permanently? @@ -86,7 +86,7 @@ Volume buttons Continue Warning - This may download a lot of power + This may transfer a lot of data Don\'t ask again Cancelling… Error @@ -129,7 +129,7 @@ Shelf Recent Page animation - Downloads + Folder for downloads Not available No available storage Other storage @@ -175,7 +175,7 @@ Fit to width Keep at start Black - Useful for AMOLED screens + Uses less power on AMOLED screens Restart required Create data backup @@ -245,7 +245,7 @@ Date format Default Exclude NSFW manga from history - You must pick a name + You must enter a name Numbered pages Used sources Available sources From 722b6d1e59159d1ce63095d3e9ac9598ce6a6a6b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 16 Jan 2022 19:19:40 +0200 Subject: [PATCH 06/19] Cleanup MangaSource fields --- app/build.gradle | 1 + .../kotatsu/base/domain/MangaUtils.kt | 3 +- .../kotatsu/core/model/MangaSource.kt | 61 ++++++++----------- .../kotatsu/core/network/NetworkModule.kt | 2 + .../kotatsu/core/parser/MangaRepository.kt | 2 + .../kotatsu/core/parser/ParserModule.kt | 3 - .../core/parser/RemoteMangaRepository.kt | 5 +- .../kotatsu/details/ui/DetailsViewModel.kt | 2 +- .../local/domain/LocalMangaRepository.kt | 1 + .../kotatsu/reader/domain/PageLoader.kt | 2 +- .../kotatsu/reader/ui/ReaderViewModel.kt | 5 +- .../search/domain/MangaSearchRepository.kt | 3 +- .../core/parser/RemoteMangaRepositoryTest.kt | 2 +- .../core/parser/RepositoryTestModule.kt | 9 --- 14 files changed, 41 insertions(+), 60 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c342da9ae..f2c4622f9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,6 +59,7 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { jvmTarget = JavaVersion.VERSION_1_8.toString() freeCompilerArgs += [ '-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi', + '-Xopt-in=kotlinx.coroutines.FlowPreview', '-Xopt-in=kotlin.contracts.ExperimentalContracts', ] } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt index 83bd5dc25..d9f845c65 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaUtils.kt @@ -11,6 +11,7 @@ import org.koin.core.component.get import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.ext.await import org.koitharu.kotatsu.utils.ext.medianOrNull @@ -28,7 +29,7 @@ object MangaUtils : KoinComponent { suspend fun determineMangaIsWebtoon(pages: List): Boolean? { try { val page = pages.medianOrNull() ?: return null - val url = page.source.repository.getPageUrl(page) + val url = MangaRepository(page.source).getPageUrl(page) val uri = Uri.parse(url) val size = if (uri.scheme == "cbz") { val zip = ZipFile(uri.schemeSpecificPart) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index 4ef49374c..a9fc84da5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -2,51 +2,38 @@ package org.koitharu.kotatsu.core.model import android.os.Parcelable import kotlinx.parcelize.Parcelize -import org.koin.core.context.GlobalContext -import org.koin.core.error.NoBeanDefFoundException -import org.koin.core.qualifier.named -import org.koitharu.kotatsu.core.parser.MangaRepository -import org.koitharu.kotatsu.core.parser.site.* -import org.koitharu.kotatsu.local.domain.LocalMangaRepository @Suppress("SpellCheckingInspection") @Parcelize enum class MangaSource( val title: String, val locale: String?, - val cls: Class, ) : Parcelable { - LOCAL("Local", null, LocalMangaRepository::class.java), - READMANGA_RU("ReadManga", "ru", ReadmangaRepository::class.java), - MINTMANGA("MintManga", "ru", MintMangaRepository::class.java), - SELFMANGA("SelfManga", "ru", SelfMangaRepository::class.java), - MANGACHAN("Манга-тян", "ru", MangaChanRepository::class.java), - DESUME("Desu.me", "ru", DesuMeRepository::class.java), - HENCHAN("Хентай-тян", "ru", HenChanRepository::class.java), - YAOICHAN("Яой-тян", "ru", YaoiChanRepository::class.java), - MANGATOWN("MangaTown", "en", MangaTownRepository::class.java), - MANGALIB("MangaLib", "ru", MangaLibRepository::class.java), + LOCAL("Local", null), + READMANGA_RU("ReadManga", "ru"), + MINTMANGA("MintManga", "ru"), + SELFMANGA("SelfManga", "ru"), + MANGACHAN("Манга-тян", "ru"), + DESUME("Desu.me", "ru"), + HENCHAN("Хентай-тян", "ru"), + YAOICHAN("Яой-тян", "ru"), + MANGATOWN("MangaTown", "en"), + MANGALIB("MangaLib", "ru"), // NUDEMOON("Nude-Moon", "ru", NudeMoonRepository::class.java), - MANGAREAD("MangaRead", "en", MangareadRepository::class.java), - REMANGA("Remanga", "ru", RemangaRepository::class.java), - HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java), - ANIBEL("Anibel", "be", AnibelRepository::class.java), - NINEMANGA_EN("NineManga English", "en", NineMangaRepository.English::class.java), - NINEMANGA_ES("NineManga Español", "es", NineMangaRepository.Spanish::class.java), - NINEMANGA_RU("NineManga Русский", "ru", NineMangaRepository.Russian::class.java), - NINEMANGA_DE("NineManga Deutsch", "de", NineMangaRepository.Deutsch::class.java), - NINEMANGA_IT("NineManga Italiano", "it", NineMangaRepository.Italiano::class.java), - NINEMANGA_BR("NineManga Brasil", "pt", NineMangaRepository.Brazil::class.java), - NINEMANGA_FR("NineManga Français", "fr", NineMangaRepository.Francais::class.java), - EXHENTAI("ExHentai", null, ExHentaiRepository::class.java), - MANGAOWL("MangaOwl", "en", MangaOwlRepository::class.java), - MANGADEX("MangaDex", null, MangaDexRepository::class.java), + MANGAREAD("MangaRead", "en"), + REMANGA("Remanga", "ru"), + HENTAILIB("HentaiLib", "ru"), + ANIBEL("Anibel", "be"), + NINEMANGA_EN("NineManga English", "en"), + NINEMANGA_ES("NineManga Español", "es"), + NINEMANGA_RU("NineManga Русский", "ru"), + NINEMANGA_DE("NineManga Deutsch", "de"), + NINEMANGA_IT("NineManga Italiano", "it"), + NINEMANGA_BR("NineManga Brasil", "pt"), + NINEMANGA_FR("NineManga Français", "fr"), + EXHENTAI("ExHentai", null), + MANGAOWL("MangaOwl", "en"), + MANGADEX("MangaDex", null), ; - - @get:Throws(NoBeanDefFoundException::class) - @Deprecated("", ReplaceWith("MangaRepository(this)", - "org.koitharu.kotatsu.core.parser.MangaRepository")) - val repository: MangaRepository - get() = GlobalContext.get().get(named(this)) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt index 6c41a5291..5627e8637 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/network/NetworkModule.kt @@ -7,6 +7,7 @@ import org.koin.core.qualifier.named import org.koin.dsl.bind import org.koin.dsl.module import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.utils.CacheUtils import org.koitharu.kotatsu.utils.DownloadManagerHelper import java.util.concurrent.TimeUnit @@ -30,4 +31,5 @@ val networkModule }.build() } factory { DownloadManagerHelper(get(), get()) } + single { MangaLoaderContext(get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt index c8904b2a8..45103954f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/MangaRepository.kt @@ -7,6 +7,8 @@ import org.koitharu.kotatsu.core.model.* interface MangaRepository { + val source: MangaSource + val sortOrders: Set suspend fun getList2( diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt index c97f7a4eb..944bd1ac6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/ParserModule.kt @@ -2,15 +2,12 @@ package org.koitharu.kotatsu.core.parser import org.koin.core.qualifier.named import org.koin.dsl.module -import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.parser.site.* val parserModule get() = module { - single { MangaLoaderContext(get(), get()) } - factory(named(MangaSource.READMANGA_RU)) { ReadmangaRepository(get()) } factory(named(MangaSource.MINTMANGA)) { MintMangaRepository(get()) } factory(named(MangaSource.SELFMANGA)) { SelfMangaRepository(get()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index ef4c55817..12572a5d4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -3,7 +3,6 @@ package org.koitharu.kotatsu.core.parser import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.model.MangaPage -import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.prefs.SourceSettings @@ -12,8 +11,6 @@ abstract class RemoteMangaRepository( protected val loaderContext: MangaLoaderContext ) : MangaRepository { - protected abstract val source: MangaSource - protected abstract val defaultDomain: String private val conf by lazy { @@ -29,6 +26,8 @@ abstract class RemoteMangaRepository( override suspend fun getTags(): Set = emptySet() + fun getFaviconUrl() = "https://${getDomain()}/favicon.ico" + open fun onCreatePreferences(map: MutableMap) { map[SourceSettings.KEY_DOMAIN] = defaultDomain } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 8b7b241e6..3f8ee5a8f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -123,7 +123,7 @@ class DetailsViewModel( var manga = mangaDataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga") mangaData.value = manga - manga = manga.source.repository.getDetails(manga) + manga = MangaRepository(manga.source).getDetails(manga) // find default branch val hist = historyRepository.getOne(manga) selectedBranch.value = if (hist != null) { diff --git a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt index 3742f007e..cedfe10a9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/domain/LocalMangaRepository.kt @@ -23,6 +23,7 @@ import java.util.zip.ZipFile class LocalMangaRepository(private val context: Context) : MangaRepository { + override val source = MangaSource.LOCAL private val filenameFilter = CbzFilter() override suspend fun getList2( diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 38cfe450e..8e4e8316d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -50,7 +50,7 @@ class PageLoader( private fun loadAsync(page: MangaPage): Deferred { var repo = repository - if (repo?.javaClass != page.source.cls) { + if (repo?.source != page.source) { repo = mangaRepositoryOf(page.source) repository = repo } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt index cb4a0e38e..fa6203f55 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.reader.ui -import android.content.ContentResolver import android.net.Uri import android.util.LongSparseArray import androidx.lifecycle.MutableLiveData @@ -77,7 +76,7 @@ class ReaderViewModel( var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga") mangaData.value = manga - val repo = manga.source.repository + val repo = MangaRepository(manga.source) manga = repo.getDetails(manga) manga.chapters?.forEach { chapters.put(it.id, it) @@ -206,7 +205,7 @@ class ReaderViewModel( private suspend fun loadChapter(chapterId: Long): List { val manga = checkNotNull(mangaData.value) { "Manga is null" } val chapter = checkNotNull(chapters[chapterId]) { "Requested chapter not found" } - val repo = manga.source.repository + val repo = MangaRepository(manga.source) return repo.getPages(chapter).mapIndexed { index, page -> ReaderPage.from(page, index, chapterId) } diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index d099f54c5..efb736d7b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -14,6 +14,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.SortOrder +import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider import org.koitharu.kotatsu.utils.ext.levenshteinDistance @@ -29,7 +30,7 @@ class MangaSearchRepository( MangaProviderFactory.getSources(settings, includeHidden = false).asFlow() .flatMapMerge(concurrency) { source -> runCatching { - source.repository.getList2( + MangaRepository(source).getList2( offset = 0, query = query, sortOrder = SortOrder.POPULARITY diff --git a/app/src/test/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepositoryTest.kt b/app/src/test/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepositoryTest.kt index bdfa3f735..6cf57a268 100644 --- a/app/src/test/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepositoryTest.kt +++ b/app/src/test/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepositoryTest.kt @@ -31,7 +31,7 @@ class RemoteMangaRepositoryTest(private val source: MangaSource) : KoinTest { @get:Rule val koinTestRule = KoinTestRule.create { printLogger(Level.ERROR) - modules(repositoryTestModule) + modules(repositoryTestModule, parserModule) } @get:Rule diff --git a/app/src/test/java/org/koitharu/kotatsu/core/parser/RepositoryTestModule.kt b/app/src/test/java/org/koitharu/kotatsu/core/parser/RepositoryTestModule.kt index c28ccdec8..81d536fea 100644 --- a/app/src/test/java/org/koitharu/kotatsu/core/parser/RepositoryTestModule.kt +++ b/app/src/test/java/org/koitharu/kotatsu/core/parser/RepositoryTestModule.kt @@ -7,7 +7,6 @@ import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.network.TestCookieJar import org.koitharu.kotatsu.core.network.UserAgentInterceptor -import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.core.parser.SourceSettingsStub import org.koitharu.kotatsu.core.prefs.SourceSettings import java.util.concurrent.TimeUnit @@ -31,12 +30,4 @@ val repositoryTestModule } } } - factory { (source: MangaSource) -> - runCatching { - source.cls.getDeclaredConstructor(MangaLoaderContext::class.java) - .newInstance(get()) - }.recoverCatching { - source.cls.newInstance() - }.getOrThrow() as RemoteMangaRepository - } } \ No newline at end of file From 852f31574f3bc53f8eee6f7f7dd0a5c39fdd3486 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 23 Jan 2022 07:49:03 +0200 Subject: [PATCH 07/19] Fix MangaRead parse #87 --- app/build.gradle | 4 ++-- .../kotatsu/core/parser/site/MangareadRepository.kt | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 68bb3d879..9f01df521 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 31 - versionCode 378 - versionName '2.1.2' + versionCode 379 + versionName '2.1.3' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt index 7999fb7f2..2b2f9b8cd 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangareadRepository.kt @@ -151,8 +151,10 @@ class MangareadRepository( ?.selectFirst("div.reading-content") ?: throw ParseException("Root not found") return root.select("div.page-break").map { div -> - val img = div.selectFirst("img") - val url = img?.relUrl("src") ?: parseFailed("Page image not found") + val img = div.selectFirst("img") ?: parseFailed("Page image not found") + val url = img.relUrl("data-src").ifEmpty { + img.relUrl("src") + } MangaPage( id = generateUid(url), url = url, From 2a7761dbc3fa901721f5fb4b6d0d363b292f40fe Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 23 Jan 2022 08:05:07 +0200 Subject: [PATCH 08/19] Fix widgets #86 --- .../java/org/koitharu/kotatsu/utils/PendingIntentCompat.kt | 6 ++++++ .../koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt | 2 +- .../koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/PendingIntentCompat.kt b/app/src/main/java/org/koitharu/kotatsu/utils/PendingIntentCompat.kt index 4169a571d..c99050819 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/PendingIntentCompat.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/PendingIntentCompat.kt @@ -10,4 +10,10 @@ object PendingIntentCompat { } else { 0 } + + val FLAG_MUTABLE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_MUTABLE + } else { + 0 + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt index 63a2816ae..a5c6bb748 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt @@ -31,7 +31,7 @@ class RecentWidgetProvider : AppWidgetProvider() { context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE ) ) views.setEmptyView(R.id.stackView, R.id.textView_holder) diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt index 334941d51..7b3ba2059 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt @@ -31,7 +31,7 @@ class ShelfWidgetProvider : AppWidgetProvider() { context, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntentCompat.FLAG_MUTABLE ) ) views.setEmptyView(R.id.gridView, R.id.textView_holder) From fc3efbabbd522a126953e1c9d6cd3582271742bf Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 23 Jan 2022 08:14:56 +0200 Subject: [PATCH 09/19] Fix missing fragment crash #91 --- app/proguard-rules.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 74c093891..5d74551b7 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -7,5 +7,6 @@ public static void checkParameterIsNotNull(...); public static void checkNotNullParameter(...); } +-keep public class ** extends org.koitharu.kotatsu.base.ui.BaseFragment -keep class org.koitharu.kotatsu.core.db.entity.* { *; } -dontwarn okhttp3.internal.platform.ConscryptPlatform \ No newline at end of file From b6e13de73f97a5798820e37456bd8629b19abefa Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 23 Jan 2022 09:46:26 +0200 Subject: [PATCH 10/19] Fix MangaTown licensed chapters --- .../core/parser/RemoteMangaRepository.kt | 4 ++- .../core/parser/site/MangaTownRepository.kt | 28 ++++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index ef4c55817..0062bc1d0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -53,8 +53,10 @@ abstract class RemoteMangaRepository( if (subdomain != null) { append(subdomain) append('.') + append(conf.getDomain(defaultDomain).removePrefix("www.")) + } else { + append(conf.getDomain(defaultDomain)) } - append(conf.getDomain(defaultDomain)) append(this@withDomain) } else -> this diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt index 7f1285369..afe3750c3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaTownRepository.kt @@ -128,7 +128,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) : scanlator = null, branch = null, ) - } + } ?: bypassLicensedChapters(manga) ) } @@ -191,6 +191,32 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) : map[SourceSettings.KEY_USE_SSL] = true } + private suspend fun bypassLicensedChapters(manga: Manga): List { + val doc = loaderContext.httpGet(manga.url.withDomain("m")).parseHtml() + val list = doc.body().selectFirst("ul.detail-ch-list") ?: return emptyList() + val dateFormat = SimpleDateFormat("MMM dd,yyyy", Locale.US) + return list.select("li").asReversed().mapIndexedNotNull { i, li -> + val a = li.selectFirst("a") ?: return@mapIndexedNotNull null + val href = a.relUrl("href") + val name = a.selectFirst("span.vol")?.text().orEmpty().ifEmpty { + a.ownText() + } + MangaChapter( + id = generateUid(href), + url = href, + source = MangaSource.MANGATOWN, + number = i + 1, + uploadDate = parseChapterDate( + dateFormat, + li.selectFirst("span.time")?.text() + ), + name = name.ifEmpty { "${manga.title} - ${i + 1}" }, + scanlator = null, + branch = null, + ) + } + } + private fun String.parseTagKey() = split('/').findLast { TAG_REGEX matches it } private companion object { From 2535739c2b30934bba8594045a2e2a58d41a628a Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 23 Jan 2022 10:13:32 +0200 Subject: [PATCH 11/19] Enhance download cancellation in blocking io tasks #90 --- .../download/domain/DownloadManager.kt | 2 +- .../koitharu/kotatsu/local/data/MangaZip.kt | 8 +-- .../kotatsu/local/data/WritableCbzFile.kt | 58 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt index 75905aa51..1a36ec7fc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt @@ -145,7 +145,7 @@ class DownloadManager( while (true) { try { val response = call.clone().await() - withContext(Dispatchers.IO) { + runInterruptible(Dispatchers.IO) { file.outputStream().use { out -> checkNotNull(response.body).byteStream().copyTo(out) } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt index 2904910d6..c9d93f147 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/MangaZip.kt @@ -31,7 +31,7 @@ class MangaZip(val file: File) { return writableCbz.flush() } - fun addCover(file: File, ext: String) { + suspend fun addCover(file: File, ext: String) { val name = buildString { append(FILENAME_PATTERN.format(0, 0)) if (ext.isNotEmpty() && ext.length <= 4) { @@ -39,11 +39,11 @@ class MangaZip(val file: File) { append(ext) } } - writableCbz[name] = file + writableCbz.put(name, file) index.setCoverEntry(name) } - fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) { + suspend fun addPage(chapter: MangaChapter, file: File, pageNumber: Int, ext: String) { val name = buildString { append(FILENAME_PATTERN.format(chapter.number, pageNumber)) if (ext.isNotEmpty() && ext.length <= 4) { @@ -51,7 +51,7 @@ class MangaZip(val file: File) { append(ext) } } - writableCbz[name] = file + writableCbz.put(name, file) index.addChapter(chapter) } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt index 5a591740f..b7c5f7b9f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/WritableCbzFile.kt @@ -1,8 +1,7 @@ package org.koitharu.kotatsu.local.data import androidx.annotation.CheckResult -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.* import java.io.File import java.io.FileInputStream import java.io.FileOutputStream @@ -27,11 +26,13 @@ class WritableCbzFile(private val file: File) { } ZipInputStream(FileInputStream(file)).use { zip -> var entry = zip.nextEntry - while (entry != null) { + while (entry != null && currentCoroutineContext().isActive) { val target = File(dir.path + File.separator + entry.name) - target.parentFile?.mkdirs() - target.outputStream().use { out -> - zip.copyTo(out) + runInterruptible { + target.parentFile?.mkdirs() + target.outputStream().use { out -> + zip.copyTo(out) + } } zip.closeEntry() entry = zip.nextEntry @@ -51,11 +52,13 @@ class WritableCbzFile(private val file: File) { tempFile.delete() } try { - ZipOutputStream(FileOutputStream(tempFile)).use { zip -> - dir.listFiles()?.forEach { - zipFile(it, it.name, zip) + runInterruptible { + ZipOutputStream(FileOutputStream(tempFile)).use { zip -> + dir.listFiles()?.forEach { + zipFile(it, it.name, zip) + } + zip.flush() } - zip.flush() } tempFile.renameTo(file) } finally { @@ -67,29 +70,26 @@ class WritableCbzFile(private val file: File) { operator fun get(name: String) = File(dir, name) - operator fun set(name: String, file: File) { + suspend fun put(name: String, file: File) = runInterruptible(Dispatchers.IO) { file.copyTo(this[name], overwrite = true) } - companion object { - - private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) { - if (fileToZip.isDirectory) { - if (fileName.endsWith("/")) { - zipOut.putNextEntry(ZipEntry(fileName)) - } else { - zipOut.putNextEntry(ZipEntry("$fileName/")) - } - zipOut.closeEntry() - fileToZip.listFiles()?.forEach { childFile -> - zipFile(childFile, "$fileName/${childFile.name}", zipOut) - } + private fun zipFile(fileToZip: File, fileName: String, zipOut: ZipOutputStream) { + if (fileToZip.isDirectory) { + if (fileName.endsWith("/")) { + zipOut.putNextEntry(ZipEntry(fileName)) } else { - FileInputStream(fileToZip).use { fis -> - val zipEntry = ZipEntry(fileName) - zipOut.putNextEntry(zipEntry) - fis.copyTo(zipOut) - } + zipOut.putNextEntry(ZipEntry("$fileName/")) + } + zipOut.closeEntry() + fileToZip.listFiles()?.forEach { childFile -> + zipFile(childFile, "$fileName/${childFile.name}", zipOut) + } + } else { + FileInputStream(fileToZip).use { fis -> + val zipEntry = ZipEntry(fileName) + zipOut.putNextEntry(zipEntry) + fis.copyTo(zipOut) } } } From 2ac6b84f871d32066e263276622b9deedc45794f Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 25 Jan 2022 19:56:37 +0200 Subject: [PATCH 12/19] Show favicons in sources list --- .../kotatsu/core/parser/FaviconMapper.kt | 18 ++++++ .../core/parser/RemoteMangaRepository.kt | 2 +- .../core/parser/site/AnibelRepository.kt | 4 ++ .../org/koitharu/kotatsu/core/ui/uiModule.kt | 2 + .../koitharu/kotatsu/local/data/CbzFetcher.kt | 7 ++- .../sources/SourcesSettingsFragment.kt | 3 +- .../sources/SourcesSettingsViewModel.kt | 2 + .../sources/adapter/SourceConfigAdapter.kt | 7 ++- .../adapter/SourceConfigAdapterDelegates.kt | 55 +++++++++++++++++-- .../sources/model/SourceConfigItem.kt | 7 +++ .../main/res/drawable/ic_favicon_fallback.xml | 16 ++++++ .../main/res/layout/item_source_config.xml | 14 +++-- .../layout/item_source_config_draggable.xml | 46 ++++++++++++++++ 13 files changed, 167 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt create mode 100644 app/src/main/res/drawable/ic_favicon_fallback.xml create mode 100644 app/src/main/res/layout/item_source_config_draggable.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt new file mode 100644 index 000000000..44f1ca040 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt @@ -0,0 +1,18 @@ +package org.koitharu.kotatsu.core.parser + +import android.net.Uri +import coil.map.Mapper +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.koitharu.kotatsu.core.model.MangaSource + +class FaviconMapper() : Mapper { + + override fun map(data: Uri): HttpUrl { + val mangaSource = MangaSource.valueOf(data.schemeSpecificPart) + val repo = MangaRepository(mangaSource) as RemoteMangaRepository + return repo.getFaviconUrl().toHttpUrl() + } + + override fun handles(data: Uri) = data.scheme == "favicon" +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 80321b87a..3505c53b8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -26,7 +26,7 @@ abstract class RemoteMangaRepository( override suspend fun getTags(): Set = emptySet() - fun getFaviconUrl() = "https://${getDomain()}/favicon.ico" + open fun getFaviconUrl() = "https://${getDomain()}/favicon.ico" open fun onCreatePreferences(map: MutableMap) { map[SourceSettings.KEY_DOMAIN] = defaultDomain diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt index 03853cd1f..e71378eec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/AnibelRepository.kt @@ -21,6 +21,10 @@ class AnibelRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor SortOrder.NEWEST ) + override fun getFaviconUrl(): String { + return "https://cdn.${getDomain()}/favicons/favicon.png" + } + override suspend fun getList2( offset: Int, query: String?, diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt index 8032d9783..148fa409d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt @@ -5,6 +5,7 @@ import coil.ImageLoader import okhttp3.OkHttpClient import org.koin.android.ext.koin.androidContext import org.koin.dsl.module +import org.koitharu.kotatsu.core.parser.FaviconMapper import org.koitharu.kotatsu.local.data.CbzFetcher val uiModule @@ -15,6 +16,7 @@ val uiModule .componentRegistry( ComponentRegistry.Builder() .add(CbzFetcher()) + .add(FaviconMapper()) .build() ).build() } diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt index cd4f5ea83..4e2746cec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt @@ -9,23 +9,24 @@ import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.fetch.SourceResult import coil.size.Size +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import okio.buffer import okio.source import java.util.zip.ZipFile class CbzFetcher : Fetcher { - @Suppress("BlockingMethodInNonBlockingContext") override suspend fun fetch( pool: BitmapPool, data: Uri, size: Size, options: Options, - ): FetchResult { + ): FetchResult = runInterruptible(Dispatchers.IO) { val zip = ZipFile(data.schemeSpecificPart) val entry = zip.getEntry(data.fragment) val ext = MimeTypeMap.getFileExtensionFromUrl(entry.name) - return SourceResult( + SourceResult( source = ExtraCloseableBufferedSource( zip.getInputStream(entry).source().buffer(), zip, diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index 25893f670..53d165b92 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -8,6 +8,7 @@ import androidx.core.graphics.Insets import androidx.core.view.updatePadding import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView +import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment @@ -42,7 +43,7 @@ class SourcesSettingsFragment : BaseFragment(), override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val sourcesAdapter = SourceConfigAdapter(this) + val sourcesAdapter = SourceConfigAdapter(this, get(), viewLifecycleOwner) with(binding.recyclerView) { setHasFixedSize(true) addItemDecoration(SourceConfigItemDecoration(view.context)) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt index 52125df63..1bff594c9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt @@ -81,6 +81,7 @@ class SourcesSettingsViewModel( SourceConfigItem.SourceItem( source = it, isEnabled = true, + isDraggable = true, ) } } @@ -102,6 +103,7 @@ class SourcesSettingsViewModel( SourceConfigItem.SourceItem( source = it, isEnabled = false, + isDraggable = false, ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt index d04d22fcc..79cab4f39 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt @@ -1,13 +1,18 @@ package org.koitharu.kotatsu.settings.sources.adapter +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem class SourceConfigAdapter( listener: SourceConfigListener, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, ) : AsyncListDifferDelegationAdapter( SourceConfigDiffCallback(), sourceConfigHeaderDelegate(), sourceConfigGroupDelegate(listener), - sourceConfigItemDelegate(listener), + sourceConfigItemDelegate(listener, coil, lifecycleOwner), + sourceConfigDraggableItemDelegate(listener), ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index df7435bac..df2b734b9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -6,12 +6,18 @@ import android.view.View import android.widget.CompoundButton import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative +import androidx.lifecycle.LifecycleOwner +import coil.ImageLoader +import coil.request.Disposable +import coil.request.ImageRequest import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemExpandableBinding import org.koitharu.kotatsu.databinding.ItemFilterHeaderBinding import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding +import org.koitharu.kotatsu.databinding.ItemSourceConfigDraggableBinding import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import org.koitharu.kotatsu.utils.ext.enqueueWith fun sourceConfigHeaderDelegate() = adapterDelegateViewBinding( { layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) } @@ -38,11 +44,54 @@ fun sourceConfigGroupDelegate( } } -@SuppressLint("ClickableViewAccessibility") fun sourceConfigItemDelegate( listener: SourceConfigListener, + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( - { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) } + { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) }, + on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable } +) { + + val eventListener = object : View.OnClickListener, CompoundButton.OnCheckedChangeListener { + override fun onClick(v: View?) = listener.onItemSettingsClick(item) + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + listener.onItemEnabledChanged(item, isChecked) + } + } + var imageRequest: Disposable? = null + + binding.imageViewConfig.setOnClickListener(eventListener) + binding.switchToggle.setOnCheckedChangeListener(eventListener) + + bind { + binding.textViewTitle.text = item.source.title + binding.switchToggle.isChecked = item.isEnabled + binding.imageViewConfig.isVisible = item.isEnabled + binding.root.updatePaddingRelative( + end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, + ) + imageRequest = ImageRequest.Builder(context) + .data(item.faviconUrl) + .error(R.drawable.ic_favicon_fallback) + .target(binding.imageViewIcon) + .lifecycle(lifecycleOwner) + .enqueueWith(coil) + } + + onViewRecycled { + imageRequest?.dispose() + imageRequest = null + } +} + +@SuppressLint("ClickableViewAccessibility") +fun sourceConfigDraggableItemDelegate( + listener: SourceConfigListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) }, + on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable } ) { val eventListener = object : View.OnClickListener, View.OnTouchListener, @@ -70,10 +119,8 @@ fun sourceConfigItemDelegate( bind { binding.textViewTitle.text = item.source.title binding.switchToggle.isChecked = item.isEnabled - binding.imageViewHandle.isVisible = item.isEnabled binding.imageViewConfig.isVisible = item.isEnabled binding.root.updatePaddingRelative( - start = if (item.isEnabled) 0 else binding.imageViewHandle.paddingStart * 2, end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt index 965ea1171..a3e398a03 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.settings.sources.model +import android.net.Uri import androidx.annotation.StringRes import org.koitharu.kotatsu.core.model.MangaSource @@ -49,8 +50,12 @@ sealed interface SourceConfigItem { class SourceItem( val source: MangaSource, val isEnabled: Boolean, + val isDraggable: Boolean, ) : SourceConfigItem { + val faviconUrl: Uri + get() = Uri.fromParts("favicon", source.name, null) + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -59,6 +64,7 @@ sealed interface SourceConfigItem { if (source != other.source) return false if (isEnabled != other.isEnabled) return false + if (isDraggable != other.isDraggable) return false return true } @@ -66,6 +72,7 @@ sealed interface SourceConfigItem { override fun hashCode(): Int { var result = source.hashCode() result = 31 * result + isEnabled.hashCode() + result = 31 * result + isDraggable.hashCode() return result } } diff --git a/app/src/main/res/drawable/ic_favicon_fallback.xml b/app/src/main/res/drawable/ic_favicon_fallback.xml new file mode 100644 index 000000000..24996b554 --- /dev/null +++ b/app/src/main/res/drawable/ic_favicon_fallback.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index ffa9a68e5..2d225fea2 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -9,12 +9,14 @@ android:orientation="horizontal"> + android:id="@+id/imageView_icon" + android:layout_width="?android:listPreferredItemHeightSmall" + android:layout_height="?android:listPreferredItemHeightSmall" + android:layout_marginHorizontal="?listPreferredItemPaddingStart" + android:labelFor="@id/textView_title" + android:padding="6dp" + android:scaleType="fitCenter" + tools:src="@tools:sample/avatars" /> + + + + + + + + + + + \ No newline at end of file From e280aa49633cd2007f1711a1410587787d5b4f53 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 26 Jan 2022 08:21:53 +0200 Subject: [PATCH 13/19] Quick search across manga sources --- .../sources/SourcesSettingsFragment.kt | 32 ++++++++++++++++--- .../sources/SourcesSettingsViewModel.kt | 22 +++++++++++++ .../sources/adapter/SourceConfigAdapter.kt | 1 + .../adapter/SourceConfigAdapterDelegates.kt | 29 ++++++----------- .../adapter/SourceConfigDiffCallback.kt | 10 ++++-- .../sources/model/SourceConfigItem.kt | 2 ++ .../main/res/layout/item_source_config.xml | 16 ++-------- .../main/res/layout/item_sources_empty.xml | 9 ++++++ app/src/main/res/menu/opt_sources.xml | 9 +++--- 9 files changed, 86 insertions(+), 44 deletions(-) create mode 100644 app/src/main/res/layout/item_sources_empty.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt index 53d165b92..5cbb3ac41 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt @@ -1,9 +1,8 @@ package org.koitharu.kotatsu.settings.sources import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.view.* +import androidx.appcompat.widget.SearchView import androidx.core.graphics.Insets import androidx.core.view.updatePadding import androidx.recyclerview.widget.ItemTouchHelper @@ -20,7 +19,7 @@ import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem class SourcesSettingsFragment : BaseFragment(), - SourceConfigListener { + SourceConfigListener, SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener { private lateinit var reorderHelper: ItemTouchHelper private val viewModel by viewModel() @@ -60,6 +59,17 @@ class SourcesSettingsFragment : BaseFragment(), super.onDestroyView() } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.opt_sources, menu) + val searchMenuItem = menu.findItem(R.id.action_search) + searchMenuItem.setOnActionExpandListener(this) + val searchView = searchMenuItem.actionView as SearchView + searchView.setOnQueryTextListener(this) + searchView.setIconifiedByDefault(false) + searchView.queryHint = searchMenuItem.title + } + override fun onWindowInsetsChanged(insets: Insets) { binding.recyclerView.updatePadding( bottom = insets.bottom, @@ -84,6 +94,20 @@ class SourcesSettingsFragment : BaseFragment(), viewModel.expandOrCollapse(header.localeId) } + override fun onQueryTextSubmit(query: String?): Boolean = false + + override fun onQueryTextChange(newText: String?): Boolean { + viewModel.performSearch(newText) + return true + } + + override fun onMenuItemActionExpand(item: MenuItem?): Boolean = true + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean { + (item.actionView as SearchView).setQuery("", false) + return true + } + private inner class SourcesReorderCallback : ItemTouchHelper.SimpleCallback( ItemTouchHelper.DOWN or ItemTouchHelper.UP, 0, diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt index 1bff594c9..a908ccf4e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt @@ -21,6 +21,7 @@ class SourcesSettingsViewModel( val items = MutableLiveData>(emptyList()) private val expandedGroups = HashSet() + private var searchQuery: String? = null init { buildList() @@ -63,9 +64,30 @@ class SourcesSettingsViewModel( buildList() } + fun performSearch(query: String?) { + searchQuery = query?.trim() + buildList() + } + private fun buildList() { val sources = MangaProviderFactory.getSources(settings, includeHidden = true) val hiddenSources = settings.hiddenSources + val query = searchQuery + if (!query.isNullOrEmpty()) { + items.value = sources.mapNotNull { + if (!it.title.contains(query, ignoreCase = true)) { + return@mapNotNull null + } + SourceConfigItem.SourceItem( + source = it, + isEnabled = it.name !in hiddenSources, + isDraggable = false, + ) + }.ifEmpty { + listOf(SourceConfigItem.EmptySearchResult) + } + return + } val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) { if (it.name !in hiddenSources) { KEY_ENABLED diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt index 79cab4f39..d580684be 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt @@ -15,4 +15,5 @@ class SourceConfigAdapter( sourceConfigGroupDelegate(listener), sourceConfigItemDelegate(listener, coil, lifecycleOwner), sourceConfigDraggableItemDelegate(listener), + sourceConfigEmptySearchDelegate(), ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index df2b734b9..aa8c9fb35 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -4,12 +4,11 @@ import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View import android.widget.CompoundButton -import androidx.core.view.isVisible -import androidx.core.view.updatePaddingRelative import androidx.lifecycle.LifecycleOwner import coil.ImageLoader import coil.request.Disposable import coil.request.ImageRequest +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.databinding.ItemExpandableBinding @@ -53,25 +52,15 @@ fun sourceConfigItemDelegate( on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable } ) { - val eventListener = object : View.OnClickListener, CompoundButton.OnCheckedChangeListener { - override fun onClick(v: View?) = listener.onItemSettingsClick(item) - - override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { - listener.onItemEnabledChanged(item, isChecked) - } - } var imageRequest: Disposable? = null - binding.imageViewConfig.setOnClickListener(eventListener) - binding.switchToggle.setOnCheckedChangeListener(eventListener) + binding.switchToggle.setOnCheckedChangeListener { _, isChecked -> + listener.onItemEnabledChanged(item, isChecked) + } bind { binding.textViewTitle.text = item.source.title binding.switchToggle.isChecked = item.isEnabled - binding.imageViewConfig.isVisible = item.isEnabled - binding.root.updatePaddingRelative( - end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, - ) imageRequest = ImageRequest.Builder(context) .data(item.faviconUrl) .error(R.drawable.ic_favicon_fallback) @@ -119,9 +108,9 @@ fun sourceConfigDraggableItemDelegate( bind { binding.textViewTitle.text = item.source.title binding.switchToggle.isChecked = item.isEnabled - binding.imageViewConfig.isVisible = item.isEnabled - binding.root.updatePaddingRelative( - end = if (item.isEnabled) 0 else binding.imageViewConfig.paddingEnd, - ) } -} \ No newline at end of file +} + +fun sourceConfigEmptySearchDelegate() = adapterDelegate( + R.layout.item_sources_empty +) { } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt index 370cca88d..8bab50c2a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigDiffCallback.kt @@ -2,21 +2,25 @@ package org.koitharu.kotatsu.settings.sources.adapter import androidx.recyclerview.widget.DiffUtil import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem +import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem.* class SourceConfigDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: SourceConfigItem, newItem: SourceConfigItem): Boolean { return when { oldItem.javaClass != newItem.javaClass -> false - oldItem is SourceConfigItem.LocaleGroup && newItem is SourceConfigItem.LocaleGroup -> { + oldItem is LocaleGroup && newItem is LocaleGroup -> { oldItem.localeId == newItem.localeId } - oldItem is SourceConfigItem.SourceItem && newItem is SourceConfigItem.SourceItem -> { + oldItem is SourceItem && newItem is SourceItem -> { oldItem.source == newItem.source } - oldItem is SourceConfigItem.Header && newItem is SourceConfigItem.Header -> { + oldItem is Header && newItem is Header -> { oldItem.titleResId == newItem.titleResId } + oldItem == EmptySearchResult && newItem == EmptySearchResult -> { + true + } else -> false } } diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt index a3e398a03..dd998ddac 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/model/SourceConfigItem.kt @@ -76,4 +76,6 @@ sealed interface SourceConfigItem { return result } } + + object EmptySearchResult : SourceConfigItem } \ No newline at end of file diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index 2d225fea2..a13432590 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -4,7 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="?android:listPreferredItemHeightSmall" - android:background="?android:windowBackground" android:gravity="center_vertical" android:orientation="horizontal"> @@ -14,7 +13,7 @@ android:layout_height="?android:listPreferredItemHeightSmall" android:layout_marginHorizontal="?listPreferredItemPaddingStart" android:labelFor="@id/textView_title" - android:padding="6dp" + android:padding="8dp" android:scaleType="fitCenter" tools:src="@tools:sample/avatars" /> @@ -33,16 +32,7 @@ - - + android:layout_height="wrap_content" + android:layout_marginEnd="?listPreferredItemPaddingEnd" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_sources_empty.xml b/app/src/main/res/layout/item_sources_empty.xml new file mode 100644 index 000000000..3aad1bbe4 --- /dev/null +++ b/app/src/main/res/layout/item_sources_empty.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/menu/opt_sources.xml b/app/src/main/res/menu/opt_sources.xml index 35c7034be..5128fbe66 100644 --- a/app/src/main/res/menu/opt_sources.xml +++ b/app/src/main/res/menu/opt_sources.xml @@ -4,9 +4,10 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_search" + android:icon="@drawable/ic_search" + android:title="@string/search" + app:actionViewClass="androidx.appcompat.widget.SearchView" + app:showAsAction="ifRoom|collapseActionView" /> \ No newline at end of file From e18f911b1bc1b60c5c272c25864c600912180294 Mon Sep 17 00:00:00 2001 From: b0ywearngtights Date: Sun, 16 Jan 2022 09:26:07 +0000 Subject: [PATCH 14/19] Translated using Weblate (Spanish) Currently translated at 100.0% (249 of 249 strings) Translation: Kotatsu/Strings Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/ --- app/src/main/res/values-es/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index cb4c26f34..5deb17d71 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -242,8 +242,11 @@ Formato de la fecha Por defecto Algunos fabricantes pueden cambiar el comportamiento del sistema, lo que podría interrumpir las tareas en segundo plano. - Nombre no debe estar vacío + El nombre no debe estar vacío Autorización en %s no es compatible Se cerrará la sesión de todas las fuentes en las que esté autorizado Excluye manga NSFW del historial + Mostrar los números de páginas + Fuentes activadas + Fuentes disponibles \ No newline at end of file From 1404a83c1015f34a27853491ac4688491b46281e Mon Sep 17 00:00:00 2001 From: b0ywearngtights Date: Sun, 16 Jan 2022 09:33:43 +0000 Subject: [PATCH 15/19] Translated using Weblate (Portuguese) Currently translated at 45.3% (113 of 249 strings) Translation: Kotatsu/Strings Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/ --- app/src/main/res/values-pt/strings.xml | 115 ++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index a6b3daec9..8580703c0 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1,2 +1,115 @@ - \ No newline at end of file + + Abrir menu + Armazenamento local + Favoritos + Ocorreu um erro + Erro de conexão de rede + Detalhes + Lista + Lista detalhada + Grade + Modo lista + Configurações + Carregando… + Capítulo %1$d de %2$d + Tente novamente + Limpar histórico + Nada encontrado + Histórico vazio + Add marca páginas + Você não tem favoritos ainda + Adicionar aos favoritos + Add + Nomeie a categoria + Salvar + Compartilhar + Criar atalho… + Compartilhar %s + Pesquisar + Pesquisar mangá + Baixando mangá… + Download completo + Downloads + Por nome + Popular + Por avaliação + Todos + Ordem de classificação + Gênero + Filtro + Escuro + Automático + Páginas + Limpar + Você realmente quer limpar todo o seu histórico de leitura\? Essa ação não pode ser desfeita. + Remover + \"%s\" removido do histórico + \"%s\" deletado do armazenamento local + Aguarde o carregamento para finalizar + Salvar página + Página salva com sucesso + Compartilhar imagem + Importar + Utualizado + Deletar + Essa operação não é suportada + Histórico e cache + Limpar cache de páginas + Cache + B|kB|MB|GB|TB + Padrão + Webtoon + Modo leitura + Tamanho de grade + Pesquisar em %s + Deletar mangá + Configurações de leitura + Mudar páginas + Cliques na borda + Botões de volume + Essa operação pode consumir muito tráfego de rede + Não pergunte novamente + Cancelando… + Erro + Limpar cache de thumbnails + Histórico de pesquisa limpo + Apenas gestos + Armazenamento interno + Armazenamento externo + Domínio + Verificar automaticamente se há actualizações + Actualização da aplicação está disponível + Mostrar notificação se a actualização estiver disponível + Aberto no navegador + Esta manga tem %s. Quer salvar tudo isto\? + Salvar mangá + Notificações + Novos capítulos + Notifique sobre atualizações de mangá que está lendo + Download + Ler desde o início + Reiniciar + Configurações das notificações + Indicador luminoso + Fechar menu + Fontes remotas + Fechar + Brilho + Histórico + Ler + Processando… + Novos + Tema + Sem descrição + Continuar + Capítulos + Add nova categoria + Aviso + Você realmente quer deletar \"%s\" do armazenamento local de seu celular\? +\nEssa operação não pode ser desfeita. + Arquivo inválido. Apenas ZIP e CBZ são suportados. + Limpar histórico de pesquisa + Activado %1$d de %2$d + Som de notificação + \ No newline at end of file From 1fb9eb3e3bfe47ad00319ff40c249058e63a9d8b Mon Sep 17 00:00:00 2001 From: Mohammad Date: Mon, 17 Jan 2022 18:39:49 +0100 Subject: [PATCH 16/19] Added translation using Weblate (Persian) --- app/src/main/res/values-fa/strings.xml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 app/src/main/res/values-fa/strings.xml diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml new file mode 100644 index 000000000..a6b3daec9 --- /dev/null +++ b/app/src/main/res/values-fa/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file From b14d629a453829cc527eda6c8aa2b110f87f4bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliaksiej=20Razuma=C5=AD?= Date: Mon, 17 Jan 2022 14:20:13 +0000 Subject: [PATCH 17/19] Translated using Weblate (Belarusian) Currently translated at 99.5% (248 of 249 strings) Translation: Kotatsu/Strings Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/ --- app/src/main/res/values-be/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 505eb3643..fb9e9af25 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -246,4 +246,7 @@ Па змаўчанні Не паказваць NSFW мангу з гісторыі Імя не можа быць пустым + Паказваць нумары старонак + Уключаныя крыніцы + Даступныя крыніцы \ No newline at end of file From 67306734fa35d5ff79cd14294c7b228af48aeb6c Mon Sep 17 00:00:00 2001 From: Mohammad Date: Mon, 17 Jan 2022 17:41:31 +0000 Subject: [PATCH 18/19] Translated using Weblate (Persian) Currently translated at 3.6% (9 of 249 strings) Translation: Kotatsu/Strings Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fa/ --- app/src/main/res/values-fa/strings.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index a6b3daec9..54bac4ed8 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,2 +1,10 @@ - \ No newline at end of file + + بستن منو + بازکردن منو + محل ذخیره سازی + موارد دلخواه + تاریخچه + خطایی رخ داده است + خطای اتصال به شبکه + \ No newline at end of file From 970200aa40c3ab2d1ee6d2c5fff28e3c30b15bee Mon Sep 17 00:00:00 2001 From: Luiz-bro Date: Thu, 20 Jan 2022 18:24:08 +0000 Subject: [PATCH 19/19] Translated using Weblate (Portuguese) Currently translated at 100.0% (249 of 249 strings) Translation: Kotatsu/Strings Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/ --- app/src/main/res/values-pt/strings.xml | 141 ++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 8580703c0..17fe50b16 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -32,7 +32,7 @@ Download completo Downloads Por nome - Popular + Populares Por avaliação Todos Ordem de classificação @@ -86,7 +86,7 @@ Salvar mangá Notificações Novos capítulos - Notifique sobre atualizações de mangá que está lendo + Notifique sobre atualizações do mangá que está lendo Download Ler desde o início Reiniciar @@ -112,4 +112,141 @@ Limpar histórico de pesquisa Activado %1$d de %2$d Som de notificação + Mostrar números de páginas + Concluído + em andamento + Categorias… + Renomear + Remover categoria + Está meio vazio aqui… + Prateleira de manga + Feito + Relacionado + Manter no início + Limpar feed de atualizações + Feed de atualizações limpo + Atualizar + A atualização do feed começará em breve + Confira as atualizações do mangá + Não verifique + Digite a senha + Senha incorreta + Repita a senha + As senhas não coincidem + Cerca de + Versão %s + Verifique se há atualizações + Verificando atualizações… + Nenhuma atualização disponível + Direita para esquerda + Nova categoria + Criar problema no GitHub + Modo de escala + Centro de ajuste + Ajustar à largura + É necessário reiniciar + Restauração de backup + Criar backup de dados + Restaurar do backup + Dados restaurados + Preparando… + Arquivo não encontrado + Todos os dados restaurados com sucesso + Os dados foram restaurados, mas há erros + Agora mesmo + Ontem + Muito tempo atrás + Grupo + Hoje + Toque para tentar novamente + Silencioso + O CAPTCHA é obrigatório + Resolver + Todos os cookies foram removidos + Verificando novos capítulos: %1$d de %2$d + Limpar feed + Todo o histórico de atualizações será apagado e esta ação não poderá ser desfeita. Tem certeza\? + Verificação de novos capítulos + Reverter + Entrar + Padrão: %s + …e %1$d mais + Próximo + Digite a senha que será necessária quando o aplicativo for iniciado + Confirme + A senha deve ter pelo menos 4 caracteres + Descrição + Cópias de segurança salvas com sucesso + Alguns fabricantes podem alterar o comportamento do sistema, o que pode quebrar as tarefas de fundo. + Leia mais + Ocultar a barra de ferramentas ao rolar + Pesquise apenas em %s + Outros + Línguas + Bem vindo + Fontes disponíveis + Fontes ativadas + Enfileirado + No momento, não há downloads ativos + O nome não deve estar vazio + Traduzir esta aplicação + Autor + Comentar + Tópico no 4PDA + Apoiar o desenvolvedor + Se gostar desta aplicação, pode ajudar financeiramente através de Yoomoney (ex. Yandex.Money) + agradecimento + Estas pessoas fazem o Kotatsu tornar-se melhor! + Direitos de autor e licenças + Licença + Este capítulo está em falta no seu dispositivo. Descarregue ou leia-o online. + Falta um capítulo + Autorização completa + A autorização em %s não é suportada + Géneros + Tradução + Será desconectado de todas as fontes em que estiver autorizado + Vibração + Não é possível encontrar nenhum armazenamento disponível + Categorias favoritas + Quer realmente remover a categoria \"%s\" dos seus favoritos\? +\nSerá perdido todos os mangas contidos. + Pode encontrar o que ler no menu lateral. + Pode salvá-lo a partir de fontes online ou importá-lo a partir de ficheiro. + Manga recente + Outro armazenamento + Tente reformular a consulta. + Prefira o leitor da direita para a esquerda + Não disponível + Tamanho: %s + Mangá que está a ler será afixada aqui + Ainda não tem nenhuma mangá salvo + Animação de páginas + Esta categoria está vazia + Leia mais tarde + atualizações + Todos os favoritos + À espera de rede… + Utilizar ligação segura (HTTPS) + Resultados da pesquisa + Aqui verá os novos capítulos do mangá que está a ler + Nova versão: %s + Girar a tela + Falha na verificação de atualização + Proteger aplicativo + Pedir senha no início do aplicativo + Ajustar à altura + Tema Black dark + Útil para telas AMOLED + A configuração escolhida será lembrada para este mangá + Você pode criar backup de seu histórico e favoritos e restaurá-lo + Limpar cookies + Você realmente deseja remover todas as consultas de pesquisa recentes\? Essa ação não pode ser desfeita. + Você deve autorizar a visualização deste conteúdo + Pode usar categorias para organizar seu mangá favorito. Pressione <<+>> para criar uma categoria + Você pode configurar o modo de leitura para cada mangá separadamente + Local onde serão armazenados os mangás baixados + Excluir manga NSFW da história + Formato da data + Padrão \ No newline at end of file