diff --git a/app/build.gradle b/app/build.gradle index d8ecc81bb..edcbe2105 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,15 +8,15 @@ plugins { android { compileSdk = 33 - buildToolsVersion = '33.0.1' + buildToolsVersion = '33.0.2' namespace = 'org.koitharu.kotatsu' defaultConfig { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 33 - versionCode 519 - versionName '4.4.3' + versionCode 521 + versionName '4.4.5' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -25,15 +25,6 @@ android { arg 'room.schemaLocation', "$projectDir/schemas".toString() } } - - // define this values in your local.properties file - buildConfigField 'String', 'SHIKIMORI_CLIENT_ID', "\"${localProperty('shikimori.clientId')}\"" - buildConfigField 'String', 'SHIKIMORI_CLIENT_SECRET', "\"${localProperty('shikimori.clientSecret')}\"" - buildConfigField 'String', 'ANILIST_CLIENT_ID', "\"${localProperty('anilist.clientId')}\"" - buildConfigField 'String', 'ANILIST_CLIENT_SECRET', "\"${localProperty('anilist.clientSecret')}\"" - buildConfigField 'String', 'MAL_CLIENT_ID', "\"${localProperty('mal.clientId')}\"" - resValue "string", "acra_login", "${localProperty('acra.login')}" - resValue "string", "acra_password", "${localProperty('acra.password')}" } buildTypes { debug { @@ -87,7 +78,8 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:1093584202') { + //noinspection GradleDependency + implementation('com.github.KotatsuApp:kotatsu-parsers:413f4a2f10') { exclude group: 'org.json', module: 'json' } @@ -141,7 +133,7 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.json:json:20220924' + testImplementation 'org.json:json:20230227' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' androidTestImplementation 'androidx.test:runner:1.5.2' 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 a0a2da35e..6b14c5bc0 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 @@ -138,7 +138,7 @@ class DownloadManager @AssistedInject constructor( for ((pageIndex, page) in pages.withIndex()) { runFailsafe(outState, pausingHandle) { val url = repo.getPageUrl(page) - val file = cache[url] + val file = cache.get(url) ?: downloadFile(url, page.referer, destination, tempFileName, repo.source) output.addPage( chapter = chapter, diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt index 095a55b76..246e5e931 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt @@ -4,6 +4,7 @@ import android.content.Context import com.tomclaw.cache.DiskLruCache import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.ext.copyToSuspending @@ -30,8 +31,8 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) { size = FileSize.MEGABYTES.convert(200, FileSize.BYTES), ) - operator fun get(url: String): File? { - return lruCache.get(url)?.takeIfReadable() + suspend fun get(url: String): File? = runInterruptible(Dispatchers.IO) { + lruCache.get(url)?.takeIfReadable() } suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) { 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 3fa7c48dd..496c0270e 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 @@ -5,7 +5,6 @@ import android.graphics.BitmapFactory import android.net.Uri import androidx.collection.LongSparseArray import androidx.collection.set -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,7 +12,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -54,11 +53,11 @@ class PageLoader @Inject constructor( private val tasks = LongSparseArray>() private val convertLock = Mutex() + private val prefetchLock = Mutex() private var repository: MangaRepository? = null private val prefetchQueue = LinkedList() private val counter = AtomicInteger(0) private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive - private val emptyProgressFlow: StateFlow = MutableStateFlow(-1f) override fun close() { loaderScope.cancel() @@ -71,8 +70,8 @@ class PageLoader @Inject constructor( return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() } - fun prefetch(pages: List) { - synchronized(prefetchQueue) { + fun prefetch(pages: List) = loaderScope.launch { + prefetchLock.withLock { for (page in pages.asReversed()) { if (tasks.containsKey(page.id)) { continue @@ -89,18 +88,13 @@ class PageLoader @Inject constructor( } fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred { - if (!force) { - cache[page.url]?.let { - return getCompletedTask(it) - } - } var task = tasks[page.id] if (force) { task?.cancel() } else if (task?.isCancelled == false) { return task } - task = loadPageAsyncImpl(page) + task = loadPageAsyncImpl(page, force) synchronized(tasks) { tasks[page.id] = task } @@ -130,23 +124,26 @@ class PageLoader @Inject constructor( return getRepository(page.source).getPageUrl(page) } - private fun onIdle() { - synchronized(prefetchQueue) { + private fun onIdle() = loaderScope.launch { + prefetchLock.withLock { while (prefetchQueue.isNotEmpty()) { - val page = prefetchQueue.pollFirst() ?: return - if (cache[page.url] == null) { + val page = prefetchQueue.pollFirst() ?: return@launch + if (cache.get(page.url) == null) { synchronized(tasks) { - tasks[page.id] = loadPageAsyncImpl(page) + tasks[page.id] = loadPageAsyncImpl(page, false) } - return + return@launch } } } } - private fun loadPageAsyncImpl(page: MangaPage): ProgressDeferred { + private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred { val progress = MutableStateFlow(PROGRESS_UNDEFINED) val deferred = loaderScope.async { + if (!skipCache) { + cache.get(page.url)?.let { return@async it } + } counter.incrementAndGet() try { loadPageImpl(page, progress) @@ -207,11 +204,6 @@ class PageLoader @Inject constructor( } } - private fun getCompletedTask(file: File): ProgressDeferred { - val deferred = CompletableDeferred(file) - return ProgressDeferred(deferred, emptyProgressFlow) - } - private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt index 18d484565..f4300792b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt @@ -36,6 +36,7 @@ object ScrobblingModule { @Provides @Singleton fun provideShikimoriRepository( + @ApplicationContext context: Context, @ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage, database: MangaDatabase, authenticator: ShikimoriAuthenticator, @@ -47,12 +48,13 @@ object ScrobblingModule { addInterceptor(CurlLoggingInterceptor()) } }.build() - return ShikimoriRepository(okHttp, storage, database) + return ShikimoriRepository(context, okHttp, storage, database) } @Provides @Singleton fun provideMALRepository( + @ApplicationContext context: Context, @ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage, database: MangaDatabase, authenticator: MALAuthenticator, @@ -64,12 +66,13 @@ object ScrobblingModule { addInterceptor(CurlLoggingInterceptor()) } }.build() - return MALRepository(okHttp, storage, database) + return MALRepository(context, okHttp, storage, database) } @Provides @Singleton fun provideAniListRepository( + @ApplicationContext context: Context, @ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage, database: MangaDatabase, authenticator: AniListAuthenticator, @@ -81,7 +84,7 @@ object ScrobblingModule { addInterceptor(CurlLoggingInterceptor()) } }.build() - return AniListRepository(okHttp, storage, database) + return AniListRepository(context, okHttp, storage, database) } @Provides diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt index 571a8bef0..97d93c7b1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt @@ -1,12 +1,14 @@ package org.koitharu.kotatsu.scrobbling.anilist.data +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import okhttp3.FormBody import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject -import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.parsers.exception.GraphQLException import org.koitharu.kotatsu.parsers.model.MangaChapter @@ -15,6 +17,7 @@ import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.toIntUp +import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga @@ -32,13 +35,17 @@ private const val REQUEST_MUTATION = "mutation" private const val KEY_SCORE_FORMAT = "score_format" class AniListRepository( + @ApplicationContext context: Context, private val okHttp: OkHttpClient, private val storage: ScrobblerStorage, private val db: MangaDatabase, -) : org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository { +) : ScrobblerRepository { + + private val clientId = context.getString(R.string.anilist_clientId) + private val clientSecret = context.getString(R.string.anilist_clientSecret) override val oauthUrl: String - get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.ANILIST_CLIENT_ID}&" + + get() = "${BASE_URL}oauth/authorize?client_id=$clientId&" + "redirect_uri=${REDIRECT_URI}&response_type=code" override val isAuthorized: Boolean @@ -48,8 +55,8 @@ class AniListRepository( override suspend fun authorize(code: String?) { val body = FormBody.Builder() - body.add("client_id", BuildConfig.ANILIST_CLIENT_ID) - body.add("client_secret", BuildConfig.ANILIST_CLIENT_SECRET) + body.add("client_id", clientId) + body.add("client_secret", clientSecret) if (code != null) { body.add("grant_type", "authorization_code") body.add("redirect_uri", REDIRECT_URI) diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/mal/data/MALRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/mal/data/MALRepository.kt index b4f026298..1a5b78080 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/mal/data/MALRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/mal/data/MALRepository.kt @@ -1,16 +1,19 @@ package org.koitharu.kotatsu.scrobbling.mal.data +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONObject -import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull import org.koitharu.kotatsu.parsers.util.parseJson +import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga @@ -25,17 +28,19 @@ private const val BASE_API_URL = "https://api.myanimelist.net/v2" private const val AVATAR_STUB = "https://cdn.myanimelist.net/images/questionmark_50.gif" class MALRepository( + @ApplicationContext context: Context, private val okHttp: OkHttpClient, private val storage: ScrobblerStorage, private val db: MangaDatabase, -) : org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository { +) : ScrobblerRepository { + private val clientId = context.getString(R.string.mal_clientId) private var codeVerifier: String = getPKCEChallengeCode() override val oauthUrl: String get() = "$BASE_WEB_URL/v1/oauth2/authorize?" + "response_type=code" + - "&client_id=${BuildConfig.MAL_CLIENT_ID}" + + "&client_id=$clientId" + "&redirect_uri=$REDIRECT_URI" + "&code_challenge=$codeVerifier" + "&code_challenge_method=plain" @@ -51,7 +56,7 @@ class MALRepository( override suspend fun authorize(code: String?) { val body = FormBody.Builder() if (code != null) { - body.add("client_id", BuildConfig.MAL_CLIENT_ID) + body.add("client_id", clientId) body.add("grant_type", "authorization_code") body.add("code", code) body.add("redirect_uri", REDIRECT_URI) @@ -205,5 +210,4 @@ class MALRepository( avatar = json.getString("picture") ?: AVATAR_STUB, service = ScrobblerService.MAL, ) - } diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt index ecee1125a..fb843b20e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt @@ -1,11 +1,13 @@ package org.koitharu.kotatsu.scrobbling.shikimori.data +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext import okhttp3.FormBody import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import org.json.JSONObject -import org.koitharu.kotatsu.BuildConfig +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.util.await @@ -28,13 +30,17 @@ private const val BASE_URL = "https://shikimori.one/" private const val MANGA_PAGE_SIZE = 10 class ShikimoriRepository( + @ApplicationContext context: Context, private val okHttp: OkHttpClient, private val storage: ScrobblerStorage, private val db: MangaDatabase, ) : ScrobblerRepository { + private val clientId = context.getString(R.string.shikimori_clientId) + private val clientSecret = context.getString(R.string.shikimori_clientSecret) + override val oauthUrl: String - get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.SHIKIMORI_CLIENT_ID}&" + + get() = "${BASE_URL}oauth/authorize?client_id=$clientId&" + "redirect_uri=$REDIRECT_URI&response_type=code&scope=" override val isAuthorized: Boolean @@ -42,8 +48,8 @@ class ShikimoriRepository( override suspend fun authorize(code: String?) { val body = FormBody.Builder() - body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID) - body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET) + body.add("client_id", clientId) + body.add("client_secret", clientSecret) if (code != null) { body.add("grant_type", "authorization_code") body.add("redirect_uri", REDIRECT_URI) @@ -98,13 +104,13 @@ class ShikimoriRepository( return if (pageOffset != 0) list.drop(pageOffset) else list } - override suspend fun createRate(mangaId: Long, shikiMangaId: Long) { + override suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) { val user = cachedUser ?: loadUser() val payload = JSONObject() payload.put( "user_rate", JSONObject().apply { - put("target_id", shikiMangaId) + put("target_id", scrobblerMangaId) put("target_type", "Manga") put("user_id", user.id) }, diff --git a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 71d4925d8..79d8a1d90 100644 --- a/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -269,4 +269,4 @@ class SyncHelper( private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt index a78792727..a38596cb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/HttpExt.kt @@ -3,8 +3,9 @@ package org.koitharu.kotatsu.utils.ext import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response +import okhttp3.internal.closeQuietly +import okio.IOException import org.json.JSONObject -import org.koitharu.kotatsu.parsers.util.parseJson import java.net.HttpURLConnection private val TYPE_JSON = "application/json".toMediaType() @@ -12,9 +13,13 @@ private val TYPE_JSON = "application/json".toMediaType() fun JSONObject.toRequestBody() = toString().toRequestBody(TYPE_JSON) fun Response.parseJsonOrNull(): JSONObject? { - return if (code == HttpURLConnection.HTTP_NO_CONTENT) { - null - } else { - parseJson() + return try { + when { + !isSuccessful -> throw IOException(body?.string()) + code == HttpURLConnection.HTTP_NO_CONTENT -> null + else -> JSONObject(body?.string() ?: return null) + } + } finally { + closeQuietly() } } diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 4f2580543..b2680bc63 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -41,7 +41,7 @@ Ang manga na ito ay may %s. I-save ang lahat ng ito\? Mga abiso %1$d ng %2$d sa - Bagong mga kabanata + Mga bagong kabanata Basahin mula sa simula I-restart Mga kategorya… diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 4e8b326f2..589bfec4a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -5,8 +5,8 @@ Penyimpanan lokal Favorit Riwayat - Terjadi galat - Tidak bisa menyambung ke internet + Terjadi kesalahan + Kesalahan jaringan Detail Kisi Mode daftar @@ -17,7 +17,7 @@ Bab %1$d dari %2$d Tutup Coba lagi - Tidak ditemukan apa-apa + Tidak ada yang ditemukan Belum ada riwayat Baca Belum ada favorit @@ -39,9 +39,9 @@ Populer Diperbarui Terbaru - Rating + Peringkat Urutan penyortiran - Filter + Saring Tema Terang Gelap @@ -61,9 +61,9 @@ Disimpan Bagikan gambar Impor - Riwayat dan tembolok - Bersihkan tembolok halaman - Tembolok + Riwayat dan cache + Bersihkan cache halaman + Cache B|kB|MB|GB|TB Standar Ukuran kisi @@ -84,7 +84,7 @@ Ini mungkin akan mentransfer banyak data Jangan tanya lagi Membatalkan… - Bersihkan tembolok keluku + Bersihkan cache gambar mini Bersihkan riwayat pencarian Dibersihkan Domain @@ -103,7 +103,7 @@ Kategori… Ubah Nama Hapus kategori \"%s\" dari favorit Anda\? -\nSemua manga di situ akan hilang. +\nSemua manga di dalamnya akan hilang. Sepi juga di sini… Anda bisa menggunakan kategori untuk mengelola favorit Anda. Tekan «+» untuk membuat kategori Apa yang Anda baca akan ditampilkan di sini @@ -139,10 +139,10 @@ Dipulihkan Buat isu di GitHub Semua data dipulihkan - Data berhasil dipulihkan, tapi ada galat + Data berhasil dipulihkan, tapi ada kesalahan Anda bisa membuat cadangan riwayat dan favorit Anda dan memulihkannya Baru saja - Dulu kala + Lama Kelompok Hari ini Ketuk untuk coba lagi @@ -156,7 +156,7 @@ Bersihkan umpan Periksa bab baru Masuk untuk melihat konten ini - …dan %1$d lagi + …dan %1$d lainnya Selanjutnya Masukkan kata sandi untuk memulai aplikasi Konfirmasi @@ -175,7 +175,7 @@ Anda akan keluar dari semua sumber Genre Selesai - Sedang dibaca + Berlanjut Format tanggal Standar Kecualikan manga NSFW dari riwayat @@ -217,14 +217,14 @@ Tentukan genre yang Anda tidak ingin lihat di saran Hapus yang dipilih dari perangkat secara permanen\? Selesai menghapus - Apakah Anda yakin ingin mengunduh semua manga yang dipilih dengan semua babnya\? Tindakan ini akan memakan banyak data dan penyimpanan + Unduh semua manga yang dipilih dan babnya\? Ini dapat mengkonsumsi banyak lalu lintas jaringan dan penyimpanan. Unduhan paralel - Perlambatan unduhan - Saran diperbarui + Lambatkan unduhan + Pembaruan saran Membantu menghidari pemblokiran alamat IP Anda Bab akan dihapus di latar belakang. Akan memakan beberapa waktu Sembunyikan - Sumber manga baru tersedia + Tersedia sumber manga baru Periksa bab baru dan beri tahu tentang itu Anda akan menerima pemberitahuan tentang pembaruan manga yang Anda baca Anda tidak akan menerima pemberitahuan, tapi bab baru akan disorot di daftar @@ -250,7 +250,7 @@ Kategori favorit Hapus Bersihkan umpan pembaruan - Kanan ke kiri (←) + Kanan ke kiri Menunggu jaringan… Putar layar Pembaruan umpan akan dimulai @@ -262,7 +262,7 @@ Versi %s Penyimpanan internal Penyimpanan eksternal - Galat + Kesalahan Memeriksa pembaruan… Tidak ada pembaruan yang tersedia Menggunakan daya lebih sedikit pada layar AMOLED @@ -286,8 +286,8 @@ Cocokkan dengan tinggi Cocokkan dengan lebar Balik - Diantrikan - Berhasil Diotorisasi + Antri + Diotorisasi Simpan dari sumber daring atau berkas impor. Manga Anda akan ditampilkan di sini Cari apa untuk dibaca di bagian «Jelajah» @@ -328,8 +328,8 @@ Konten tidak ditemukan atau dihapus Tekan Kembali dua kali untuk keluar dari aplikasi Konfirmasi keluar - Tembolok halaman - Tembolok lainnya + Cache halaman + Cache lainnya Penggunaan penyimpanan Tersedia Mode Incognito @@ -339,12 +339,12 @@ Folder dengan gambar Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang Umpan - Detail galat:<br><tt>%1$s</tt><br><br>1. Coba untuk <a href=%2$s>membuka manga di peramban web</a> untuk memastikan bahwa itu tersedia di sumbernya<br>2. Jika tersedia, kirim laporan galat ke pengembang. + Detail kesalahan:<br><tt>%1$s</tt><br><br>1. Coba untuk <a href=%2$s>membuka manga di peramban web</a> untuk memastikan bahwa itu tersedia di sumbernya<br>2. Jika tersedia, kirim laporan kesalahan ke pengembang. Kecerahan Kontras Atur Ulang Pengaturan warna yang dipilih akan diingat untuk manga ini - Anda memiliki perubahan belum tersimpan, apakah Anda ingin menyimpannya atau membuangnya\? + Menyimpan atau membuang perubahan yang belum disimpan\? Buang Gunakan sidik jari jika tersedia Manga dari favorit Anda @@ -373,4 +373,52 @@ DNS melalui HTTPS Istirahat Mati + Mamimi + Kesalahan sisi server (%1$d). Silakan coba lagi nanti + kompak + Pramuat konten + %s - %s + Tidak ada apapun di sini + Ketuk di tepi kanan atau menekan tombol kanan akan selalu beralih ke halaman berikutnya + Sumber dinonaktifkan + Tandai sebagai saat ini + Tampilkan konten yang mencurigakan + Untuk melacak kemajuan membaca, pilih Menu → Lacak di layar detail manga. + Layanan + Juga informasi yang jelas tentang bab baru + Nyalakan Wi-Fi atau jaringan seluler untuk membaca manga online + Tajuk Agen Pengguna + Mulai ulang aplikasi untuk menerapkan perubahan ini + Kanade + Bagikan log + Tidak ada ruang tersisa di perangkat + Izinkan pembaruan yang tidak stabil + Usulkan pembaruan ke versi beta + Unduh dimulai + Tampilkan indikator kemajuan membaca + Tampilkan persentase baca dalam riwayat dan favorit + Bahasa + Cobalah untuk merubah kueri. + Tetap di awal + %ss + Bab. %1$d/%2$d Hal. %3$d/%4$d + Aktifkan pencatatan + Rekam beberapa tindakan untuk tujuan debug + Dinamis + Skema warna + Perlihatkan dalam tampilan kisi + Miku + Asuka + Mion + Rikka + Sakura + Pemrosesan manga tersimpan + Nonaktifkan pengoptimalan baterai + Kontrol pembaca ergonomis + Koreksi warna + Perlihatkan penggeser peralihan halaman + Zoom webtoon + Izinkan gerakan memperbesar/memperkecil tampilan dalam mode webtoon (beta) + Berbagai bahasa + Jaringan tidak tersedia \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 69701e235..e1243092d 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -86,7 +86,7 @@ 웹툰 검색 기록 지우기 읽기 모드 - 이 동작은 많은 데이터 사용을 + 이 동작은 많은 데이터 사용을 야기할 수 있습니다 썸네일 캐시 지우기 다시 묻지 않음 취소 중… @@ -105,4 +105,250 @@ 나중에 읽기 검색 결과 크기: %s + 계산중… + 지우기 + 모든 기록을 영구적으로 삭제하시겠어요\? + 재시작 + 카테고리를 이용하여 선호작을 관리하세요. «+»을 눌러 카테고리를 만들 수 있습니다 + 먼저 아무거나 저장해보세요 + 여기서 읽고 있는 만화의 새로운 챕터들을 확인할 수 있습니다 + 연관된 + 앱 잠금 활성화 + Kotatsu를 실행할 때마다 비밀번호 묻기 + 업데이트 확인하기 + 업데이트를 찾을 수 없음 + 오른쪽에서 왼쪽 + 가운데 맞춤 + 가능한 업데이트 없음 + 세로 맞춤 + 탭해서 재시도 + 백업한 데이터 복원하기 + 백업하기 + 복원됨 + 준비중… + 환영합니다 + 인증됨 + 이름을 입력해야 합니다 + 피드백 + 번역 + 이 앱을 번역하기 + 기기의 배경화면 색상을 기반으로 한 테마 + 성인 컨텐츠에서만 차단 + 항상 허용 + 성인 만화(NSFW)는 추천하지 않기 + 비활성화됨 + 장르 목록을 불러올 수 없음 + 이 만화는 챕터로 나눠져 있지 않습니다 + 챕터 찾아보기 + 챕터들이 백그라운드에서 제거됩니다. 이 작업은 많은 시간이 소요될 수 있습니다 + IP 차단을 회피할 수 있게 합니다 + 새로운 챕터가 나오면 알려주기 + 스탠다드 + 온라인 소스 혹은 직접 파일을 불러와 저장하기. + 모든 데이터는 기기 안에서만 분석 및 사용되며 어떠한 서드파티 서비스들과도 공유되지 않습니다 + 당신의 선호도를 바탕으로 만화를 추천합니다 + 북마크에 추가 + 북마크 제거 + 북마크 + 데이터 삭제 + 만화 소스 사이트 없음 + 체인지로그 + 최근에 본 만화 + 오류 발생 + 세부정보 + 챕터 + 자세한 목록 + 설정 + 그리드 + 소스 사이트 관리 + 기록 삭제 + 추가 + 아직 기록이 없습니다 + 카테고리 이름 추가 + 바로가기 추가… + 테마 + \"%s\"가 라이브러리에서 삭제됨 + \"%s\"가 로컬 저장소에서 삭제됨 + 지원되지 않는 동작입니다 + 설명없음 + 계속 + 삭제됨 + 제스쳐만 사용 + 새로운 챕터 + 이 항목은 비어있는 것 같습니다… + 최근에 추가된 + 다운로드를 저장하기 위한 폴더 + 책장 + 기타 저장소 + 삭제됨 + 업데이트 + 피드가 곧 업데이트 됩니다 + 확인하지 않기 + 비밀번호를 입력하세요 + 잘못된 비밀번호 + 화면 회전 + 업데이트 확인하기 + 비밀번호 재입력 + 일치하지 않는 비밀번호 + %s 버전 + 정보 + 업데이트 확인중… + 새로운 카테고리 + 확대 설정 + 가로 맞춤 + 검은색 + AMOLED 화면에서의 전력소모를 줄입니다 + 백업 및 복원 + 파일을 찾을 수 없음 + 모든 데이터 복원됨 + 데이터가 복원되었지만 오류가 존재합니다 + 선호작이나 읽은 기록들을 백업 및 복원할 수 있습니다 + 어제 + 오래전 + 그룹 + 오늘 + CAPTCHA 설정이 필요합니다 + 해결됨 + 쿠키 삭제 + 첫 칸에 맞춤 + Github에 문제 제기하기 + 무음 + 선택된 설정값이 항상 이 만화에 적용됩니다 + 모든 쿠키가 삭제되었습니다 + 모든 업데이트 기록을 영구적으로 삭제하시겠습니까\? + 새로운 챕터 확인중: %2$d의 %1$d + 피드 정리 + 새로운 챕터 확인하기 + 로그인 + 로그인이 필요합니다 + 기본값: %s + 다음 + 비밀번호를 입력하세요 + 확인 + 최소 4자리 이상의 비밀번호를 입력해 주세요 + 백업 저장됨 + 몇몇 기기들은 시스템이 백그라운드 작업을 방해할 수 있습니다. + 다운로드 중인 도서 없음 + …그리고 %1$d만큼 더 + %s에 대해서만 검색하기 + 모든 검색 기록을 영구적으로 삭제 하시겠어요\? + 기타 + 더 읽기 + 대기열 + 이 챕터는 다운로드 하거나 온라인으로 읽어야 합니다. + 모든 사이트에서 로그아웃됩니다 + 장르 + 완료됨 + 날짜 초기화 + 기본 + 기록에서 성인 만화(NSFW) 제외하기 + 찾으시는 챕터가 존재하지 않습니다 + 4PDA에 토픽 생성하기 + %s에 로그인은 지원되지 않습니다 + 사용 가능한 소스 사이트 + 동적 테마 + 스크린샷 규칙 + 항상 차단 + 추천 + 추천 켜기 + 만화를 추가하는 중: %2$d의 %1$d + 아무 만화나 읽어보세요 당신의 기록을 바탕으로 개인화된 추천 만화를 제공합니다 + 활성화됨 + 필터 초기화 + 장르 찾기 + 와이파이에 연결된 경우에만 + 항상 + 무슨 언어의 만화를 읽을지 선택하세요. 나중에 설정에서 이를 변경할 수 있습니다. + 18+ + %1$s%% + 컨텐츠 + 페이지 미리 로드하기 + %s로 로그인 됨 + 외관 + 제외할 장르 선택 + 추천 목록에서 보고 싶지 않은 장르를 특정합니다 + 선택된 항목을 기기에서 영구히 제거하시겠어요\? + 제거 완료 + 선택된 모든 만화와 챕터들을 다운로드 받으시겠어요\? 많은 데이터와 기기 저장소 용량을 소비합니다. + 추천 항목 업데이트 중 + 취소됨 + 이름 + 배터리 최적화 해제하기 + 백그라운드에서의 업데이트 확인이 안드로이드 시스템에 의해 중지되지 않습니다 + 보내기 + 가능할 경우 지문 사용 + 범위 선택 + 기록 삭제됨 + 랜덤 + 컨텐츠를 찾을 수 없거나 제거되었습니다 + 만화 다운로드 중 + 사생활 보호 모드 + 애플리케이션 업데이트가 가능합니다: %s + 챕터 없음 + 자동 스크롤 + 리더 안에서 만화 정보 보여주기 + 피드 + 읽고 있는 만화의 업데이트 알림을 수신할 수 있게 됩니다 + 알림은 받지 못하지만 여전히 새로운 챕터들이 목록 상에서 강조 표시되어 보여집니다 + 알림 활성화 + 수정 + 카테고리 수정 + 새로운 소식 추적 + 선호 표시된 카테고리 없음 + 로그아웃 + 북마크 제거됨 + 북마크 추가됨 + 실행취소 + 기록에서 지우기 + DNS over HTTPS 활성화 + 기본 모드 + 왭툰 자동 감지 + 오류. 저희가 고칠 수 있게 버그 정보를 보내주세요. + 계획됨 + 읽는 중 + 다시 읽는 중 + 완료됨 + 모두 비활성화 + 선호작 목록에 존재하는 만화 + 최근에 본 만화 + 제보하기 + 읽기 진행 상황 표시 + 성인 만화는 읽은 기록에 포함되지 않으며 읽기 진행 상황은 저장되지 않습니다 + 모두 보여주기 + 유효하지 않은 도메인 + 모든 기록 삭제 + 지난 2시간 + 관리 + 아직 추가된 븍마크가 없습니다 + 만화를 읽는 도중 북마크를 추가할 수 있습니다 + 북마크 제거됨 + 만화를 읽기 위해 만화 소스 사이트를 활성화 하세요 + 정말 선택된 즐겨 찾는 카테고리를 삭제하시겠습니까\? +\n해당 카테고리의 모든 만화가 손실되며 취소할 수 없습니다. + 비어있음 + 뒤로가기 버튼을 다시 눌러 나가기 + 뒤로가기 버튼을 두 번 눌러 앱을 종료할 수 있습니다 + 앱 종료 확인 + 저장된 만화 + 페이지 캐시 + 기타 캐시 + 저장소 사용량 + %s -%s + 이메일을 입력하여 계속 + 즐겨찾기 목록에서 제거됨 + \"%s\"에서 제거됨 + 챕터.%1$d/%2$d 페이지.%3$d/%4$d + 코믹스 모음 + 가져오기 완료 + 본 파일을 제거함으로써 저장소 공간을 아낄 수 있습니다 + 가져오기가 곧 시작됩니다 + 최근 추가된 만화 바로가기 보여주기 + 숨기기 + 새로운 만화 소스 사이트 사용 가능 + 계정이 이미 존재합니다 + 뒤로가기 + 동기화 + 데이터 동기화 하기 + 이메일을 입력하여 계속 + 다운로드 속도 늦추기 \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 42d3adde8..fd68659bf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -410,4 +410,7 @@ Rikka Sakura Fonte desativada + Gravar algumas ações para propósitos de depuração + Mamimi + Miku \ No newline at end of file diff --git a/app/src/main/res/values-ru/plurals.xml b/app/src/main/res/values-ru/plurals.xml index fe5761afa..ca03bb638 100644 --- a/app/src/main/res/values-ru/plurals.xml +++ b/app/src/main/res/values-ru/plurals.xml @@ -12,7 +12,7 @@ %1$d новая глава - %1$d новых главы + %1$d новые главы %1$d новых глав %1$d новых глав diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index 44bc52eed..8838eeedd 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -9,6 +9,13 @@ https://acra.rumblur.space/report org.kotatsu.sync http://86.57.183.214:8081 + Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU + euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8 + 9887 + wrMqFosItQWsmB8dtAHfIFPDt15FfQi2ZGiKkJoW + 6cd8e6349e9a36bc1fc1ab97703c9fd1 + SxhkCVnqVLbGogvi + xPDACTLHnHU9Nfjv -1 1 diff --git a/build.gradle b/build.gradle index db65af4c4..45e8be05e 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' + classpath 'com.android.tools.build:gradle:7.4.2' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10' classpath 'com.google.dagger:hilt-android-gradle-plugin:2.45' } @@ -20,15 +20,6 @@ allprojects { } } -String localProperty(String name, String defaultValue = 'null') { - Properties localProperties = new Properties() - project.rootProject.file('local.properties').withInputStream { localProperties.load(it) } - - def value = localProperties[name] - - return value != null ? value : defaultValue -} - String currentBranch() { def branchName = "" try {