Compare commits

..

No commits in common. '861c21faeac14abf5c911f78009b86b55a7afe16' and 'a624bffea3b9c0e40dffe0c24c2ca45ba307fd29' have entirely different histories.

2
.idea/.gitignore vendored

@ -3,5 +3,3 @@
/workspace.xml
/migrations.xml
/runConfigurations.xml
/appInsightsSettings.xml
/kotlinCodeInsightSettings.xml

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

@ -6,7 +6,7 @@
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="gradleJvm" value="jbr-21" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
@ -16,4 +16,4 @@
</GradleProjectSettings>
</option>
</component>
</project>
</project>

@ -21,8 +21,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 23
targetSdk = 36
versionCode = 1030
versionName = '9.2'
versionCode = 1028
versionName = '9.1.4'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@ -87,7 +87,6 @@ android {
'-opt-in=coil3.annotation.InternalCoilApi',
'-opt-in=kotlinx.serialization.ExperimentalSerializationApi',
'-Xjspecify-annotations=strict',
'-Xannotation-default-target=first-only',
'-Xtype-enhancement-improvements-strict-mode'
]
}

@ -8,7 +8,8 @@
public static void checkParameterIsNotNull(...);
public static void checkNotNullParameter(...);
}
-keep public class ** extends org.koitharu.kotatsu.core.ui.BaseFragment
-keep class org.koitharu.kotatsu.core.db.entity.* { *; }
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
@ -16,10 +17,8 @@
-dontwarn com.google.j2objc.annotations.**
-dontwarn coil3.PlatformContext
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
-keep class org.koitharu.kotatsu.settings.about.changelog.ChangelogFragment
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
-keep class org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy { *; }
-keep class org.koitharu.kotatsu.backups.ui.periodical.PeriodicalBackupSettingsFragment { *; }
-keep class org.jsoup.parser.Tag

