Compare commits
71 Commits
a624bffea3
...
861c21faea
| Author | SHA1 | Date | |
|---|---|---|---|
| 861c21faea | |||
|
|
9b4d014b21 | ||
|
c6da7de699
|
|||
|
ef3aa40acc
|
|||
|
07af3ea703
|
|||
|
391c8ab649
|
|||
| 6b1885c89d | |||
| 8423b48fb9 | |||
|
803c825d91
|
|||
|
6a9682a077
|
|||
|
9197b9cc3a
|
|||
|
|
02ea804874 | ||
|
c424466198
|
|||
|
|
18b312dde6 | ||
|
f78262b1a0
|
|||
|
c557a51c4d
|
|||
|
|
8995762935 | ||
|
|
ed2664db78 | ||
|
|
f5a5e53b5a | ||
|
|
9ef961590d | ||
|
|
9b569615ee | ||
|
|
f48cf2efe4 | ||
|
|
18094a310c | ||
|
|
320c49a831 | ||
|
|
2a971d5dae | ||
|
|
4467e79ae6 | ||
|
|
c68b180bf6 | ||
|
|
5f879f6c83 | ||
|
|
aeb3732d75 | ||
|
|
6292a0fd6b | ||
|
|
8985b4135d | ||
|
f8a5397542
|
|||
|
5f51041220
|
|||
|
5a14412b62
|
|||
|
be012f631a
|
|||
|
0165f43603
|
|||
|
55801a1488
|
|||
|
77103f016f
|
|||
|
|
6b6719a259 | ||
|
|
822642abb0 | ||
|
|
260745fb95 | ||
|
|
024ec0388f | ||
|
|
5345998eec | ||
|
|
3d56190e71 | ||
|
|
954431d0a5 | ||
|
|
afec63b443 | ||
|
|
ac5b29c35a | ||
|
|
59f5578b66 | ||
|
|
391dbb4237 | ||
|
|
7d4505eb78 | ||
|
|
e6ceb20cf7 | ||
|
|
8004f8c093 | ||
|
61bf2abb6c
|
|||
|
d9612f3427
|
|||
|
435c3824f7
|
|||
|
c846693570
|
|||
|
123937cd01
|
|||
|
9f56554313
|
|||
|
f8687bb697
|
|||
|
43d3a2cc6a
|
|||
|
|
a95db6ed21 | ||
|
fd0bb57338
|
|||
|
6b94bc2632
|
|||
|
c8b91599c6
|
|||
|
|
3a8b0f9e93 | ||
|
|
17a0725666 | ||
|
|
3be7848ad9 | ||
|
|
08202c11a3 | ||
|
|
5ef907d046 | ||
|
|
c3776ea3c6 | ||
|
|
1d1e49123a |
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@@ -3,3 +3,5 @@
|
||||
/workspace.xml
|
||||
/migrations.xml
|
||||
/runConfigurations.xml
|
||||
/appInsightsSettings.xml
|
||||
/kotlinCodeInsightSettings.xml
|
||||
|
||||
26
.idea/appInsightsSettings.xml
generated
Normal file
26
.idea/appInsightsSettings.xml
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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>
|
||||
4
.idea/gradle.xml
generated
4
.idea/gradle.xml
generated
@@ -6,7 +6,7 @@
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-21" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<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 = 1028
|
||||
versionName = '9.1.4'
|
||||
versionCode = 1030
|
||||
versionName = '9.2'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
@@ -87,6 +87,7 @@ android {
|
||||
'-opt-in=coil3.annotation.InternalCoilApi',
|
||||
'-opt-in=kotlinx.serialization.ExperimentalSerializationApi',
|
||||
'-Xjspecify-annotations=strict',
|
||||
'-Xannotation-default-target=first-only',
|
||||
'-Xtype-enhancement-improvements-strict-mode'
|
||||
]
|
||||
}
|
||||
|
||||
7
app/proguard-rules.pro
vendored
7
app/proguard-rules.pro
vendored
@@ -8,8 +8,7 @@
|
||||
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.**
|
||||
@@ -17,8 +16,10 @@
|
||||
-dontwarn com.google.j2objc.annotations.**
|
||||
-dontwarn coil3.PlatformContext
|
||||
|
||||
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
|
||||
-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.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,9 +51,11 @@
|
||||
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,15 +36,14 @@ 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 {
|
||||
@@ -90,8 +89,12 @@ 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, EnumSet.allOf(BackupSection::class.java), null)
|
||||
repository.restoreBackup(input, sections, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ enum class BackupSection(
|
||||
|
||||
fun of(entry: ZipEntry): BackupSection? {
|
||||
val name = entry.name.lowercase(Locale.ROOT)
|
||||
return entries.first { x -> x.entryName == name }
|
||||
return entries.find { 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.Companion.FORM)
|
||||
.setType(MultipartBody.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,7 +27,6 @@ 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
|
||||
@@ -62,9 +61,6 @@ open class BaseApp : Application(), Configuration.Provider {
|
||||
@Inject
|
||||
lateinit var workScheduleManager: WorkScheduleManager
|
||||
|
||||
@Inject
|
||||
lateinit var workManagerProvider: Provider<WorkManager>
|
||||
|
||||
@Inject
|
||||
lateinit var localMangaIndexProvider: Provider<LocalMangaIndex>
|
||||
|
||||
@@ -79,6 +75,7 @@ open class BaseApp : Application(), Configuration.Provider {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
PlatformRegistry.applicationContext = this // TODO replace with OkHttp.initialize
|
||||
if (ACRA.isACRASenderServiceProcess()) {
|
||||
return
|
||||
}
|
||||
@@ -97,7 +94,6 @@ open class BaseApp : Application(), Configuration.Provider {
|
||||
localStorageChanges.collect(localMangaIndexProvider.get())
|
||||
}
|
||||
workScheduleManager.init()
|
||||
WorkServiceStopHelper(workManagerProvider).setup()
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
@@ -43,6 +44,7 @@ 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
|
||||
@@ -65,11 +67,13 @@ 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) {
|
||||
@@ -79,10 +83,18 @@ class CaptchaHandler @Inject constructor(
|
||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
||||
super.onError(request, result)
|
||||
val e = result.throwable
|
||||
if (e is CloudFlareException && request.extras[ignoreCaptchaKey] != true) {
|
||||
if (e is CloudFlareException) {
|
||||
val scope = request.lifecycle?.coroutineScope ?: processLifecycleScope
|
||||
scope.launch {
|
||||
handleException(e.source, e, true)
|
||||
if (
|
||||
handleException(
|
||||
source = e.source,
|
||||
exception = e,
|
||||
notify = request.extras[suppressCaptchaKey] != true,
|
||||
)
|
||||
) {
|
||||
coilProvider.get().enqueue(request) // TODO check if ok
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,11 +102,14 @@ 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) {
|
||||
@@ -119,7 +134,7 @@ class CaptchaHandler @Inject constructor(
|
||||
notify(exceptions)
|
||||
}
|
||||
}
|
||||
true
|
||||
false
|
||||
}
|
||||
|
||||
@RequiresPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||
@@ -234,7 +249,7 @@ class CaptchaHandler @Inject constructor(
|
||||
.data(source.faviconUri())
|
||||
.allowHardware(false)
|
||||
.allowConversionToBitmap(true)
|
||||
.ignoreCaptchaErrors()
|
||||
.suppressCaptchaErrors()
|
||||
.mangaSourceExtra(source)
|
||||
.size(context.resources.getNotificationIconSize())
|
||||
.scale(Scale.FILL)
|
||||
@@ -260,11 +275,11 @@ class CaptchaHandler @Inject constructor(
|
||||
|
||||
companion object {
|
||||
|
||||
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
|
||||
extras[ignoreCaptchaKey] = true
|
||||
fun ImageRequest.Builder.suppressCaptchaErrors() = apply {
|
||||
extras[suppressCaptchaKey] = true
|
||||
}
|
||||
|
||||
val ignoreCaptchaKey = Extras.Key(false)
|
||||
private val suppressCaptchaKey = Extras.Key(false)
|
||||
|
||||
private const val CHANNEL_ID = "captcha"
|
||||
private const val TAG = CHANNEL_ID
|
||||
@@ -272,5 +287,6 @@ 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.Builder()
|
||||
fun shortMangaUrl(mangaId: Long): Uri = Uri.Builder()
|
||||
.scheme("kotatsu")
|
||||
.path("manga")
|
||||
.appendQueryParameter("id", mangaId.toString())
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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,15 +2,22 @@ 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
|
||||
|
||||
class ContinuationResumeWebViewClient(
|
||||
open class ContinuationResumeWebViewClient(
|
||||
private val continuation: Continuation<Unit>,
|
||||
) : WebViewClient() {
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
view?.webViewClient = WebViewClient() // reset to default
|
||||
continuation.resume(Unit)
|
||||
resumeContinuation(view)
|
||||
}
|
||||
|
||||
protected fun resumeContinuation(view: WebView?) {
|
||||
if (continuation !is CancellableContinuation || continuation.isActive) {
|
||||
view?.webViewClient = WebViewClient() // reset to default
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,127 @@
|
||||
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.core.util.ext.sanitizeHeaderValue
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
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
|
||||
@ApplicationContext private val context: Context,
|
||||
private val proxyProvider: ProxyProvider,
|
||||
private val cookieJar: MutableCookieJar,
|
||||
private val mangaRepositoryFactoryProvider: Provider<MangaRepository.Factory>,
|
||||
) {
|
||||
|
||||
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()
|
||||
if (!baseUrl.isNullOrEmpty()) {
|
||||
try {
|
||||
if (!baseUrl.isNullOrEmpty()) {
|
||||
suspendCoroutine { cont ->
|
||||
webView.webViewClient = ContinuationResumeWebViewClient(cont)
|
||||
webView.loadDataWithBaseURL(baseUrl, " ", "text/html", null, null)
|
||||
}
|
||||
}
|
||||
suspendCoroutine { cont ->
|
||||
webView.webViewClient = ContinuationResumeWebViewClient(cont)
|
||||
webView.loadDataWithBaseURL(baseUrl, " ", "text/html", null, null)
|
||||
}
|
||||
}
|
||||
suspendCoroutine { cont ->
|
||||
webView.evaluateJavascript(script) { result ->
|
||||
cont.resume(result?.takeUnless { it == "null" })
|
||||
webView.evaluateJavascript(script) { result ->
|
||||
cont.resume(result?.takeUnless { it == "null" })
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
webView.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
fun getDefaultUserAgent() = runCatching {
|
||||
obtainWebView().settings.userAgentString.sanitizeHeaderValue().trim().nullIfEmpty()
|
||||
}.onFailure { e ->
|
||||
e.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}.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
|
||||
private fun obtainWebView(): WebView = webViewCached?.get() ?: WebView(context).also {
|
||||
it.configureForParser(null)
|
||||
webViewCached = WeakReference(it)
|
||||
private fun WebView.reset() {
|
||||
stopLoading()
|
||||
webViewClient = WebViewClient()
|
||||
settings.userAgentString = defaultUserAgent
|
||||
loadDataWithBaseURL(null, " ", "text/html", null, null)
|
||||
clearHistory()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +80,7 @@ class NetworkState(
|
||||
if (settings.isOfflineCheckDisabled) {
|
||||
return true
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activeNetwork?.let { isOnline(it) } == true
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
activeNetworkInfo?.isConnected == true
|
||||
}
|
||||
return activeNetwork?.let { isOnline(it) } == true
|
||||
}
|
||||
|
||||
private fun ConnectivityManager.isOnline(network: Network): Boolean {
|
||||
|
||||
@@ -9,6 +9,8 @@ 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
|
||||
@@ -189,6 +191,11 @@ 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,8 +5,6 @@ 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
|
||||
@@ -33,7 +31,6 @@ 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(
|
||||
@@ -43,7 +40,6 @@ 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")
|
||||
@@ -54,7 +50,7 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
webViewExecutor.evaluateJs(baseUrl, script)
|
||||
}
|
||||
|
||||
override fun getDefaultUserAgent(): String = webViewUserAgent
|
||||
override fun getDefaultUserAgent(): String = webViewExecutor.defaultUserAgent ?: UserAgents.FIREFOX_MOBILE
|
||||
|
||||
override fun getConfig(source: MangaSource): MangaSourceConfig {
|
||||
return SourceSettings(androidContext, source)
|
||||
@@ -91,15 +87,4 @@ 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,6 +19,7 @@ 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
|
||||
@@ -41,7 +42,6 @@ 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()) {
|
||||
coroutineContext.ensureActive()
|
||||
currentCoroutineContext().ensureActive()
|
||||
val icon = favicons.find(sizePx) ?: throwNSEE(lastError)
|
||||
try {
|
||||
val result = imageLoader.fetch(icon.url, options)
|
||||
|
||||
@@ -138,6 +138,11 @@ 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
|
||||
@@ -488,6 +493,10 @@ 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
|
||||
@@ -669,6 +678,7 @@ 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"
|
||||
@@ -748,6 +758,7 @@ 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"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
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.ignoreCaptchaErrors
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.CaptchaHandler.Companion.suppressCaptchaErrors
|
||||
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)
|
||||
.ignoreCaptchaErrors()
|
||||
.suppressCaptchaErrors()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@ 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, OnContextClickListenerCompat {
|
||||
) : OnClickListener, OnLongClickListener, OnContextClickListener {
|
||||
|
||||
override fun onClick(v: View) {
|
||||
clickListener.onItemClick(mappedItem(), v)
|
||||
@@ -33,7 +32,7 @@ class AdapterDelegateClickListenerAdapter<I, O>(
|
||||
fun attach(itemView: View) {
|
||||
itemView.setOnClickListener(this)
|
||||
itemView.setOnLongClickListener(this)
|
||||
itemView.setOnContextClickListenerCompat(this)
|
||||
itemView.setOnContextClickListener(this)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -14,7 +13,6 @@ 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
|
||||
|
||||
@@ -37,14 +35,10 @@ class ActionModeDelegate : OnBackPressedCallback(false) {
|
||||
listeners?.forEach { it.onActionModeStarted(mode) }
|
||||
if (window != null) {
|
||||
val ctx = window.context
|
||||
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)
|
||||
}
|
||||
val actionModeColor = ColorUtils.compositeColors(
|
||||
ContextCompat.getColor(ctx, materialR.color.m3_appbar_overlay_color),
|
||||
ctx.getThemeColor(materialR.attr.colorSurface),
|
||||
)
|
||||
defaultStatusBarColor = window.statusBarColor
|
||||
window.statusBarColor = actionModeColor
|
||||
val insets = ViewCompat.getRootWindowInsets(window.decorView)
|
||||
|
||||
@@ -4,12 +4,10 @@ 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, OnContextClickListenerCompat, PopupMenu.OnMenuItemClickListener,
|
||||
) : View.OnLongClickListener, View.OnContextClickListener, PopupMenu.OnMenuItemClickListener,
|
||||
PopupMenu.OnDismissListener {
|
||||
|
||||
override fun onContextClick(v: View): Boolean = onLongClick(v)
|
||||
@@ -37,6 +35,6 @@ class PopupMenuMediator(
|
||||
|
||||
fun attach(view: View) {
|
||||
view.setOnLongClickListener(this)
|
||||
view.setOnContextClickListenerCompat(this)
|
||||
view.setOnContextClickListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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,7 +28,6 @@ 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 {
|
||||
@@ -169,12 +168,6 @@ 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,13 +1,11 @@
|
||||
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,12 +34,17 @@ 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 = repo.getPages(chapter).size
|
||||
val pagesCount = chapterRepo.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 - checkNotNull(history).percent)).roundToInt()
|
||||
averageTimeSec = (averageTimeSec * (1f - history.percent)).roundToInt()
|
||||
}
|
||||
if (averageTimeSec < 60) {
|
||||
return null
|
||||
|
||||
@@ -2,7 +2,6 @@ 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
|
||||
@@ -209,9 +208,7 @@ class DetailsActivity :
|
||||
|
||||
override fun onProvideAssistContent(outContent: AssistContent) {
|
||||
super.onProvideAssistContent(outContent)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
|
||||
}
|
||||
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
|
||||
}
|
||||
|
||||
override fun isNsfwContent(): Flow<Boolean> = viewModel.manga.map { it?.contentRating == ContentRating.ADULT }
|
||||
|
||||
@@ -140,6 +140,7 @@ 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,7 +105,14 @@ class PagesViewModel @Inject constructor(
|
||||
chaptersLoader.peekChapter(it) != null
|
||||
} ?: state.details.allChapters.firstOrNull()?.id ?: return
|
||||
if (!chaptersLoader.hasPages(initialChapterId)) {
|
||||
chaptersLoader.loadSingleChapter(initialChapterId)
|
||||
var hasPages = chaptersLoader.loadSingleChapter(initialChapterId)
|
||||
while (!hasPages) {
|
||||
if (chaptersLoader.loadPrevNextChapter(state.details, initialChapterId, isNext = true)) {
|
||||
hasPages = chaptersLoader.snapshot().isNotEmpty()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
updateList(state.readerState)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
@@ -25,6 +26,8 @@ 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
|
||||
|
||||
@@ -35,7 +38,8 @@ class RelatedListViewModel @Inject constructor(
|
||||
settings: AppSettings,
|
||||
private val mangaListMapper: MangaListMapper,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings, mangaDataRepository) {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {
|
||||
|
||||
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()) repo.getDetails(manga) else manga
|
||||
val mangaDetails = if (manga.chapters.isNullOrEmpty() || manga.description.isNullOrEmpty()) repo.getDetails(manga) else manga
|
||||
output = LocalMangaOutput.getOrCreate(
|
||||
root = destination,
|
||||
manga = mangaDetails,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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,6 +40,9 @@ 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
|
||||
|
||||
@@ -52,7 +55,8 @@ class FavouritesListViewModel @Inject constructor(
|
||||
quickFilterFactory: FavoritesListQuickFilter.Factory,
|
||||
settings: AppSettings,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener {
|
||||
|
||||
val categoryId: Long = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID
|
||||
private val quickFilter = quickFilterFactory.create(categoryId)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.filter.ui.sheet
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -51,9 +50,7 @@ class FilterSheetFragment : BaseAdaptiveSheet<SheetFilterBinding>(),
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
if (dialog == null) {
|
||||
binding.layoutBody.updatePadding(top = binding.layoutBody.paddingBottom)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
binding.scrollView.scrollIndicators = 0
|
||||
}
|
||||
binding.scrollView.scrollIndicators = 0
|
||||
}
|
||||
val filter = FilterCoordinator.require(this)
|
||||
filter.sortOrder.observe(viewLifecycleOwner, this::onSortOrderChanged)
|
||||
|
||||
@@ -43,6 +43,9 @@ 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
|
||||
|
||||
@@ -54,7 +57,8 @@ class HistoryListViewModel @Inject constructor(
|
||||
private val markAsReadUseCase: MarkAsReadUseCase,
|
||||
private val quickFilter: HistoryListQuickFilter,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
|
||||
|
||||
private val sortOrder: StateFlow<ListSortOrder> = settings.observeAsStateFlow(
|
||||
scope = viewModelScope + Dispatchers.IO,
|
||||
|
||||
@@ -2,14 +2,12 @@ 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
|
||||
@@ -83,9 +81,7 @@ class CoverImageView @JvmOverloads constructor(
|
||||
if (fallbackDrawable == null) {
|
||||
fallbackDrawable = context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable()
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
addImageRequestListener(ErrorForegroundListener())
|
||||
}
|
||||
addImageRequestListener(ErrorForegroundListener())
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
@@ -169,7 +165,6 @@ class CoverImageView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private inner class ErrorForegroundListener : ImageRequest.Listener {
|
||||
|
||||
override fun onSuccess(request: ImageRequest, result: SuccessResult) {
|
||||
@@ -208,6 +203,7 @@ class CoverImageView @JvmOverloads constructor(
|
||||
is HttpStatusException -> statusCode.toString()
|
||||
is ContentUnavailableException,
|
||||
is FileNotFoundException -> "404"
|
||||
|
||||
is TooManyRequestExceptions -> "429"
|
||||
is ParseException -> "</>"
|
||||
is UnsupportedSourceException -> "X"
|
||||
@@ -269,7 +265,7 @@ class CoverImageView @JvmOverloads constructor(
|
||||
width = Dimension(height.px * view.aspectRationWidth / view.aspectRationHeight)
|
||||
}
|
||||
}
|
||||
return Size(checkNotNull(width), checkNotNull(height))
|
||||
return Size(width, height)
|
||||
}
|
||||
|
||||
private fun getWidth() = getDimension(
|
||||
|
||||
@@ -3,10 +3,12 @@ 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
|
||||
@@ -22,10 +24,13 @@ 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>>
|
||||
@@ -63,7 +68,11 @@ abstract class MangaListViewModel(
|
||||
|
||||
protected fun observeListModeWithTriggers(): Flow<ListMode> = combine(
|
||||
listMode,
|
||||
mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
|
||||
merge(
|
||||
mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
|
||||
mangaDataRepository.observeFavoritesTrigger(emitInitialState = true),
|
||||
localStorageChanges.onStart { emit(null) },
|
||||
),
|
||||
settings.observeChanges().filter { key ->
|
||||
key == AppSettings.KEY_PROGRESS_INDICATORS
|
||||
|| key == AppSettings.KEY_TRACKER_ENABLED
|
||||
|
||||
@@ -27,7 +27,6 @@ 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,7 +61,9 @@ 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 }
|
||||
val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it }?.takeIf {
|
||||
fileSystem.exists(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,
|
||||
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
||||
@param:LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
||||
private val localStorageManager: LocalStorageManager,
|
||||
sourcesRepository: MangaSourcesRepository,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
@@ -58,6 +58,7 @@ 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) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || !resources.getBoolean(R.bool.is_predictive_back_enabled)) {
|
||||
val legacySearchCallback = SearchViewLegacyBackCallback(viewBinding.searchView)
|
||||
viewBinding.searchView.addTransitionListener(legacySearchCallback)
|
||||
onBackPressedDispatcher.addCallback(legacySearchCallback)
|
||||
|
||||
@@ -20,6 +20,9 @@ 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(
|
||||
@@ -28,7 +31,8 @@ class MangaPickerViewModel @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val mangaListMapper: MangaListMapper,
|
||||
) : MangaListViewModel(settings, mangaDataRepository) {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges) {
|
||||
|
||||
override val content: StateFlow<List<ListModel>>
|
||||
get() = flow {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
@@ -32,12 +33,12 @@ class ChaptersLoader @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean) {
|
||||
suspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean): 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
|
||||
val newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return
|
||||
if (index == -1) return false
|
||||
val newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return false
|
||||
val newPages = loadChapter(newChapter.id)
|
||||
mutex.withLock {
|
||||
if (chapterPages.chaptersSize > 1) {
|
||||
@@ -56,13 +57,16 @@ class ChaptersLoader @Inject constructor(
|
||||
chapterPages.addFirst(newChapter.id, newPages)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun loadSingleChapter(chapterId: Long) {
|
||||
@CheckResult
|
||||
suspend fun loadSingleChapter(chapterId: Long): Boolean {
|
||||
val pages = loadChapter(chapterId)
|
||||
mutex.withLock {
|
||||
return mutex.withLock {
|
||||
chapterPages.clear()
|
||||
chapterPages.addLast(chapterId, pages)
|
||||
pages.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,9 @@ 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
|
||||
@@ -23,7 +21,6 @@ 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
|
||||
@@ -46,19 +43,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
|
||||
@@ -91,10 +88,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
|
||||
@@ -103,16 +100,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
|
||||
@@ -141,10 +138,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
|
||||
@@ -153,16 +150,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
|
||||
@@ -218,4 +215,4 @@ class EdgeDetector(private val context: Context) {
|
||||
|
||||
private fun region(x: Int, y: Int) = Rect(x, y, x + BLOCK_SIZE, y + BLOCK_SIZE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
@@ -215,9 +214,7 @@ class ReaderActivity :
|
||||
|
||||
override fun onProvideAssistContent(outContent: AssistContent) {
|
||||
super.onProvideAssistContent(outContent)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
|
||||
}
|
||||
viewModel.getMangaOrNull()?.publicUrl?.toUriOrNull()?.let { outContent.webUri = it }
|
||||
}
|
||||
|
||||
override fun isNsfwContent(): Flow<Boolean> = viewModel.isMangaNsfw
|
||||
@@ -374,6 +371,7 @@ class ReaderActivity :
|
||||
viewBinding.infoBar.isTimeVisible = isFullscreen
|
||||
updateScrollTimerButton()
|
||||
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
|
||||
viewBinding.root.requestApplyInsets()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,8 +393,14 @@ 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(), Insets.NONE)
|
||||
.setInsets(WindowInsetsCompat.Type.systemBars(), innerInsets)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -157,6 +157,12 @@ 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()
|
||||
@@ -345,11 +351,14 @@ class ReaderViewModel @Inject constructor(
|
||||
return@launchJob
|
||||
}
|
||||
ensureActive()
|
||||
if (upperPos >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.last().chapterId, isNext = true)
|
||||
}
|
||||
if (lowerPos <= BOUNDS_PAGE_OFFSET) {
|
||||
loadPrevNextChapter(pages.first().chapterId, isNext = false)
|
||||
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 (pageLoader.isPrefetchApplicable()) {
|
||||
pageLoader.prefetch(pages.trySublist(upperPos + 1, upperPos + PREFETCH_LIMIT))
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
@@ -25,7 +26,9 @@ 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
|
||||
@@ -37,7 +40,8 @@ class ReaderConfigSheet :
|
||||
BaseAdaptiveSheet<SheetReaderConfigBinding>(),
|
||||
View.OnClickListener,
|
||||
MaterialButtonToggleGroup.OnButtonCheckedListener,
|
||||
CompoundButton.OnCheckedChangeListener {
|
||||
CompoundButton.OnCheckedChangeListener,
|
||||
Slider.OnChangeListener {
|
||||
|
||||
private val viewModel by activityViewModels<ReaderViewModel>()
|
||||
|
||||
@@ -86,6 +90,13 @@ 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)
|
||||
@@ -96,6 +107,8 @@ 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)
|
||||
@@ -170,11 +183,21 @@ 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,
|
||||
@@ -191,6 +214,7 @@ class ReaderConfigSheet :
|
||||
else -> return
|
||||
}
|
||||
viewBinding?.switchDoubleReader?.isEnabled = newMode == ReaderMode.STANDARD || newMode == ReaderMode.REVERSED
|
||||
viewBinding?.switchPullGesture?.isEnabled = newMode == ReaderMode.WEBTOON
|
||||
if (newMode == mode) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -11,11 +11,14 @@ 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 : SnapHelper() {
|
||||
class DoublePageSnapHelper(private val settings: AppSettings) : SnapHelper() {
|
||||
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
|
||||
@@ -248,28 +251,27 @@ class DoublePageSnapHelper : SnapHelper() {
|
||||
equal to zero.
|
||||
*/
|
||||
fun getPositionsToMove(llm: LinearLayoutManager, scroll: Int, itemSize: Int): Int {
|
||||
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
|
||||
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
|
||||
}
|
||||
if (scroll < 0) {
|
||||
positionsToMove *= -1
|
||||
|
||||
// Apply a minimum threshold
|
||||
if (positionsToMove == 0 && scroll.absoluteValue > itemSize * 0.2) {
|
||||
positionsToMove = 1 * scroll.sign
|
||||
}
|
||||
if (isRTL) {
|
||||
positionsToMove *= -1
|
||||
}
|
||||
return if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
|
||||
// Scrolling toward the bottom of data.
|
||||
roundDownToBlockSize(llm.findFirstVisibleItemPosition()) + positionsToMove
|
||||
|
||||
val currentPosition = if (layoutDirectionHelper.isDirectionToBottom(scroll < 0)) {
|
||||
llm.findFirstVisibleItemPosition()
|
||||
} else {
|
||||
roundDownToBlockSize(llm.findLastVisibleItemPosition()) + positionsToMove
|
||||
llm.findLastVisibleItemPosition()
|
||||
}
|
||||
// Scrolling toward the top of the data.
|
||||
val targetPos = currentPosition + positionsToMove * 2
|
||||
return roundDownToBlockSize(targetPos)
|
||||
}
|
||||
|
||||
fun isDirectionToBottom(velocityNegative: Boolean): Boolean {
|
||||
|
||||
@@ -13,6 +13,7 @@ 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
|
||||
@@ -33,6 +34,9 @@ open class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding
|
||||
@Inject
|
||||
lateinit var pageLoader: PageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private var recyclerLifecycleDispatcher: RecyclerViewLifecycleDispatcher? = null
|
||||
|
||||
override fun onCreateViewBinding(
|
||||
@@ -51,7 +55,7 @@ open class DoubleReaderFragment : BaseReaderFragment<FragmentReaderDoubleBinding
|
||||
addOnScrollListener(it)
|
||||
}
|
||||
addOnScrollListener(PageScrollListener())
|
||||
DoublePageSnapHelper().attachToRecyclerView(this)
|
||||
DoublePageSnapHelper(settings).attachToRecyclerView(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ class WebtoonImageView @JvmOverloads constructor(
|
||||
fun scrollTo(y: Int) {
|
||||
val maxScroll = getScrollRange()
|
||||
if (maxScroll == 0) {
|
||||
resetScaleAndCenter()
|
||||
scrollToInternal(0)
|
||||
return
|
||||
}
|
||||
scrollToInternal(y.coerceIn(0, maxScroll))
|
||||
|
||||
@@ -2,8 +2,13 @@ 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
|
||||
@@ -27,7 +32,8 @@ import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>(),
|
||||
WebtoonRecyclerView.OnWebtoonScrollListener {
|
||||
WebtoonRecyclerView.OnWebtoonScrollListener,
|
||||
WebtoonRecyclerView.OnPullGestureListener {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
@@ -38,6 +44,8 @@ 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,
|
||||
@@ -53,6 +61,7 @@ class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>()
|
||||
recyclerLifecycleDispatcher = RecyclerViewLifecycleDispatcher().also {
|
||||
addOnScrollListener(it)
|
||||
}
|
||||
setOnPullGestureListener(this@WebtoonReaderFragment)
|
||||
}
|
||||
viewModel.isWebtoonZooEnabled.observe(viewLifecycleOwner) {
|
||||
binding.frame.isZoomEnable = it
|
||||
@@ -70,6 +79,18 @@ 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() {
|
||||
@@ -78,6 +99,19 @@ 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,
|
||||
@@ -168,6 +202,47 @@ 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)
|
||||
@@ -177,4 +252,25 @@ 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,8 +1,10 @@
|
||||
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
|
||||
@@ -10,6 +12,8 @@ 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
|
||||
@@ -23,6 +27,26 @@ 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)
|
||||
@@ -179,6 +203,68 @@ 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(
|
||||
@@ -188,4 +274,12 @@ class WebtoonRecyclerView @JvmOverloads constructor(
|
||||
lastVisiblePosition: Int,
|
||||
)
|
||||
}
|
||||
|
||||
interface OnPullGestureListener {
|
||||
fun onPullProgressTop(progress: Float)
|
||||
fun onPullProgressBottom(progress: Float)
|
||||
fun onPullTriggeredTop()
|
||||
fun onPullTriggeredBottom()
|
||||
fun onPullCancelled()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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
|
||||
@@ -40,6 +41,8 @@ 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
|
||||
@@ -55,8 +58,9 @@ open class RemoteListViewModel @Inject constructor(
|
||||
protected val mangaListMapper: MangaListMapper,
|
||||
private val exploreRepository: ExploreRepository,
|
||||
sourcesRepository: MangaSourcesRepository,
|
||||
mangaDataRepository: MangaDataRepository
|
||||
) : MangaListViewModel(settings, mangaDataRepository), FilterCoordinator.Owner {
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), FilterCoordinator.Owner {
|
||||
|
||||
val source = MangaSource(savedStateHandle[RemoteListFragment.ARG_SOURCE])
|
||||
val isRandomLoading = MutableStateFlow(false)
|
||||
|
||||
@@ -7,7 +7,6 @@ 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
|
||||
@@ -47,7 +46,7 @@ class ScrobblerConfigViewModel @Inject constructor(
|
||||
val content = scrobbler.observeAllScrobblingInfo()
|
||||
.onStart { loadingCounter.increment() }
|
||||
.onFirst { loadingCounter.decrement() }
|
||||
.catch { errorEvent.call(it) }
|
||||
.withErrorHandling()
|
||||
.map { buildContentList(it) }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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
|
||||
@@ -14,6 +15,7 @@ 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
|
||||
@@ -35,6 +37,7 @@ 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(
|
||||
@@ -49,6 +52,7 @@ 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
|
||||
|
||||
@@ -68,6 +72,7 @@ class DiscordRpc @Inject constructor(
|
||||
fun clearRpc() = synchronized(this) {
|
||||
rpc?.closeRPC()
|
||||
rpc = null
|
||||
lastUpdate = 0L
|
||||
}
|
||||
|
||||
fun setIdle() {
|
||||
@@ -114,6 +119,10 @@ 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 {
|
||||
@@ -131,6 +140,7 @@ 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,6 +1,5 @@
|
||||
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,6 +8,7 @@ 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
|
||||
@@ -18,7 +19,6 @@ 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 (coroutineContext.isActive) {
|
||||
while (currentCoroutineContext().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.Companion.KEY_DISCORD_TOKEN)?.let { pref ->
|
||||
findPreference<EditTextPreference>(AppSettings.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.Companion.KEY_DISCORD_TOKEN) {
|
||||
if (parentFragmentManager.findFragmentByTag(TokenDialogFragment.Companion.DIALOG_FRAGMENT_TAG) != null) {
|
||||
if (preference is EditTextPreference && preference.key == AppSettings.KEY_DISCORD_TOKEN) {
|
||||
if (parentFragmentManager.findFragmentByTag(TokenDialogFragment.DIALOG_FRAGMENT_TAG) != null) {
|
||||
return
|
||||
}
|
||||
val f = TokenDialogFragment.newInstance(preference.key)
|
||||
@Suppress("DEPRECATION")
|
||||
f.setTargetFragment(this, 0)
|
||||
f.show(parentFragmentManager, TokenDialogFragment.Companion.DIALOG_FRAGMENT_TAG)
|
||||
f.show(parentFragmentManager, TokenDialogFragment.DIALOG_FRAGMENT_TAG)
|
||||
return
|
||||
}
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
|
||||
private fun bindTokenPreference(state: TokenState, token: String?) {
|
||||
val pref = findPreference<EditTextPreference>(AppSettings.Companion.KEY_DISCORD_TOKEN) ?: return
|
||||
val pref = findPreference<EditTextPreference>(AppSettings.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 suspend fun checkToken(): Flow<Pair<TokenState, String?>> = flow {
|
||||
private fun checkToken(): Flow<Pair<TokenState, String?>> = flow {
|
||||
val token = settings.discordToken
|
||||
if (!settings.isDiscordRpcEnabled) {
|
||||
emit(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -115,7 +114,6 @@ class ProtectSetupActivity :
|
||||
}
|
||||
|
||||
private fun isBiometricAvailable(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
|
||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -33,10 +32,6 @@ 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
|
||||
@@ -58,9 +53,6 @@ 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,6 +24,9 @@ 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(
|
||||
@@ -33,7 +36,8 @@ class SuggestionsViewModel @Inject constructor(
|
||||
private val quickFilter: SuggestionsListQuickFilter,
|
||||
private val suggestionsScheduler: SuggestionsWorker.Scheduler,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
|
||||
|
||||
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_SUGGESTIONS) { suggestionsListMode }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.suggestionsListMode)
|
||||
|
||||
@@ -46,7 +46,6 @@ 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>?,
|
||||
@SerialName("categories") val categories: List<FavouriteCategorySyncDto>?,
|
||||
@SerialName("favourites") val favourites: List<FavouriteSyncDto>?,
|
||||
@SerialName("history") val history: List<HistorySyncDto>? = null,
|
||||
@SerialName("categories") val categories: List<FavouriteCategorySyncDto>? = null,
|
||||
@SerialName("favourites") val favourites: List<FavouriteSyncDto>? = null,
|
||||
@SerialName("timestamp") val timestamp: Long,
|
||||
)
|
||||
|
||||
@@ -22,7 +22,6 @@ 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
|
||||
@@ -89,12 +88,12 @@ class SyncHelper @AssistedInject constructor(
|
||||
val response = httpClient.newCall(request).execute().parseDtoOrNull()
|
||||
response?.categories?.let { categories ->
|
||||
val categoriesResult = upsertFavouriteCategories(categories)
|
||||
stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L
|
||||
stats.numDeletes += categoriesResult.firstOrNull()?.count?.toLong() ?: 0L
|
||||
stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||
}
|
||||
response?.favourites?.let { favourites ->
|
||||
val favouritesResult = upsertFavourites(favourites)
|
||||
stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L
|
||||
stats.numDeletes += favouritesResult.firstOrNull()?.count?.toLong() ?: 0L
|
||||
stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||
stats.numEntries += stats.numInserts + stats.numDeletes
|
||||
}
|
||||
@@ -119,7 +118,7 @@ class SyncHelper @AssistedInject constructor(
|
||||
val response = httpClient.newCall(request).execute().parseDtoOrNull()
|
||||
response?.history?.let { history ->
|
||||
val result = upsertHistory(history)
|
||||
stats.numDeletes += result.first().count?.toLong() ?: 0L
|
||||
stats.numDeletes += result.firstOrNull()?.count?.toLong() ?: 0L
|
||||
stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L }
|
||||
stats.numEntries += stats.numInserts + stats.numDeletes
|
||||
}
|
||||
@@ -286,15 +285,11 @@ class SyncHelper @AssistedInject constructor(
|
||||
|
||||
private fun uri(authority: String, table: String) = "content://$authority/$table".toUri()
|
||||
|
||||
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()
|
||||
private fun Response.parseDtoOrNull(): SyncDto? = use {
|
||||
when {
|
||||
!isSuccessful -> throw IOException(body.string())
|
||||
code == HttpURLConnection.HTTP_NO_CONTENT -> null
|
||||
else -> Json.decodeFromString<SyncDto>(body.string())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ 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(
|
||||
@@ -39,7 +42,8 @@ class UpdatesViewModel @Inject constructor(
|
||||
private val mangaListMapper: MangaListMapper,
|
||||
private val quickFilter: UpdatesListQuickFilter,
|
||||
mangaDataRepository: MangaDataRepository,
|
||||
) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
|
||||
@LocalStorageChanges localStorageChanges: SharedFlow<LocalManga?>,
|
||||
) : MangaListViewModel(settings, mangaDataRepository, localStorageChanges), QuickFilterListener by quickFilter {
|
||||
|
||||
override val content = combine(
|
||||
quickFilter.appliedOptions.flatMapLatest { filterOptions ->
|
||||
|
||||
@@ -24,7 +24,6 @@ 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
|
||||
|
||||
11
app/src/main/res/drawable/ic_gesture_vertical.xml
Normal file
11
app/src/main/res/drawable/ic_gesture_vertical.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<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,7 +32,9 @@
|
||||
style="?materialButtonTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/open_in_browser" />
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<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"
|
||||
@@ -16,4 +17,36 @@
|
||||
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,6 +129,40 @@
|
||||
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.95"
|
||||
android:valueTo="0.97"
|
||||
app:labelBehavior="floating"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/label_timer"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?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">
|
||||
|
||||
@@ -9,7 +10,8 @@
|
||||
android:orderInCategory="10"
|
||||
android:title="@string/search_chapters"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="ifRoom|collapseActionView" />
|
||||
app:showAsAction="always|collapseActionView"
|
||||
tools:ignore="AlwaysShowAction" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_downloaded"
|
||||
|
||||
3
app/src/main/res/values-arz/plurals.xml
Normal file
3
app/src/main/res/values-arz/plurals.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
3
app/src/main/res/values-arz/strings.xml
Normal file
3
app/src/main/res/values-arz/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -859,4 +859,9 @@
|
||||
<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,4 +784,84 @@
|
||||
<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,4 +853,10 @@
|
||||
<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,4 +857,11 @@
|
||||
<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>
|
||||
|
||||
19
app/src/main/res/values-got/plurals.xml
Normal file
19
app/src/main/res/values-got/plurals.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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>
|
||||
11
app/src/main/res/values-got/strings.xml
Normal file
11
app/src/main/res/values-got/strings.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?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 stavkih</item>
|
||||
<item quantity="other">%1$d stavki</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 minuta</item>
|
||||
<item quantity="other">prije %1$d minuta</item>
|
||||
<item quantity="one">Prije %1$d minute</item>
|
||||
<item quantity="few">Prije %1$d minute</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 pretraživanja prilikom pomicanja</string>
|
||||
<string name="pin_navigation_ui_summary">Nemoj skrivati navigacijsku traku i prikaz pretrage prilikom listanja</string>
|
||||
<string name="suggested_queries">Predloženi upiti</string>
|
||||
<string name="last_used">Zadnje korišteno</string>
|
||||
<string name="webtoon_gaps">Praznine u webtoon modu</string>
|
||||
<string name="webtoon_gaps">Razmaci u webtoon modusu</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šajte ponovno</string>
|
||||
<string name="try_again">Pokušaj ponovo</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">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="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="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">Način čitanja</string>
|
||||
<string name="read_mode">Modus č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">Način skaliranja</string>
|
||||
<string name="scale_mode">Modus 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 bit će zapamćena za ovu mangu</string>
|
||||
<string name="reader_mode_hint">Odabrana konfiguracija će se zapamtiti za ovaj manga</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">Automatsko otkrivanje načina čitanja</string>
|
||||
<string name="detect_reader_mode">Automatski otkrij modus č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">Planirani</string>
|
||||
<string name="status_planned">Planirano</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">Podijelite zapise</string>
|
||||
<string name="share_logs">Dijeli dnevnike</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 način rada u boji</string>
|
||||
<string name="enhanced_colors">32-bitni modus boje</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 neće biti spremljen</string>
|
||||
<string name="incognito_mode_hint">Vaš napredak u čitanju se neće spremiti</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">Cijeli zaslon</string>
|
||||
<string name="reader_fullscreen_summary">Sakrij status sustava i navigacijske trake</string>
|
||||
<string name="fullscreen_mode">Cjeloekranski modus</string>
|
||||
<string name="reader_fullscreen_summary">Sakrij stanje 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">Najnoviji</string>
|
||||
<string name="newest">Najnovije</string>
|
||||
<string name="sort_order">Redoslijed sortiranja</string>
|
||||
<string name="filter">Filter</string>
|
||||
<string name="follow_system">Slijedite sustav</string>
|
||||
<string name="follow_system">Slijedi 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 način rada</string>
|
||||
<string name="default_mode">Zadani modus</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 uklonjen</string>
|
||||
<string name="not_found_404">Sadržaj nije pronađen ili je 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 način rada</string>
|
||||
<string name="incognito_mode">Anonimni modus</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 pomicanje</string>
|
||||
<string name="automatic_scroll">Automatsko listanje</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 načinu webtoona</string>
|
||||
<string name="webtoon_zoom_summary">Dopusti gestu zumiranja u modusu 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šajte ponovno</string>
|
||||
<string name="retry">Pokušaj ponovo</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,4 +802,63 @@
|
||||
<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 bab baru</item>
|
||||
<item quantity="other">%1$d Sub-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">Domain</string>
|
||||
<string name="domain">Ranah web</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">Bab baru</string>
|
||||
<string name="new_chapters">Sub-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">Gulir otomatis</string>
|
||||
<string name="automatic_scroll">Pengguliran 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 baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
|
||||
<string name="history_shortcuts_summary">Buat manga terbaru tersedia dengan menekan lama ikon aplikasi</string>
|
||||
<string name="select_range">Pilih rentang</string>
|
||||
<string name="disable_all">Matikan semua</string>
|
||||
<string name="dns_over_https">DNS over HTTPS</string>
|
||||
<string name="dns_over_https">DNS melalui HTTPS</string>
|
||||
<string name="status_dropped">Didrop</string>
|
||||
<string name="theme_name_mamimi">Mamimi</string>
|
||||
<string name="server_error">Galat sisi server (%1$d). Silakan coba lagi nanti</string>
|
||||
<string name="server_error">Kesalahan di 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 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="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="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">Zum Webtoon</string>
|
||||
<string name="webtoon_zoom">Pembesaran 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 and sembunyikan manga dewasa dari daftar jika memungkinkan</string>
|
||||
<string name="disable_nsfw_summary">Nonaktifkan sumber TAUSB dan 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">Manhua</string>
|
||||
<string name="content_type_manhua">Komik Mandarin (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">Manhwa</string>
|
||||
<string name="content_type_manhwa">Komik Korea (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">Bahasa</string>
|
||||
<string name="added_long_ago">Ditambahkan sejak lama</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">Indonesian</string>
|
||||
<string name="pick_custom_file">Indonesian</string>
|
||||
<string name="change_cover">Indonesian</string>
|
||||
<string name="theme_name_expressive">Indonesian</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="page_switch_timer">Halaman akan berganti setiap ~%d detik</string>
|
||||
<string name="expand">Memperluas</string>
|
||||
<string name="adblock">Blokir iklan di browser</string>
|
||||
@@ -846,4 +846,23 @@
|
||||
<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,4 +859,11 @@
|
||||
<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,4 +474,197 @@
|
||||
<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>
|
||||
|
||||
30
app/src/main/res/values-jv/plurals.xml
Normal file
30
app/src/main/res/values-jv/plurals.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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>
|
||||
68
app/src/main/res/values-jv/strings.xml
Normal file
68
app/src/main/res/values-jv/strings.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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">Popular</string>
|
||||
<string name="popular">Populer</string>
|
||||
<string name="updated">Dikemaskini</string>
|
||||
<string name="newest">Terbaharu</string>
|
||||
<string name="by_rating">Rating</string>
|
||||
<string name="by_rating">Peringkat</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">Disimpan</string>
|
||||
<string name="page_saved">Halaman 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,4 +302,562 @@
|
||||
<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:<br><tt>%1$s</tt><br><br>1. Cuba <a href=%2$s>buka manga dalam pelayar web</a> untuk memastikan ia tersedia pada sumbernya<br>2. Pastikan anda menggunakan <a href=kotatsu://about>versi terbaharu Kotatsu</a><br>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,4 +832,12 @@
|
||||
<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">Детаљи о грешци:<br><tt>%1$s</tt><br><br>1. Покушај да <a href=%2$s>отвориш мангу у веб прегледачу</a> да би био сигуран да је доступна на извору<br>2. Увери се да користите <a href=kotatsu://about>latest version of Kotatsu>најновију верзију Котатсу-а</a><br>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,4 +837,33 @@
|
||||
<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,4 +792,70 @@
|
||||
<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
Reference in New Issue
Block a user