Merge branch 'devel' into release/5

This commit is contained in:
Koitharu
2023-03-11 08:21:00 +02:00
17 changed files with 409 additions and 104 deletions

View File

@@ -8,15 +8,15 @@ plugins {
android { android {
compileSdk = 33 compileSdk = 33
buildToolsVersion = '33.0.1' buildToolsVersion = '33.0.2'
namespace = 'org.koitharu.kotatsu' namespace = 'org.koitharu.kotatsu'
defaultConfig { defaultConfig {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 33
versionCode 519 versionCode 521
versionName '4.4.3' versionName '4.4.5'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -25,15 +25,6 @@ android {
arg 'room.schemaLocation', "$projectDir/schemas".toString() 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 { buildTypes {
debug { debug {
@@ -87,7 +78,8 @@ afterEvaluate {
} }
} }
dependencies { dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:1093584202') { //noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:413f4a2f10') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
@@ -141,7 +133,7 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
testImplementation 'junit:junit:4.13.2' 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' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:runner:1.5.2'

View File

@@ -138,7 +138,7 @@ class DownloadManager @AssistedInject constructor(
for ((pageIndex, page) in pages.withIndex()) { for ((pageIndex, page) in pages.withIndex()) {
runFailsafe(outState, pausingHandle) { runFailsafe(outState, pausingHandle) {
val url = repo.getPageUrl(page) val url = repo.getPageUrl(page)
val file = cache[url] val file = cache.get(url)
?: downloadFile(url, page.referer, destination, tempFileName, repo.source) ?: downloadFile(url, page.referer, destination, tempFileName, repo.source)
output.addPage( output.addPage(
chapter = chapter, chapter = chapter,

View File

@@ -4,6 +4,7 @@ import android.content.Context
import com.tomclaw.cache.DiskLruCache import com.tomclaw.cache.DiskLruCache
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.FileSize
import org.koitharu.kotatsu.utils.ext.copyToSuspending 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), size = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
) )
operator fun get(url: String): File? { suspend fun get(url: String): File? = runInterruptible(Dispatchers.IO) {
return lruCache.get(url)?.takeIfReadable() lruCache.get(url)?.takeIfReadable()
} }
suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) { suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) {

View File

@@ -5,7 +5,6 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.collection.set import androidx.collection.set
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -13,7 +12,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -54,11 +53,11 @@ class PageLoader @Inject constructor(
private val tasks = LongSparseArray<ProgressDeferred<File, Float>>() private val tasks = LongSparseArray<ProgressDeferred<File, Float>>()
private val convertLock = Mutex() private val convertLock = Mutex()
private val prefetchLock = Mutex()
private var repository: MangaRepository? = null private var repository: MangaRepository? = null
private val prefetchQueue = LinkedList<MangaPage>() private val prefetchQueue = LinkedList<MangaPage>()
private val counter = AtomicInteger(0) private val counter = AtomicInteger(0)
private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive
private val emptyProgressFlow: StateFlow<Float> = MutableStateFlow(-1f)
override fun close() { override fun close() {
loaderScope.cancel() loaderScope.cancel()
@@ -71,8 +70,8 @@ class PageLoader @Inject constructor(
return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled()
} }
fun prefetch(pages: List<ReaderPage>) { fun prefetch(pages: List<ReaderPage>) = loaderScope.launch {
synchronized(prefetchQueue) { prefetchLock.withLock {
for (page in pages.asReversed()) { for (page in pages.asReversed()) {
if (tasks.containsKey(page.id)) { if (tasks.containsKey(page.id)) {
continue continue
@@ -89,18 +88,13 @@ class PageLoader @Inject constructor(
} }
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> { fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> {
if (!force) {
cache[page.url]?.let {
return getCompletedTask(it)
}
}
var task = tasks[page.id] var task = tasks[page.id]
if (force) { if (force) {
task?.cancel() task?.cancel()
} else if (task?.isCancelled == false) { } else if (task?.isCancelled == false) {
return task return task
} }
task = loadPageAsyncImpl(page) task = loadPageAsyncImpl(page, force)
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = task tasks[page.id] = task
} }
@@ -130,23 +124,26 @@ class PageLoader @Inject constructor(
return getRepository(page.source).getPageUrl(page) return getRepository(page.source).getPageUrl(page)
} }
private fun onIdle() { private fun onIdle() = loaderScope.launch {
synchronized(prefetchQueue) { prefetchLock.withLock {
while (prefetchQueue.isNotEmpty()) { while (prefetchQueue.isNotEmpty()) {
val page = prefetchQueue.pollFirst() ?: return val page = prefetchQueue.pollFirst() ?: return@launch
if (cache[page.url] == null) { if (cache.get(page.url) == null) {
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = loadPageAsyncImpl(page) tasks[page.id] = loadPageAsyncImpl(page, false)
} }
return return@launch
} }
} }
} }
} }
private fun loadPageAsyncImpl(page: MangaPage): ProgressDeferred<File, Float> { private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred<File, Float> {
val progress = MutableStateFlow(PROGRESS_UNDEFINED) val progress = MutableStateFlow(PROGRESS_UNDEFINED)
val deferred = loaderScope.async { val deferred = loaderScope.async {
if (!skipCache) {
cache.get(page.url)?.let { return@async it }
}
counter.incrementAndGet() counter.incrementAndGet()
try { try {
loadPageImpl(page, progress) loadPageImpl(page, progress)
@@ -207,11 +204,6 @@ class PageLoader @Inject constructor(
} }
} }
private fun getCompletedTask(file: File): ProgressDeferred<File, Float> {
val deferred = CompletableDeferred(file)
return ProgressDeferred(deferred, emptyProgressFlow)
}
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
CoroutineExceptionHandler { CoroutineExceptionHandler {

View File

@@ -36,6 +36,7 @@ object ScrobblingModule {
@Provides @Provides
@Singleton @Singleton
fun provideShikimoriRepository( fun provideShikimoriRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage, @ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage,
database: MangaDatabase, database: MangaDatabase,
authenticator: ShikimoriAuthenticator, authenticator: ShikimoriAuthenticator,
@@ -47,12 +48,13 @@ object ScrobblingModule {
addInterceptor(CurlLoggingInterceptor()) addInterceptor(CurlLoggingInterceptor())
} }
}.build() }.build()
return ShikimoriRepository(okHttp, storage, database) return ShikimoriRepository(context, okHttp, storage, database)
} }
@Provides @Provides
@Singleton @Singleton
fun provideMALRepository( fun provideMALRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage, @ScrobblerType(ScrobblerService.MAL) storage: ScrobblerStorage,
database: MangaDatabase, database: MangaDatabase,
authenticator: MALAuthenticator, authenticator: MALAuthenticator,
@@ -64,12 +66,13 @@ object ScrobblingModule {
addInterceptor(CurlLoggingInterceptor()) addInterceptor(CurlLoggingInterceptor())
} }
}.build() }.build()
return MALRepository(okHttp, storage, database) return MALRepository(context, okHttp, storage, database)
} }
@Provides @Provides
@Singleton @Singleton
fun provideAniListRepository( fun provideAniListRepository(
@ApplicationContext context: Context,
@ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage, @ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage,
database: MangaDatabase, database: MangaDatabase,
authenticator: AniListAuthenticator, authenticator: AniListAuthenticator,
@@ -81,7 +84,7 @@ object ScrobblingModule {
addInterceptor(CurlLoggingInterceptor()) addInterceptor(CurlLoggingInterceptor())
} }
}.build() }.build()
return AniListRepository(okHttp, storage, database) return AniListRepository(context, okHttp, storage, database)
} }
@Provides @Provides

View File

@@ -1,12 +1,14 @@
package org.koitharu.kotatsu.scrobbling.anilist.data package org.koitharu.kotatsu.scrobbling.anilist.data
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject 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.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.exception.GraphQLException import org.koitharu.kotatsu.parsers.exception.GraphQLException
import org.koitharu.kotatsu.parsers.model.MangaChapter 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.json.mapJSON
import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.parsers.util.toIntUp 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.ScrobblerStorage
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga 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" private const val KEY_SCORE_FORMAT = "score_format"
class AniListRepository( class AniListRepository(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient, private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage, private val storage: ScrobblerStorage,
private val db: MangaDatabase, 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 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" "redirect_uri=${REDIRECT_URI}&response_type=code"
override val isAuthorized: Boolean override val isAuthorized: Boolean
@@ -48,8 +55,8 @@ class AniListRepository(
override suspend fun authorize(code: String?) { override suspend fun authorize(code: String?) {
val body = FormBody.Builder() val body = FormBody.Builder()
body.add("client_id", BuildConfig.ANILIST_CLIENT_ID) body.add("client_id", clientId)
body.add("client_secret", BuildConfig.ANILIST_CLIENT_SECRET) body.add("client_secret", clientSecret)
if (code != null) { if (code != null) {
body.add("grant_type", "authorization_code") body.add("grant_type", "authorization_code")
body.add("redirect_uri", REDIRECT_URI) body.add("redirect_uri", REDIRECT_URI)

View File

@@ -1,16 +1,19 @@
package org.koitharu.kotatsu.scrobbling.mal.data package org.koitharu.kotatsu.scrobbling.mal.data
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONObject 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.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull import org.koitharu.kotatsu.parsers.util.json.mapJSONNotNull
import org.koitharu.kotatsu.parsers.util.parseJson 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.ScrobblerStorage
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.common.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga 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" private const val AVATAR_STUB = "https://cdn.myanimelist.net/images/questionmark_50.gif"
class MALRepository( class MALRepository(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient, private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage, private val storage: ScrobblerStorage,
private val db: MangaDatabase, 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() private var codeVerifier: String = getPKCEChallengeCode()
override val oauthUrl: String override val oauthUrl: String
get() = "$BASE_WEB_URL/v1/oauth2/authorize?" + get() = "$BASE_WEB_URL/v1/oauth2/authorize?" +
"response_type=code" + "response_type=code" +
"&client_id=${BuildConfig.MAL_CLIENT_ID}" + "&client_id=$clientId" +
"&redirect_uri=$REDIRECT_URI" + "&redirect_uri=$REDIRECT_URI" +
"&code_challenge=$codeVerifier" + "&code_challenge=$codeVerifier" +
"&code_challenge_method=plain" "&code_challenge_method=plain"
@@ -51,7 +56,7 @@ class MALRepository(
override suspend fun authorize(code: String?) { override suspend fun authorize(code: String?) {
val body = FormBody.Builder() val body = FormBody.Builder()
if (code != null) { if (code != null) {
body.add("client_id", BuildConfig.MAL_CLIENT_ID) body.add("client_id", clientId)
body.add("grant_type", "authorization_code") body.add("grant_type", "authorization_code")
body.add("code", code) body.add("code", code)
body.add("redirect_uri", REDIRECT_URI) body.add("redirect_uri", REDIRECT_URI)
@@ -205,5 +210,4 @@ class MALRepository(
avatar = json.getString("picture") ?: AVATAR_STUB, avatar = json.getString("picture") ?: AVATAR_STUB,
service = ScrobblerService.MAL, service = ScrobblerService.MAL,
) )
} }

View File

@@ -1,11 +1,13 @@
package org.koitharu.kotatsu.scrobbling.shikimori.data package org.koitharu.kotatsu.scrobbling.shikimori.data
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.json.JSONObject 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.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.await 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 private const val MANGA_PAGE_SIZE = 10
class ShikimoriRepository( class ShikimoriRepository(
@ApplicationContext context: Context,
private val okHttp: OkHttpClient, private val okHttp: OkHttpClient,
private val storage: ScrobblerStorage, private val storage: ScrobblerStorage,
private val db: MangaDatabase, private val db: MangaDatabase,
) : ScrobblerRepository { ) : ScrobblerRepository {
private val clientId = context.getString(R.string.shikimori_clientId)
private val clientSecret = context.getString(R.string.shikimori_clientSecret)
override val oauthUrl: String 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=" "redirect_uri=$REDIRECT_URI&response_type=code&scope="
override val isAuthorized: Boolean override val isAuthorized: Boolean
@@ -42,8 +48,8 @@ class ShikimoriRepository(
override suspend fun authorize(code: String?) { override suspend fun authorize(code: String?) {
val body = FormBody.Builder() val body = FormBody.Builder()
body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID) body.add("client_id", clientId)
body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET) body.add("client_secret", clientSecret)
if (code != null) { if (code != null) {
body.add("grant_type", "authorization_code") body.add("grant_type", "authorization_code")
body.add("redirect_uri", REDIRECT_URI) body.add("redirect_uri", REDIRECT_URI)
@@ -98,13 +104,13 @@ class ShikimoriRepository(
return if (pageOffset != 0) list.drop(pageOffset) else list 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 user = cachedUser ?: loadUser()
val payload = JSONObject() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
JSONObject().apply { JSONObject().apply {
put("target_id", shikiMangaId) put("target_id", scrobblerMangaId)
put("target_type", "Manga") put("target_type", "Manga")
put("user_id", user.id) put("user_id", user.id)
}, },

View File

@@ -269,4 +269,4 @@ class SyncHelper(
private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject private fun JSONObject.removeJSONObject(name: String) = remove(name) as JSONObject
private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray private fun JSONObject.removeJSONArray(name: String) = remove(name) as JSONArray
} }

View File

@@ -3,8 +3,9 @@ package org.koitharu.kotatsu.utils.ext
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.internal.closeQuietly
import okio.IOException
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.util.parseJson
import java.net.HttpURLConnection import java.net.HttpURLConnection
private val TYPE_JSON = "application/json".toMediaType() 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 JSONObject.toRequestBody() = toString().toRequestBody(TYPE_JSON)
fun Response.parseJsonOrNull(): JSONObject? { fun Response.parseJsonOrNull(): JSONObject? {
return if (code == HttpURLConnection.HTTP_NO_CONTENT) { return try {
null when {
} else { !isSuccessful -> throw IOException(body?.string())
parseJson() code == HttpURLConnection.HTTP_NO_CONTENT -> null
else -> JSONObject(body?.string() ?: return null)
}
} finally {
closeQuietly()
} }
} }

View File

@@ -41,7 +41,7 @@
<string name="large_manga_save_confirm">Ang manga na ito ay may %s. I-save ang lahat ng ito\?</string> <string name="large_manga_save_confirm">Ang manga na ito ay may %s. I-save ang lahat ng ito\?</string>
<string name="notifications">Mga abiso</string> <string name="notifications">Mga abiso</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d ng %2$d sa</string> <string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d ng %2$d sa</string>
<string name="new_chapters">Bagong mga kabanata</string> <string name="new_chapters">Mga bagong kabanata</string>
<string name="read_from_start">Basahin mula sa simula</string> <string name="read_from_start">Basahin mula sa simula</string>
<string name="restart">I-restart</string> <string name="restart">I-restart</string>
<string name="categories_">Mga kategorya…</string> <string name="categories_">Mga kategorya…</string>

View File

@@ -5,8 +5,8 @@
<string name="local_storage">Penyimpanan lokal</string> <string name="local_storage">Penyimpanan lokal</string>
<string name="favourites">Favorit</string> <string name="favourites">Favorit</string>
<string name="history">Riwayat</string> <string name="history">Riwayat</string>
<string name="error_occurred">Terjadi galat</string> <string name="error_occurred">Terjadi kesalahan</string>
<string name="network_error">Tidak bisa menyambung ke internet</string> <string name="network_error">Kesalahan jaringan</string>
<string name="details">Detail</string> <string name="details">Detail</string>
<string name="grid">Kisi</string> <string name="grid">Kisi</string>
<string name="list_mode">Mode daftar</string> <string name="list_mode">Mode daftar</string>
@@ -17,7 +17,7 @@
<string name="chapter_d_of_d">Bab %1$d dari %2$d</string> <string name="chapter_d_of_d">Bab %1$d dari %2$d</string>
<string name="close">Tutup</string> <string name="close">Tutup</string>
<string name="try_again">Coba lagi</string> <string name="try_again">Coba lagi</string>
<string name="nothing_found">Tidak ditemukan apa-apa</string> <string name="nothing_found">Tidak ada yang ditemukan</string>
<string name="history_is_empty">Belum ada riwayat</string> <string name="history_is_empty">Belum ada riwayat</string>
<string name="read">Baca</string> <string name="read">Baca</string>
<string name="you_have_not_favourites_yet">Belum ada favorit</string> <string name="you_have_not_favourites_yet">Belum ada favorit</string>
@@ -39,9 +39,9 @@
<string name="popular">Populer</string> <string name="popular">Populer</string>
<string name="updated">Diperbarui</string> <string name="updated">Diperbarui</string>
<string name="newest">Terbaru</string> <string name="newest">Terbaru</string>
<string name="by_rating">Rating</string> <string name="by_rating">Peringkat</string>
<string name="sort_order">Urutan penyortiran</string> <string name="sort_order">Urutan penyortiran</string>
<string name="filter">Filter</string> <string name="filter">Saring</string>
<string name="theme">Tema</string> <string name="theme">Tema</string>
<string name="light">Terang</string> <string name="light">Terang</string>
<string name="dark">Gelap</string> <string name="dark">Gelap</string>
@@ -61,9 +61,9 @@
<string name="page_saved">Disimpan</string> <string name="page_saved">Disimpan</string>
<string name="share_image">Bagikan gambar</string> <string name="share_image">Bagikan gambar</string>
<string name="_import">Impor</string> <string name="_import">Impor</string>
<string name="history_and_cache">Riwayat dan tembolok</string> <string name="history_and_cache">Riwayat dan cache</string>
<string name="clear_pages_cache">Bersihkan tembolok halaman</string> <string name="clear_pages_cache">Bersihkan cache halaman</string>
<string name="cache">Tembolok</string> <string name="cache">Cache</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string> <string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">Standar</string> <string name="standard">Standar</string>
<string name="grid_size">Ukuran kisi</string> <string name="grid_size">Ukuran kisi</string>
@@ -84,7 +84,7 @@
<string name="network_consumption_warning">Ini mungkin akan mentransfer banyak data</string> <string name="network_consumption_warning">Ini mungkin akan mentransfer banyak data</string>
<string name="dont_ask_again">Jangan tanya lagi</string> <string name="dont_ask_again">Jangan tanya lagi</string>
<string name="cancelling_">Membatalkan…</string> <string name="cancelling_">Membatalkan…</string>
<string name="clear_thumbs_cache">Bersihkan tembolok keluku</string> <string name="clear_thumbs_cache">Bersihkan cache gambar mini</string>
<string name="clear_search_history">Bersihkan riwayat pencarian</string> <string name="clear_search_history">Bersihkan riwayat pencarian</string>
<string name="search_history_cleared">Dibersihkan</string> <string name="search_history_cleared">Dibersihkan</string>
<string name="domain">Domain</string> <string name="domain">Domain</string>
@@ -103,7 +103,7 @@
<string name="categories_">Kategori…</string> <string name="categories_">Kategori…</string>
<string name="rename">Ubah Nama</string> <string name="rename">Ubah Nama</string>
<string name="category_delete_confirm">Hapus kategori \"%s\" dari favorit Anda\? <string name="category_delete_confirm">Hapus kategori \"%s\" dari favorit Anda\?
\nSemua manga di situ akan hilang.</string> \nSemua manga di dalamnya akan hilang.</string>
<string name="text_empty_holder_primary">Sepi juga di sini…</string> <string name="text_empty_holder_primary">Sepi juga di sini…</string>
<string name="text_categories_holder">Anda bisa menggunakan kategori untuk mengelola favorit Anda. Tekan «+» untuk membuat kategori</string> <string name="text_categories_holder">Anda bisa menggunakan kategori untuk mengelola favorit Anda. Tekan «+» untuk membuat kategori</string>
<string name="text_history_holder_primary">Apa yang Anda baca akan ditampilkan di sini</string> <string name="text_history_holder_primary">Apa yang Anda baca akan ditampilkan di sini</string>
@@ -139,10 +139,10 @@
<string name="data_restored">Dipulihkan</string> <string name="data_restored">Dipulihkan</string>
<string name="report_github">Buat isu di GitHub</string> <string name="report_github">Buat isu di GitHub</string>
<string name="data_restored_success">Semua data dipulihkan</string> <string name="data_restored_success">Semua data dipulihkan</string>
<string name="data_restored_with_errors">Data berhasil dipulihkan, tapi ada galat</string> <string name="data_restored_with_errors">Data berhasil dipulihkan, tapi ada kesalahan</string>
<string name="backup_information">Anda bisa membuat cadangan riwayat dan favorit Anda dan memulihkannya</string> <string name="backup_information">Anda bisa membuat cadangan riwayat dan favorit Anda dan memulihkannya</string>
<string name="just_now">Baru saja</string> <string name="just_now">Baru saja</string>
<string name="long_ago">Dulu kala</string> <string name="long_ago">Lama</string>
<string name="group">Kelompok</string> <string name="group">Kelompok</string>
<string name="today">Hari ini</string> <string name="today">Hari ini</string>
<string name="tap_to_try_again">Ketuk untuk coba lagi</string> <string name="tap_to_try_again">Ketuk untuk coba lagi</string>
@@ -156,7 +156,7 @@
<string name="clear_feed">Bersihkan umpan</string> <string name="clear_feed">Bersihkan umpan</string>
<string name="check_for_new_chapters">Periksa bab baru</string> <string name="check_for_new_chapters">Periksa bab baru</string>
<string name="auth_required">Masuk untuk melihat konten ini</string> <string name="auth_required">Masuk untuk melihat konten ini</string>
<string name="_and_x_more">…dan %1$d lagi</string> <string name="_and_x_more">…dan %1$d lainnya</string>
<string name="next">Selanjutnya</string> <string name="next">Selanjutnya</string>
<string name="protect_application_subtitle">Masukkan kata sandi untuk memulai aplikasi</string> <string name="protect_application_subtitle">Masukkan kata sandi untuk memulai aplikasi</string>
<string name="confirm">Konfirmasi</string> <string name="confirm">Konfirmasi</string>
@@ -175,7 +175,7 @@
<string name="text_clear_cookies_prompt">Anda akan keluar dari semua sumber</string> <string name="text_clear_cookies_prompt">Anda akan keluar dari semua sumber</string>
<string name="genres">Genre</string> <string name="genres">Genre</string>
<string name="state_finished">Selesai</string> <string name="state_finished">Selesai</string>
<string name="state_ongoing">Sedang dibaca</string> <string name="state_ongoing">Berlanjut</string>
<string name="date_format">Format tanggal</string> <string name="date_format">Format tanggal</string>
<string name="system_default">Standar</string> <string name="system_default">Standar</string>
<string name="exclude_nsfw_from_history">Kecualikan manga NSFW dari riwayat</string> <string name="exclude_nsfw_from_history">Kecualikan manga NSFW dari riwayat</string>
@@ -217,14 +217,14 @@
<string name="suggestions_excluded_genres_summary">Tentukan genre yang Anda tidak ingin lihat di saran</string> <string name="suggestions_excluded_genres_summary">Tentukan genre yang Anda tidak ingin lihat di saran</string>
<string name="text_delete_local_manga_batch">Hapus yang dipilih dari perangkat secara permanen\?</string> <string name="text_delete_local_manga_batch">Hapus yang dipilih dari perangkat secara permanen\?</string>
<string name="removal_completed">Selesai menghapus</string> <string name="removal_completed">Selesai menghapus</string>
<string name="batch_manga_save_confirm">Apakah Anda yakin ingin mengunduh semua manga yang dipilih dengan semua babnya\? Tindakan ini akan memakan banyak data dan penyimpanan</string> <string name="batch_manga_save_confirm">Unduh semua manga yang dipilih dan babnya\? Ini dapat mengkonsumsi banyak lalu lintas jaringan dan penyimpanan.</string>
<string name="parallel_downloads">Unduhan paralel</string> <string name="parallel_downloads">Unduhan paralel</string>
<string name="download_slowdown">Perlambatan unduhan</string> <string name="download_slowdown">Lambatkan unduhan</string>
<string name="suggestions_updating">Saran diperbarui</string> <string name="suggestions_updating">Pembaruan saran</string>
<string name="download_slowdown_summary">Membantu menghidari pemblokiran alamat IP Anda</string> <string name="download_slowdown_summary">Membantu menghidari pemblokiran alamat IP Anda</string>
<string name="chapters_will_removed_background">Bab akan dihapus di latar belakang. Akan memakan beberapa waktu</string> <string name="chapters_will_removed_background">Bab akan dihapus di latar belakang. Akan memakan beberapa waktu</string>
<string name="hide">Sembunyikan</string> <string name="hide">Sembunyikan</string>
<string name="new_sources_text">Sumber manga baru tersedia</string> <string name="new_sources_text">Tersedia sumber manga baru</string>
<string name="check_new_chapters_title">Periksa bab baru dan beri tahu tentang itu</string> <string name="check_new_chapters_title">Periksa bab baru dan beri tahu tentang itu</string>
<string name="show_notification_new_chapters_on">Anda akan menerima pemberitahuan tentang pembaruan manga yang Anda baca</string> <string name="show_notification_new_chapters_on">Anda akan menerima pemberitahuan tentang pembaruan manga yang Anda baca</string>
<string name="show_notification_new_chapters_off">Anda tidak akan menerima pemberitahuan, tapi bab baru akan disorot di daftar</string> <string name="show_notification_new_chapters_off">Anda tidak akan menerima pemberitahuan, tapi bab baru akan disorot di daftar</string>
@@ -250,7 +250,7 @@
<string name="favourites_categories">Kategori favorit</string> <string name="favourites_categories">Kategori favorit</string>
<string name="remove_category">Hapus</string> <string name="remove_category">Hapus</string>
<string name="clear_updates_feed">Bersihkan umpan pembaruan</string> <string name="clear_updates_feed">Bersihkan umpan pembaruan</string>
<string name="right_to_left">Kanan ke kiri (←)</string> <string name="right_to_left">Kanan ke kiri</string>
<string name="waiting_for_network">Menunggu jaringan…</string> <string name="waiting_for_network">Menunggu jaringan…</string>
<string name="rotate_screen">Putar layar</string> <string name="rotate_screen">Putar layar</string>
<string name="feed_will_update_soon">Pembaruan umpan akan dimulai</string> <string name="feed_will_update_soon">Pembaruan umpan akan dimulai</string>
@@ -262,7 +262,7 @@
<string name="app_version">Versi %s</string> <string name="app_version">Versi %s</string>
<string name="internal_storage">Penyimpanan internal</string> <string name="internal_storage">Penyimpanan internal</string>
<string name="external_storage">Penyimpanan eksternal</string> <string name="external_storage">Penyimpanan eksternal</string>
<string name="error">Galat</string> <string name="error">Kesalahan</string>
<string name="checking_for_updates">Memeriksa pembaruan…</string> <string name="checking_for_updates">Memeriksa pembaruan…</string>
<string name="no_update_available">Tidak ada pembaruan yang tersedia</string> <string name="no_update_available">Tidak ada pembaruan yang tersedia</string>
<string name="black_dark_theme_summary">Menggunakan daya lebih sedikit pada layar AMOLED</string> <string name="black_dark_theme_summary">Menggunakan daya lebih sedikit pada layar AMOLED</string>
@@ -286,8 +286,8 @@
<string name="zoom_mode_fit_height">Cocokkan dengan tinggi</string> <string name="zoom_mode_fit_height">Cocokkan dengan tinggi</string>
<string name="zoom_mode_fit_width">Cocokkan dengan lebar</string> <string name="zoom_mode_fit_width">Cocokkan dengan lebar</string>
<string name="reverse">Balik</string> <string name="reverse">Balik</string>
<string name="queued">Diantrikan</string> <string name="queued">Antri</string>
<string name="auth_complete">Berhasil Diotorisasi</string> <string name="auth_complete">Diotorisasi</string>
<string name="text_local_holder_secondary">Simpan dari sumber daring atau berkas impor.</string> <string name="text_local_holder_secondary">Simpan dari sumber daring atau berkas impor.</string>
<string name="text_shelf_holder_primary">Manga Anda akan ditampilkan di sini</string> <string name="text_shelf_holder_primary">Manga Anda akan ditampilkan di sini</string>
<string name="text_shelf_holder_secondary">Cari apa untuk dibaca di bagian «Jelajah»</string> <string name="text_shelf_holder_secondary">Cari apa untuk dibaca di bagian «Jelajah»</string>
@@ -328,8 +328,8 @@
<string name="not_found_404">Konten tidak ditemukan atau dihapus</string> <string name="not_found_404">Konten tidak ditemukan atau dihapus</string>
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string> <string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string>
<string name="exit_confirmation">Konfirmasi keluar</string> <string name="exit_confirmation">Konfirmasi keluar</string>
<string name="pages_cache">Tembolok halaman</string> <string name="pages_cache">Cache halaman</string>
<string name="other_cache">Tembolok lainnya</string> <string name="other_cache">Cache lainnya</string>
<string name="storage_usage">Penggunaan penyimpanan</string> <string name="storage_usage">Penggunaan penyimpanan</string>
<string name="available">Tersedia</string> <string name="available">Tersedia</string>
<string name="incognito_mode">Mode Incognito</string> <string name="incognito_mode">Mode Incognito</string>
@@ -339,12 +339,12 @@
<string name="folder_with_images">Folder dengan gambar</string> <string name="folder_with_images">Folder dengan gambar</string>
<string name="import_completed_hint">Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang</string> <string name="import_completed_hint">Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang</string>
<string name="feed">Umpan</string> <string name="feed">Umpan</string>
<string name="manga_error_description_pattern">Detail galat:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Coba untuk &lt;a href=%2$s&gt;membuka manga di peramban web&lt;/a&gt; untuk memastikan bahwa itu tersedia di sumbernya&lt;br&gt;2. Jika tersedia, kirim laporan galat ke pengembang.</string> <string name="manga_error_description_pattern">Detail kesalahan:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Coba untuk &lt;a href=%2$s&gt;membuka manga di peramban web&lt;/a&gt; untuk memastikan bahwa itu tersedia di sumbernya&lt;br&gt;2. Jika tersedia, kirim laporan kesalahan ke pengembang.</string>
<string name="brightness">Kecerahan</string> <string name="brightness">Kecerahan</string>
<string name="contrast">Kontras</string> <string name="contrast">Kontras</string>
<string name="reset">Atur Ulang</string> <string name="reset">Atur Ulang</string>
<string name="color_correction_hint">Pengaturan warna yang dipilih akan diingat untuk manga ini</string> <string name="color_correction_hint">Pengaturan warna yang dipilih akan diingat untuk manga ini</string>
<string name="text_unsaved_changes_prompt">Anda memiliki perubahan belum tersimpan, apakah Anda ingin menyimpannya atau membuangnya\?</string> <string name="text_unsaved_changes_prompt">Menyimpan atau membuang perubahan yang belum disimpan\?</string>
<string name="discard">Buang</string> <string name="discard">Buang</string>
<string name="use_fingerprint">Gunakan sidik jari jika tersedia</string> <string name="use_fingerprint">Gunakan sidik jari jika tersedia</string>
<string name="appwidget_shelf_description">Manga dari favorit Anda</string> <string name="appwidget_shelf_description">Manga dari favorit Anda</string>
@@ -373,4 +373,52 @@
<string name="dns_over_https">DNS melalui HTTPS</string> <string name="dns_over_https">DNS melalui HTTPS</string>
<string name="status_dropped">Istirahat</string> <string name="status_dropped">Istirahat</string>
<string name="off_short">Mati</string> <string name="off_short">Mati</string>
<string name="theme_name_mamimi">Mamimi</string>
<string name="server_error">Kesalahan sisi server (%1$d). Silakan coba lagi nanti</string>
<string name="compact">kompak</string>
<string name="prefetch_content">Pramuat konten</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="nothing_here">Tidak ada apapun di sini</string>
<string name="reader_control_ltr_summary">Ketuk di tepi kanan atau menekan tombol kanan akan selalu beralih ke halaman berikutnya</string>
<string name="source_disabled">Sumber dinonaktifkan</string>
<string name="mark_as_current">Tandai sebagai saat ini</string>
<string name="show_suspicious_content">Tampilkan konten yang mencurigakan</string>
<string name="scrobbling_empty_hint">Untuk melacak kemajuan membaca, pilih Menu → Lacak di layar detail manga.</string>
<string name="services">Layanan</string>
<string name="clear_new_chapters_counters">Juga informasi yang jelas tentang bab baru</string>
<string name="network_unavailable_hint">Nyalakan Wi-Fi atau jaringan seluler untuk membaca manga online</string>
<string name="user_agent">Tajuk Agen Pengguna</string>
<string name="settings_apply_restart_required">Mulai ulang aplikasi untuk menerapkan perubahan ini</string>
<string name="theme_name_kanade">Kanade</string>
<string name="share_logs">Bagikan log</string>
<string name="error_no_space_left">Tidak ada ruang tersisa di perangkat</string>
<string name="allow_unstable_updates">Izinkan pembaruan yang tidak stabil</string>
<string name="allow_unstable_updates_summary">Usulkan pembaruan ke versi beta</string>
<string name="download_started">Unduh dimulai</string>
<string name="show_reading_indicators">Tampilkan indikator kemajuan membaca</string>
<string name="show_reading_indicators_summary">Tampilkan persentase baca dalam riwayat dan favorit</string>
<string name="language">Bahasa</string>
<string name="text_search_holder_secondary">Cobalah untuk merubah kueri.</string>
<string name="zoom_mode_keep_start">Tetap di awal</string>
<string name="seconds_pattern">%ss</string>
<string name="reader_info_pattern">Bab. %1$d/%2$d Hal. %3$d/%4$d</string>
<string name="enable_logging">Aktifkan pencatatan</string>
<string name="enable_logging_summary">Rekam beberapa tindakan untuk tujuan debug</string>
<string name="theme_name_dynamic">Dinamis</string>
<string name="color_theme">Skema warna</string>
<string name="show_in_grid_view">Perlihatkan dalam tampilan kisi</string>
<string name="theme_name_miku">Miku</string>
<string name="theme_name_asuka">Asuka</string>
<string name="theme_name_mion">Mion</string>
<string name="theme_name_rikka">Rikka</string>
<string name="theme_name_sakura">Sakura</string>
<string name="local_manga_processing">Pemrosesan manga tersimpan</string>
<string name="disable_battery_optimization">Nonaktifkan pengoptimalan baterai</string>
<string name="reader_control_ltr">Kontrol pembaca ergonomis</string>
<string name="color_correction">Koreksi warna</string>
<string name="reader_slider">Perlihatkan penggeser peralihan halaman</string>
<string name="webtoon_zoom">Zoom webtoon</string>
<string name="webtoon_zoom_summary">Izinkan gerakan memperbesar/memperkecil tampilan dalam mode webtoon (beta)</string>
<string name="different_languages">Berbagai bahasa</string>
<string name="network_unavailable">Jaringan tidak tersedia</string>
</resources> </resources>

View File

@@ -86,7 +86,7 @@
<string name="webtoon">웹툰</string> <string name="webtoon">웹툰</string>
<string name="clear_search_history">검색 기록 지우기</string> <string name="clear_search_history">검색 기록 지우기</string>
<string name="reader_settings">읽기 모드</string> <string name="reader_settings">읽기 모드</string>
<string name="network_consumption_warning">이 동작은 많은 데이터 사용을</string> <string name="network_consumption_warning">이 동작은 많은 데이터 사용을 야기할 수 있습니다</string>
<string name="clear_thumbs_cache">썸네일 캐시 지우기</string> <string name="clear_thumbs_cache">썸네일 캐시 지우기</string>
<string name="dont_ask_again">다시 묻지 않음</string> <string name="dont_ask_again">다시 묻지 않음</string>
<string name="cancelling_">취소 중…</string> <string name="cancelling_">취소 중…</string>
@@ -105,4 +105,250 @@
<string name="read_later">나중에 읽기</string> <string name="read_later">나중에 읽기</string>
<string name="search_results">검색 결과</string> <string name="search_results">검색 결과</string>
<string name="size_s">크기: %s</string> <string name="size_s">크기: %s</string>
<string name="computing_">계산중…</string>
<string name="clear">지우기</string>
<string name="text_clear_history_prompt">모든 기록을 영구적으로 삭제하시겠어요\?</string>
<string name="restart">재시작</string>
<string name="text_categories_holder">카테고리를 이용하여 선호작을 관리하세요. «+»을 눌러 카테고리를 만들 수 있습니다</string>
<string name="text_local_holder_primary">먼저 아무거나 저장해보세요</string>
<string name="text_feed_holder">여기서 읽고 있는 만화의 새로운 챕터들을 확인할 수 있습니다</string>
<string name="related">연관된</string>
<string name="protect_application">앱 잠금 활성화</string>
<string name="protect_application_summary">Kotatsu를 실행할 때마다 비밀번호 묻기</string>
<string name="check_for_updates">업데이트 확인하기</string>
<string name="update_check_failed">업데이트를 찾을 수 없음</string>
<string name="right_to_left">오른쪽에서 왼쪽</string>
<string name="zoom_mode_fit_center">가운데 맞춤</string>
<string name="no_update_available">가능한 업데이트 없음</string>
<string name="zoom_mode_fit_height">세로 맞춤</string>
<string name="tap_to_try_again">탭해서 재시도</string>
<string name="restore_backup">백업한 데이터 복원하기</string>
<string name="create_backup">백업하기</string>
<string name="data_restored">복원됨</string>
<string name="preparing_">준비중…</string>
<string name="welcome">환영합니다</string>
<string name="auth_complete">인증됨</string>
<string name="error_empty_name">이름을 입력해야 합니다</string>
<string name="about_feedback">피드백</string>
<string name="about_app_translation">번역</string>
<string name="about_app_translation_summary">이 앱을 번역하기</string>
<string name="dynamic_theme_summary">기기의 배경화면 색상을 기반으로 한 테마</string>
<string name="screenshots_block_nsfw">성인 컨텐츠에서만 차단</string>
<string name="screenshots_allow">항상 허용</string>
<string name="exclude_nsfw_from_suggestions">성인 만화(NSFW)는 추천하지 않기</string>
<string name="disabled">비활성화됨</string>
<string name="filter_load_error">장르 목록을 불러올 수 없음</string>
<string name="chapters_empty">이 만화는 챕터로 나눠져 있지 않습니다</string>
<string name="search_chapters">챕터 찾아보기</string>
<string name="chapters_will_removed_background">챕터들이 백그라운드에서 제거됩니다. 이 작업은 많은 시간이 소요될 수 있습니다</string>
<string name="download_slowdown_summary">IP 차단을 회피할 수 있게 합니다</string>
<string name="check_new_chapters_title">새로운 챕터가 나오면 알려주기</string>
<string name="standard">스탠다드</string>
<string name="text_local_holder_secondary">온라인 소스 혹은 직접 파일을 불러와 저장하기.</string>
<string name="suggestions_info">모든 데이터는 기기 안에서만 분석 및 사용되며 어떠한 서드파티 서비스들과도 공유되지 않습니다</string>
<string name="suggestions_summary">당신의 선호도를 바탕으로 만화를 추천합니다</string>
<string name="bookmark_add">북마크에 추가</string>
<string name="bookmark_remove">북마크 제거</string>
<string name="bookmarks">북마크</string>
<string name="data_deletion">데이터 삭제</string>
<string name="no_manga_sources">만화 소스 사이트 없음</string>
<string name="changelog">체인지로그</string>
<string name="history">최근에 본 만화</string>
<string name="error_occurred">오류 발생</string>
<string name="details">세부정보</string>
<string name="chapters">챕터</string>
<string name="detailed_list">자세한 목록</string>
<string name="list_mode">설정</string>
<string name="grid">그리드</string>
<string name="remote_sources">소스 사이트 관리</string>
<string name="clear_history">기록 삭제</string>
<string name="add">추가</string>
<string name="history_is_empty">아직 기록이 없습니다</string>
<string name="enter_category_name">카테고리 이름 추가</string>
<string name="create_shortcut">바로가기 추가…</string>
<string name="theme">테마</string>
<string name="_s_removed_from_history">\"%s\"가 라이브러리에서 삭제됨</string>
<string name="_s_deleted_from_local_storage">\"%s\"가 로컬 저장소에서 삭제됨</string>
<string name="operation_not_supported">지원되지 않는 동작입니다</string>
<string name="no_description">설명없음</string>
<string name="_continue">계속</string>
<string name="search_history_cleared">삭제됨</string>
<string name="gestures_only">제스쳐만 사용</string>
<string name="new_chapters">새로운 챕터</string>
<string name="text_empty_holder_primary">이 항목은 비어있는 것 같습니다…</string>
<string name="recent_manga">최근에 추가된</string>
<string name="manga_save_location">다운로드를 저장하기 위한 폴더</string>
<string name="manga_shelf">책장</string>
<string name="other_storage">기타 저장소</string>
<string name="updates_feed_cleared">삭제됨</string>
<string name="update">업데이트</string>
<string name="feed_will_update_soon">피드가 곧 업데이트 됩니다</string>
<string name="dont_check">확인하지 않기</string>
<string name="enter_password">비밀번호를 입력하세요</string>
<string name="wrong_password">잘못된 비밀번호</string>
<string name="rotate_screen">화면 회전</string>
<string name="track_sources">업데이트 확인하기</string>
<string name="repeat_password">비밀번호 재입력</string>
<string name="passwords_mismatch">일치하지 않는 비밀번호</string>
<string name="app_version">%s 버전</string>
<string name="about">정보</string>
<string name="checking_for_updates">업데이트 확인중…</string>
<string name="create_category">새로운 카테고리</string>
<string name="scale_mode">확대 설정</string>
<string name="zoom_mode_fit_width">가로 맞춤</string>
<string name="black_dark_theme">검은색</string>
<string name="black_dark_theme_summary">AMOLED 화면에서의 전력소모를 줄입니다</string>
<string name="backup_restore">백업 및 복원</string>
<string name="file_not_found">파일을 찾을 수 없음</string>
<string name="data_restored_success">모든 데이터 복원됨</string>
<string name="data_restored_with_errors">데이터가 복원되었지만 오류가 존재합니다</string>
<string name="backup_information">선호작이나 읽은 기록들을 백업 및 복원할 수 있습니다</string>
<string name="yesterday">어제</string>
<string name="long_ago">오래전</string>
<string name="group">그룹</string>
<string name="today">오늘</string>
<string name="captcha_required">CAPTCHA 설정이 필요합니다</string>
<string name="captcha_solve">해결됨</string>
<string name="clear_cookies">쿠키 삭제</string>
<string name="zoom_mode_keep_start">첫 칸에 맞춤</string>
<string name="report_github">Github에 문제 제기하기</string>
<string name="silent">무음</string>
<string name="reader_mode_hint">선택된 설정값이 항상 이 만화에 적용됩니다</string>
<string name="cookies_cleared">모든 쿠키가 삭제되었습니다</string>
<string name="text_clear_updates_feed_prompt">모든 업데이트 기록을 영구적으로 삭제하시겠습니까\?</string>
<string name="chapters_checking_progress">새로운 챕터 확인중: %2$d의 %1$d</string>
<string name="clear_feed">피드 정리</string>
<string name="check_for_new_chapters">새로운 챕터 확인하기</string>
<string name="sign_in">로그인</string>
<string name="auth_required">로그인이 필요합니다</string>
<string name="default_s">기본값: %s</string>
<string name="next">다음</string>
<string name="protect_application_subtitle">비밀번호를 입력하세요</string>
<string name="confirm">확인</string>
<string name="password_length_hint">최소 4자리 이상의 비밀번호를 입력해 주세요</string>
<string name="backup_saved">백업 저장됨</string>
<string name="tracker_warning">몇몇 기기들은 시스템이 백그라운드 작업을 방해할 수 있습니다.</string>
<string name="text_downloads_holder">다운로드 중인 도서 없음</string>
<string name="_and_x_more">…그리고 %1$d만큼 더</string>
<string name="search_only_on_s">%s에 대해서만 검색하기</string>
<string name="text_clear_search_history_prompt">모든 검색 기록을 영구적으로 삭제 하시겠어요\?</string>
<string name="other">기타</string>
<string name="read_more">더 읽기</string>
<string name="queued">대기열</string>
<string name="chapter_is_missing_text">이 챕터는 다운로드 하거나 온라인으로 읽어야 합니다.</string>
<string name="text_clear_cookies_prompt">모든 사이트에서 로그아웃됩니다</string>
<string name="genres">장르</string>
<string name="state_finished">완료됨</string>
<string name="date_format">날짜 초기화</string>
<string name="system_default">기본</string>
<string name="exclude_nsfw_from_history">기록에서 성인 만화(NSFW) 제외하기</string>
<string name="chapter_is_missing">찾으시는 챕터가 존재하지 않습니다</string>
<string name="about_feedback_4pda">4PDA에 토픽 생성하기</string>
<string name="auth_not_supported_by">%s에 로그인은 지원되지 않습니다</string>
<string name="available_sources">사용 가능한 소스 사이트</string>
<string name="dynamic_theme">동적 테마</string>
<string name="screenshots_policy">스크린샷 규칙</string>
<string name="screenshots_block_all">항상 차단</string>
<string name="suggestions">추천</string>
<string name="suggestions_enable">추천 켜기</string>
<string name="importing_progress">만화를 추가하는 중: %2$d의 %1$d</string>
<string name="text_suggestion_holder">아무 만화나 읽어보세요 당신의 기록을 바탕으로 개인화된 추천 만화를 제공합니다</string>
<string name="enabled">활성화됨</string>
<string name="reset_filter">필터 초기화</string>
<string name="find_genre">장르 찾기</string>
<string name="only_using_wifi">와이파이에 연결된 경우에만</string>
<string name="always">항상</string>
<string name="onboard_text">무슨 언어의 만화를 읽을지 선택하세요. 나중에 설정에서 이를 변경할 수 있습니다.</string>
<string name="nsfw">18+</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="content">컨텐츠</string>
<string name="preload_pages">페이지 미리 로드하기</string>
<string name="logged_in_as">%s로 로그인 됨</string>
<string name="appearance">외관</string>
<string name="suggestions_excluded_genres">제외할 장르 선택</string>
<string name="suggestions_excluded_genres_summary">추천 목록에서 보고 싶지 않은 장르를 특정합니다</string>
<string name="text_delete_local_manga_batch">선택된 항목을 기기에서 영구히 제거하시겠어요\?</string>
<string name="removal_completed">제거 완료</string>
<string name="batch_manga_save_confirm">선택된 모든 만화와 챕터들을 다운로드 받으시겠어요\? 많은 데이터와 기기 저장소 용량을 소비합니다.</string>
<string name="suggestions_updating">추천 항목 업데이트 중</string>
<string name="canceled">취소됨</string>
<string name="name">이름</string>
<string name="disable_battery_optimization">배터리 최적화 해제하기</string>
<string name="disable_battery_optimization_summary">백그라운드에서의 업데이트 확인이 안드로이드 시스템에 의해 중지되지 않습니다</string>
<string name="send">보내기</string>
<string name="use_fingerprint">가능할 경우 지문 사용</string>
<string name="select_range">범위 선택</string>
<string name="history_cleared">기록 삭제됨</string>
<string name="random">랜덤</string>
<string name="not_found_404">컨텐츠를 찾을 수 없거나 제거되었습니다</string>
<string name="downloading_manga">만화 다운로드 중</string>
<string name="incognito_mode">사생활 보호 모드</string>
<string name="app_update_available_s">애플리케이션 업데이트가 가능합니다: %s</string>
<string name="no_chapters">챕터 없음</string>
<string name="automatic_scroll">자동 스크롤</string>
<string name="reader_info_bar">리더 안에서 만화 정보 보여주기</string>
<string name="feed">피드</string>
<string name="show_notification_new_chapters_on">읽고 있는 만화의 업데이트 알림을 수신할 수 있게 됩니다</string>
<string name="show_notification_new_chapters_off">알림은 받지 못하지만 여전히 새로운 챕터들이 목록 상에서 강조 표시되어 보여집니다</string>
<string name="notifications_enable">알림 활성화</string>
<string name="edit">수정</string>
<string name="edit_category">카테고리 수정</string>
<string name="tracking">새로운 소식 추적</string>
<string name="empty_favourite_categories">선호 표시된 카테고리 없음</string>
<string name="logout">로그아웃</string>
<string name="bookmark_removed">북마크 제거됨</string>
<string name="bookmark_added">북마크 추가됨</string>
<string name="undo">실행취소</string>
<string name="removed_from_history">기록에서 지우기</string>
<string name="dns_over_https">DNS over HTTPS 활성화</string>
<string name="default_mode">기본 모드</string>
<string name="detect_reader_mode_summary">왭툰 자동 감지</string>
<string name="crash_text">오류. 저희가 고칠 수 있게 버그 정보를 보내주세요.</string>
<string name="status_planned">계획됨</string>
<string name="status_reading">읽는 중</string>
<string name="status_re_reading">다시 읽는 중</string>
<string name="status_completed">완료됨</string>
<string name="disable_all">모두 비활성화</string>
<string name="appwidget_shelf_description">선호작 목록에 존재하는 만화</string>
<string name="appwidget_recent_description">최근에 본 만화</string>
<string name="report">제보하기</string>
<string name="show_reading_indicators">읽기 진행 상황 표시</string>
<string name="exclude_nsfw_from_history_summary">성인 만화는 읽은 기록에 포함되지 않으며 읽기 진행 상황은 저장되지 않습니다</string>
<string name="show_all">모두 보여주기</string>
<string name="invalid_domain_message">유효하지 않은 도메인</string>
<string name="clear_all_history">모든 기록 삭제</string>
<string name="last_2_hours">지난 2시간</string>
<string name="manage">관리</string>
<string name="no_bookmarks_yet">아직 추가된 븍마크가 없습니다</string>
<string name="no_bookmarks_summary">만화를 읽는 도중 북마크를 추가할 수 있습니다</string>
<string name="bookmarks_removed">북마크 제거됨</string>
<string name="no_manga_sources_text">만화를 읽기 위해 만화 소스 사이트를 활성화 하세요</string>
<string name="categories_delete_confirm">정말 선택된 즐겨 찾는 카테고리를 삭제하시겠습니까\?
\n해당 카테고리의 모든 만화가 손실되며 취소할 수 없습니다.</string>
<string name="empty">비어있음</string>
<string name="confirm_exit">뒤로가기 버튼을 다시 눌러 나가기</string>
<string name="exit_confirmation_summary">뒤로가기 버튼을 두 번 눌러 앱을 종료할 수 있습니다</string>
<string name="exit_confirmation">앱 종료 확인</string>
<string name="saved_manga">저장된 만화</string>
<string name="pages_cache">페이지 캐시</string>
<string name="other_cache">기타 캐시</string>
<string name="storage_usage">저장소 사용량</string>
<string name="memory_usage_pattern">%s -%s</string>
<string name="enter_email_text">이메일을 입력하여 계속</string>
<string name="removed_from_favourites">즐겨찾기 목록에서 제거됨</string>
<string name="removed_from_s">\"%s\"에서 제거됨</string>
<string name="reader_info_pattern">챕터.%1$d/%2$d 페이지.%3$d/%4$d</string>
<string name="comics_archive">코믹스 모음</string>
<string name="import_completed">가져오기 완료</string>
<string name="import_completed_hint">본 파일을 제거함으로써 저장소 공간을 아낄 수 있습니다</string>
<string name="import_will_start_soon">가져오기가 곧 시작됩니다</string>
<string name="history_shortcuts">최근 추가된 만화 바로가기 보여주기</string>
<string name="hide">숨기기</string>
<string name="new_sources_text">새로운 만화 소스 사이트 사용 가능</string>
<string name="account_already_exists">계정이 이미 존재합니다</string>
<string name="back">뒤로가기</string>
<string name="sync">동기화</string>
<string name="sync_title">데이터 동기화 하기</string>
<string name="email_enter_hint">이메일을 입력하여 계속</string>
<string name="download_slowdown">다운로드 속도 늦추기</string>
</resources> </resources>

View File

@@ -410,4 +410,7 @@
<string name="theme_name_rikka">Rikka</string> <string name="theme_name_rikka">Rikka</string>
<string name="theme_name_sakura">Sakura</string> <string name="theme_name_sakura">Sakura</string>
<string name="source_disabled">Fonte desativada</string> <string name="source_disabled">Fonte desativada</string>
<string name="enable_logging_summary">Gravar algumas ações para propósitos de depuração</string>
<string name="theme_name_mamimi">Mamimi</string>
<string name="theme_name_miku">Miku</string>
</resources> </resources>

View File

@@ -12,7 +12,7 @@
</plurals> </plurals>
<plurals name="new_chapters"> <plurals name="new_chapters">
<item quantity="one">%1$d новая глава</item> <item quantity="one">%1$d новая глава</item>
<item quantity="few">%1$d новых главы</item> <item quantity="few">%1$d новые главы</item>
<item quantity="many">%1$d новых глав</item> <item quantity="many">%1$d новых глав</item>
<item quantity="other">%1$d новых глав</item> <item quantity="other">%1$d новых глав</item>
</plurals> </plurals>

View File

@@ -9,6 +9,13 @@
<string name="url_error_report" translatable="false">https://acra.rumblur.space/report</string> <string name="url_error_report" translatable="false">https://acra.rumblur.space/report</string>
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string> <string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
<string name="url_sync_server" translatable="false">http://86.57.183.214:8081</string> <string name="url_sync_server" translatable="false">http://86.57.183.214:8081</string>
<string name="shikimori_clientId" translatable="false">Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU</string>
<string name="shikimori_clientSecret" translatable="false">euBMt1GGRSDpVIFQVPxZrO7Kh6X4gWyv0dABuj4B-M8</string>
<string name="anilist_clientId" translatable="false">9887</string>
<string name="anilist_clientSecret" translatable="false">wrMqFosItQWsmB8dtAHfIFPDt15FfQi2ZGiKkJoW</string>
<string name="mal_clientId" translatable="false">6cd8e6349e9a36bc1fc1ab97703c9fd1</string>
<string name="acra_login" translatable="false">SxhkCVnqVLbGogvi</string>
<string name="acra_password" translatable="false">xPDACTLHnHU9Nfjv</string>
<string-array name="values_theme" translatable="false"> <string-array name="values_theme" translatable="false">
<item>-1</item> <item>-1</item>
<item>1</item> <item>1</item>

View File

@@ -4,7 +4,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { 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 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.45' 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() { String currentBranch() {
def branchName = "" def branchName = ""
try { try {