@ -41,8 +41,8 @@ class KotatsuApp : BaseApp() {
detectNetwork()
detectDiskWrites()
detectCustomSlowCalls()
detectResourceMismatches()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) detectUnbufferedIo()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) detectResourceMismatches()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) detectExplicitGc()
penaltyLog()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && notifier != null) {

@ -51,11 +51,9 @@
android:backupAgent="org.koitharu.kotatsu.backups.domain.AppBackupAgent"
android:dataExtractionRules="@xml/backup_rules"
android:enableOnBackInvokedCallback="@bool/is_predictive_back_enabled"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_content"
android:fullBackupOnly="true"
android:hasFragileUserData="true"
android:restoreAnyVersion="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"

@ -36,14 +36,15 @@ class AppBackupAgent : BackupAgent() {
override fun onFullBackup(data: FullBackupDataOutput) {
super.onFullBackup(data)
val file = createBackupFile(
this,
BackupRepository(
MangaDatabase(context = applicationContext),
AppSettings(applicationContext),
TapGridSettings(applicationContext),
),
)
val file =
createBackupFile(
this,
BackupRepository(
MangaDatabase(context = applicationContext),
AppSettings(applicationContext),
TapGridSettings(applicationContext),
),
)
try {
fullBackupFile(file, data)
} finally {
@ -89,12 +90,8 @@ class AppBackupAgent : BackupAgent() {
@VisibleForTesting
fun restoreBackupFile(fd: FileDescriptor, size: Long, repository: BackupRepository) {
ZipInputStream(ByteStreams.limit(FileInputStream(fd), size)).use { input ->
val sections = EnumSet.allOf(BackupSection::class.java)
// managed externally
sections.remove(BackupSection.SETTINGS)
sections.remove(BackupSection.SETTINGS_READER_GRID)
runBlocking {
repository.restoreBackup(input, sections, null)
repository.restoreBackup(input, EnumSet.allOf(BackupSection::class.java), null)
}
}
}

@ -21,7 +21,7 @@ enum class BackupSection(
fun of(entry: ZipEntry): BackupSection? {
val name = entry.name.lowercase(Locale.ROOT)
return entries.find { x -> x.entryName == name }
return entries.first { x -> x.entryName == name }
}
}
}

@ -36,7 +36,7 @@ class TelegramBackupUploader @Inject constructor(
suspend fun uploadBackup(file: File) {
val requestBody = file.asRequestBody("application/zip".toMediaTypeOrNull())
val multipartBody = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.setType(MultipartBody.Companion.FORM)
.addFormDataPart("chat_id", requireChatId())
.addFormDataPart("document", file.name, requestBody)
.build()

@ -8,11 +8,11 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.hilt.work.HiltWorkerFactory
import androidx.room.InvalidationTracker
import androidx.work.Configuration
import androidx.work.WorkManager
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import okhttp3.internal.platform.PlatformRegistry
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.dialog
@ -27,6 +27,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.os.AppValidator
import org.koitharu.kotatsu.core.os.RomCompat
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
@ -61,6 +62,9 @@ open class BaseApp : Application(), Configuration.Provider {
@Inject
lateinit var workScheduleManager: WorkScheduleManager
@Inject
lateinit var workManagerProvider: Provider<WorkManager>
@Inject
lateinit var localMangaIndexProvider: Provider<LocalMangaIndex>
@ -75,7 +79,6 @@ open class BaseApp : Application(), Configuration.Provider {
override fun onCreate() {
super.onCreate()
PlatformRegistry.applicationContext = this // TODO replace with OkHttp.initialize
if (ACRA.isACRASenderServiceProcess()) {
return
}
@ -94,6 +97,7 @@ open class BaseApp : Application(), Configuration.Provider {
localStorageChanges.collect(localMangaIndexProvider.get())
}
workScheduleManager.init()
WorkServiceStopHelper(workManagerProvider).setup()
}
override fun attachBaseContext(base: Context) {

@ -7,7 +7,6 @@ import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Settings
import androidx.annotation.CheckResult
import androidx.annotation.RequiresPermission
import androidx.collection.MutableScatterMap
import androidx.core.app.NotificationChannelCompat
@ -44,7 +43,6 @@ import org.koitharu.kotatsu.core.model.UnknownMangaSource
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.network.webview.WebViewExecutor
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
@ -67,13 +65,11 @@ class CaptchaHandler @Inject constructor(
@LocalizedAppContext private val context: Context,
private val databaseProvider: Provider<MangaDatabase>,
private val coilProvider: Provider<ImageLoader>,
private val webViewExecutor: WebViewExecutor,
) : EventListener() {
private val exceptionMap = MutableScatterMap<MangaSource, CloudFlareProtectedException>()
private val mutex = Mutex()
@CheckResult
suspend fun handle(exception: CloudFlareException): Boolean = handleException(exception.source, exception, true)
suspend fun discard(source: MangaSource) {
@ -83,18 +79,10 @@ class CaptchaHandler @Inject constructor(
override fun onError(request: ImageRequest, result: ErrorResult) {
super.onError(request, result)
val e = result.throwable
if (e is CloudFlareException) {
if (e is CloudFlareException && request.extras[ignoreCaptchaKey] != true) {
val scope = request.lifecycle?.coroutineScope ?: processLifecycleScope
scope.launch {
if (
handleException(
source = e.source,
exception = e,
notify = request.extras[suppressCaptchaKey] != true,
)
) {
coilProvider.get().enqueue(request) // TODO check if ok
}
handleException(e.source, e, true)
}
}
}
@ -102,14 +90,11 @@ class CaptchaHandler @Inject constructor(
private suspend fun handleException(
source: MangaSource,
exception: CloudFlareException?,
notify: Boolean,
notify: Boolean
): Boolean = withContext(Dispatchers.Default) {
if (source == UnknownMangaSource) {
return@withContext false
}
if (exception != null && webViewExecutor.tryResolveCaptcha(exception, RESOLVE_TIMEOUT)) {
return@withContext true
}
mutex.withLock {
var removedException: CloudFlareProtectedException? = null
if (exception is CloudFlareProtectedException) {
@ -134,7 +119,7 @@ class CaptchaHandler @Inject constructor(
notify(exceptions)
}
}
false
true
}
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
@ -249,7 +234,7 @@ class CaptchaHandler @Inject constructor(
.data(source.faviconUri())
.allowHardware(false)
.allowConversionToBitmap(true)
.suppressCaptchaErrors()
.ignoreCaptchaErrors()
.mangaSourceExtra(source)
.size(context.resources.getNotificationIconSize())
.scale(Scale.FILL)
@ -275,11 +260,11 @@ class CaptchaHandler @Inject constructor(
companion object {
fun ImageRequest.Builder.suppressCaptchaErrors() = apply {
extras[suppressCaptchaKey] = true
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
extras[ignoreCaptchaKey] = true
}
private val suppressCaptchaKey = Extras.Key(false)
val ignoreCaptchaKey = Extras.Key(false)
private const val CHANNEL_ID = "captcha"
private const val TAG = CHANNEL_ID
@ -287,6 +272,5 @@ class CaptchaHandler @Inject constructor(
private const val GROUP_NOTIFICATION_ID = 34
private const val SETTINGS_ACTION_CODE = 3
private const val ACTION_DISCARD = "org.koitharu.kotatsu.CAPTCHA_DISCARD"
private const val RESOLVE_TIMEOUT = 20_000L
}
}

@ -798,7 +798,7 @@ class AppRouter private constructor(
else -> true
}
fun shortMangaUrl(mangaId: Long): Uri = Uri.Builder()
fun shortMangaUrl(mangaId: Long) = Uri.Builder()
.scheme("kotatsu")
.path("manga")
.appendQueryParameter("id", mangaId.toString())

@ -1,30 +0,0 @@
package org.koitharu.kotatsu.core.network.webview
import android.graphics.Bitmap
import android.webkit.WebView
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.parsers.network.CloudFlareHelper
import kotlin.coroutines.Continuation
class CaptchaContinuationClient(
private val cookieJar: MutableCookieJar,
private val targetUrl: String,
continuation: Continuation<Unit>,
) : ContinuationResumeWebViewClient(continuation) {
private val oldClearance = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)
override fun onPageFinished(view: WebView?, url: String?) = Unit
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
checkClearance(view)
}
private fun checkClearance(view: WebView?) {
val clearance = CloudFlareHelper.getClearanceCookie(cookieJar, targetUrl)
if (clearance != null && clearance != oldClearance) {
resumeContinuation(view)
}
}
}

@ -2,22 +2,15 @@ package org.koitharu.kotatsu.core.network.webview
import android.webkit.WebView
import android.webkit.WebViewClient
import kotlinx.coroutines.CancellableContinuation
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
open class ContinuationResumeWebViewClient(
class ContinuationResumeWebViewClient(
private val continuation: Continuation<Unit>,
) : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
resumeContinuation(view)
}
protected fun resumeContinuation(view: WebView?) {
if (continuation !is CancellableContinuation || continuation.isActive) {
view?.webViewClient = WebViewClient() // reset to default
continuation.resume(Unit)
}
view?.webViewClient = WebViewClient() // reset to default
continuation.resume(Unit)
}
}

@ -1,127 +1,58 @@
package org.koitharu.kotatsu.core.network.webview
import android.content.Context
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.annotation.MainThread
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import org.koitharu.kotatsu.core.exceptions.CloudFlareException
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.network.proxy.ProxyProvider
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import java.lang.ref.WeakReference
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@Singleton
class WebViewExecutor @Inject constructor(
@ApplicationContext private val context: Context,
private val proxyProvider: ProxyProvider,
private val cookieJar: MutableCookieJar,
private val mangaRepositoryFactoryProvider: Provider<MangaRepository.Factory>,
@ApplicationContext private val context: Context
) {
private var webViewCached: WeakReference<WebView>? = null
private val mutex = Mutex()
val defaultUserAgent: String? by lazy {
WebSettings.getDefaultUserAgent(context)
}
suspend fun evaluateJs(baseUrl: String?, script: String): String? = mutex.withLock {
withContext(Dispatchers.Main.immediate) {
val webView = obtainWebView()
try {
if (!baseUrl.isNullOrEmpty()) {
suspendCoroutine { cont ->
webView.webViewClient = ContinuationResumeWebViewClient(cont)
webView.loadDataWithBaseURL(baseUrl, " ", "text/html", null, null)
}
}
if (!baseUrl.isNullOrEmpty()) {
suspendCoroutine { cont ->
webView.evaluateJavascript(script) { result ->
cont.resume(result?.takeUnless { it == "null" })
}
webView.webViewClient = ContinuationResumeWebViewClient(cont)
webView.loadDataWithBaseURL(baseUrl, " ", "text/html", null, null)
}
} finally {
webView.reset()
}
}
}
suspend fun tryResolveCaptcha(exception: CloudFlareException, timeout: Long): Boolean = mutex.withLock {
runCatchingCancellable {
withContext(Dispatchers.Main.immediate) {
val webView = obtainWebView()
try {
exception.source.getUserAgent()?.let {
webView.settings.userAgentString = it
}
withTimeout(timeout) {
suspendCancellableCoroutine { cont ->
webView.webViewClient = CaptchaContinuationClient(
cookieJar = cookieJar,
targetUrl = exception.url,
continuation = cont,
)
webView.loadUrl(exception.url)
}
}
} finally {
webView.reset()
suspendCoroutine { cont ->
webView.evaluateJavascript(script) { result ->
cont.resume(result?.takeUnless { it == "null" })
}
}
}.onFailure { e ->
exception.addSuppressed(e)
e.printStackTraceDebug()
}.isSuccess
}
private suspend fun obtainWebView(): WebView {
webViewCached?.get()?.let {
return it
}
return withContext(Dispatchers.Main.immediate) {
webViewCached?.get()?.let {
return@withContext it
}
WebView(context).also {
it.configureForParser(null)
webViewCached = WeakReference(it)
proxyProvider.applyWebViewConfig()
it.onResume()
it.resumeTimers()
}
}
}
private fun MangaSource.getUserAgent(): String? {
val repository = mangaRepositoryFactoryProvider.get().create(this) as? ParserMangaRepository
return repository?.getRequestHeaders()?.get(CommonHeaders.USER_AGENT)
}
@MainThread
fun getDefaultUserAgent() = runCatching {
obtainWebView().settings.userAgentString.sanitizeHeaderValue().trim().nullIfEmpty()
}.onFailure { e ->
e.printStackTraceDebug()
}.getOrNull()
@MainThread
private fun WebView.reset() {
stopLoading()
webViewClient = WebViewClient()
settings.userAgentString = defaultUserAgent
loadDataWithBaseURL(null, " ", "text/html", null, null)
clearHistory()
private fun obtainWebView(): WebView = webViewCached?.get() ?: WebView(context).also {
it.configureForParser(null)
webViewCached = WeakReference(it)
}
}

@ -80,7 +80,12 @@ class NetworkState(
if (settings.isOfflineCheckDisabled) {
return true
}
return activeNetwork?.let { isOnline(it) } == true
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activeNetwork?.let { isOnline(it) } == true
} else {
@Suppress("DEPRECATION")
activeNetworkInfo?.isConnected == true
}
}
private fun ConnectivityManager.isOnline(network: Network): Boolean {

@ -9,8 +9,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITES
import org.koitharu.kotatsu.core.db.TABLE_FAVOURITE_CATEGORIES
import org.koitharu.kotatsu.core.db.TABLE_PREFERENCES
import org.koitharu.kotatsu.core.db.entity.ContentRating
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
@ -191,11 +189,6 @@ class MangaDataRepository @Inject constructor(
emitInitialState = emitInitialState,
)
fun observeFavoritesTrigger(emitInitialState: Boolean) = db.invalidationTracker.createFlow(
tables = arrayOf(TABLE_FAVOURITES, TABLE_FAVOURITE_CATEGORIES),
emitInitialState = emitInitialState,
)
private suspend fun Manga.withCachedChaptersIfNeeded(flag: Boolean): Manga = if (flag && !isLocal && chapters.isNullOrEmpty()) {
val cachedChapters = db.getChaptersDao().findAll(id)
if (cachedChapters.isEmpty()) {

@ -5,6 +5,8 @@ import android.content.Context
import android.util.Base64
import androidx.core.os.LocaleListCompat
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
@ -31,6 +33,7 @@ import java.util.Locale
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.EmptyCoroutineContext
@Singleton
class MangaLoaderContextImpl @Inject constructor(
@ -40,6 +43,7 @@ class MangaLoaderContextImpl @Inject constructor(
private val webViewExecutor: WebViewExecutor,
) : MangaLoaderContext() {
private val webViewUserAgent by lazy { obtainWebViewUserAgent() }
private val jsTimeout = TimeUnit.SECONDS.toMillis(4)
@Deprecated("Provide a base url")
@ -50,7 +54,7 @@ class MangaLoaderContextImpl @Inject constructor(
webViewExecutor.evaluateJs(baseUrl, script)
}
override fun getDefaultUserAgent(): String = webViewExecutor.defaultUserAgent ?: UserAgents.FIREFOX_MOBILE
override fun getDefaultUserAgent(): String = webViewUserAgent
override fun getConfig(source: MangaSource): MangaSourceConfig {
return SourceSettings(androidContext, source)
@ -87,4 +91,15 @@ class MangaLoaderContextImpl @Inject constructor(
}
override fun createBitmap(width: Int, height: Int): Bitmap = BitmapWrapper.create(width, height)
private fun obtainWebViewUserAgent(): String {
val mainDispatcher = Dispatchers.Main.immediate
return if (!mainDispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
webViewExecutor.getDefaultUserAgent()
} else {
runBlocking(mainDispatcher) {
webViewExecutor.getDefaultUserAgent()
}
} ?: UserAgents.FIREFOX_MOBILE
}
}

@ -19,7 +19,6 @@ import coil3.request.Options
import coil3.size.pxOrElse
import coil3.toAndroidUri
import coil3.toBitmap
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runInterruptible
import okio.FileSystem
@ -42,6 +41,7 @@ import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
import coil3.Uri as CoilUri
class FaviconFetcher(
@ -88,7 +88,7 @@ class FaviconFetcher(
var favicons = repository.getFavicons()
var lastError: Exception? = null
while (favicons.isNotEmpty()) {
currentCoroutineContext().ensureActive()
coroutineContext.ensureActive()
val icon = favicons.find(sizePx) ?: throwNSEE(lastError)
try {
val result = imageLoader.fetch(icon.url, options)

@ -138,11 +138,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getBoolean(KEY_READER_DOUBLE_PAGES, false)
set(value) = prefs.edit { putBoolean(KEY_READER_DOUBLE_PAGES, value) }
@get:FloatRange(0.0, 1.0)
var readerDoublePagesSensitivity: Float
get() = prefs.getFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, 0.5f)
set(@FloatRange(0.0, 1.0) value) = prefs.edit { putFloat(KEY_READER_DOUBLE_PAGES_SENSITIVITY, value) }
val readerScreenOrientation: Int
get() = prefs.getString(KEY_READER_ORIENTATION, null)?.toIntOrNull()
?: ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
@ -493,10 +488,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getBoolean(KEY_WEBTOON_GAPS, false)
set(value) = prefs.edit { putBoolean(KEY_WEBTOON_GAPS, value) }
var isWebtoonPullGestureEnabled: Boolean
get() = prefs.getBoolean(KEY_WEBTOON_PULL_GESTURE, false)
set(value) = prefs.edit { putBoolean(KEY_WEBTOON_PULL_GESTURE, value) }
@get:FloatRange(from = 0.0, to = 0.5)
val defaultWebtoonZoomOut: Float
get() = prefs.getInt(KEY_WEBTOON_ZOOM_OUT, 0).coerceIn(0, 50) / 100f
@ -678,7 +669,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_REMOTE_SOURCES = "remote_sources"
const val KEY_LOCAL_STORAGE = "local_storage"
const val KEY_READER_DOUBLE_PAGES = "reader_double_pages"
const val KEY_READER_DOUBLE_PAGES_SENSITIVITY = "reader_double_pages_sensitivity"
const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons"
const val KEY_READER_CONTROL_LTR = "reader_taps_ltr"
const val KEY_READER_NAVIGATION_INVERTED = "reader_navigation_inverted"
@ -758,7 +748,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_WEBTOON_GAPS = "webtoon_gaps"
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
const val KEY_WEBTOON_ZOOM_OUT = "webtoon_zoom_out"
const val KEY_WEBTOON_PULL_GESTURE = "webtoon_pull_gesture"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"
const val KEY_SOURCES_GRID = "sources_grid"

@ -0,0 +1,8 @@
package org.koitharu.kotatsu.core.ui
import android.view.View
fun interface OnContextClickListenerCompat {
fun onContextClick(v: View): Boolean
}

@ -10,7 +10,7 @@ import coil3.asImage
import coil3.request.Disposable
import coil3.request.ImageRequest
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler.Companion.suppressCaptchaErrors
import org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler.Companion.ignoreCaptchaErrors
import org.koitharu.kotatsu.core.image.CoilImageView
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
@ -57,7 +57,7 @@ class FaviconView @JvmOverloads constructor(
.fallback(fallbackFactory)
.placeholder(placeholderFactory)
.mangaSourceExtra(mangaSource)
.suppressCaptchaErrors()
.ignoreCaptchaErrors()
.build(),
)
}

@ -2,16 +2,17 @@ package org.koitharu.kotatsu.core.ui.list
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnContextClickListener
import android.view.View.OnLongClickListener
import androidx.core.util.Function
import com.hannesdorfmann.adapterdelegates4.dsl.AdapterDelegateViewBindingViewHolder
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
class AdapterDelegateClickListenerAdapter<I, O>(
private val adapterDelegate: AdapterDelegateViewBindingViewHolder<out I, *>,
private val clickListener: OnListItemClickListener<O>,
private val itemMapper: Function<I, O>,
) : OnClickListener, OnLongClickListener, OnContextClickListener {
) : OnClickListener, OnLongClickListener, OnContextClickListenerCompat {
override fun onClick(v: View) {
clickListener.onItemClick(mappedItem(), v)
@ -32,7 +33,7 @@ class AdapterDelegateClickListenerAdapter<I, O>(
fun attach(itemView: View) {
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)
itemView.setOnContextClickListener(this)
itemView.setOnContextClickListenerCompat(this)
}
companion object {

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.ui.util
import android.graphics.Color
import android.os.Build
import android.view.ViewGroup
import android.view.Window
import androidx.activity.OnBackPressedCallback
@ -13,6 +14,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import com.google.android.material.R as materialR
@ -35,10 +37,14 @@ class ActionModeDelegate : OnBackPressedCallback(false) {
listeners?.forEach { it.onActionModeStarted(mode) }
if (window != null) {
val ctx = window.context
val actionModeColor = ColorUtils.compositeColors(
ContextCompat.getColor(ctx, materialR.color.m3_appbar_overlay_color),
ctx.getThemeColor(materialR.attr.colorSurface),
)
val actionModeColor = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ColorUtils.compositeColors(
ContextCompat.getColor(ctx, materialR.color.m3_appbar_overlay_color),
ctx.getThemeColor(materialR.attr.colorSurface),
)
} else {
ContextCompat.getColor(ctx, R.color.kotatsu_surface)
}
defaultStatusBarColor = window.statusBarColor
window.statusBarColor = actionModeColor
val insets = ViewCompat.getRootWindowInsets(window.decorView)

@ -4,10 +4,12 @@ import android.view.MenuItem
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
class PopupMenuMediator(
private val provider: MenuProvider,
) : View.OnLongClickListener, View.OnContextClickListener, PopupMenu.OnMenuItemClickListener,
) : View.OnLongClickListener, OnContextClickListenerCompat, PopupMenu.OnMenuItemClickListener,
PopupMenu.OnDismissListener {
override fun onContextClick(v: View): Boolean = onLongClick(v)
@ -35,6 +37,6 @@ class PopupMenuMediator(
fun attach(view: View) {
view.setOnLongClickListener(this)
view.setOnContextClickListener(this)
view.setOnContextClickListenerCompat(this)
}
}

@ -0,0 +1,46 @@
package org.koitharu.kotatsu.core.util
import android.annotation.SuppressLint
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.impl.foreground.SystemForegroundService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import javax.inject.Provider
/**
* Workaround for issue
* https://issuetracker.google.com/issues/270245927
* https://issuetracker.google.com/issues/280504155
*/
class WorkServiceStopHelper(
private val workManagerProvider: Provider<WorkManager>,
) {
fun setup() {
processLifecycleScope.launch(Dispatchers.Default) {
workManagerProvider.get()
.getWorkInfosFlow(WorkQuery.fromStates(WorkInfo.State.RUNNING))
.map { it.isEmpty() }
.distinctUntilChanged()
.collectLatest {
if (it) {
delay(1_000)
stopWorkerService()
}
}
}
}
@SuppressLint("RestrictedApi")
private fun stopWorkerService() {
SystemForegroundService.getInstance()?.stop()
}
}

@ -28,6 +28,7 @@ import com.google.android.material.progressindicator.BaseProgressIndicator
import com.google.android.material.slider.RangeSlider
import com.google.android.material.slider.Slider
import com.google.android.material.tabs.TabLayout
import org.koitharu.kotatsu.core.ui.OnContextClickListenerCompat
import kotlin.math.roundToInt
fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
@ -168,6 +169,12 @@ fun BaseProgressIndicator<*>.showOrHide(value: Boolean) {
}
}
fun View.setOnContextClickListenerCompat(listener: OnContextClickListenerCompat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setOnContextClickListener(listener::onContextClick)
}
}
fun View.setTooltipCompat(tooltip: CharSequence?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
tooltipText = tooltip

@ -1,11 +1,13 @@
package org.koitharu.kotatsu.details.domain
import android.util.Log
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.prefs.AppSettings

@ -34,17 +34,12 @@ class ProgressUpdateUseCase @Inject constructor(
}
val chapter = details.findChapterById(history.chapterId) ?: return PROGRESS_NONE
val chapters = details.getChapters(chapter.branch)
val chapterRepo = if (repo.source == chapter.source) {
repo
} else {
mangaRepositoryFactory.create(chapter.source)
}
val chaptersCount = chapters.size
if (chaptersCount == 0) {
return PROGRESS_NONE
}
val chapterIndex = chapters.indexOfFirst { x -> x.id == history.chapterId }
val pagesCount = chapterRepo.getPages(chapter).size
val pagesCount = repo.getPages(chapter).size
if (pagesCount == 0) {
return PROGRESS_NONE
}

@ -27,7 +27,7 @@ class ReadingTimeUseCase @Inject constructor(
// Impossible task, I guess. Good luck on this.
var averageTimeSec: Int = 20 /* pages */ * getSecondsPerPage(manga.id) * chapters.size
if (isOnHistoryBranch) {
averageTimeSec = (averageTimeSec * (1f - history.percent)).roundToInt()
averageTimeSec = (averageTimeSec * (1f - checkNotNull(history).percent)).roundToInt()
}
if (averageTimeSec < 60) {
return null

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.details.ui
import android.app.assist.AssistContent
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.text.SpannedString
import android.view.Gravity
@ -208,7 +209,9 @@ class DetailsActivity :
override fun onProvideAssistContent(outContent: AssistContent) {
super.onProvideAssistContent(outContent)
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
}
}
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.contentRating == ContentRating.ADULT }

@ -140,7 +140,6 @@ class DetailsViewModel @Inject constructor(
get() = scrobblers.any { it.isEnabled }
val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val relatedManga: StateFlow<List<MangaListModel>> = manga.mapLatest {

@ -105,14 +105,7 @@ class PagesViewModel @Inject constructor(
chaptersLoader.peekChapter(it) != null
} ?: state.details.allChapters.firstOrNull()?.id ?: return
if (!chaptersLoader.hasPages(initialChapterId)) {
var hasPages = chaptersLoader.loadSingleChapter(initialChapterId)
while (!hasPages) {
if (chaptersLoader.loadPrevNextChapter(state.details, initialChapterId, isNext = true)) {
hasPages = chaptersLoader.snapshot().isNotEmpty()
} else {
break
}
}
chaptersLoader.loadSingleChapter(initialChapterId)
}
updateList(state.readerState)
}

@ -7,7 +7,6 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
@ -26,8 +25,6 @@ import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject
@ -38,8 +35,7 @@ class RelatedListViewModel @Inject constructor(
settings: AppSettings,
private val mangaListMapper: MangaListMapper,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {
) : MangaListViewModel(settings, mangaDataRepository) {
private val seed = savedStateHandle.require<ParcelableManga>(AppRouter.KEY_MANGA).manga
private val repository = mangaRepositoryFactory.create(seed.source)

@ -202,7 +202,7 @@ class DownloadWorker @AssistedInject constructor(
?: error("Cannot obtain remote manga instance")
}
val repo = mangaRepositoryFactory.create(manga.source)
val mangaDetails = if (manga.chapters.isNullOrEmpty() || manga.description.isNullOrEmpty()) repo.getDetails(manga) else manga
val mangaDetails = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
output = LocalMangaOutput.getOrCreate(
root = destination,
manga = mangaDetails,

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.explore.ui.adapter
import android.view.View
import androidx.appcompat.widget.TooltipCompat
import androidx.core.content.ContextCompat
import androidx.core.text.bold
import androidx.core.text.buildSpannedString

@ -40,9 +40,6 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.parsers.model.Manga
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import kotlinx.coroutines.flow.SharedFlow
private const val PAGE_SIZE = 16
@ -55,8 +52,7 @@ class FavouritesListViewModel @Inject constructor(
quickFilterFactory: FavoritesListQuickFilter.Factory,
settings: AppSettings,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener {
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener {
val categoryId: Long = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID
private val quickFilter = quickFilterFactory.create(categoryId)

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.filter.ui.sheet
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -50,7 +51,9 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
super.onViewBindingCreated(binding, savedInstanceState)
if (dialog == null) {
binding.layoutBody.updatePadding(top = binding.layoutBody.paddingBottom)
binding.scrollView.scrollIndicators = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
binding.scrollView.scrollIndicators = 0
}
}
val filter = FilterCoordinator.require(this)
filter.sortOrder.observe(viewLifecycleOwner, this::onSortOrderChanged)

@ -43,9 +43,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import kotlinx.coroutines.flow.SharedFlow
private const val PAGE_SIZE = 16
@ -57,8 +54,7 @@ class HistoryListViewModel @Inject constructor(
private val markAsReadUseCase: MarkAsReadUseCase,
private val quickFilter: HistoryListQuickFilter,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
private val sortOrder: StateFlow<ListSortOrder> = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.IO,

@ -2,12 +2,14 @@ package org.koitharu.kotatsu.image.ui
import android.content.Context
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.util.AttributeSet
import android.view.Gravity
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.OnPreDrawListener
import androidx.annotation.AttrRes
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes
import androidx.core.graphics.ColorUtils
@ -81,7 +83,9 @@ class CoverImageView @JvmOverloads constructor(
if (fallbackDrawable == null) {
fallbackDrawable = context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable()
}
addImageRequestListener(ErrorForegroundListener())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
addImageRequestListener(ErrorForegroundListener())
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@ -165,6 +169,7 @@ class CoverImageView @JvmOverloads constructor(
}
}
@RequiresApi(Build.VERSION_CODES.M)
private inner class ErrorForegroundListener : ImageRequest.Listener {
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
@ -203,7 +208,6 @@ class CoverImageView @JvmOverloads constructor(
is HttpStatusException -> statusCode.toString()
is ContentUnavailableException,
is FileNotFoundException -> "404"
is TooManyRequestExceptions -> "429"
is ParseException -> "</>"
is UnsupportedSourceException -> "X"
@ -265,7 +269,7 @@ class CoverImageView @JvmOverloads constructor(
width = Dimension(height.px * view.aspectRationWidth / view.aspectRationHeight)
}
}
return Size(width, height)
return Size(checkNotNull(width), checkNotNull(height))
}
private fun getWidth() = getDimension(

@ -3,12 +3,10 @@ package org.koitharu.kotatsu.list.ui
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
@ -24,13 +22,10 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
abstract class MangaListViewModel(
private val settings: AppSettings,
private val mangaDataRepository: MangaDataRepository,
@param:LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
) : BaseViewModel() {
abstract val content: StateFlow<List<ListModel>>
@ -68,11 +63,7 @@ abstract class MangaListViewModel(
protected fun observeListModeWithTriggers(): Flow<ListMode> = combine(
listMode,
merge(
mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
mangaDataRepository.observeFavoritesTrigger(emitInitialState = true),
localStorageChanges.onStart { emit(null) },
),
mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
settings.observeChanges().filter { key ->
key == AppSettings.KEY_PROGRESS_INDICATORS
|| key == AppSettings.KEY_TRACKER_ENABLED

@ -27,6 +27,7 @@ import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import org.koitharu.kotatsu.parsers.util.json.toStringSet
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.io.File

@ -61,9 +61,7 @@ class LocalMangaParser(private val uri: Uri) {
val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX)
val mangaInfo = index?.getMangaInfo()
if (mangaInfo != null) {
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it }?.takeIf {
fileSystem.exists(it)
}
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it }
mangaInfo.copy(
source = LocalMangaSource,
url = rootFile.toUri().toString(),

@ -45,7 +45,7 @@ class LocalListViewModel @Inject constructor(
mangaListMapper: MangaListMapper,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
exploreRepository: ExploreRepository,
@param:LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
private val localStorageManager: LocalStorageManager,
sourcesRepository: MangaSourcesRepository,
mangaDataRepository: MangaDataRepository,
@ -58,7 +58,6 @@ class LocalListViewModel @Inject constructor(
exploreRepository = exploreRepository,
sourcesRepository = sourcesRepository,
mangaDataRepository = mangaDataRepository,
localStorageChanges = localStorageChanges,
), SharedPreferences.OnSharedPreferenceChangeListener, QuickFilterListener {
val onMangaRemoved = MutableEventFlow<Unit>()

@ -131,7 +131,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
onBackPressedDispatcher.addCallback(exitCallback)
onBackPressedDispatcher.addCallback(navigationDelegate)
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || !resources.getBoolean(R.bool.is_predictive_back_enabled)) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
val legacySearchCallback = SearchViewLegacyBackCallback(viewBinding.searchView)
viewBinding.searchView.addTransitionListener(legacySearchCallback)
onBackPressedDispatcher.addCallback(legacySearchCallback)

@ -20,9 +20,6 @@ import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import javax.inject.Inject
import kotlinx.coroutines.flow.SharedFlow
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
@HiltViewModel
class MangaPickerViewModel @Inject constructor(
@ -31,8 +28,7 @@ class MangaPickerViewModel @Inject constructor(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val mangaListMapper: MangaListMapper,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {
) : MangaListViewModel(settings, mangaDataRepository) {
override val content: StateFlow<List<ListModel>>
get() = flow {

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.reader.domain
import android.util.LongSparseArray
import androidx.annotation.CheckResult
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -33,12 +32,12 @@ class ChaptersLoader @Inject constructor(
}
}
suspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean): Boolean {
suspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean) {
val chapters = manga.allChapters
val predicate: (MangaChapter) -> Boolean = { it.id == currentId }
val index = if (isNext) chapters.indexOfFirst(predicate) else chapters.indexOfLast(predicate)
if (index == -1) return false
val newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return false
if (index == -1) return
val newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return
val newPages = loadChapter(newChapter.id)
mutex.withLock {
if (chapterPages.chaptersSize > 1) {
@ -57,16 +56,13 @@ class ChaptersLoader @Inject constructor(
chapterPages.addFirst(newChapter.id, newPages)
}
}
return true
}
@CheckResult
suspend fun loadSingleChapter(chapterId: Long): Boolean {
suspend fun loadSingleChapter(chapterId: Long) {
val pages = loadChapter(chapterId)
return mutex.withLock {
mutex.withLock {
chapterPages.clear()
chapterPages.addLast(chapterId, pages)
pages.isNotEmpty()
}
}

@ -8,9 +8,11 @@ import android.graphics.Rect
import androidx.annotation.ColorInt
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder
import com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@ -21,6 +23,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.util.SynchronizedSieveCache
import org.koitharu.kotatsu.core.util.ext.use
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@ -43,19 +46,19 @@ class EdgeDetector(private val context: Context) {
}
val scaleFactor = calculateScaleFactor(size)
val sampleSize = (1f / scaleFactor).toInt().coerceAtLeast(1)
val fullBitmap = decoder.decodeRegion(
Rect(0, 0, size.x, size.y),
sampleSize,
Rect(0, 0, size.x, size.y),
sampleSize
)
try {
val edges = coroutineScope {
listOf(
async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = true) },
async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = true) },
async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = false) },
async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = false) },
async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = true) },
async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = true) },
async { detectLeftRightEdge(fullBitmap, size, sampleSize, isLeft = false) },
async { detectTopBottomEdge(fullBitmap, size, sampleSize, isTop = false) },
).awaitAll()
}
var hasEdges = false
@ -88,10 +91,10 @@ class EdgeDetector(private val context: Context) {
val rectCount = size.x / BLOCK_SIZE
val maxRect = rectCount / 3
val blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
for (i in 0 until rectCount) {
if (i > maxRect) {
return -1
@ -100,16 +103,16 @@ class EdgeDetector(private val context: Context) {
for (j in 0 until size.y / BLOCK_SIZE) {
val regionX = if (isLeft) i * BLOCK_SIZE else size.x - (i + 1) * BLOCK_SIZE
val regionY = j * BLOCK_SIZE
// Convert to bitmap coordinates
val bitmapX = regionX / sampleSize
val bitmapY = regionY / sampleSize
val blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)
val blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)
if (blockWidth > 0 && blockHeight > 0) {
bitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)
for (ii in 0 until minOf(blockWidth, dd / sampleSize)) {
for (jj in 0 until blockHeight) {
val bi = if (isLeft) ii else blockWidth - ii - 1
@ -138,10 +141,10 @@ class EdgeDetector(private val context: Context) {
val rectCount = size.y / BLOCK_SIZE
val maxRect = rectCount / 3
val blockPixels = IntArray(BLOCK_SIZE * BLOCK_SIZE)
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
for (j in 0 until rectCount) {
if (j > maxRect) {
return -1
@ -150,16 +153,16 @@ class EdgeDetector(private val context: Context) {
for (i in 0 until size.x / BLOCK_SIZE) {
val regionX = i * BLOCK_SIZE
val regionY = if (isTop) j * BLOCK_SIZE else size.y - (j + 1) * BLOCK_SIZE
// Convert to bitmap coordinates
val bitmapX = regionX / sampleSize
val bitmapY = regionY / sampleSize
val blockWidth = min(BLOCK_SIZE / sampleSize, bitmapWidth - bitmapX)
val blockHeight = min(BLOCK_SIZE / sampleSize, bitmapHeight - bitmapY)
if (blockWidth > 0 && blockHeight > 0) {
bitmap.getPixels(blockPixels, 0, blockWidth, bitmapX, bitmapY, blockWidth, blockHeight)
for (jj in 0 until minOf(blockHeight, dd / sampleSize)) {
for (ii in 0 until blockWidth) {
val bj = if (isTop) jj else blockHeight - jj - 1
@ -215,4 +218,4 @@ class EdgeDetector(private val context: Context) {
private fun region(x: Int, y: Int) = Rect(x, y, x + BLOCK_SIZE, y + BLOCK_SIZE)
}
}
}

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.ui
import android.app.assist.AssistContent
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.Gravity
import android.view.KeyEvent
@ -214,7 +215,9 @@ class ReaderActivity :
override fun onProvideAssistContent(outContent: AssistContent) {
super.onProvideAssistContent(outContent)
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
}
}
override fun isNsfwContent(): Flow<Boolean> = viewModel.isMangaNsfw
@ -371,7 +374,6 @@ class ReaderActivity :
viewBinding.infoBar.isTimeVisible = isFullscreen
updateScrollTimerButton()
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
viewBinding.root.requestApplyInsets()
}
}
@ -393,14 +395,8 @@ class ReaderActivity :
viewBinding.infoBar.updatePadding(
top = systemBars.top,
)
val innerInsets = Insets.of(
systemBars.left,
if (viewBinding.appbarTop.isVisible) viewBinding.appbarTop.height else systemBars.top,
systemBars.right,
viewBinding.toolbarDocked?.takeIf { it.isVisible }?.height ?: systemBars.bottom,
)
return WindowInsetsCompat.Builder(insets)
.setInsets(WindowInsetsCompat.Type.systemBars(), innerInsets)
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
.build()
}

@ -157,12 +157,6 @@ class ReaderViewModel @Inject constructor(
valueProducer = { isWebtoonGapsEnabled },
)
val isWebtoonPullGestureEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_WEBTOON_PULL_GESTURE,
valueProducer = { isWebtoonPullGestureEnabled },
)
val defaultWebtoonZoomOut = observeIsWebtoonZoomEnabled().flatMapLatest {
if (it) {
observeWebtoonZoomOut()
@ -351,14 +345,11 @@ class ReaderViewModel @Inject constructor(
return@launchJob
}
ensureActive()
val autoLoadAllowed = readerMode.value != ReaderMode.WEBTOON || !isWebtoonPullGestureEnabled.value
if (autoLoadAllowed) {
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (pageLoader.isPrefetchApplicable()) {
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))

@ -11,7 +11,6 @@ import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.activityViewModels
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -26,9 +25,7 @@ import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
import org.koitharu.kotatsu.core.util.ext.consume
import org.koitharu.kotatsu.core.util.ext.findParentCallback
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.core.util.progress.IntPercentLabelFormatter
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
@ -40,8 +37,7 @@ class ReaderConfigSheet :
BaseAdaptiveSheet<SheetReaderConfigBinding>(),
View.OnClickListener,
MaterialButtonToggleGroup.OnButtonCheckedListener,
CompoundButton.OnCheckedChangeListener,
Slider.OnChangeListener {
CompoundButton.OnCheckedChangeListener {
private val viewModel by activityViewModels<ReaderViewModel>()
@ -90,13 +86,6 @@ class ReaderConfigSheet :
binding.buttonVertical.isChecked = mode == ReaderMode.VERTICAL
binding.switchDoubleReader.isChecked = settings.isReaderDoubleOnLandscape
binding.switchDoubleReader.isEnabled = mode == ReaderMode.STANDARD || mode == ReaderMode.REVERSED
binding.switchPullGesture.isChecked = settings.isWebtoonPullGestureEnabled
binding.switchPullGesture.isEnabled = mode == ReaderMode.WEBTOON
binding.textSensitivity.isVisible = settings.isReaderDoubleOnLandscape
binding.seekbarSensitivity.isVisible = settings.isReaderDoubleOnLandscape
binding.seekbarSensitivity.setValueRounded(settings.readerDoublePagesSensitivity * 100f)
binding.seekbarSensitivity.setLabelFormatter(IntPercentLabelFormatter(binding.root.context))
binding.checkableGroup.addOnButtonCheckedListener(this)
binding.buttonSavePage.setOnClickListener(this)
@ -107,8 +96,6 @@ class ReaderConfigSheet :
binding.buttonScrollTimer.setOnClickListener(this)
binding.buttonBookmark.setOnClickListener(this)
binding.switchDoubleReader.setOnCheckedChangeListener(this)
binding.switchPullGesture.setOnCheckedChangeListener(this)
binding.seekbarSensitivity.addOnChangeListener(this)
viewModel.isBookmarkAdded.observe(viewLifecycleOwner) {
binding.buttonBookmark.setText(if (it) R.string.bookmark_remove else R.string.bookmark_add)
@ -183,21 +170,11 @@ class ReaderConfigSheet :
R.id.switch_double_reader -> {
settings.isReaderDoubleOnLandscape = isChecked
viewBinding?.textSensitivity?.isVisible = isChecked
viewBinding?.seekbarSensitivity?.isVisible = isChecked
findParentCallback(Callback::class.java)?.onDoubleModeChanged(isChecked)
}
R.id.switch_pull_gesture -> {
settings.isWebtoonPullGestureEnabled = isChecked
}
}
}
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
settings.readerDoublePagesSensitivity = value / 100f
}
override fun onButtonChecked(
group: MaterialButtonToggleGroup?,
checkedId: Int,
@ -214,7 +191,6 @@ class ReaderConfigSheet :
else -> return
}
viewBinding?.switchDoubleReader?.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED
viewBinding?.switchPullGesture?.isEnabled = newMode == ReaderMode.WEBTOON
if (newMode == mode) {
return
}

@ -11,14 +11,11 @@ import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.SmoothScroller.ScrollVectorProvider
import androidx.recyclerview.widget.SnapHelper
import org.koitharu.kotatsu.core.prefs.AppSettings
import kotlin.math.abs
import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.sign
class DoublePageSnapHelper(private val settings: AppSettings) : SnapHelper() {
class DoublePageSnapHelper : SnapHelper() {
private lateinit var recyclerView: RecyclerView
@ -251,27 +248,28 @@ class DoublePageSnapHelper(private val settings: AppSettings) : SnapHelper() {
equal to zero.
*/
fun getPositionsToMove(llm: LinearLayoutManager, scroll: Int, itemSize: Int): Int {
val sensitivity = settings.readerDoublePagesSensitivity.coerceIn(0f, 1f) * 2.5
var positionsToMove = (scroll.toDouble() / (itemSize * (2.5 - sensitivity))).roundToInt()
// Apply a maximum threshold
val maxPages = (4 * sensitivity).roundToInt().coerceAtLeast(1)
if (positionsToMove.absoluteValue > maxPages) {
positionsToMove = maxPages * positionsToMove.sign
var positionsToMove: Int
positionsToMove = roundUpToBlockSize(abs((scroll.toDouble()) / itemSize).roundToInt())
if (positionsToMove < blockSize) {
// Must move at least one block
positionsToMove = blockSize
} else if (positionsToMove > maxPositionsToMove) {
// Clamp number of positions to move, so we don't get wild flinging.
positionsToMove = maxPositionsToMove
}
// Apply a minimum threshold
if (positionsToMove == 0 && scroll.absoluteValue > itemSize * 0.2) {
positionsToMove = 1 * scroll.sign
if (scroll < 0) {
positionsToMove *= -1
}
val currentPosition = if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
llm.findFirstVisibleItemPosition()
if (isRTL) {
positionsToMove *= -1
}
return if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
// Scrolling toward the bottom of data.
roundDownToBlockSize(llm.findFirstVisibleItemPosition()) + positionsToMove
} else {
llm.findLastVisibleItemPosition()
roundDownToBlockSize(llm.findLastVisibleItemPosition()) + positionsToMove
}
val targetPos = currentPosition + positionsToMove * 2
return roundDownToBlockSize(targetPos)
// Scrolling toward the top of the data.
}
fun isDirectionToBottom(velocityNegative: Boolean): Boolean {

@ -13,7 +13,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.list.lifecycle.RecyclerViewLifecycleDispatcher
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
import org.koitharu.kotatsu.databinding.FragmentReaderDoubleBinding
@ -34,9 +33,6 @@ open class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding
@Inject
lateinit var pageLoader: PageLoader
@Inject
lateinit var settings: AppSettings
private var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null
override fun onCreateViewBinding(
@ -55,7 +51,7 @@ open class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding
addOnScrollListener(it)
}
addOnScrollListener(PageScrollListener())
DoublePageSnapHelper(settings).attachToRecyclerView(this)
DoublePageSnapHelper().attachToRecyclerView(this)
}
}

@ -40,7 +40,7 @@ class WebtoonImageView @JvmOverloads constructor(
fun scrollTo(y: Int) {
val maxScroll = getScrollRange()
if (maxScroll == 0) {
scrollToInternal(0)
resetScaleAndCenter()
return
}
scrollToInternal(y.coerceIn(0, maxScroll))

@ -2,13 +2,8 @@ package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.animation.DecelerateInterpolator
import android.widget.TextView
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
@ -32,8 +27,7 @@ import javax.inject.Inject
@AndroidEntryPoint
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
WebtoonRecyclerView.OnWebtoonScrollListener,
WebtoonRecyclerView.OnPullGestureListener {
WebtoonRecyclerView.OnWebtoonScrollListener {
@Inject
lateinit var networkState: NetworkState
@ -44,8 +38,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
private val scrollInterpolator = DecelerateInterpolator()
private var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null
private var canGoPrev = true
private var canGoNext = true
override fun onCreateViewBinding(
inflater: LayoutInflater,
@ -61,7 +53,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
addOnScrollListener(it)
}
setOnPullGestureListener(this@WebtoonReaderFragment)
}
viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {
binding.frame.isZoomEnable = it
@ -79,18 +70,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
viewModel.readerSettingsProducer.observe(viewLifecycleOwner) {
it.applyBackground(binding.root)
}
viewModel.isWebtoonPullGestureEnabled.observe(viewLifecycleOwner) { enabled ->
binding.recyclerView.isPullGestureEnabled = enabled
}
viewModel.uiState.observe(viewLifecycleOwner) { state ->
if (state != null) {
canGoPrev = state.chapterIndex > 0
canGoNext = state.chapterIndex < state.chaptersTotal - 1
} else {
canGoPrev = true
canGoNext = true
}
}
}
override fun onDestroyView() {
@ -99,19 +78,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
super.onDestroyView()
}
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
val offsetInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
viewBinding?.apply {
feedbackTop.updateLayoutParams<MarginLayoutParams> {
topMargin = bottomMargin + offsetInsets.top
}
feedbackBottom.updateLayoutParams<MarginLayoutParams> {
bottomMargin = topMargin + offsetInsets.bottom
}
}
return super.onApplyWindowInsets(v, insets)
}
override fun onCreateAdapter() = WebtoonAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,
@ -202,47 +168,6 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
return true
}
override fun onPullProgressTop(progress: Float) {
val binding = viewBinding ?: return
if (canGoPrev) {
binding.feedbackTop.setFeedbackText(getString(R.string.pull_to_prev_chapter))
} else {
binding.feedbackTop.setFeedbackText(getString(R.string.pull_top_no_prev))
}
binding.feedbackTop.updateFeedback(progress)
}
override fun onPullProgressBottom(progress: Float) {
val binding = viewBinding ?: return
if (canGoNext) {
binding.feedbackBottom.setFeedbackText(getString(R.string.pull_to_next_chapter))
} else {
binding.feedbackBottom.setFeedbackText(getString(R.string.pull_bottom_no_next))
}
binding.feedbackBottom.updateFeedback(progress)
}
override fun onPullTriggeredTop() {
(viewBinding ?: return).feedbackTop.fadeOut()
if (canGoPrev) {
viewModel.switchChapterBy(-1)
}
}
override fun onPullTriggeredBottom() {
(viewBinding ?: return).feedbackBottom.fadeOut()
if (canGoNext) {
viewModel.switchChapterBy(1)
}
}
override fun onPullCancelled() {
viewBinding?.apply {
feedbackTop.fadeOut()
feedbackBottom.fadeOut()
}
}
private fun RecyclerView.findCurrentPagePosition(): Int {
val centerX = width / 2f
val centerY = height - resources.getDimension(R.dimen.webtoon_pages_gap)
@ -252,25 +177,4 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
val view = findChildViewUnder(centerX, centerY) ?: return RecyclerView.NO_POSITION
return getChildAdapterPosition(view)
}
private fun TextView.updateFeedback(progress: Float) {
val clamped = progress.coerceIn(0f, 1.2f)
this.alpha = clamped.coerceAtMost(1f)
this.scaleX = 0.9f + 0.1f * clamped.coerceAtMost(1f)
this.scaleY = this.scaleX
}
private fun TextView.fadeOut() {
animate().alpha(0f).setDuration(150L).start()
}
private fun TextView.setFeedbackText(text: CharSequence) {
if (this.alpha <= 0f && text.isNotEmpty()) {
this.alpha = 0f
this.text = text
animate().alpha(1f).setDuration(120L).start()
} else {
this.text = text
}
}
}

@ -1,10 +1,8 @@
package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
import android.widget.EdgeEffect
import androidx.core.view.ViewCompat.TYPE_TOUCH
import androidx.core.view.forEach
import androidx.core.view.isEmpty
@ -12,8 +10,6 @@ import androidx.core.view.isNotEmpty
import androidx.core.view.iterator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_BOTTOM
import androidx.recyclerview.widget.RecyclerView.EdgeEffectFactory.DIRECTION_TOP
import java.util.Collections
import java.util.LinkedList
import java.util.WeakHashMap
@ -27,26 +23,6 @@ class WebtoonRecyclerView @JvmOverloads constructor(
private val detachedViews = Collections.newSetFromMap(WeakHashMap<View, Boolean>())
private var isFixingScroll = false
var isPullGestureEnabled: Boolean = false
set(value) {
if (field != value) {
field = value
setEdgeEffectFactory(
if (value) {
PullEffect.Factory()
} else {
EdgeEffectFactory()
},
)
}
}
var pullThreshold: Float = 0.3f
private var pullListener: OnPullGestureListener? = null
fun setOnPullGestureListener(listener: OnPullGestureListener?) {
pullListener = listener
}
override fun onChildDetachedFromWindow(child: View) {
super.onChildDetachedFromWindow(child)
detachedViews.add(child)
@ -203,68 +179,6 @@ class WebtoonRecyclerView @JvmOverloads constructor(
}
}
private class PullEffect(
view: RecyclerView,
private val direction: Int,
private val pullThreshold: Float,
private val pullListener: OnPullGestureListener,
) : EdgeEffect(view.context) {
private var pullProgressTop: Float = 0f
private var pullProgressBottom: Float = 0f
override fun onPull(deltaDistance: Float) {
val sign = if (direction == DIRECTION_TOP) 1f else if (direction == DIRECTION_BOTTOM) 1f else 0f
if (sign != 0f) onPull(deltaDistance, 0.5f)
}
override fun onPull(deltaDistance: Float, displacement: Float) {
if (direction == DIRECTION_TOP) {
pullProgressTop = (pullProgressTop + deltaDistance).coerceAtLeast(0f)
pullListener.onPullProgressTop(pullProgressTop / pullThreshold)
} else if (direction == DIRECTION_BOTTOM) {
pullProgressBottom = (pullProgressBottom + deltaDistance).coerceAtLeast(0f)
pullListener.onPullProgressBottom(pullProgressBottom / pullThreshold)
}
}
override fun onRelease() {
var triggered = false
if (direction == DIRECTION_TOP) {
if (pullProgressTop >= pullThreshold) {
pullListener.onPullTriggeredTop()
triggered = true
}
pullProgressTop = 0f
pullListener.onPullProgressTop(0f)
} else if (direction == DIRECTION_BOTTOM) {
if (pullProgressBottom >= pullThreshold) {
pullListener.onPullTriggeredBottom()
triggered = true
}
pullProgressBottom = 0f
pullListener.onPullProgressBottom(0f)
}
if (!triggered) {
pullListener.onPullCancelled()
}
}
override fun draw(canvas: Canvas?): Boolean = false
class Factory : EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
val pullListener = (view as? WebtoonRecyclerView)?.pullListener
return if (pullListener != null) {
PullEffect(view, direction, view.pullThreshold, pullListener)
} else {
super.createEdgeEffect(view, direction)
}
}
}
}
interface OnWebtoonScrollListener {
fun onScrollChanged(
@ -274,12 +188,4 @@ class WebtoonRecyclerView @JvmOverloads constructor(
lastVisiblePosition: Int,
)
}
interface OnPullGestureListener {
fun onPullProgressTop(progress: Float)
fun onPullProgressBottom(progress: Float)
fun onPullTriggeredTop()
fun onPullTriggeredBottom()
fun onPullCancelled()
}
}

@ -8,7 +8,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
@ -41,8 +40,6 @@ import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.sizeOrZero
import javax.inject.Inject
@ -58,9 +55,8 @@ open class RemoteListViewModel @Inject constructor(
protected val mangaListMapper: MangaListMapper,
private val exploreRepository: ExploreRepository,
sourcesRepository: MangaSourcesRepository,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), FilterCoordinator.Owner {
mangaDataRepository: MangaDataRepository
) : MangaListViewModel(settings, mangaDataRepository), FilterCoordinator.Owner {
val source = MangaSource(savedStateHandle[RemoteListFragment.ARG_SOURCE])
val isRandomLoading = MutableStateFlow(false)

@ -7,6 +7,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@ -46,7 +47,7 @@ class ScrobblerConfigViewModel @Inject constructor(
val content = scrobbler.observeAllScrobblingInfo()
.onStart { loadingCounter.increment() }
.onFirst { loadingCounter.decrement() }
.withErrorHandling()
.catch { errorEvent.call(it) }
.map { buildContentList(it) }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())

@ -1,7 +1,6 @@
package org.koitharu.kotatsu.scrobbling.discord.ui
import android.content.Context
import android.os.SystemClock
import androidx.annotation.AnyThread
import androidx.collection.ArrayMap
import com.my.kizzyrpc.KizzyRPC
@ -15,7 +14,6 @@ import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import okio.utf8Size
@ -37,7 +35,6 @@ import javax.inject.Inject
private const val STATUS_ONLINE = "online"
private const val STATUS_IDLE = "idle"
private const val BUTTON_TEXT_LIMIT = 32
private const val DEBOUNCE_TIMEOUT = 16_000L // 16 sec
@ViewModelScoped
class DiscordRpc @Inject constructor(
@ -52,7 +49,6 @@ class DiscordRpc @Inject constructor(
private val appName = context.getString(R.string.app_name)
private val appIcon = context.getString(R.string.app_icon_url)
private val mpCache = Collections.synchronizedMap(ArrayMap<String, String>())
private var lastUpdate = 0L
private var rpc: KizzyRPC? = null
@ -72,7 +68,6 @@ class DiscordRpc @Inject constructor(
fun clearRpc() = synchronized(this) {
rpc?.closeRPC()
rpc = null
lastUpdate = 0L
}
fun setIdle() {
@ -119,10 +114,6 @@ class DiscordRpc @Inject constructor(
val prevJob = rpcUpdateJob
rpcUpdateJob = coroutineScope.launch {
prevJob?.cancelAndJoin()
val debounceTime = lastUpdate + DEBOUNCE_TIMEOUT - SystemClock.elapsedRealtime()
if (debounceTime > 0) {
delay(debounceTime)
}
val hideButtons = activity.buttons?.any { it != null && it.utf8Size() > BUTTON_TEXT_LIMIT } ?: false
val mappedActivity = activity.copy(
assets = activity.assets?.let {
@ -140,7 +131,6 @@ class DiscordRpc @Inject constructor(
status = if (idle) STATUS_IDLE else STATUS_ONLINE,
since = activity.timestamps?.start ?: System.currentTimeMillis(),
)
lastUpdate = SystemClock.elapsedRealtime()
}
}

@ -183,7 +183,7 @@ class MALRepository @Inject constructor(
storage.clear()
}
private fun jsonToManga(json: JSONObject, sourceTitle: String): ScrobblerManga {
private fun jsonToManga(json: JSONObject, sourceTitle: String): ScrobblerManga? {
val node = json.getJSONObject("node")
val title = node.getString("title")
return ScrobblerManga(

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import androidx.appcompat.widget.TooltipCompat
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView

@ -8,7 +8,6 @@ import androidx.core.net.toUri
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.isActive
@ -19,6 +18,7 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.requireValue
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
@HiltViewModel
class AppUpdateViewModel @Inject constructor(
@ -79,7 +79,7 @@ class AppUpdateViewModel @Inject constructor(
private suspend fun observeDownload(id: Long) {
val query = DownloadManager.Query()
query.setFilterById(id)
while (currentCoroutineContext().isActive) {
while (coroutineContext.isActive) {
downloadManager.query(query).use { cursor ->
if (cursor.moveToFirst()) {
val bytesDownloaded = cursor.getLong(

@ -24,7 +24,7 @@ class DiscordSettingsFragment : BasePreferenceFragment(R.string.discord) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_discord)
findPreference<EditTextPreference>(AppSettings.KEY_DISCORD_TOKEN)?.let { pref ->
findPreference<EditTextPreference>(AppSettings.Companion.KEY_DISCORD_TOKEN)?.let { pref ->
pref.dialogMessage = pref.context.getString(
R.string.discord_token_description,
pref.context.getString(R.string.sign_in),
@ -44,21 +44,21 @@ class DiscordSettingsFragment : BasePreferenceFragment(R.string.discord) {
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference is EditTextPreference && preference.key == AppSettings.KEY_DISCORD_TOKEN) {
if (parentFragmentManager.findFragmentByTag(TokenDialogFragment.DIALOG_FRAGMENT_TAG) != null) {
if (preference is EditTextPreference && preference.key == AppSettings.Companion.KEY_DISCORD_TOKEN) {
if (parentFragmentManager.findFragmentByTag(TokenDialogFragment.Companion.DIALOG_FRAGMENT_TAG) != null) {
return
}
val f = TokenDialogFragment.newInstance(preference.key)
@Suppress("DEPRECATION")
f.setTargetFragment(this, 0)
f.show(parentFragmentManager, TokenDialogFragment.DIALOG_FRAGMENT_TAG)
f.show(parentFragmentManager, TokenDialogFragment.Companion.DIALOG_FRAGMENT_TAG)
return
}
super.onDisplayPreferenceDialog(preference)
}
private fun bindTokenPreference(state: TokenState, token: String?) {
val pref = findPreference<EditTextPreference>(AppSettings.KEY_DISCORD_TOKEN) ?: return
val pref = findPreference<EditTextPreference>(AppSettings.Companion.KEY_DISCORD_TOKEN) ?: return
when (state) {
TokenState.EMPTY -> {
pref.icon = null

@ -34,7 +34,7 @@ class DiscordSettingsViewModel @Inject constructor(
TokenState.CHECKING to settings.discordToken,
)
private fun checkToken(): Flow<Pair<TokenState, String?>> = flow {
private suspend fun checkToken(): Flow<Pair<TokenState, String?>> = flow {
val token = settings.discordToken
if (!settings.isDiscordRpcEnabled) {
emit(

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.settings.protect
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.KeyEvent
@ -114,6 +115,7 @@ class ProtectSetupActivity :
}
private fun isBiometricAvailable(): Boolean {
return packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
}
}

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
@ -32,6 +33,10 @@ class DozeHelper(
}
fun startIgnoreDoseActivity(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Snackbar.make(fragment.listView ?: return false, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
return false
}
val context = fragment.context ?: return false
val packageName = context.packageName
val powerManager = context.powerManager ?: return false
@ -53,6 +58,9 @@ class DozeHelper(
}
private fun isDozeIgnoreAvailable(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false
}
val context = fragment.context ?: return false
val packageName = context.packageName
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager

@ -24,9 +24,6 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.suggestions.domain.SuggestionRepository
import org.koitharu.kotatsu.suggestions.domain.SuggestionsListQuickFilter
import javax.inject.Inject
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import kotlinx.coroutines.flow.SharedFlow
@HiltViewModel
class SuggestionsViewModel @Inject constructor(
@ -36,8 +33,7 @@ class SuggestionsViewModel @Inject constructor(
private val quickFilter: SuggestionsListQuickFilter,
private val suggestionsScheduler: SuggestionsWorker.Scheduler,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_SUGGESTIONS) { suggestionsListMode }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.suggestionsListMode)

@ -46,6 +46,7 @@ import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler
import org.koitharu.kotatsu.core.model.distinctById
import org.koitharu.kotatsu.core.model.getLocale

@ -34,7 +34,7 @@ data class FavouriteCategorySyncDto(
put("created_at", createdAt)
put("sort_key", sortKey)
put("title", title)
put("`order`", order)
put("order", order)
put("track", track)
put("show_in_lib", isVisibleInLibrary)
put("deleted_at", deletedAt)

@ -5,8 +5,8 @@ import kotlinx.serialization.Serializable
@Serializable
data class SyncDto(
@SerialName("history") val history: List<HistorySyncDto>? = null,
@SerialName("categories") val categories: List<FavouriteCategorySyncDto>? = null,
@SerialName("favourites") val favourites: List<FavouriteSyncDto>? = null,
@SerialName("history") val history: List<HistorySyncDto>?,
@SerialName("categories") val categories: List<FavouriteCategorySyncDto>?,
@SerialName("favourites") val favourites: List<FavouriteSyncDto>?,
@SerialName("timestamp") val timestamp: Long,
)

@ -22,6 +22,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import okhttp3.internal.closeQuietly
import okio.IOException
import org.jetbrains.annotations.Blocking
import org.koitharu.kotatsu.BuildConfig
@ -88,12 +89,12 @@ class SyncHelper @AssistedInject constructor(
val response = httpClient.newCall(request).execute().parseDtoOrNull()
response?.categories?.let { categories ->
val categoriesResult = upsertFavouriteCategories(categories)
stats.numDeletes += categoriesResult.firstOrNull()?.count?.toLong() ?: 0L
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
}
response?.favourites?.let { favourites ->
val favouritesResult = upsertFavourites(favourites)
stats.numDeletes += favouritesResult.firstOrNull()?.count?.toLong() ?: 0L
stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
stats.numEntries += stats.numInserts + stats.numDeletes
}
@ -118,7 +119,7 @@ class SyncHelper @AssistedInject constructor(
val response = httpClient.newCall(request).execute().parseDtoOrNull()
response?.history?.let { history ->
val result = upsertHistory(history)
stats.numDeletes += result.firstOrNull()?.count?.toLong() ?: 0L
stats.numDeletes += result.first().count?.toLong() ?: 0L
stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
stats.numEntries += stats.numInserts + stats.numDeletes
}
@ -285,11 +286,15 @@ class SyncHelper @AssistedInject constructor(
private fun uri(authority: String, table: String) = "content://$authority/$table".toUri()
private fun Response.parseDtoOrNull(): SyncDto? = use {
when {
!isSuccessful -> throw IOException(body.string())
code == HttpURLConnection.HTTP_NO_CONTENT -> null
else -> Json.decodeFromString<SyncDto>(body.string())
private fun Response.parseDtoOrNull(): SyncDto? {
return try {
when {
!isSuccessful -> throw IOException(body?.string())
code == HttpURLConnection.HTTP_NO_CONTENT -> null
else -> Json.decodeFromString<SyncDto>(body?.string() ?: return null)
}
} finally {
closeQuietly()
}
}

@ -31,9 +31,6 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.domain.UpdatesListQuickFilter
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import javax.inject.Inject
import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.domain.model.LocalManga
import kotlinx.coroutines.flow.SharedFlow
@HiltViewModel
class UpdatesViewModel @Inject constructor(
@ -42,8 +39,7 @@ class UpdatesViewModel @Inject constructor(
private val mangaListMapper: MangaListMapper,
private val quickFilter: UpdatesListQuickFilter,
mangaDataRepository: MangaDataRepository,
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
override val content = combine(
quickFilter.appliedOptions.flatMapLatest { filterOptions ->

@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.parsers.model.ContentRating
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import javax.inject.Inject

@ -1,11 +0,0 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M4,3L1,6H3V9H1L4,12L7,9H5V6H7L4,3M11,8A1,1 0 0,0 10,9V19L6.8,17.28H6.58C6.3,17.28 6.03,17.39 5.84,17.6L5.1,18.37L10,22.57C10.26,22.85 10.62,23 11,23H17.5A1.5,1.5 0 0,0 19,21.5V17.14C19,16.56 18.68,16.03 18.15,15.79L13.21,13.6L12,13.47V9A1,1 0 0,0 11,8Z" />
</vector>

@ -32,9 +32,7 @@
style="?materialButtonTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="2dp"
android:layout_marginTop="10dp"
android:layout_marginTop="6dp"
android:text="@string/open_in_browser" />
<TextView

@ -2,7 +2,6 @@
<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -17,36 +16,4 @@
android:orientation="vertical"
app:layoutManager="org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonLayoutManager" />
<TextView
android:id="@+id/feedbackTop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_margin="@dimen/screen_padding"
android:alpha="0"
android:background="@drawable/bg_reader_indicator"
android:gravity="center"
android:paddingHorizontal="@dimen/margin_normal"
android:paddingVertical="@dimen/margin_small"
android:text="@string/pull_to_prev_chapter"
android:textAppearance="?textAppearanceBodyLarge"
android:theme="@style/ThemeOverlay.Material3.Dark"
tools:alpha="1" />
<TextView
android:id="@+id/feedbackBottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="@dimen/screen_padding"
android:alpha="0"
android:background="@drawable/bg_reader_indicator"
android:gravity="center"
android:paddingHorizontal="@dimen/margin_normal"
android:paddingVertical="@dimen/margin_small"
android:text="@string/pull_to_next_chapter"
android:textAppearance="?textAppearanceBodyLarge"
android:theme="@style/ThemeOverlay.Material3.Dark"
tools:alpha="1" />
</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame>

@ -129,40 +129,6 @@
android:textColor="?colorOnSurfaceVariant"
app:drawableStartCompat="@drawable/ic_split_horizontal" />
<TextView
android:id="@+id/text_sensitivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:text="@string/two_page_scroll_sensitivity"
android:textAppearance="@style/TextAppearance.Kotatsu.GridTitle" />
<com.google.android.material.slider.Slider
android:id="@+id/seekbar_sensitivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_small"
android:layout_marginTop="@dimen/margin_small"
android:valueFrom="0"
android:valueTo="100"
app:labelBehavior="floating"
tools:value="50" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_pull_gesture"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_small"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:minHeight="?android:listPreferredItemHeightSmall"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/enable_pull_gesture_title"
android:textAppearance="?textAppearanceListItem"
android:textColor="?colorOnSurfaceVariant"
app:drawableStartCompat="@drawable/ic_gesture_vertical" />
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
android:id="@+id/button_screen_rotate"
android:layout_width="match_parent"

@ -67,7 +67,7 @@
android:contentDescription="@string/speed"
android:labelFor="@id/switch_scroll_timer"
android:valueFrom="0.000001"
android:valueTo="0.97"
android:valueTo="0.95"
app:labelBehavior="floating"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/label_timer"

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
@ -10,8 +9,7 @@
android:orderInCategory="10"
android:title="@string/search_chapters"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="always|collapseActionView"
tools:ignore="AlwaysShowAction" />
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_downloaded"

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>

@ -859,9 +859,4 @@
<string name="no_chapters_in_manga">Гэтая манга не ўтрымлівае раздзелаў</string>
<string name="chapters_load_failed">Не атрымалася загрузіць спіс раздзелаў</string>
<string name="telegram_integration">Інтэграцыя з Telegram</string>
<string name="pull_top_no_prev">Няма папярэдняй главы</string>
<string name="pull_bottom_no_next">Няма наступнай главы</string>
<string name="enable_pull_gesture_title">Уключыць жэст перацягвання</string>
<string name="enable_pull_gesture_summary">Выкарыстоўвайце жэст пацягвання, каб пераключацца паміж главамі ў манхве</string>
<string name="test_parser">Праверыць крыніцу мангі</string>
</resources>

@ -784,84 +784,4 @@
<string name="screen_rotation_unlocked">La rotación de pantalla se ha desbloqueado</string>
<string name="simple">Simple</string>
<string name="global_search">Búsqueda global</string>
<string name="clear_database">Limpiar base de datos</string>
<string name="clear_database_summary">Eliminar información en desuso del manga</string>
<string name="enable_all_sources">Habilitar todas las fuentes de manga</string>
<string name="reader_info_bar_transparent">Barra de información del lector transparente</string>
<string name="reader_controls_in_bottom_bar">Controles del lector en la barra inferior</string>
<string name="chapters_and_pages">Capítulos y páginas</string>
<string name="pages_slider">Deslizar para cambiar de página</string>
<string name="badges_in_lists">Insignias en listas</string>
<string name="search_everywhere">Buscar en todas partes</string>
<string name="disable_captcha_notifications">Deshabilitar las notificaciones de captcha</string>
<string name="disable_captcha_notifications_summary">No recibirás notificaciones sobre la resolución de CAPTCHA para esta fuente, pero esto puede provocar la interrupción de las operaciones en segundo plano (comprobación de nuevos capítulos, obtención de recomendaciones, etc.)</string>
<string name="chapter_volume_number">Vol %1$s Capítulo %2$s</string>
<string name="chapter_number">Capítulo %s</string>
<string name="unnamed_chapter">Capítulo sin título</string>
<string name="search_disabled_sources">Buscar en fuentes deshabilitadas</string>
<string name="error_details">Detalles del error</string>
<string name="error_disclaimer_manga">Intenta abrir el manga en un navegador web para asegurarte de que está disponible en su fuente.</string>
<string name="error_disclaimer_app_outdated">Parece que tu versión de Kotatsu está desactualizada. Instala la última versión para obtener todas las correcciones disponibles.</string>
<string name="error_disclaimer_report">Puede enviar un informe de error a los desarrolladores. Esto nos ayudará a investigar y solucionar el problema.</string>
<string name="link_to_manga_on_s">Enlace al manga en %s</string>
<string name="link_to_manga_in_app">Enlace al manga en Kotatsu</string>
<string name="clear_browser_data">Borrar datos del navegador</string>
<string name="clear_browser_data_summary">Borra los datos del navegador, como la caché y las cookies. Advertencia: la autorización en las fuentes de manga puede dejar de ser válida</string>
<string name="no_write_permission_to_file">No tiene permiso para escribir el archivo</string>
<string name="exclude_nsfw_from_suggestions_summary">El manga para adultos no aparecerá en las sugerencias. Esta opción puede funcionar de forma inexacta con algunas fuentes</string>
<string name="include_disabled_sources">Incluir fuentes deshabilitadas</string>
<string name="suggestions_disabled_sources_summary">Mostrar sugerencias de todas las fuentes de manga, incluidas las deshabilitadas</string>
<string name="tags_warnings">Destacar géneros peligrosos</string>
<string name="tags_warnings_summary">Resaltar los géneros que pueden ser inapropiados para la mayoría de los usuarios</string>
<string name="error_non_file_uri">La ruta seleccionada no se puede utilizar porque no indica un archivo o directorio</string>
<string name="manga_override_hint">Estos cambios afectarán a la forma en que se muestra el manga en la aplicación</string>
<string name="pick_manga_page">Seleccionar página</string>
<string name="incognito_for_nsfw">Modo incógnito para manga NSFW</string>
<string name="theme_name_expressive">Expresivo (Prueba)</string>
<string name="additional_action_required">Se requieren medidas adicionales</string>
<string name="hide_from_main_screen">Ocultar de la pantalla principal</string>
<string name="changelog">Registro de cambios</string>
<string name="changelog_summary">Historial de cambios de las versiones publicadas recientemente</string>
<string name="collapse">Plegar</string>
<string name="adblock">Bloquear anuncios en el navegador</string>
<string name="adblock_summary">Bloquear anuncios en el navegador integrado (beta)</string>
<string name="collapse_long_description">Plegar descripción larga</string>
<string name="creating_backup">Creando copia de seguridad</string>
<string name="share_backup">Compartir copia de seguridad</string>
<string name="reader_multitask">Abrir lector en una tarea separada</string>
<string name="reader_multitask_summary">Te permite mantener abiertos varios lectores con diferentes mangas al mismo tiempo</string>
<string name="reader_navigation_inverted">Invertir controles de navegación</string>
<string name="reader_navigation_inverted_summary">Cambiar la dirección del botón de volumen y la navegación con las teclas de dirección (izquierda/arriba/abajo/derecha)</string>
<string name="book_effect">Filtro de luz azul</string>
<string name="local_storage_cleanup">Limpieza del almacenamiento local</string>
<string name="packup_creation_failed">No se pudo crear la copia de seguridad</string>
<string name="main_screen">Pantalla principal</string>
<string name="main_screen_fab">Mostrar botón Continuar, flotante</string>
<string name="main_screen_fab_summary">Permite continuar leyendo con un solo clic. Este botón no aparecerá en modo incógnito ni cuando el historial esté vacío</string>
<string name="error_corrupted_zip">Archivo ZIP dañado (%s)</string>
<string name="discord_rpc">Presencia enriquecida con Discord</string>
<string name="discord_token">Token de Discord</string>
<string name="discord_token_summary">Introduce tu token de Discord para habilitar la presencia enriquecida</string>
<string name="discord_token_description">Introduce tu token de Discord o haz clic en %s para obtenerlo mediante el navegador</string>
<string name="discord_token_hint">Pega aquí tu token de Discord</string>
<string name="discord_rpc_summary">Muestra tu estado de lectura en Discord</string>
<string name="obtain">Obtener</string>
<string name="discord_rpc_description">Leer manga en Kotatsu: una aplicación para leer manga</string>
<string name="reading_s">Leyendo %s</string>
<string name="read_on_s">Sigue leyendo en %s</string>
<string name="rpc_skip_nsfw_summary">No utilice RPC para contenido de adultos</string>
<string name="invalid_token">Token no válido: %s</string>
<string name="show_floating_control_button">Mostrar botón de control, flotante</string>
<string name="unavailable">No disponible</string>
<string name="manga_restricted_description">Este manga no está disponible para leer en esta fuente. Intenta buscarlo en otras fuentes o ábrelo en un navegador para obtener más información</string>
<string name="no_chapters_in_manga">Este manga no contiene ningún capítulo</string>
<string name="chapters_load_failed">No se pudo cargar la lista de capítulos</string>
<string name="telegram_integration">Integración con Telegram</string>
<string name="test_parser">Probar la fuente de manga</string>
<string name="pull_to_prev_chapter">Abrir el capítulo anterior</string>
<string name="pull_to_next_chapter">Abrir el siguiente capítulo</string>
<string name="pull_top_no_prev">No existe capítulo anterior</string>
<string name="pull_bottom_no_next">No existe capítulo siguiente</string>
<string name="enable_pull_gesture_title">Habilitar gesto de arrastrar</string>
<string name="enable_pull_gesture_summary">Usa el gesto de arrastrar para cambiar de capítulo en webtoon</string>
</resources>

@ -853,10 +853,4 @@
<string name="manga_restricted_description">Ang manga na ito ay hindi magagamit na basahin sa source na ito. Subukang hanapin ito sa ibang mga source o buksan ito sa isang browser para sa higit pang impormasyon</string>
<string name="no_chapters_in_manga">Ang manga na ito ay walang anumang mga kabanata</string>
<string name="chapters_load_failed">Nabigong i-load ang listahan ng kabanata</string>
<string name="pull_to_prev_chapter">Bitawan upang mabuksan ang nakaraang kabanata</string>
<string name="pull_to_next_chapter">Bitawan upang mabuksan ang susunod na kabanata</string>
<string name="pull_top_no_prev">Walang nakaraang kabanata</string>
<string name="pull_bottom_no_next">Walang susunod na kabanata</string>
<string name="enable_pull_gesture_title">Paganahin ang paghila na gesture</string>
<string name="enable_pull_gesture_summary">Gamitin ang paghila na gesture para makapagpalit ng kabanata sa webtoon</string>
</resources>

@ -857,11 +857,4 @@
<string name="no_chapters_in_manga">Ce manga ne contient aucun chapitre</string>
<string name="chapters_load_failed">Échec du chargement de la liste des chapitres</string>
<string name="telegram_integration">Intégration Telegram</string>
<string name="pull_to_prev_chapter">Relâchez pour ouvrir le chapitre précédent</string>
<string name="pull_to_next_chapter">Relâchez pour ouvrir le chapitre suivant</string>
<string name="pull_top_no_prev">Pas de chapitre précédent</string>
<string name="pull_bottom_no_next">Pas de chapitre suivant</string>
<string name="enable_pull_gesture_title">Activer le geste de glissement</string>
<string name="enable_pull_gesture_summary">Utilisez le geste de glissement pour changer de chapitre dans un webtoon</string>
<string name="test_parser">Tester la source manga</string>
</resources>

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="hours">
<item quantity="one">%1$d 𐍈𐌴𐌹𐌻𐌰</item>
<item quantity="other">%1$d 𐍈𐌴𐌹𐌻𐍉𐍃</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%1$d 𐌼𐌹𐌽𐌿𐍄𐌿𐍃</item>
<item quantity="other">%1$d 𐌼𐌹𐌽𐌿𐍄𐌾𐌿𐍃</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">𐍆𐌰𐌿𐍂𐌰 %1$d 𐌳𐌰𐌲</item>
<item quantity="other">𐍆𐌰𐌿𐍂𐌰 %1$d 𐌳𐌰𐌲𐌰𐌽𐍃</item>
</plurals>
<plurals name="months_ago">
<item quantity="one">𐍆𐌰𐌿𐍂𐌰 𐌼𐌴𐌽𐍉𐌸 %1$d</item>
<item quantity="other">𐍆𐌰𐌿𐍂𐌰 𐌼𐌴𐌽𐍉𐌸𐌿𐌼 %1$d</item>
</plurals>
</resources>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="history">𐍃𐍀𐌹𐌻𐌻</string>
<string name="error_occurred">𐌰𐌹𐍂𐌶𐌴𐌹 𐍅𐌰𐍂𐌸</string>
<string name="remote_sources">𐌼𐌰𐌲𐌲𐌹𐌽𐍃 𐌱𐍂𐌿𐌽𐌽𐌰𐌽𐍃</string>
<string name="computing_">𐍂𐌰𐌷𐌽𐌾𐌰𐌳𐌰…</string>
<string name="read">𐌰𐌽𐌰𐌺𐌿𐌽𐌽𐌰𐌽</string>
<string name="share_s">%s 𐌳𐌰𐌹𐌻𐌾𐌰𐌽</string>
<string name="search">𐍃𐍉𐌺𐌾𐌰𐌽</string>
<string name="search_manga">𐌼𐌰𐌲𐌲𐌰 𐍃𐍉𐌺𐌾𐌰𐌽</string>
</resources>

@ -3,7 +3,7 @@
<plurals name="items">
<item quantity="one">%1$d stavka</item>
<item quantity="few">%1$d stavke</item>
<item quantity="other">%1$d stavki</item>
<item quantity="other">%1$d stavkih</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d novo poglavlje</item>
@ -16,24 +16,24 @@
<item quantity="other">%1$d poglavlja</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">Prije %1$d minute</item>
<item quantity="few">Prije %1$d minute</item>
<item quantity="other">Prije %1$d minuta</item>
<item quantity="one">prije %1$d minute</item>
<item quantity="few">prije %1$d minuta</item>
<item quantity="other">prije %1$d minuta</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">Prije %1$d sat</item>
<item quantity="few">Prije %1$d sata</item>
<item quantity="other">Prije %1$d sati</item>
<item quantity="one">prije %1$d sat</item>
<item quantity="few">prije %1$d sata</item>
<item quantity="other">prije %1$d sati</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">Prije %1$d dan</item>
<item quantity="few">Prije %1$d dana</item>
<item quantity="other">Prije %1$d dana</item>
<item quantity="one">prije %1$d dan</item>
<item quantity="few">prije %1$d dana</item>
<item quantity="other">prije %1$d dana</item>
</plurals>
<plurals name="months_ago">
<item quantity="one">Prije %1$d mjesec</item>
<item quantity="few">Prije %1$d mjeseca</item>
<item quantity="other">Prije %1$d mjeseci</item>
<item quantity="one">prije %1$d mjesec</item>
<item quantity="few">prije %1$d mjeseca</item>
<item quantity="other">prije %1$d mjeseci</item>
</plurals>
<plurals name="hours">
<item quantity="one">%1$d sat</item>
@ -45,4 +45,4 @@
<item quantity="few">%1$d minute</item>
<item quantity="other">%1$d minuta</item>
</plurals>
</resources>
</resources>

@ -11,10 +11,10 @@
<string name="show_updated">Prikaži ažurirano</string>
<string name="webtoon_gaps_summary">Prikaži okomite razmake između stranica u načinu webtoon</string>
<string name="more_frequently">Češće</string>
<string name="pin_navigation_ui_summary">Nemoj skrivati navigacijsku traku i prikaz pretrage prilikom listanja</string>
<string name="pin_navigation_ui_summary">Nemoj skrivati navigacijsku traku i prikaz pretraživanja prilikom pomicanja</string>
<string name="suggested_queries">Predloženi upiti</string>
<string name="last_used">Zadnje korišteno</string>
<string name="webtoon_gaps">Razmaci u webtoon modusu</string>
<string name="webtoon_gaps">Praznine u webtoon modu</string>
<string name="blocked_by_server_message">Poslužitelj vas je blokirao. Pokušajte koristiti drugu mrežnu vezu (VPN, proxy itd.)</string>
<string name="less_frequently">Rjeđe</string>
<string name="frequency_of_check">Učestalost provjere</string>
@ -39,7 +39,7 @@
<string name="computing_">Računanje…</string>
<string name="chapter_d_of_d">Poglavlje %1$d od %2$d</string>
<string name="close">Zatvori</string>
<string name="try_again">Pokušaj ponovo</string>
<string name="try_again">Pokušajte ponovno</string>
<string name="clear_history">Obriši povijest</string>
<string name="nothing_found">Ništa nije pronađeno</string>
<string name="read">Čitaj</string>
@ -48,11 +48,11 @@
<string name="add_new_category">Nova kategorija</string>
<string name="add">Dodaj</string>
<string name="save">Sačuvaj</string>
<string name="share">Dijeli</string>
<string name="create_shortcut">Stvori prečac</string>
<string name="share_s">Dijeli %s</string>
<string name="search">Traži</string>
<string name="search_manga">Traži mangu</string>
<string name="share">Podijeli</string>
<string name="create_shortcut">Napravi prečicu</string>
<string name="share_s">Podijeli %s</string>
<string name="search">Pretraži</string>
<string name="search_manga">Pretraži mangu</string>
<string name="manga_downloading_">Preuzimanje…</string>
<string name="network_error">Pogreška mreže</string>
<string name="list_mode">Modus popisa</string>
@ -70,7 +70,7 @@
<string name="text_file_not_supported">Odaberite ZIP ili CBZ datoteku.</string>
<string name="no_description">Bez opisa</string>
<string name="clear_pages_cache">Očisti predmemoriju stranice</string>
<string name="read_mode">Modus čitanja</string>
<string name="read_mode">Način čitanja</string>
<string name="grid_size">Veličina mreže</string>
<string name="search_on_s">Traži na %s</string>
<string name="delete_manga">Izbriši mangu</string>
@ -123,7 +123,7 @@
<string name="no_update_available">Nema dostupnih ažuriranja</string>
<string name="right_to_left">S desna na lijevo</string>
<string name="create_category">Nova kategorija</string>
<string name="scale_mode">Modus skaliranja</string>
<string name="scale_mode">Način skaliranja</string>
<string name="zoom_mode_fit_width">Prilagodi širini</string>
<string name="zoom_mode_fit_height">Prilagodi visini</string>
<string name="zoom_mode_fit_center">Prilagodi sredini</string>
@ -135,7 +135,7 @@
<string name="group">Grupa</string>
<string name="today">Danas</string>
<string name="tap_to_try_again">Dodirnite za ponovni pokušaj</string>
<string name="reader_mode_hint">Odabrana konfiguracija će se zapamtiti za ovaj manga</string>
<string name="reader_mode_hint">Odabrana konfiguracija bit će zapamćena za ovu mangu</string>
<string name="silent">Tiho</string>
<string name="captcha_required">Potrebna CAPTCHA</string>
<string name="captcha_solve">Riješi</string>
@ -202,11 +202,11 @@
<string name="bookmark_remove">Ukloni zabilješku</string>
<string name="bookmark_added">Zabilješka dodana</string>
<string name="removed_from_history">Uklonjeno iz povijesti</string>
<string name="detect_reader_mode">Automatski otkrij modus čitanja</string>
<string name="detect_reader_mode">Automatsko otkrivanje načina čitanja</string>
<string name="disable_battery_optimization_summary">Pomaže pri provjerama ažuriranja u pozadini</string>
<string name="crash_text">Nešto je pošlo po zlu. Pošaljite izvješće o pogrešci razvojnim programerima kako biste nam pomogli da je popravimo.</string>
<string name="send">Pošalji</string>
<string name="status_planned">Planirano</string>
<string name="status_planned">Planirani</string>
<string name="status_reading">Čitam</string>
<string name="status_re_reading">Ponovo čitam</string>
<string name="status_completed">Dovršeno</string>
@ -262,7 +262,7 @@
<string name="prefetch_content">Predučitavanje sadržaja</string>
<string name="mark_as_current">Označi kao trenutno</string>
<string name="language">Jezik</string>
<string name="share_logs">Dijeli dnevnike</string>
<string name="share_logs">Podijelite zapise</string>
<string name="enable_logging">Omogući bilježenje</string>
<string name="enable_logging_summary">Snimite neke radnje u svrhu otklanjanja pogrešaka. Nemojte ga uključivati ako niste sigurni što radite</string>
<string name="show_suspicious_content">Prikaži sumnjiv sadržaj</string>
@ -339,7 +339,7 @@
<string name="keep_screen_on_summary">Ne isključujte ekran dok čitate mangu</string>
<string name="state_abandoned">Izbačeno</string>
<string name="enhanced_colors_summary">Smanjuje trake, ali može utjecati na performanse</string>
<string name="enhanced_colors">32-bitni modus boje</string>
<string name="enhanced_colors">32-bitni način rada u boji</string>
<string name="suggest_new_sources">Predloži nove izvore nakon ažuriranja aplikacije</string>
<string name="suggest_new_sources_summary">Upit za omogućavanje novododanih izvora nakon ažuriranja aplikacije</string>
<string name="list_options">Popis opcija</string>
@ -384,7 +384,7 @@
<string name="category_hidden_done">Ova je kategorija bila skrivena s glavnog zaslona i dostupna je kroz Meni → Upravljanje kategorijama</string>
<string name="volume_">Svezak %d</string>
<string name="volume_unknown">Nepoznati svezak</string>
<string name="incognito_mode_hint">Vaš napredak u čitanju se neće spremiti</string>
<string name="incognito_mode_hint">Vaš napredak u čitanju neće biti spremljen</string>
<string name="vertical">Okomito</string>
<string name="last_read">Zadnje pročitano</string>
<string name="show_menu">Prikaži meni</string>
@ -393,8 +393,8 @@
<string name="next_chapter">Sljedeće poglavlje</string>
<string name="next_page">Sljedeća stranica</string>
<string name="default_webtoon_zoom_out">Zadani webtoon smanji</string>
<string name="fullscreen_mode">Cjeloekranski modus</string>
<string name="reader_fullscreen_summary">Sakrij stanje sustava i navigacijske trake</string>
<string name="fullscreen_mode">Cijeli zaslon</string>
<string name="reader_fullscreen_summary">Sakrij status sustava i navigacijske trake</string>
<string name="reading_time_estimation">Prikaži procijenjeno vrijeme čitanja</string>
<string name="reading_time_estimation_summary">Vrijednost procjene vremena može biti netočna</string>
<string name="suggestions_unavailable_text">Značajka prijedloga je onemogućena</string>
@ -440,10 +440,10 @@
<string name="processing_">Obrada…</string>
<string name="download_complete">Preuzeto</string>
<string name="by_name">Ime</string>
<string name="newest">Najnovije</string>
<string name="newest">Najnoviji</string>
<string name="sort_order">Redoslijed sortiranja</string>
<string name="filter">Filter</string>
<string name="follow_system">Slijedi sustav</string>
<string name="follow_system">Slijedite sustav</string>
<string name="pages">Stranice</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">Standard</string>
@ -516,20 +516,20 @@
<string name="other_cache">Drugi cache</string>
<string name="available">Dostupno</string>
<string name="disabled">Onemogućeno</string>
<string name="default_mode">Zadani modus</string>
<string name="default_mode">Zadani način rada</string>
<string name="detect_reader_mode_summary">Automatski otkrij je li manga webtoon</string>
<string name="removed_from_favourites">Uklonjeno iz favorita</string>
<string name="not_found_404">Sadržaj nije pronađen ili je uklonjen</string>
<string name="not_found_404">Sadržaj nije pronađen ili uklonjen</string>
<string name="no_chapters">Nema poglavlja</string>
<string name="options">Opcije</string>
<string name="download_slowdown">Usporavanje preuzimanja</string>
<string name="logout">Odjavite se</string>
<string name="reorder">Rasporedi</string>
<string name="incognito_mode">Anonimni modus</string>
<string name="incognito_mode">Anonimni način rada</string>
<string name="suggestions_excluded_genres">Ispostavite žanrove</string>
<string name="text_delete_local_manga_batch">Trajno izbrisati odabrane stavke s uređaja?</string>
<string name="removal_completed">Uklanjanje dovršeno</string>
<string name="automatic_scroll">Automatsko listanje</string>
<string name="automatic_scroll">Automatsko pomicanje</string>
<string name="screenshots_block_nsfw">Blokiraj na NSFW-u</string>
<string name="suggestions_enable">Omogući prijedloge</string>
<string name="suggestions_summary">Predložiti mangu na temelju vaših preferencija</string>
@ -550,7 +550,7 @@
<string name="clear_network_cache">Očisti mrežnu predmemoriju</string>
<string name="address">Adresa</string>
<string name="images_proxy_title">Proxy za optimizaciju slika</string>
<string name="webtoon_zoom_summary">Dopusti gestu zumiranja u modusu webtoona</string>
<string name="webtoon_zoom_summary">Dopusti gestu zumiranja u načinu webtoona</string>
<string name="download_option_all_chapters">Sva poglavlja s prijevodom %s</string>
<string name="this_month">Ovaj mjesec</string>
<string name="color_light">Svjetla</string>
@ -660,7 +660,7 @@
<string name="show_quick_filters_summary">Pruža mogućnost filtriranja popisa manga prema određenim parametrima</string>
<string name="invalid_server_address_message">Nevažeća adresa servera</string>
<string name="sfw">SFW</string>
<string name="retry">Pokušaj ponovo</string>
<string name="retry">Pokušajte ponovno</string>
<string name="too_many_requests_message_retry">Previše zahtjeva. Pokušajte ponovno nakon %s</string>
<string name="seconds_short">%d s</string>
<string name="minutes_seconds_short">%1$d min %2$d s</string>
@ -802,63 +802,4 @@
<string name="suggestions_disabled_sources_summary">Prikaži prijedloge iz svih izvora mange, uključujući deaktivirane</string>
<string name="tags_warnings">Istakni opasne žanrove</string>
<string name="tags_warnings_summary">Istakni žanrove koji bi mogli biti neprikladni za većinu korisnika</string>
<string name="pull_to_prev_chapter">Otpusti za otvaranje prethodnog poglavlja</string>
<string name="pull_to_next_chapter">Otpusti za otvaranje sljedećeg poglavlja</string>
<string name="pull_top_no_prev">Nema prethodnog poglavlja</string>
<string name="pull_bottom_no_next">Nema sljedećeg poglavlja</string>
<string name="reader_navigation_inverted">Preokreni navigacijske kontrole</string>
<string name="reader_navigation_inverted_summary">Zamijeni gumb za glasnoću i fizičke navigacijske tipke (lijevo/gore/dolje/desno)</string>
<string name="enable_pull_gesture_title">Aktiviraj geste povlačenja</string>
<string name="enable_pull_gesture_summary">Aktiviraj geste povlačenja za mijenjanje poglavlja u webtoonu</string>
<string name="error_non_file_uri">Odabrana staza se ne može koristiti jer ne označava datoteku ili direktorij</string>
<string name="manga_override_hint">Ove promjene će utjecati na način prikaza mange u aplikaciji</string>
<string name="use_default_cover">Koristi zadanu naslovnicu</string>
<string name="change_cover">Promijeni naslovnicu</string>
<string name="pick_manga_page">Odaberi stranicu mange</string>
<string name="pick_custom_file">Odaberi prilagođenu datoteku</string>
<string name="page_switch_timer">Stranica će se mijenjati svakih ~%d sekundi</string>
<string name="dont_ask_again">Ne pitaj ponovo</string>
<string name="incognito_mode_hint_nsfw">Ovaj manga može sadržati sadržaj za odrasle. Želiš li koristiti anonimni modus?</string>
<string name="incognito_for_nsfw">Anonimni modus za mange s neprimjerenim sadržajem (NSFW)</string>
<string name="additional_action_required">Potrebna je dodatna radnja</string>
<string name="hide_from_main_screen">Sakrij s glavnog ekrana</string>
<string name="changelog">Dnevnik promjena</string>
<string name="changelog_summary">Povijest promjena za nedavno objavljene verzije</string>
<string name="collapse">Sažmi</string>
<string name="expand">Proširi</string>
<string name="adblock">Blokiraj oglase u pregledniku</string>
<string name="adblock_summary">Blokiraj oglase u ugrađenom pregledniku (beta)</string>
<string name="collapse_long_description">Sažmi dugi opis</string>
<string name="creating_backup">Izrada sigurnosne kopije</string>
<string name="share_backup">Dijeli sigurnosnu kopiju</string>
<string name="reader_multitask">Otvori čitač u zasebnom zadatku</string>
<string name="reader_multitask_summary">Omogućuje istovremeno otvaranje više čitača s različitim mangama</string>
<string name="theme_name_itsuka">Itsuka</string>
<string name="theme_name_totoro">Totoro</string>
<string name="book_effect">Žućkasta pozadina (plavi filtar)</string>
<string name="local_storage_cleanup">Čišćenje lokalne memorije</string>
<string name="packup_creation_failed">Neuspjelo stvaranje sigurnosne kopije</string>
<string name="main_screen">Glavni ekran</string>
<string name="main_screen_fab">Prikaži plutajući gumb „Nastavi”</string>
<string name="main_screen_fab_summary">Omogućuje nastavak čitanja jednim klikom. Ovaj se gumb neće prikazivati u anonimnom modusu ili kada je povijest prazna</string>
<string name="error_corrupted_zip">Oštećena ZIP arhiva (%s)</string>
<string name="discord_token">Discord token</string>
<string name="discord_token_description">Unesi svoj Discord token ili klikni %s za dobivanje tokena putem preglednika</string>
<string name="discord_token_hint">Ovdje umetni svoj Discord token</string>
<string name="discord_rpc_summary">Prikaži svoje stanje čitanja na Discordu</string>
<string name="obtain">Nabavi</string>
<string name="discord_rpc_description">Čitanje manga na Kotatsu aplikaciji za čitanje manga</string>
<string name="reading_s">Čitaš %s</string>
<string name="read_on_s">Čitaj na %s</string>
<string name="rpc_skip_nsfw_summary">Ne koristi Rich Presence klijent (RPC) za sadržaj za odrasle</string>
<string name="invalid_token">Nevažeći token: %s</string>
<string name="show_floating_control_button">Prikaži plutajući gumb kontrola</string>
<string name="unavailable">Nedostupno</string>
<string name="manga_restricted_description">Ovaj manga nije dostupan za čitanje na ovom izvoru. Pokušaj ga potražiti u drugim izvorima ili otvoriti u pregledniku za više informacija</string>
<string name="no_chapters_in_manga">Ovaj manga ne sadrži nijedno poglavlje</string>
<string name="chapters_load_failed">Neuspjelo učitavanje popisa poglavlja</string>
<string name="telegram_integration">Telegram integracija</string>
<string name="test_parser">Testiraj izvor mange</string>
<string name="discord_rpc">Discord Rich Presence</string>
<string name="discord_token_summary">Unesi svoj Discord token za uključivanje Rich Presence podataka</string>
</resources>

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="items">
<item quantity="other">%1$d butir</item>
<item quantity="other">%1$d butir</item>
</plurals>
<plurals name="new_chapters">
<item quantity="other">%1$d Sub-bab Baru</item>
<item quantity="other">%1$d bab baru</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d bab</item>
@ -27,4 +27,4 @@
<plurals name="minutes">
<item quantity="other">%1$d menit</item>
</plurals>
</resources>
</resources>

@ -74,12 +74,12 @@
<string name="clear_thumbs_cache">Bersihkan singgahan gambar mini</string>
<string name="clear_search_history">Bersihkan riwayat pencarian</string>
<string name="search_history_cleared">Dibersihkan</string>
<string name="domain">Ranah web</string>
<string name="domain">Domain</string>
<string name="app_update_available">Versi baru aplikasi tersedia</string>
<string name="open_in_browser">Buka di peramban web</string>
<string name="notifications">Notifikasi</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d dari %2$d diaktifkan</string>
<string name="new_chapters">Sub-bab baru</string>
<string name="new_chapters">Bab baru</string>
<string name="download">Unduh</string>
<string name="notifications_settings">Pengaturan notifikasi</string>
<string name="notification_sound">Suara notifikasi</string>
@ -277,7 +277,7 @@
<string name="storage_usage">Penggunaan penyimpanan</string>
<string name="available">Tersedia</string>
<string name="incognito_mode">Mode penyamaran</string>
<string name="automatic_scroll">Pengguliran otomatis</string>
<string name="automatic_scroll">Gulir otomatis</string>
<string name="comics_archive">Arsip komik</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>
@ -307,18 +307,18 @@
<string name="confirm_exit">Tekan Kembali lagi untuk keluar</string>
<string name="no_chapters">Tidak ada bab</string>
<string name="history_shortcuts">Tampilkan pintasan manga baru-baru ini</string>
<string name="history_shortcuts_summary">Buat manga terbaru tersedia dengan menekan lama ikon aplikasi</string>
<string name="history_shortcuts_summary">Buat manga baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
<string name="select_range">Pilih rentang</string>
<string name="disable_all">Matikan semua</string>
<string name="dns_over_https">DNS melalui HTTPS</string>
<string name="dns_over_https">DNS over HTTPS</string>
<string name="status_dropped">Didrop</string>
<string name="theme_name_mamimi">Mamimi</string>
<string name="server_error">Kesalahan di sisi server (%1$d). Silakan coba lagi nanti</string>
<string name="server_error">Galat sisi server (%1$d). Silakan coba lagi nanti</string>
<string name="compact">Padat</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">Jangan ubah arah pergantian halaman ke mode pembaca, misalnya, menekan tombol kanan selalu beralih ke halaman berikutnya. Opsi ini hanya berlaku untuk perangkat masukan hardware</string>
<string name="reader_control_ltr_summary">Jangan sesuaikan arah pindah halaman ke mode pembaca, mis. menekan tombol kanan selalu pindah ke halaman selanjutnya. Opsi ini hanya berdampak pada perangkat dengan tombol masukan perangkat keras</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>
@ -355,7 +355,7 @@
<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">Pembesaran Webtoon</string>
<string name="webtoon_zoom">Zum Webtoon</string>
<string name="network_unavailable">Jaringan tidak tersedia</string>
<string name="got_it">Oke</string>
<string name="sources_reorder_tip">Ketuk dan tahan item untuk menyusun ulang</string>
@ -509,7 +509,7 @@
<string name="state_upcoming">Mendatang</string>
<string name="color_correction_apply_text">Pengaturan ini dapat diterapkan secara menyeluruh atau hanya pada manga saat ini. Jika diterapkan secara menyeluruh, pengaturan pada manga tidak akan ditimpa.</string>
<string name="source_enabled">Sumber yang diaktifkan</string>
<string name="disable_nsfw_summary">Nonaktifkan sumber TAUSB dan sembunyikan manga dewasa dari daftar jika memungkinkan</string>
<string name="disable_nsfw_summary">Nonaktifkan sumber TAUSB and sembunyikan manga dewasa dari daftar jika memungkinkan</string>
<string name="content_rating">Peringkat konten</string>
<string name="backup_date_">Tanggal dicadangkan %s</string>
<string name="available_d">Tersedia:%1$d</string>
@ -729,13 +729,13 @@
<string name="filter_search_warning">Sumber ini tidak mendukung pencarian dengan filter. Filter Anda telah dikosongkan</string>
<string name="demographic_shoujo">Shoujo</string>
<string name="manga_replaced">Manga \"%1$s\" (%2$s) diganti dengan \"%3$s\" (%4$s)</string>
<string name="content_type_manhua">Komik Mandarin (manhua)</string>
<string name="content_type_manhua">Manhua</string>
<string name="user_manual">Manual pengguna</string>
<string name="destination_directory">Direktori tujuan</string>
<string name="demographic_josei">Josei</string>
<string name="download_new_chapters">Unduh bab baru</string>
<string name="content_type_novel">Novel</string>
<string name="content_type_manhwa">Komik Korea (Manhwa)</string>
<string name="content_type_manhwa">Manhwa</string>
<string name="source_code">Kode sumber</string>
<string name="download_cellular_confirm">Izinkan mengunduh melalui jaringan seluler?</string>
<string name="demographics">Demografis</string>
@ -757,7 +757,7 @@
<string name="chapter_selection_hint">Anda bisa memilih bab untuk diunduh dengan menekan lama item di dalam daftar bab.</string>
<string name="rating">Nilai</string>
<string name="source">Sumber</string>
<string name="added_long_ago">Ditambahkan sejak lama</string>
<string name="added_long_ago">Bahasa</string>
<string name="popular_in_hour">Populer Dalam Satu Jam Terakhir</string>
<string name="stuck">Bahasa</string>
<string name="error_connection_reset">Koneksi diatur ulang oleh host jarak jauh</string>
@ -807,10 +807,10 @@
<string name="tags_warnings_summary">Tandai genre yang mungkin tidak pantas untuk sebagian besar pengguna</string>
<string name="error_non_file_uri">Jalur yang dipilih tidak dapat digunakan karena tidak menunjukkan berkas atau direktori</string>
<string name="use_default_cover">gunakan penutup default</string>
<string name="pick_manga_page">Pilih halaman manga</string>
<string name="pick_custom_file">Pilih file khusus</string>
<string name="change_cover">Ganti penutup</string>
<string name="theme_name_expressive">Ekspresif (Test)</string>
<string name="pick_manga_page">Indonesian</string>
<string name="pick_custom_file">Indonesian</string>
<string name="change_cover">Indonesian</string>
<string name="theme_name_expressive">Indonesian</string>
<string name="page_switch_timer">Halaman akan berganti setiap ~%d detik</string>
<string name="expand">Memperluas</string>
<string name="adblock">Blokir iklan di browser</string>
@ -846,23 +846,4 @@
<string name="discord_token_description">Masukkan Token Discord Anda atau klik %s untuk mendapatkannya melalui browser</string>
<string name="discord_token_hint">Tempelkan Token Discord Anda di sini</string>
<string name="discord_rpc_summary">Tampilkan status membaca Anda di Discord</string>
<string name="discord_rpc_description">Membaca manga di Kotatsu - aplikasi pembaca manga</string>
<string name="manga_restricted_description">Manga ini tidak tersedia untuk dibaca di sumber ini. Coba cari di sumber lain atau buka di browser untuk informasi lebih lanjut</string>
<string name="chapters_load_failed">Gagal memuat daftar bab</string>
<string name="rpc_skip_nsfw_summary">Jangan gunakan RPC untuk konten dewasa</string>
<string name="show_floating_control_button">Tampilkan tombol kontrol mengambang</string>
<string name="unavailable">Tidak tersedia</string>
<string name="invalid_token">Token tidak valid: %s</string>
<string name="no_chapters_in_manga">Manga ini tidak mengandung bab apa pun</string>
<string name="telegram_integration">Integrasi Telegram</string>
<string name="test_parser">Uji sumber manga</string>
<string name="reading_s">Membaca %s</string>
<string name="read_on_s">Baca terus %s</string>
<string name="obtain">Memperoleh</string>
<string name="pull_to_prev_chapter">Lepas untuk membuka bab sebelumnya</string>
<string name="pull_to_next_chapter">Lepas untuk membuka bab selanjutnya</string>
<string name="pull_top_no_prev">Tidak ada bab sebelumnya</string>
<string name="pull_bottom_no_next">Tidak ada bab berikutnya</string>
<string name="enable_pull_gesture_title">Aktifkan gerakan tarik</string>
<string name="enable_pull_gesture_summary">Gunakan gerakan tarik untuk pindah bab di webtoon</string>
</resources>

@ -859,11 +859,4 @@
<string name="no_chapters_in_manga">Questo manga non contiene capitoli</string>
<string name="telegram_integration">Integrazione con Telegram</string>
<string name="chapters_load_failed">Errore nel caricamento della lista dei capitoli</string>
<string name="test_parser">Testa fonte manga</string>
<string name="pull_to_prev_chapter">Rilascia per aprire il capitolo precedente</string>
<string name="pull_to_next_chapter">Rilascia per aprire il capitolo successivo</string>
<string name="pull_top_no_prev">Nessun capitolo precedente</string>
<string name="pull_bottom_no_next">Nessun capitolo successivo</string>
<string name="enable_pull_gesture_title">Abilita gesto di scorrimento</string>
<string name="enable_pull_gesture_summary">Abilita gesto di scorrimento per cambiare capitolo in webtoon</string>
</resources>

@ -474,197 +474,4 @@
<string name="all_sources_enabled">すべてのソースが有効</string>
<string name="automatic">自動</string>
<string name="invalid_server_address_message">無効なサーバーアドレス</string>
<string name="text_empty_holder_secondary_filtered">選択した条件に一致するマンガはありません</string>
<string name="email_password_enter_hint">メールアドレスとパスワードを入力して、続行してください</string>
<string name="pull_top_no_prev">前のチャプターがありません</string>
<string name="pull_bottom_no_next">次のチャプターがありません</string>
<string name="too_many_requests_message_retry">リクエストが多すぎます。%s 秒後に再度お試しください。</string>
<string name="keep_screen_on">常に画面表示</string>
<string name="keep_screen_on_summary">漫画を読んでいる間は画面をオフにしない</string>
<string name="enhanced_colors_summary">バンドノイズを低減しますが、パフォーマンスへの影響が生じる場合があります</string>
<string name="enhanced_colors">32bitカラーモード</string>
<string name="suggest_new_sources">アプリ更新後に新しいソースを提案する</string>
<string name="suggest_new_sources_summary">アプリケーション更新後に新しく追加されたソースを使用できるようにするかどうか確認する</string>
<string name="by_relevance">関連性</string>
<string name="categories">カテゴリ</string>
<string name="online_variant">オンライン版</string>
<string name="periodic_backups">定期的バックアップ</string>
<string name="backup_frequency">バックアップ頻度</string>
<string name="pages_saved">保存しました</string>
<string name="list_options">リストオプション</string>
<string name="frequency_every_day">毎日</string>
<string name="frequency_every_2_days">2日毎に</string>
<string name="frequency_once_per_week">週に一度</string>
<string name="frequency_twice_per_month">月2回</string>
<string name="frequency_once_per_month">月に一度</string>
<string name="periodic_backups_enable">定期的なバックアップを有効にする</string>
<string name="backups_output_directory">バックアップを保存するディレクトリ</string>
<string name="last_successful_backup">最後の成功したバックアップ:%s</string>
<string name="content_type_manga">マンガ</string>
<string name="content_type_hentai">ヘンタイ</string>
<string name="content_type_comics">コミック</string>
<string name="content_type_other">その他</string>
<string name="sources_catalog">ソースカタログ</string>
<string name="source_enabled">ソースの有効化</string>
<string name="no_manga_sources_catalog_text">このセクションには利用可能なソースがありません。あるいは、すでにすべて追加済みである可能性があります。\n続報をお待ちください</string>
<string name="no_manga_sources_found">お探しの漫画が見つかりませんでした</string>
<string name="catalog">カタログ</string>
<string name="disable_nsfw_summary">NSFWを無効化し、可能であれば成人向け漫画をリストから非表示にする</string>
<string name="state_paused">ポーズ</string>
<string name="reader_optimize">メモリ消費の削減(ベータ版)</string>
<string name="error_multiple_genres_not_supported">このマンガソースでは、複数のジャンルによるフィルタリングはサポートされていません</string>
<string name="error_multiple_states_not_supported">この漫画ソースでは、複数の状態によるフィルタリングはサポートされていません</string>
<string name="error_search_not_supported">この漫画ソースでは検索機能はサポートされていません</string>
<string name="downloads_settings_info">サーバー側のブロックに問題がある場合、ソース設定で各漫画ソースごとに個別にダウンロード速度制限を有効にできます</string>
<string name="skip">スキップ</string>
<string name="grayscale">グレースケール</string>
<string name="globally">グローバル</string>
<string name="this_manga">マンガ</string>
<string name="color_correction_apply_text">これらの設定は、全体に適用することも、現在の漫画だけに適用することもできます。全体に適用した場合、個別の設定は上書きされません。</string>
<string name="apply">適用</string>
<string name="error_filter_locale_genre_not_supported">このソースでは、ジャンルとロケールの両方でフィルタリングすることはサポートされていません</string>
<string name="genres_search_hint">ジャンル名を入力してください</string>
<string name="disable_battery_optimization_summary_downloads">ダウンロードに問題がある場合、これを試すと開始できるかもしれません</string>
<string name="welcome_text">有効にしたいコンテンツソースを選択してください。設定画面で後から変更することも可能です。</string>
<string name="sync_auth">アカウントを同期するためにログインしてください</string>
<string name="restore">復元</string>
<string name="backup_date_">バックアップ日時: %s</string>
<string name="state_upcoming">近日公開</string>
<string name="genres_exclude">除外するジャンル</string>
<string name="rating_safe">安全</string>
<string name="rating_adult">アダルト</string>
<string name="rating_suggestive">示唆的</string>
<string name="default_tab">既定のタブ</string>
<string name="mark_as_completed">完了としてマークする</string>
<string name="mark_as_completed_prompt">マークした漫画を完全に読み終えたものとしますか?\n\n警告現在の読書進捗が消去されます。</string>
<string name="category_hidden_done">このカテゴリはメイン画面から非表示になっており、メニュー → カテゴリ管理 からアクセスできます</string>
<string name="volume_">%d巻</string>
<string name="incognito_mode_hint">読書進捗は保存されません</string>
<string name="show_menu">メニュー表示</string>
<string name="toggle_ui">UIの 表示/非表示</string>
<string name="prev_chapter">前のチャプター</string>
<string name="next_chapter">次のチャプター</string>
<string name="prev_page">前のページ</string>
<string name="next_page">次のページ</string>
<string name="reader_actions_summary">タップ可能な画面領域のアクションを設定する</string>
<string name="switch_pages_volume_buttons">音量ボタンを有効化</string>
<string name="switch_pages_volume_buttons_summary">ページの切り替えに、音量ボタンを使用します</string>
<string name="reader_navigation_inverted">ナビゲーションコントロールを反転</string>
<string name="reader_navigation_inverted_summary">音量ボタンと方向キー(左/上/下/右)の操作方向を入れ替える</string>
<string name="tap_action">タップ時のアクション</string>
<string name="long_tap_action">長押し時のアクション</string>
<string name="config_reset_confirm">設定をデフォルト値に戻しますか? この操作は元に戻せません。</string>
<string name="use_two_pages_landscape">横向きレイアウトで2ページ構成を使用するベータ版</string>
<string name="default_webtoon_zoom_out">デフォルトのウェブトゥーンを縮小表示</string>
<string name="fullscreen_mode">フルスクリーンモード</string>
<string name="reader_fullscreen_summary">システムステータスとナビゲーションバーを非表示にする</string>
<string name="reading_time_estimation">推定読了時間</string>
<string name="reading_time_estimation_summary">時間見積もりは不正確な場合があります</string>
<string name="suggestions_unavailable_text">提案機能は無効化されています</string>
<string name="check_for_new_chapters_disabled">新章の確認は無効化されています</string>
<string name="show_labels_in_navbar">ナビゲーションバーにラベルを表示する</string>
<string name="pages_saving">ページを保存</string>
<string name="remove_from_history">履歴から削除</string>
<string name="preferred_download_format">希望するダウンロード形式</string>
<string name="single_cbz_file">単一のCBZファイル</string>
<string name="multiple_cbz_files">複数のCBZファイル</string>
<string name="other_manga">他のマンガ</string>
<string name="less_than_minute">1分未満</string>
<string name="statistics">統計</string>
<string name="clear_stats_confirm">本当にすべての閲覧統計を消去しますか?この操作は元に戻せません。</string>
<string name="stats_cleared">統計を消去した</string>
<string name="clear_stats">統計を消去</string>
<string name="week"></string>
<string name="month"></string>
<string name="day"></string>
<string name="three_months">三ヶ月</string>
<string name="empty_stats_text">選択した期間の統計データはありません</string>
<string name="alternatives">代替案</string>
<string name="migrate">移行</string>
<string name="manga_migration">マンガの移行</string>
<string name="migration_completed">移行完了</string>
<string name="delete_read_chapters">既読のチャプターを削除する</string>
<string name="no_chapters_deleted">削除されたチャプターはありません</string>
<string name="delete_read_chapters_summary">ローカルストレージから既読のチャプターを削除して空き容量を増やす</string>
<string name="delete_read_chapters_prompt">これにより、ローカルストレージから既読としてマークされた全てのチャプターが完全に削除されます。後で再ダウンロードすることは可能ですが、インポートされたチャプターは永久に失われる可能性があります。</string>
<string name="delete_read_chapters_auto">既読のチャプターを自動的に削除する</string>
<string name="runs_on_app_start">kotatsuの起動時に実行</string>
<string name="split_by_translations_summary">異なる翻訳のチャプターを、一つのリストではなく別々に表示する</string>
<string name="unread">未読</string>
<string name="unsupported_source">このマンガソースはサポートされていません</string>
<string name="show_pages_thumbs">サムネイルを表示</string>
<string name="show_pages_thumbs_summary">詳細画面で「ページ」タブを有効にする</string>
<string name="error_no_data_received">サーバーからデータを受信できませんでした</string>
<string name="unsupported_backup_message">kotatsuのバックアップファイルを選択してください。</string>
<string name="hours_short">%d 時</string>
<string name="minutes_short">%d 分</string>
<string name="seconds_short">%d 秒</string>
<string name="hours_minutes_short">%1$d 時 %2$d 分</string>
<string name="minutes_seconds_short">%1$d 分 %2$d 秒</string>
<string name="fix">修正</string>
<string name="missing_storage_permission">外部ストレージ上のマンガへのアクセスが許可されていません</string>
<string name="webtoon_gaps">ウェブトゥーンモードの空白部分</string>
<string name="webtoon_gaps_summary">ウェブトゥーンモードでページ間の縦方向の空白を表示する</string>
<string name="enable_pull_gesture_title">プルジェスチャーを有効にする</string>
<string name="enable_pull_gesture_summary">プル操作でウェブトゥーンのチャプターを切り替える</string>
<string name="less_frequently">あまり頻繁ではない</string>
<string name="frequency_of_check">チェック頻度</string>
<string name="pin_navigation_ui">ナビゲーションUIを固定</string>
<string name="pin_navigation_ui_summary">スクロール時にナビゲーション バーと検索ビューを非表示にしない</string>
<string name="search_suggestions">検索候補</string>
<string name="authors">著者</string>
<string name="blocked_by_server_message">サーバーによってブロックされています。別のネットワーク接続VPN、プロキシなどをお試しください。</string>
<string name="disable">無効化</string>
<string name="ignore_ssl_errors_summary">ネットワークリソースへのアクセス時にSSL関連の問題が発生した場合、SSL証明書の検証を無効にできます。これによりセキュリティに影響が生じる可能性があります。この設定変更後はkotatsuの再起動が必要です。</string>
<string name="disable_connectivity_check_summary">接続確認に問題がある場合(例:ネットワークが接続されているにもかかわらずオフラインモードになる場合など)、接続確認をスキップしてください。</string>
<string name="disable_nsfw_notifications">NSFW通知を無効にする</string>
<string name="disable_nsfw_notifications_summary">NSFWマンガの更新に関する通知を表示しない</string>
<string name="all_languages">すべての言語</string>
<string name="screenshots_block_incognito">シークレットモード時にブロック</string>
<string name="crop_pages">ページをトリミングする</string>
<string name="percent_read">既読率</string>
<string name="plugin_incompatible">互換性のないプラグインまたは内部エラーが発生しました。プラグインとKotatsuの最新バージョンを使用していることを確認してください。</string>
<string name="plugin_incompatible_with_cause">プラグインエラー: %s\nプラグインとKotatsuの最新バージョンを使用していることを確認してください</string>
<string name="connection_ok">正常に接続できました</string>
<string name="invalid_proxy_configuration">無効なプロキシ設定</string>
<string name="show_quick_filters">クイックフィルターを表示</string>
<string name="sfw">安全なコンテンツ(SFW)</string>
<string name="skip_all">すべてスキップ</string>
<string name="updated_long_ago">長らく更新されていません</string>
<string name="unpopular">不人気</string>
<string name="low_rating">低評価</string>
<string name="sort_order_asc">上昇中</string>
<string name="sort_order_desc">下降中</string>
<string name="by_date">日付</string>
<string name="popularity">人気</string>
<string name="scrobbler_auth_required">%s にサインインして続行してください</string>
<string name="scrobbler_auth_intro">%sとの連携を設定するにはサインインしてください。これにより、マンガの読了状況や進捗を追跡できるようになります。</string>
<string name="unstable_feature">不安定な機能</string>
<string name="unstable_feature_summary">本機能は試験的なものです。データ損失を防ぐため、必ずバックアップを取ってください。</string>
<string name="downloads_background">バックグラウンドでのダウンロード</string>
<string name="download_new_chapters">新しいチャプターをダウンロード</string>
<string name="manga_replaced">マンガ「%1$s」(%2$s) が「%3$s」(%4$s) に置き換えられました</string>
<string name="fixing_manga">マンガの修正</string>
<string name="fixed">正常に修正されました</string>
<string name="no_fix_required">「%s」に対する修正は不要です</string>
<string name="no_alternatives_found">「%s」に対する修正は不要です</string>
<string name="manga_fix_prompt">この機能は選択したマンガの代替ソースを検索します。処理には時間がかかり、バックグラウンドで実行されます。</string>
<string name="content_type_novel">小説</string>
<string name="content_type_manhua">マンガ</string>
<string name="content_type_manhwa">マンガ</string>
<string name="recently_added">最近追加された</string>
<string name="added_long_ago">かなり前につけ加えられました</string>
<string name="demographic_shounen">少年</string>
<string name="demographic_shoujo">少女</string>
<string name="demographic_seinen">青年</string>
<string name="demographic_josei">女性</string>
<string name="filter_search_warning">このソースはフィルター付き検索をサポートしていません。フィルターがクリアされました</string>
<string name="content_type_doujinshi">同人誌</string>
<string name="debug">デバッグ</string>
<string name="source_code">ソースコード</string>
<string name="user_manual">ユーザーマニュアル</string>
<string name="error_image_format">サポートされていない画像形式: %s</string>
<string name="error_not_image">無効な形式です: 画像が期待されましたが %s が入力されました</string>
<string name="start_download">ダウンロード開始</string>
<string name="save_manga_confirm">選択したマンガを保存しますか?通信量とディスク容量を消費する可能性があります</string>
</resources>

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="items">
<item quantity="other">%1$d glintir</item>
</plurals>
<plurals name="new_chapters">
<item quantity="other">%1$d bagéan</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d bagéyan</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d waktu kepungkur</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d jam kepungkur</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d dinten ingkang rumiyin</item>
</plurals>
<plurals name="months_ago">
<item quantity="other">%1$d sasi ingkang rumiyin</item>
</plurals>
<plurals name="hours">
<item quantity="other">%1$d tabuh</item>
</plurals>
<plurals name="minutes">
<item quantity="other">%1$d menit</item>
</plurals>
</resources>

@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="demographic_kodomo">bocah-bocah</string>
<string name="local_storage">Panyimpenan tlatah</string>
<string name="favourites">Seneng</string>
<string name="history">Babadan</string>
<string name="error_occurred">Ana kesalahan</string>
<string name="network_error">kelepatan jaringan</string>
<string name="details">rinci</string>
<string name="chapters">Jilid</string>
<string name="list">dhaptar</string>
<string name="detailed_list">dhaptar rinci</string>
<string name="grid">wewacan</string>
<string name="list_mode">modus dhaptar</string>
<string name="settings">Pathokan</string>
<string name="remote_sources">Sumber Manga</string>
<string name="loading_">Ngewrat…</string>
<string name="computing_">Ngitung…</string>
<string name="close">Tudhung</string>
<string name="try_again">Jajal Maneh</string>
<string name="retry">Baleni</string>
<string name="clear_history">Busek riwayat</string>
<string name="nothing_found">Mboten dipunpanggihaken</string>
<string name="history_is_empty">Ora ana babad maneh</string>
<string name="read">Moco</string>
<string name="you_have_not_favourites_yet">Durung ana kang disenengi</string>
<string name="add_to_favourites">favoritkan puniki</string>
<string name="add_new_category">Kategori anyar</string>
<string name="add">Nambah</string>
<string name="save">Dekek</string>
<string name="share">Edumaken</string>
<string name="create_shortcut">Damel pintasan</string>
<string name="share_s">Edumaken %s</string>
<string name="search">Luru</string>
<string name="search_manga">Golek komik</string>
<string name="manga_downloading_">Ngunduh…</string>
<string name="processing_">Mroses…</string>
<string name="download_complete">Diundhuh</string>
<string name="downloads">Unggah</string>
<string name="by_name">Jeneng</string>
<string name="popular">Kondhang</string>
<string name="updated">Dianyari</string>
<string name="newest">Paling anyar</string>
<string name="by_rating">Bijine</string>
<string name="sort_order">Urutaken</string>
<string name="filter">Milah</string>
<string name="theme">Intining</string>
<string name="light">Padhang</string>
<string name="dark">Peteng</string>
<string name="follow_system">Dereki sistem</string>
<string name="pages">Kaca</string>
<string name="standard">Pajeging</string>
<string name="read_mode">Mode waos</string>
<string name="test_parser">Mriksa asal</string>
<string name="telegram_integration">Panyawijining Telegram</string>
<string name="clear_pages_cache">Resiki singgahan platar</string>
<string name="clear">Resiki</string>
<string name="remove">Busek</string>
<string name="_s_deleted_from_local_storage">\"%s\" dibusek saka panyimpenan</string>
<string name="save_page">Dekek Plataran</string>
<string name="page_saved">Plataran dipundekek</string>
<string name="pages_saved">Plataran dipundekek</string>
<string name="share_image">Edumaken citro</string>
<string name="_import">Pundhut barang jawi</string>
<string name="delete">Busek</string>
<string name="operation_not_supported">tindakan menika mboten dipunsengkuyung</string>
<string name="text_file_not_supported">Milih antawis berkas zip utawi cbz.</string>
</resources>

@ -30,17 +30,17 @@
<string name="add">Tambah</string>
<string name="save">Simpan</string>
<string name="share">Kongsi</string>
<string name="create_shortcut">Buat pintasan</string>
<string name="create_shortcut">Buat pintasan</string>
<string name="search">Cari</string>
<string name="search_manga">Cari manga</string>
<string name="manga_downloading_">Memuat turun…</string>
<string name="download_complete">Dimuat turun</string>
<string name="downloads">Muat turun</string>
<string name="by_name">Nama</string>
<string name="popular">Populer</string>
<string name="popular">Popular</string>
<string name="updated">Dikemaskini</string>
<string name="newest">Terbaharu</string>
<string name="by_rating">Peringkat</string>
<string name="by_rating">Rating</string>
<string name="sort_order">Turutan isihan</string>
<string name="filter">Tapis</string>
<string name="light">Terang</string>
@ -258,7 +258,7 @@
<string name="chapters_will_removed_background">Bab akan dibuang dalam latar belakang</string>
<string name="operation_not_supported">Operasi ini tidak disokong</string>
<string name="delete_manga">Buang manga</string>
<string name="page_saved">Halaman disimpan</string>
<string name="page_saved">Disimpan</string>
<string name="domain">Domain</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d daripada %2$d pada</string>
<string name="read_mode">Mod pembacaan</string>
@ -302,562 +302,4 @@
<string name="error_corrupted_file">Data tidak sah dikembalikan atau fail rosak</string>
<string name="on_device">Pada peranti</string>
<string name="directories">Panduan</string>
<string name="last_read">Bacaan terakhir</string>
<string name="automatic">Automatik</string>
<string name="retry">Mencoba kembali</string>
<string name="pages_saved">Halaman disimpan</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="text_empty_holder_secondary_filtered">Tidak ada manga yang cocok dengan filter yang Anda pilih</string>
<string name="chapters_grid_view">Tampilan kisi</string>
<string name="nsfw_16">16+</string>
<string name="download_slowdown">Unduhan melambat</string>
<string name="invalid_server_address_message">Alamat pelayan tidak sah</string>
<string name="not_found_404">Kandungan tidak ditemui atau dialih keluar</string>
<string name="import_completed_hint">Anda boleh memadamkan fail asal daripada storan untuk menjimatkan ruang</string>
<string name="reader_control_ltr_summary">Jangan laraskan arah penukaran halaman kepada mod pembaca, e. g. menekan kekunci kanan sentiasa beralih ke halaman seterusnya. Pilihan ini hanya mempengaruhi peranti input perkakasan</string>
<string name="reader_control_ltr">Kawalan pembaca ergonomik</string>
<string name="history_shortcuts_summary">Jadikan manga terbaru tersedia dengan menekan lama pada ikon aplikasi</string>
<string name="import_completed">Import selesai</string>
<string name="history_shortcuts">Tunjukkan pintasan komik terbaru</string>
<string name="incognito_mode">Mod inkognito</string>
<string name="no_chapters">Tiada bab</string>
<string name="automatic_scroll">Penatalan automatik</string>
<string name="reader_info_bar">Tunjukkan bar maklumat dalam pembaca</string>
<string name="comics_archive">Arkib komik</string>
<string name="folder_with_images">Folder dengan imej</string>
<string name="importing_manga">Mengimport komik</string>
<string name="import_will_start_soon">Import akan bermula tidak lama lagi</string>
<string name="feed">Suapan</string>
<string name="manga_error_description_pattern">Butiran ralat:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Cuba &lt;a href=%2$s&gt;buka manga dalam pelayar web&lt;/a&gt; untuk memastikan ia tersedia pada sumbernya&lt;br&gt;2. Pastikan anda menggunakan &lt;a href=kotatsu://about&gt;versi terbaharu Kotatsu&lt;/a&gt;&lt;br&gt;3. Jika tersedia, hantar laporan ralat kepada pembangun.</string>
<string name="color_correction">Pembetulan warna</string>
<string name="brightness">Kecerahan</string>
<string name="contrast">Berbeza</string>
<string name="reset">Tetapkan semula</string>
<string name="text_unsaved_changes_prompt">Simpan atau buang perubahan yang belum disimpan?</string>
<string name="discard">Buang</string>
<string name="error_no_space_left">Tiada ruang yang tinggal pada peranti</string>
<string name="reader_slider">Tunjukkan peluncur penukaran halaman</string>
<string name="reader_info_pattern">Bab %1$d/%2$d Hlm. %3$d/%4$d</string>
<string name="webtoon_zoom">Webtoon zum</string>
<string name="network_unavailable">Rangkaian tidak tersedia</string>
<string name="network_unavailable_hint">Hidupkan rangkaian Wi-Fi atau mudah alih untuk membaca manga dalam talian</string>
<string name="server_error">Ralat Side Server (%1$d). Sila cuba lagi kemudian</string>
<string name="clear_new_chapters_counters">Juga jelas maklumat mengenai bab baru</string>
<string name="compact">Padat</string>
<string name="source_disabled">Sumber dilumpuhkan</string>
<string name="prefetch_content">Kandungan Preloading</string>
<string name="mark_as_current">Tandakan sebagai semasa</string>
<string name="language">Bahasa</string>
<string name="share_logs">Kongsi log</string>
<string name="enable_logging">Dayakan pembalakan</string>
<string name="enable_logging_summary">Catat beberapa tindakan untuk tujuan debug. Jangan menghidupkannya jika anda tidak pasti apa yang anda lakukan</string>
<string name="show_suspicious_content">Tunjukkan kandungan yang mencurigakan</string>
<string name="theme_name_dynamic">Dinamik</string>
<string name="theme_name_expressive">Ekspresif (ujian)</string>
<string name="color_theme">Skema warna</string>
<string name="show_in_grid_view">Tunjukkan dalam paparan grid</string>
<string name="theme_name_miku">Miku</string>
<string name="nothing_here">Tidak ada apa -apa di sini</string>
<string name="scrobbling_empty_hint">Untuk Melacak Progres Membaca, Pilih Menu → LaCak Di Layar Butiran Manga.</string>
<string name="services">Perkhidmatan</string>
<string name="allow_unstable_updates">Benarkan kemas kini yang tidak stabil</string>
<string name="allow_unstable_updates_summary">Terima pemberitahuan mengenai binaan yang tidak stabil</string>
<string name="download_started">Muat turun bermula</string>
<string name="got_it">Mendapatnya</string>
<string name="sources_reorder_tip">Ketik dan tahan item untuk menyusun semula mereka</string>
<string name="user_agent">Pengepala useragent</string>
<string name="settings_apply_restart_required">Sila mulakan semula permohonan untuk menerapkan perubahan ini</string>
<string name="comics_archive_import_description">Anda boleh memilih satu atau lebih fail .cbz atau .zip, setiap fail akan diiktiraf sebagai manga yang berasingan.</string>
<string name="folder_with_images_import_description">Anda boleh memilih direktori dengan arkib atau imej. Setiap arkib (atau subdirektori) akan diiktiraf sebagai bab.</string>
<string name="speed">Kelajuan</string>
<string name="show_on_shelf">Tunjukkan di rak</string>
<string name="sync_auth_hint">Anda boleh masuk ke akaun yang ada atau membuat yang baru</string>
<string name="find_similar">Cari serupa</string>
<string name="sync_settings">Tetapan Penyegerakan</string>
<string name="server_address">Alamat pelayan</string>
<string name="sync_host_description">Anda boleh menggunakan pelayan penyegerakan sendiri atau lalai. Jangan ubah ini jika anda tidak pasti apa yang anda lakukan.</string>
<string name="ignore_ssl_errors">Abaikan kesilapan SSL</string>
<string name="mirror_switching">Pilih cermin secara automatik</string>
<string name="mirror_switching_summary">Tukar domain secara automatik untuk sumber manga atas kesilapan jika cermin tersedia</string>
<string name="pause">Jeda</string>
<string name="resume">Teruskan</string>
<string name="paused">Jeda</string>
<string name="remove_completed">Keluarkan selesai</string>
<string name="cancel_all">Batalkan semua</string>
<string name="downloads_wifi_only">Muat turun Hanya melalui Wi-Fi</string>
<string name="downloads_wifi_only_summary">Berhenti memuat turun semasa beralih ke rangkaian mudah alih</string>
<string name="suggestion_manga">Cadangan: %s</string>
<string name="suggestions_notifications_summary">Kadang -kadang menunjukkan pemberitahuan dengan manga yang dicadangkan</string>
<string name="more">Lebih</string>
<string name="enable">Membolehkan</string>
<string name="no_thanks">Tidak terima kasih</string>
<string name="cancel_all_downloads_confirm">Semua muat turun aktif akan dibatalkan, data yang dimuat turun sebahagiannya akan hilang</string>
<string name="remove_completed_downloads_confirm">Sejarah muat turun anda akan dipadamkan secara kekal. Tiada fail yang dimuat turun akan terjejas</string>
<string name="text_downloads_list_holder">Anda tidak mempunyai muat turun</string>
<string name="downloads_resumed">Muat turun telah diteruskan</string>
<string name="downloads_paused">Muat turun telah dijeda</string>
<string name="downloads_removed">Muat turun telah dikeluarkan</string>
<string name="downloads_cancelled">Muat turun telah dibatalkan</string>
<string name="suggestions_enable_prompt">Adakah anda ingin menerima cadangan manga yang diperibadikan?</string>
<string name="web_view_unavailable">WebView Tidak Tersedia: Periksa sama ada pembekal WebView dipasang</string>
<string name="clear_network_cache">Cache Rangkaian cache</string>
<string name="type">Jenis</string>
<string name="address">Alamat</string>
<string name="port">Pelabuhan</string>
<string name="proxy">Proksi</string>
<string name="invalid_value_message">Nilai tidak sah</string>
<string name="email_password_enter_hint">Masukkan e -mel dan kata laluan anda untuk diteruskan</string>
<string name="downloaded">Dimuat turun</string>
<string name="images_proxy_title">Proksi Pengoptimuman Imej</string>
<string name="images_procy_description">Gunakan perkhidmatan WSRV.NL untuk mengurangkan penggunaan lalu lintas dan mempercepat pemuatan imej jika boleh</string>
<string name="invert_colors">Warna terbalik</string>
<string name="username">Nama pengguna</string>
<string name="password">Kata laluan</string>
<string name="authorization_optional">Kebenaran (pilihan)</string>
<string name="invalid_port_number">Nombor port tidak sah</string>
<string name="network">Rangkaian</string>
<string name="data_and_privacy">Data dan privasi</string>
<string name="restore_summary">Pulihkan sandaran yang dibuat sebelum ini</string>
<string name="webtoon_zoom_summary">Benarkan isyarat zum masuk dalam mod webtoon</string>
<string name="pull_to_prev_chapter">Siaran untuk membuka bab sebelumnya</string>
<string name="pull_to_next_chapter">Siaran untuk membuka bab seterusnya</string>
<string name="pull_top_no_prev">Tiada bab sebelumnya</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="theme_name_mamimi">Mamimi</string>
<string name="theme_name_kanade">Kanade</string>
<string name="pull_bottom_no_next">Tiada bab seterusnya</string>
<string name="reader_info_bar_summary">Tunjukkan masa sekarang dan kemajuan membaca di bahagian atas skrin</string>
<string name="show_pages_numbers_summary">Tunjukkan nombor halaman di sudut bawah</string>
<string name="clear_source_cookies_summary">Clear cookies untuk domain yang ditentukan sahaja. Dalam kebanyakan kes akan membatalkan kebenaran</string>
<string name="download_option_all_chapters">Semua bab dengan terjemahan %s</string>
<string name="download_option_whole_manga">Seluruh manga</string>
<string name="download_option_first_n_chapters">Pertama %s</string>
<string name="download_option_next_unread_n_chapters">Seterusnya belum dibaca %s</string>
<string name="download_option_all_unread">Semua bab yang belum dibaca</string>
<string name="download_option_all_unread_b">Semua bab yang belum dibaca (%s)</string>
<string name="download_option_manual_selection">Pilih bab secara manual</string>
<string name="pick_custom_directory">Pilih direktori tersuai</string>
<string name="no_access_to_file">Anda tidak mempunyai akses ke fail atau direktori ini</string>
<string name="local_manga_directories">Direktori manga tempatan</string>
<string name="description">Penerangan</string>
<string name="this_month">Bulan ini</string>
<string name="voice_search">Carian suara</string>
<string name="related_manga">Manga yang berkaitan</string>
<string name="color_light">Cahaya</string>
<string name="color_dark">Gelap</string>
<string name="color_white">Putih</string>
<string name="color_black">Hitam</string>
<string name="background">Latar belakang</string>
<string name="data_not_restored">Data tidak dipulihkan</string>
<string name="data_not_restored_text">Pastikan anda telah memilih fail sandaran yang betul</string>
<string name="manage_categories">Menguruskan kategori</string>
<string name="suggestions_wifi_only_summary">Jangan mengemas kini cadangan menggunakan sambungan rangkaian meter</string>
<string name="tracker_wifi_only_summary">Jangan periksa bab baru menggunakan sambungan rangkaian meter</string>
<string name="search_hint">Masukkan tajuk manga, genre atau nama sumber</string>
<string name="progress">Kemajuan</string>
<string name="order_added">Ditambah</string>
<string name="show">Tunjukkan</string>
<string name="captcha_required_summary">%s memerlukan captcha dapat diselesaikan untuk berfungsi dengan baik</string>
<string name="languages">Bahasa</string>
<string name="unknown">Tidak diketahui</string>
<string name="in_progress">Sedang berjalan</string>
<string name="disable_nsfw">Lumpuhkan NSFW</string>
<string name="too_many_requests_message">Terlalu banyak permintaan. Cuba lagi kemudian</string>
<string name="too_many_requests_message_retry">Terlalu banyak permintaan. Cuba lagi selepas %s</string>
<string name="related_manga_summary">Tunjukkan senarai manga yang berkaitan. Dalam beberapa kes, ia mungkin tidak tepat atau hilang</string>
<string name="main_screen_sections">Bahagian skrin utama</string>
<string name="items_limit_exceeded">Tidak ada lagi item yang boleh ditambah</string>
<string name="to_top">Ke atas</string>
<string name="moved_to_top">Berpindah ke atas</string>
<string name="zoom_out">Zum keluar</string>
<string name="zoom_in">Zum masuk</string>
<string name="reader_zoom_buttons">Tunjukkan butang zum</string>
<string name="reader_zoom_buttons_summary">Sama ada untuk menunjukkan butang kawalan zum di sudut kanan bawah</string>
<string name="keep_screen_on">Teruskan skrin</string>
<string name="keep_screen_on_summary">Jangan matikan skrin semasa anda membaca manga</string>
<string name="state_abandoned">Jatuh</string>
<string name="enhanced_colors_summary">Mengurangkan banding, tetapi mungkin memberi kesan kepada prestasi</string>
<string name="enhanced_colors">Mod warna 32-bit</string>
<string name="suggest_new_sources">Cadangkan sumber baru selepas kemas kini aplikasi</string>
<string name="suggest_new_sources_summary">Segera untuk membolehkan sumber yang baru ditambah setelah mengemas kini aplikasi</string>
<string name="list_options">Pilihan Senarai</string>
<string name="by_relevance">Kaitan</string>
<string name="categories">Kategori</string>
<string name="online_variant">Varian dalam talian</string>
<string name="periodic_backups">Sandaran berkala</string>
<string name="backup_frequency">Kekerapan penciptaan sandaran</string>
<string name="frequency_every_day">Setiap hari</string>
<string name="frequency_every_2_days">Setiap 2 hari</string>
<string name="frequency_once_per_week">Sekali seminggu</string>
<string name="frequency_twice_per_month">Dua kali sebulan</string>
<string name="frequency_once_per_month">Sekali sebulan</string>
<string name="periodic_backups_enable">Dayakan sandaran berkala</string>
<string name="backups_output_directory">Direktori Output Backup</string>
<string name="last_successful_backup">Sandaran terakhir yang berjaya: %s</string>
<string name="speed_value">x%.1f</string>
<string name="lock_screen_rotation">Putaran skrin kunci</string>
<string name="content_type_manga">Manga</string>
<string name="content_type_comics">Komik</string>
<string name="content_type_hentai">matang</string>
<string name="content_type_other">Yang lain</string>
<string name="sources_catalog">Sumber Katalog</string>
<string name="source_enabled">Sumber didayakan</string>
<string name="no_manga_sources_catalog_text">Tidak ada sumber yang terdapat di bahagian ini, atau semuanya mungkin telah ditambah.\nTinggal</string>
<string name="no_manga_sources_found">Tiada sumber manga yang ada yang dijumpai oleh pertanyaan anda</string>
<string name="catalog">Katalog</string>
<string name="manage_sources">Menguruskan sumber</string>
<string name="manual">Manual</string>
<string name="available_d">Terdapat: %1$d</string>
<string name="disable_nsfw_summary">Lumpuhkan sumber NSFW dan sembunyikan manga dewasa dari senarai jika boleh</string>
<string name="state_paused">Jeda</string>
<string name="reader_optimize">Kurangkan Penggunaan Memori (Beta)</string>
<string name="reader_optimize_summary">Kurangkan kualiti halaman offscreen untuk menggunakan memori yang kurang</string>
<string name="state">Negeri</string>
<string name="error_multiple_genres_not_supported">Penapisan oleh pelbagai genre tidak disokong oleh sumber manga ini</string>
<string name="error_multiple_states_not_supported">Penapisan oleh pelbagai negeri tidak disokong oleh sumber manga ini</string>
<string name="error_search_not_supported">Carian tidak disokong oleh sumber manga ini</string>
<string name="downloads_settings_info">Anda boleh mengaktifkan kelembapan muat turun untuk setiap sumber manga secara individu dalam tetapan sumber jika anda menghadapi masalah dengan menyekat pelayan</string>
<string name="skip">Langkau</string>
<string name="grayscale">Skala abu-abu</string>
<string name="globally">Di seluruh dunia</string>
<string name="this_manga">Manga ini</string>
<string name="color_correction_apply_text">Tetapan ini boleh digunakan secara global atau hanya kepada manga semasa. Jika digunakan di seluruh dunia, tetapan individu tidak akan ditindih.</string>
<string name="apply">Memohon</string>
<string name="error_filter_locale_genre_not_supported">Penapisan oleh kedua -dua genre dan locale tidak disokong oleh sumber ini</string>
<string name="error_filter_states_genre_not_supported">Penapisan oleh kedua -dua genre dan negeri tidak disokong oleh sumber ini</string>
<string name="genres_search_hint">Mula menaip nama genre</string>
<string name="disable_battery_optimization_summary_downloads">Mungkin membantu mendapatkan muat turun bermula jika anda mempunyai masalah dengannya</string>
<string name="welcome_text">Sila pilih sumber kandungan mana yang anda ingin aktifkan. Ini juga boleh dikonfigurasikan kemudian dalam tetapan</string>
<string name="sync_auth">Log masuk ke Akaun Menyegerakkan</string>
<string name="restore">Pulihkan</string>
<string name="backup_date_">Tarikh sandaran: %s</string>
<string name="state_upcoming">Akan datang</string>
<string name="by_name_reverse">Nama dibalikkan</string>
<string name="content_rating">Penilaian Kandungan</string>
<string name="genres_exclude">Tidak termasuk genre</string>
<string name="rating_safe">Selamat</string>
<string name="rating_suggestive">Sugestif</string>
<string name="rating_adult">Matang</string>
<string name="default_tab">Tab lalai</string>
<string name="mark_as_completed">Tanda seperti selesai</string>
<string name="mark_as_completed_prompt">Tandakan manga yang dipilih sepenuhnya dibaca?\n\nAmaran: Kemajuan bacaan semasa akan hilang.</string>
<string name="category_hidden_done">Kategori ini tersembunyi dari skrin utama dan boleh diakses melalui menu → Urus kategori</string>
<string name="volume_">Kelantangan %d</string>
<string name="volume_unknown">Jumlah yang tidak diketahui</string>
<string name="incognito_mode_hint">Kemajuan bacaan anda tidak akan disimpan</string>
<string name="vertical">Menegak</string>
<string name="show_menu">Tunjukkan menu</string>
<string name="toggle_ui">Tunjukkan/sembunyikan UI</string>
<string name="prev_chapter">Bab sebelumnya</string>
<string name="next_chapter">Bab seterusnya</string>
<string name="prev_page">Halaman sebelumnya</string>
<string name="next_page">Halaman seterusnya</string>
<string name="reader_actions">Tindakan pembaca</string>
<string name="reader_actions_summary">Konfigurasikan Tindakan untuk Kawasan Skrin Tappable</string>
<string name="switch_pages_volume_buttons">Dayakan butang kelantangan</string>
<string name="switch_pages_volume_buttons_summary">Gunakan butang kelantangan untuk menukar halaman</string>
<string name="reader_navigation_inverted">Kawalan navigasi terbalik</string>
<string name="reader_navigation_inverted_summary">Tukar arah butang kelantangan dan navigasi utama perkakasan arah (kiri/atas/bawah/kanan)</string>
<string name="tap_action">Ketik Tindakan</string>
<string name="long_tap_action">Tindakan ketuk panjang</string>
<string name="none">Tiada</string>
<string name="config_reset_confirm">Tetapkan semula tetapan ke nilai lalai? Tindakan ini tidak dapat dibatalkan.</string>
<string name="use_two_pages_landscape">Gunakan susun atur dua halaman pada orientasi landskap (beta)</string>
<string name="default_webtoon_zoom_out">Webtoon lalai zum keluar</string>
<string name="fullscreen_mode">Mod skrin penuh</string>
<string name="reader_fullscreen_summary">Menyembunyikan status sistem dan bar navigasi</string>
<string name="reading_time_estimation">Tunjukkan anggaran masa bacaan</string>
<string name="reading_time_estimation_summary">Nilai anggaran masa mungkin tidak tepat</string>
<string name="suggestions_unavailable_text">Ciri Cadangan dilumpuhkan</string>
<string name="check_for_new_chapters_disabled">Memeriksa bab baru dilumpuhkan</string>
<string name="show_labels_in_navbar">Tunjukkan label di bar navigasi</string>
<string name="pages_saving">Menyimpan halaman</string>
<string name="ask_for_dest_dir_every_time">Minta direktori destinasi setiap kali</string>
<string name="default_page_save_dir">Halaman lalai Simpan direktori</string>
<string name="remove_from_history">Keluarkan dari sejarah</string>
<string name="location">Lokasi</string>
<string name="preferred_download_format">Format muat turun pilihan</string>
<string name="single_cbz_file">Fail CBZ tunggal</string>
<string name="multiple_cbz_files">Fail CBZ berganda</string>
<string name="reading_stats">Statistik membaca</string>
<string name="other_manga">Manga lain</string>
<string name="less_than_minute">Kurang dari satu minit</string>
<string name="statistics">Statistik</string>
<string name="clear_stats">Statistik yang jelas</string>
<string name="stats_cleared">Statistik dibersihkan</string>
<string name="clear_stats_confirm">Adakah anda benar -benar ingin membersihkan semua statistik membaca? Tindakan ini tidak dapat dibatalkan.</string>
<string name="week">Minggu</string>
<string name="month">Bulan</string>
<string name="all_time">Sepanjang masa</string>
<string name="day">Hari</string>
<string name="three_months">Tiga bulan</string>
<string name="empty_stats_text">Tidak ada statistik untuk tempoh yang dipilih</string>
<string name="pages_read_s">Halaman dibaca: %s</string>
<string name="alternatives">Alternatif</string>
<string name="migrate">Berhijrah</string>
<string name="migrate_confirmation">Manga \"%1$s\" dari \"%2$s\" akan digantikan dengan \"%3$s\" dari \"%4$s\" dalam sejarah dan kegemaran anda (jika ada)</string>
<string name="manga_migration">Penghijrahan manga</string>
<string name="migration_completed">Penghijrahan selesai</string>
<string name="delete_read_chapters">Padam Baca Bab</string>
<string name="no_chapters_deleted">Tidak ada bab yang telah dipadam</string>
<string name="chapters_deleted_pattern">Dikeluarkan %1$s, dibersihkan %2$s</string>
<string name="delete_read_chapters_summary">Padamkan bab yang telah anda baca dari simpanan tempatan untuk membebaskan ruang</string>
<string name="delete_read_chapters_prompt">Ini akan memadamkan semua bab yang ditandakan secara kekal seperti yang dibaca dari storan tempatan anda. Anda boleh memuat turun semula kemudian, tetapi bab yang diimport mungkin hilang selama-lamanya</string>
<string name="delete_read_chapters_auto">Padam Baca Bab secara automatik</string>
<string name="runs_on_app_start">Berjalan ketika aplikasi bermula</string>
<string name="split_by_translations">Berpecah oleh terjemahan</string>
<string name="split_by_translations_summary">Tunjukkan bab dengan terjemahan yang berbeza secara berasingan, bukannya dalam satu senarai</string>
<string name="order_oldest">Tertua</string>
<string name="long_ago_read">Lama dahulu baca</string>
<string name="unread">Belum dibaca</string>
<string name="enable_source">Dayakan sumber</string>
<string name="unsupported_source">Sumber manga ini tidak disokong</string>
<string name="show_pages_thumbs">Tunjukkan Halaman Thumbnails</string>
<string name="show_pages_thumbs_summary">Dayakan tab \"Halaman\" pada skrin Butiran</string>
<string name="error_no_data_received">Tiada data diterima dari pelayan</string>
<string name="unsupported_backup_message">Sila pilih fail sandaran Kotatsu yang betul</string>
<string name="hours_short">%d j</string>
<string name="minutes_short">%d m</string>
<string name="seconds_short">%d s</string>
<string name="hours_minutes_short">%1$d j %2$d m</string>
<string name="minutes_seconds_short">%1$d m %2$d d</string>
<string name="fix">Betulkan</string>
<string name="missing_storage_permission">Tidak ada kebenaran untuk mengakses manga pada storan luaran</string>
<string name="last_used">Terakhir digunakan</string>
<string name="show_updated">Tunjukkan dikemas kini</string>
<string name="webtoon_gaps">Jurang dalam mod webtoon</string>
<string name="webtoon_gaps_summary">Tunjukkan jurang menegak antara halaman dalam mod webtoon</string>
<string name="enable_pull_gesture_title">Dayakan tarik isyarat</string>
<string name="enable_pull_gesture_summary">Gunakan isyarat tarik untuk menukar bab di webtoon</string>
<string name="less_frequently">Kurang kerap</string>
<string name="more_frequently">Lebih kerap</string>
<string name="frequency_of_check">Kekerapan cek</string>
<string name="pin_navigation_ui">UI navigasi pin</string>
<string name="pin_navigation_ui_summary">Jangan sembunyikan bar navigasi dan paparan cari pada tatal</string>
<string name="search_suggestions">Cadangan carian</string>
<string name="recent_queries">Pertanyaan terkini</string>
<string name="suggested_queries">Pertanyaan yang dicadangkan</string>
<string name="authors">Penulis</string>
<string name="blocked_by_server_message">Anda disekat oleh pelayan. Cuba gunakan sambungan rangkaian yang berbeza (VPN, proksi, dll.)</string>
<string name="disable">Lumpuhkan</string>
<string name="sources_disabled">Sumber dilumpuhkan</string>
<string name="disable_connectivity_check">Lumpuhkan Semak Sambungan</string>
<string name="ignore_ssl_errors_summary">Anda boleh melumpuhkan pengesahan sijil SSL sekiranya anda menghadapi masalah yang berkaitan dengan SSL semasa mengakses sumber rangkaian. Ini boleh menjejaskan keselamatan anda. Permohonan dimulakan semula diperlukan setelah berubah.</string>
<string name="disable_connectivity_check_summary">Langkau pemeriksaan sambungan sekiranya anda mempunyai masalah dengannya (mis. Mod luar talian walaupun rangkaian disambungkan)</string>
<string name="disable_nsfw_notifications">Lumpuhkan pemberitahuan NSFW</string>
<string name="disable_nsfw_notifications_summary">Jangan tunjukkan pemberitahuan mengenai kemas kini manga NSFW</string>
<string name="tracker_debug_info">Memeriksa log bab baru</string>
<string name="tracker_debug_info_summary">Maklumat debug mengenai pemeriksaan latar untuk bab baru</string>
<string name="_new">Baru</string>
<string name="all_languages">Semua bahasa</string>
<string name="screenshots_block_incognito">Blok semasa mod penyamaran</string>
<string name="image_server">Pelayan Imej Pilihan</string>
<string name="crop_pages">Halaman tanaman</string>
<string name="pin">Pin</string>
<string name="unpin">Batalkan PIN</string>
<string name="source_pinned">Sumber disematkan</string>
<string name="source_unpinned">Sumber yang tidak diingini</string>
<string name="sources_unpinned">Sumber yang tidak diingini</string>
<string name="sources_pinned">Sumber disematkan</string>
<string name="recent_sources">Sumber terkini</string>
<string name="percent_read">Peratus membaca</string>
<string name="percent_left">Peratus kiri</string>
<string name="chapters_read">Bab Baca</string>
<string name="chapters_left">Bab kiri</string>
<string name="external_source">External/plugin</string>
<string name="plugin_incompatible">Plugin yang tidak serasi atau ralat dalaman. Pastikan anda menggunakan versi terkini plugin dan kotatsu</string>
<string name="plugin_incompatible_with_cause">Ralat Plugin: %s\n Pastikan anda menggunakan versi terkini plugin dan kotatsu</string>
<string name="connection_ok">Sambungan ok</string>
<string name="invalid_proxy_configuration">Konfigurasi proksi tidak sah</string>
<string name="show_quick_filters">Tunjukkan penapis cepat</string>
<string name="show_quick_filters_summary">Memberi keupayaan untuk menapis senarai manga oleh parameter tertentu</string>
<string name="sfw">SFW</string>
<string name="skip_all">Langkau semua</string>
<string name="stuck">Terjebak</string>
<string name="not_in_favorites">Tidak sesuai</string>
<string name="updated_long_ago">Dikemas kini lama dahulu</string>
<string name="unpopular">Tidak popular</string>
<string name="low_rating">Penilaian rendah</string>
<string name="sort_order_asc">Menaik</string>
<string name="sort_order_desc">Turun</string>
<string name="by_date">Tarikh</string>
<string name="popularity">Populariti</string>
<string name="scrobbler_auth_required">Log masuk ke %s untuk meneruskan</string>
<string name="scrobbler_auth_intro">Log masuk untuk menyediakan integrasi dengan %s. Ini akan membolehkan anda menjejaki kemajuan dan status membaca manga anda</string>
<string name="unstable_feature">Ciri yang tidak stabil</string>
<string name="unstable_feature_summary">Fungsi ini adalah percubaan. Pastikan anda mempunyai sandaran untuk mengelakkan kehilangan data</string>
<string name="downloads_background">Muat turun latar belakang</string>
<string name="download_new_chapters">Muat turun bab baru</string>
<string name="manga_with_downloaded_chapters">Manga dengan bab yang dimuat turun</string>
<string name="manga_replaced">Manga \"%1$s\" (%2$s) digantikan dengan \"%3$s\" (%4$s)</string>
<string name="fixing_manga">Memperbaiki manga</string>
<string name="fixed">Tetap berjaya</string>
<string name="no_fix_required">Tiada pembetulan diperlukan untuk \"%s\"</string>
<string name="no_alternatives_found">Tiada alternatif yang dijumpai \"%s\"</string>
<string name="manga_fix_prompt">Fungsi ini akan menemui sumber alternatif untuk manga yang dipilih. Tugas akan mengambil sedikit masa dan akan diteruskan di latar belakang</string>
<string name="content_type_novel">Novel</string>
<string name="content_type_manhua">Komik Mandarin (manhua)</string>
<string name="content_type_manhwa">Komik Korea (Manhwa)</string>
<string name="recently_added">Baru-baru ini ditambah</string>
<string name="added_long_ago">Ditambah lama dahulu</string>
<string name="popular_in_hour">Popular jam ini</string>
<string name="popular_today">Popular hari ini</string>
<string name="popular_in_week">Popular minggu ini</string>
<string name="popular_in_month">Popular bulan ini</string>
<string name="popular_in_year">Popular tahun ini</string>
<string name="original_language">Bahasa asal</string>
<string name="year">Tahun</string>
<string name="demographics">Demografi</string>
<string name="demographic_shounen">Shounen</string>
<string name="demographic_shoujo">Shoujo</string>
<string name="demographic_seinen">Seinen</string>
<string name="demographic_josei">Josei</string>
<string name="years">Tahun</string>
<string name="any">Mana-mana</string>
<string name="filter_search_warning">Sumber ini tidak menyokong carian dengan penapis. Penapis anda telah dibersihkan</string>
<string name="demographic_kodomo">Kanak-kanak</string>
<string name="content_type_one_shot">Satu pukulan</string>
<string name="content_type_doujinshi">Komik independen</string>
<string name="content_type_image_set">Set imej</string>
<string name="content_type_artist_cg">Artist CG</string>
<string name="content_type_game_cg">Permainan CG</string>
<string name="debug">Awakutu</string>
<string name="source_code">Kod sumber</string>
<string name="user_manual">Manual Pengguna</string>
<string name="telegram_group">Kumpulan Telegram</string>
<string name="error_image_format">Format imej yang tidak disokong: %s</string>
<string name="error_not_image">Format tidak sah: Imej yang diharapkan tetapi mendapat %s</string>
<string name="start_download">Mula muat turun</string>
<string name="save_manga_confirm">Simpan manga terpilih? Ini mungkin mengambil ruang lalu lintas dan cakera</string>
<string name="save_manga">Simpan manga</string>
<string name="genre">Genre</string>
<string name="download_added">Muat turun ditambah</string>
<string name="more_options">Lebih banyak pilihan</string>
<string name="destination_directory">Direktori Destinasi</string>
<string name="chapter_selection_hint">Anda boleh memilih bab untuk memuat turun dengan klik lama pada item dalam senarai bab.</string>
<string name="chapters_all">Semua</string>
<string name="download_over_cellular">Memuat turun rangkaian selular</string>
<string name="download_cellular_confirm">Benarkan muat turun melalui rangkaian selular?</string>
<string name="dont_allow">Jangan biarkan</string>
<string name="allow_always">Benarkan selalu</string>
<string name="allow_once">Benarkan sekali</string>
<string name="ask_every_time">Tanya setiap masa</string>
<string name="screen_orientation">Orientasi skrin</string>
<string name="portrait">Potret</string>
<string name="landscape">Landskap</string>
<string name="access_denied_403">Akses Ditolak (403)</string>
<string name="max_backups_count">Bilangan sandaran maksimum</string>
<string name="delete_old_backups">Padam sandaran lama</string>
<string name="delete_old_backups_summary">Memadam fail sandaran lama secara automatik untuk menjimatkan ruang penyimpanan</string>
<string name="handle_links">Mengendalikan pautan</string>
<string name="handle_links_summary">Mengendalikan pautan manga dari aplikasi luaran (mis. Pelayar web). Anda juga mungkin perlu membolehkannya secara manual dalam tetapan sistem aplikasi</string>
<string name="email">E -mel</string>
<string name="captcha_required_message">Sumber ini memerlukan menyelesaikan Captcha untuk meneruskan</string>
<string name="author">Pengarang</string>
<string name="rating">Penilaian</string>
<string name="source">Sumber</string>
<string name="translation">Terjemahan</string>
<string name="show_slider">Tunjukkan gelangsar</string>
<string name="incognito">Mod penyamaran</string>
<string name="error_connection_reset">Tetapkan semula sambungan oleh tuan rumah jauh</string>
<string name="backup_tg_check">Periksa sama ada API berfungsi</string>
<string name="backup_tg_echo">Mesej ujian</string>
<string name="backup_tg_id_not_set">ID sembang tidak ditetapkan</string>
<string name="telegram_chat_id">ID sembang telegram</string>
<string name="open_telegram_bot">Buka bot Telegram</string>
<string name="send_backups_telegram">Hantar sandaran di Telegram</string>
<string name="test_connection">Sambungan ujian</string>
<string name="telegram_chat_id_summary">Masukkan ID sembang di mana sandaran harus dihantar</string>
<string name="open_telegram_bot_summary">Tekan untuk membuka sembang dengan bot sandaran Kotatsu</string>
<string name="clear_database">Pangkalan data yang jelas</string>
<string name="clear_database_summary">Padamkan maklumat mengenai manga yang tidak digunakan</string>
<string name="enable_all_sources">Dayakan semua sumber manga</string>
<string name="enable_all_sources_summary">Semua sumber manga yang ada akan didayakan secara kekal</string>
<string name="all_sources_enabled">Semua sumber diaktifkan</string>
<string name="reader_info_bar_transparent">Bar Maklumat Pembaca Telus</string>
<string name="backup_restored_background">Sandaran akan dipulihkan di latar belakang</string>
<string name="restoring_backup">Memulihkan sandaran</string>
<string name="reader_controls_in_bottom_bar">Kawalan pembaca di bar bawah</string>
<string name="chapters_and_pages">Bab dan halaman</string>
<string name="pages_slider">Slider suis halaman</string>
<string name="screen_rotation_locked">Putaran skrin telah dikunci</string>
<string name="screen_rotation_unlocked">Putaran skrin telah dibuka</string>
<string name="badges_in_lists">Lencana dalam senarai</string>
<string name="search_everywhere">Cari di mana -mana sahaja</string>
<string name="simple">Mudah</string>
<string name="global_search">Carian global</string>
<string name="disable_captcha_notifications">Lumpuhkan pemberitahuan Captcha</string>
<string name="disable_captcha_notifications_summary">Anda tidak akan menerima pemberitahuan mengenai menyelesaikan Captcha untuk sumber ini tetapi ini boleh membawa kepada operasi latar belakang (memeriksa bab baru, mendapatkan cadangan, dll)</string>
<string name="chapter_volume_number">Vol %1$s Bab %2$s</string>
<string name="chapter_number">Bab%s</string>
<string name="unnamed_chapter">Bab yang tidak dinamakan</string>
<string name="search_disabled_sources">Cari melalui sumber kurang upaya</string>
<string name="error_details">Butiran ralat</string>
<string name="error_disclaimer_manga">Cuba buka manga dalam pelayar web untuk memastikan ia tersedia di sumbernya.</string>
<string name="error_disclaimer_app_outdated">Ia kelihatan seperti versi Kotatsu anda sudah lapuk. Sila pasang versi terkini untuk mendapatkan semua pembetulan yang tersedia.</string>
<string name="error_disclaimer_report">Anda boleh menghantar laporan pepijat kepada pemaju. Ini akan membantu kami menyiasat dan membetulkan isu ini.</string>
<string name="link_to_manga_on_s">Pautan ke manga %s</string>
<string name="link_to_manga_in_app">Kotatsu, pekerja manga</string>
<string name="clear_browser_data">Data penyemak imbas yang jelas</string>
<string name="clear_browser_data_summary">Data penyemak imbas yang jelas seperti cache dan kuki. Amaran: Kebenaran dalam sumber manga mungkin tidak sah</string>
<string name="no_write_permission_to_file">Tidak mempunyai kebenaran untuk menulis fail</string>
<string name="exclude_nsfw_from_suggestions_summary">Manga dewasa tidak akan ditunjukkan dalam cadangan. Pilihan ini mungkin tidak tepat dengan beberapa sumber</string>
<string name="include_disabled_sources">Termasuk sumber kurang upaya</string>
<string name="suggestions_disabled_sources_summary">Tunjukkan cadangan dari semua sumber manga, termasuk orang kurang upaya</string>
<string name="tags_warnings">Sorot genre berbahaya</string>
<string name="tags_warnings_summary">Sorot genre yang mungkin tidak sesuai untuk kebanyakan pengguna</string>
<string name="error_non_file_uri">Laluan yang dipilih tidak dapat digunakan kerana ia tidak menandakan fail atau direktori</string>
<string name="manga_override_hint">Perubahan ini akan mempengaruhi bagaimana manga dipaparkan dalam aplikasinya</string>
<string name="use_default_cover">Gunakan penutup lalai</string>
<string name="pick_manga_page">Pilih halaman manga</string>
<string name="pick_custom_file">Pilih fail tersuai</string>
<string name="change_cover">Tukar penutup</string>
<string name="page_switch_timer">Halaman akan menukar setiap ~%d saat</string>
<string name="dont_ask_again">Jangan tanya lagi</string>
<string name="incognito_mode_hint_nsfw">Manga ini mungkin mengandungi kandungan dewasa. Adakah anda mahu menggunakan mod incognito?</string>
<string name="incognito_for_nsfw">Mod penyamaran untuk manga nsfw</string>
<string name="additional_action_required">Tindakan tambahan diperlukan</string>
<string name="hide_from_main_screen">Sembunyikan dari skrin utama</string>
<string name="changelog">Perubahan dalam perubahan</string>
<string name="changelog_summary">Perubahan sejarah untuk versi yang dikeluarkan baru -baru ini</string>
<string name="collapse">Runtuh</string>
<string name="expand">Berkembang</string>
<string name="adblock">Blok iklan dalam penyemak imbas</string>
<string name="adblock_summary">Blok Iklan di Pelayar Terbina (Beta)</string>
<string name="collapse_long_description">Kejutan panjang runtuh</string>
<string name="creating_backup">Membuat sandaran</string>
<string name="share_backup">Kongsi sandaran</string>
<string name="reader_multitask">Buka pembaca dalam tugas yang berasingan</string>
<string name="reader_multitask_summary">Membolehkan anda menyimpan beberapa pembaca dengan manga yang berbeza terbuka pada masa yang sama</string>
<string name="theme_name_itsuka">Itsuka</string>
<string name="theme_name_totoro">Totoro</string>
<string name="book_effect">Latar belakang kekuningan (penapis biru)</string>
<string name="local_storage_cleanup">Pembersihan Penyimpanan Tempatan</string>
<string name="packup_creation_failed">Gagal membuat sandaran</string>
<string name="main_screen">Skrin utama</string>
<string name="main_screen_fab">Tunjukkan butang Terus Terapung</string>
<string name="main_screen_fab_summary">Membolehkan terus membaca dalam satu klik. Butang ini tidak akan muncul dalam mod penyamaran atau ketika sejarah kosong</string>
<string name="error_corrupted_zip">Arkib zip yang rosak (%s)</string>
<string name="discord_rpc">Discord Kehadiran yang kaya</string>
<string name="discord_token">Token Discord</string>
<string name="discord_token_summary">Masukkan token Discord anda untuk membolehkan kehadiran yang kaya</string>
<string name="discord_token_description">Masukkan token atau klik Discord anda atau klik %s untuk menggunakannya menggunakan penyemak imbas</string>
<string name="discord_token_hint">Tampalkan token Discord anda di sini</string>
<string name="discord_rpc_summary">Tunjukkan status bacaan anda pada Discord</string>
<string name="obtain">Memperoleh</string>
<string name="discord_rpc_description">Membaca Manga di Kotatsu - Aplikasi Pembaca Manga</string>
<string name="reading_s">Membaca %s</string>
<string name="read_on_s">Baca terus %s</string>
<string name="rpc_skip_nsfw_summary">Jangan gunakan RPC untuk kandungan dewasa</string>
<string name="invalid_token">Token tidak sah: %s</string>
<string name="show_floating_control_button">Tunjukkan butang kawalan terapung</string>
<string name="unavailable">Tidak tersedia</string>
<string name="manga_restricted_description">Manga ini tidak tersedia untuk dibaca dalam sumber ini. Cuba cari di sumber lain atau membukanya dalam penyemak imbas untuk maklumat lanjut</string>
<string name="no_chapters_in_manga">Manga ini tidak mengandungi sebarang bab</string>
<string name="chapters_load_failed">Gagal memuatkan senarai bab</string>
<string name="telegram_integration">Telegram integrasi</string>
<string name="test_parser">Ujian manga sumber</string>
</resources>

@ -832,12 +832,4 @@
<string name="hide_from_main_screen">Ukryj na ekranie głównym</string>
<string name="theme_name_itsuka">Itsuka</string>
<string name="theme_name_totoro">Totoro</string>
<string name="book_effect">Żółtawe tło (niebieski filtr)</string>
<string name="packup_creation_failed">Nie udało się stworzyć kopii zapasowej</string>
<string name="main_screen">Ekran główny</string>
<string name="main_screen_fab_summary">Pozwala wznowić czytanie jednym kliknięciem. Ten przycisk nie będzie pojawiał się w trybie inkognito, albo kiedy historia będzie pusta</string>
<string name="unavailable">Niedostępne</string>
<string name="no_chapters_in_manga">Ta manga nie zawiera żadnych rozdziałów</string>
<string name="chapters_load_failed">Nie udało się załadować listy rozdziałów</string>
<string name="telegram_integration">Integracja z Telegramem</string>
</resources>

@ -326,7 +326,7 @@
<string name="manga_error_description_pattern">Детаљи о грешци:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Покушај да &lt;a href=%2$s&gt;отвориш мангу у веб прегледачу&lt;/a&gt; да би био сигуран да је доступна на извору&lt;br&gt;2. Увери се да користите &lt;a href=kotatsu://about&gt;latest version of Kotatsu&gt;најновију верзију Котатсу-а&lt;/a&gt;&lt;br&gt;3. Ако је доступно, пошаљи извештај о грешци програмерима.</string>
<string name="state_abandoned">Напуштен</string>
<string name="zoom_mode_fit_height">Уклопи по висини</string>
<string name="not_available">Није доступно</string>
<string name="not_available">Недоступно</string>
<string name="history_shortcuts_summary">Учини недавну мангу доступном дугим притиском на икону апликације</string>
<string name="automatic_scroll">Самостално померање</string>
<string name="download_option_first_n_chapters">Првих %s</string>
@ -837,33 +837,4 @@
<string name="local_storage_cleanup">Чишћење локалне меморије</string>
<string name="packup_creation_failed">Није успело креирање резервне копије</string>
<string name="reader_navigation_inverted">Обрни контроле навигације</string>
<string name="pull_to_prev_chapter">Отпусти да би отворио претходно поглавље</string>
<string name="pull_to_next_chapter">Отпусти да би отворио следеће поглавље</string>
<string name="pull_top_no_prev">Нема претходног поглавља</string>
<string name="pull_bottom_no_next">Нема следећег поглавља</string>
<string name="enable_pull_gesture_title">Омогући покрет повлачења</string>
<string name="enable_pull_gesture_summary">Користи покрет повлачења да би пребацивао поглавља у webtoon-у</string>
<string name="main_screen">Главни екран</string>
<string name="main_screen_fab">Прикажи плутајуће дугме Настави</string>
<string name="main_screen_fab_summary">Омогућава наставак читања једним додиром. Ово дугме се неће појавити у режиму „Без чувања“ или када је историја празна</string>
<string name="error_corrupted_zip">Оштећена ZIP архива (%s)</string>
<string name="discord_rpc">Присуство богато Discord-ом</string>
<string name="discord_token">Discord Токен</string>
<string name="discord_token_summary">Унеси свој Discord Токен да би омогућио Rich Presence</string>
<string name="discord_token_description">Унеси свој Discord Токен или додирни на %s да би га добио помоћу прегледача</string>
<string name="discord_token_hint">Налепи свој Discord Токен овде</string>
<string name="discord_rpc_summary">Прикажи свој статус читања на Дискорду</string>
<string name="obtain">Добиј</string>
<string name="discord_rpc_description">Читање манге на Котатсу - апликацији за читање манги</string>
<string name="reading_s">Читам %s</string>
<string name="read_on_s">Читај на %s</string>
<string name="rpc_skip_nsfw_summary">Не користи RPC за садржај за одрасле</string>
<string name="invalid_token">Неважећи токен: %s</string>
<string name="show_floating_control_button">Прикажи плутајуће дугме за контролу</string>
<string name="unavailable">Недоступно</string>
<string name="manga_restricted_description">Ова манга није доступна за читање у овом извору. Покушај да је потражиш у другим изворима или је отвори у прегледачу за више информација</string>
<string name="no_chapters_in_manga">Ова манга не садржи ниједно поглавље</string>
<string name="chapters_load_failed">Учитавање листе поглавља није успело</string>
<string name="telegram_integration">Интеграција Телеграма</string>
<string name="test_parser">Извор тест манге</string>
</resources>

@ -54,7 +54,7 @@
<string name="add">கூட்டு</string>
<string name="save">சேமி</string>
<string name="share">பங்கு</string>
<string name="create_shortcut">குறுக்குவழியை உருவாக்கவும்</string>
<string name="create_shortcut">குறுக்குவழியை உருவாக்கவும்</string>
<string name="search">தேடல்</string>
<string name="search_manga">மங்காவைத் தேடுங்கள்</string>
<string name="manga_downloading_">பதிவிறக்கம்…</string>
@ -792,70 +792,4 @@
<string name="link_to_manga_on_s">மங்காவிற்கான இணைப்பு இயக்கப்பட்டது %s</string>
<string name="search_everywhere">எல்லா இடங்களிலும் தேடுங்கள்</string>
<string name="error_disclaimer_report">நீங்கள் டெவலப்பர்களிடம் பிழை அறிக்கையைச் சமர்ப்பிக்கலாம். இது சிக்கலை ஆராய்ந்து சரிசெய்ய எங்களுக்கு உதவும்.</string>
<string name="nsfw_16">16+</string>
<string name="theme_name_expressive">வெளிப்படையான (சோதனை)</string>
<string name="reader_navigation_inverted">வழிசெலுத்தல் கட்டுப்பாடுகள் தலைகீழ்</string>
<string name="reader_navigation_inverted_summary">தொகுதி பொத்தான் மற்றும் திசை வன்பொருள் விசை வழிசெலுத்தலின் திசையை மாற்றவும் (இடது/மேல்/கீழ்/வலது)</string>
<string name="exclude_nsfw_from_suggestions_summary">அகவை வந்த மங்கா பரிந்துரைகளில் காட்டப்படாது. இந்த விருப்பம் சில ஆதாரங்களுடன் தவறானது</string>
<string name="include_disabled_sources">ஊனமுற்ற ஆதாரங்களைச் சேர்க்கவும்</string>
<string name="suggestions_disabled_sources_summary">ஊனமுற்றோர் உட்பட அனைத்து மங்கா மூலங்களிலிருந்தும் பரிந்துரைகளைக் காட்டுங்கள்</string>
<string name="tags_warnings">ஆபத்தான வகைகளை முன்னிலைப்படுத்தவும்</string>
<string name="tags_warnings_summary">பெரும்பாலான பயனர்களுக்கு பொருத்தமற்றதாக இருக்கும் வகைகளை முன்னிலைப்படுத்தவும்</string>
<string name="error_non_file_uri">தேர்ந்தெடுக்கப்பட்ட பாதையை பயன்படுத்த முடியாது, ஏனெனில் அது ஒரு கோப்பு அல்லது கோப்பகத்தைக் குறிக்கவில்லை</string>
<string name="manga_override_hint">இந்த மாற்றங்கள் பயன்பாட்டில் மங்கா எவ்வாறு காட்டப்படும் என்பதை பாதிக்கும்</string>
<string name="use_default_cover">இயல்புநிலை அட்டையைப் பயன்படுத்தவும்</string>
<string name="pick_manga_page">மங்கா பக்கத்தைத் தேர்ந்தெடுங்கள்</string>
<string name="pick_custom_file">தனிப்பயன் கோப்பைத் தேர்ந்தெடுக்கவும்</string>
<string name="change_cover">கவர் மாற்றவும்</string>
<string name="page_switch_timer">பக்கம் ஒவ்வொரு ~%d விநாடிகளிலும் மாறும்</string>
<string name="dont_ask_again">மீண்டும் கேட்க வேண்டாம்</string>
<string name="incognito_mode_hint_nsfw">இந்த மங்காவில் அகவை வந்தோர் உள்ளடக்கம் இருக்கலாம். நீங்கள் மறைநிலை பயன்முறையைப் பயன்படுத்த விரும்புகிறீர்களா?</string>
<string name="incognito_for_nsfw">NSFW மங்காவிற்கான மறைநிலை பயன்முறை</string>
<string name="additional_action_required">கூடுதல் நடவடிக்கை தேவை</string>
<string name="hide_from_main_screen">முதன்மையான திரையில் இருந்து மறைக்கவும்</string>
<string name="changelog">மாற்றபதிவு</string>
<string name="changelog_summary">அண்மைக் காலத்தில் வெளியிடப்பட்ட பதிப்புகளுக்கான வரலாற்றை மாற்றுகிறது</string>
<string name="collapse">சரிவு</string>
<string name="expand">விரிவாக்கு</string>
<string name="adblock">உலாவியில் விளம்பரங்களைத் தடுக்கவும்</string>
<string name="adblock_summary">உள்ளமைக்கப்பட்ட உலாவியில் (பீட்டா) தடுப்பு விளம்பரம்</string>
<string name="collapse_long_description">நீண்ட விளக்கத்தை சரிவு</string>
<string name="creating_backup">காப்புப்பிரதியை உருவாக்குதல்</string>
<string name="share_backup">காப்புப்பிரதியைப் பகிரவும்</string>
<string name="reader_multitask">ஒரு தனி பணியில் வாசகரைத் திறக்கவும்</string>
<string name="reader_multitask_summary">ஒரே நேரத்தில் வெவ்வேறு மங்காவுடன் பல வாசகர்களை திறந்து வைக்க உங்களை அனுமதிக்கிறது</string>
<string name="theme_name_itsuka">5 நாட்கள்</string>
<string name="theme_name_totoro">டுடோரோ</string>
<string name="book_effect">மஞ்சள் நிற பின்னணி (நீல வடிகட்டி)</string>
<string name="local_storage_cleanup">உள்ளக சேமிப்பக தூய்மைப்படுத்தல்</string>
<string name="packup_creation_failed">காப்புப்பிரதியை உருவாக்கத் தவறிவிட்டது</string>
<string name="main_screen">முதன்மையான திரை</string>
<string name="main_screen_fab">மிதக்கும் தொடர்ச்சியான பொத்தானைக் காட்டு</string>
<string name="main_screen_fab_summary">ஒரே கிளிக்கில் தொடர்ந்து படிக்க அனுமதிக்கிறது. இந்த பொத்தான் மறைநிலை பயன்முறையில் அல்லது வரலாறு காலியாக இருக்கும்போது தோன்றாது</string>
<string name="error_corrupted_zip">சிதைந்த சிப் காப்பகம் (%s)</string>
<string name="discord_rpc">முரண்பாடு பணக்கார இருப்பு</string>
<string name="discord_token">முரண்பாடு கிள்ளாக்கு</string>
<string name="discord_token_summary">பணக்கார இருப்பை செயல்படுத்த உங்கள் டிச்கார்ட் கிள்ளாக்கை உள்ளிடவும்</string>
<string name="discord_token_description">உங்கள் டிச்கார்ட் கிள்ளாக்கை உள்ளிடவும் அல்லது உலாவியைப் பயன்படுத்தி அதைப் பெற %s ஐக் சொடுக்கு செய்க</string>
<string name="discord_token_hint">உங்கள் டிச்கார்ட் கிள்ளாக்கை இங்கே ஒட்டவும்</string>
<string name="discord_rpc_summary">முரண்பாட்டில் உங்கள் வாசிப்பு நிலையைக் காட்டுங்கள்</string>
<string name="obtain">பெறுங்கள்</string>
<string name="discord_rpc_description">கோட்டாட்சுவில் மங்காவைப் படித்தல் - ஒரு மங்கா ரீடர் பயன்பாடு</string>
<string name="reading_s">படித்தல் %s</string>
<string name="read_on_s">%s படிக்கவும்</string>
<string name="rpc_skip_nsfw_summary">வயதுவந்த உள்ளடக்கத்திற்கு RPC ஐப் பயன்படுத்த வேண்டாம்</string>
<string name="invalid_token">தவறான டோக்கன்: %s</string>
<string name="show_floating_control_button">மிதக்கும் கட்டுப்பாட்டு பொத்தானைக் காட்டு</string>
<string name="unavailable">கிடைக்கவில்லை</string>
<string name="manga_restricted_description">இந்த மூலத்தில் படிக்க இந்த மங்கா கிடைக்கவில்லை. மற்ற மூலங்களில் இதைத் தேட முயற்சிக்கவும் அல்லது மேலும் தகவலுக்கு உலாவியில் திறக்கவும்</string>
<string name="no_chapters_in_manga">இந்த மங்காவில் எந்த அத்தியாயங்களும் இல்லை</string>
<string name="chapters_load_failed">அத்தியாய பட்டியலை ஏற்றுவதில் தோல்வி</string>
<string name="telegram_integration">தந்தி ஒருங்கிணைப்பு</string>
<string name="test_parser">சோதனை மங்கா மூல</string>
<string name="pull_to_prev_chapter">முந்தைய அத்தியாயத்தைத் திறக்க வெளியீடு</string>
<string name="pull_to_next_chapter">அடுத்த அத்தியாயத்தைத் திறக்க வெளியீடு</string>
<string name="pull_top_no_prev">முந்தைய அத்தியாயம் இல்லை</string>
<string name="pull_bottom_no_next">அடுத்த அத்தியாயம் இல்லை</string>
<string name="enable_pull_gesture_title">இழுக்கும் சைகையை இயக்கவும்</string>
<string name="enable_pull_gesture_summary">வெப்டூனில் அத்தியாயங்களை மாற்ற புல் சைகையைப் பயன்படுத்தவும்</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save