Compare commits

...

71 Commits

Author SHA1 Message Date
Koitharu 861c21faea
Merge pull request #1673 from EmilieHascoet/Feat/auto-scroll-speed 6 months ago
Emilie Hascoët 9b4d014b21
Change valueTo from 0.95 to 0.97 in XML layout 6 months ago
Koitharu c6da7de699
Fix backup rules 7 months ago
Koitharu ef3aa40acc
Fix some warnings 7 months ago
Koitharu 07af3ea703
Backup-restore fixes 7 months ago
Koitharu 391c8ab649
Fix sync issues 7 months ago
Koitharu 6b1885c89d Revert "Fix screen rotation causing reader to jump back to initial page"
This reverts commit aeb3732d75.
7 months ago
Koitharu 8423b48fb9 Revert "Fix page position loss when switching reader modes"
This reverts commit 5f879f6c83.
7 months ago
Koitharu 803c825d91
Fix ProgressUpdateUseCase 7 months ago
Koitharu 6a9682a077
Refactor reader sensitivity settings #1576 7 months ago
Koitharu 9197b9cc3a
Merge branch 'devel' of github.com:puargs/Kotatsu into devel 7 months ago
google-labs-jules[bot] 02ea804874 Fix(reader): Fix incorrect scaling for short images in webtoon reader
When an image in the webtoon reader is shorter than the screen height, it was being incorrectly scaled, causing it to appear zoomed in or cropped.

This was caused by the `scrollTo` function in `WebtoonImageView.kt` calling `resetScaleAndCenter()` for images with a scroll range of zero. This method, from the underlying SubsamplingScaleImageView library, resets the image to a default scale instead of using the custom logic required by the reader.

The fix replaces the call to `resetScaleAndCenter()` with `scrollToInternal(0)`. This ensures that the custom scaling logic, which fits the image to the screen width, is applied consistently to all images, regardless of their height.
7 months ago
Koitharu c424466198
Debounce Discord RPC 7 months ago
Quentinho199 18b312dde6 "fix/UI bug about finding chapter" 7 months ago
Koitharu f78262b1a0
Udpate dependencies 7 months ago
Koitharu c557a51c4d
Fix loading local manga in some corner cases 7 months ago
kota fujimi 8995762935 Translated using Weblate (Japanese)
Currently translated at 76.0% (665 of 875 strings)

Co-authored-by: kota fujimi <urakids@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
7 months ago
Milo Ivir ed2664db78 Translated using Weblate (Croatian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
Infy's Tagalog Translations f5a5e53b5a Translated using Weblate (Filipino)
Currently translated at 99.0% (867 of 875 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
7 months ago
MuhamadSyabitHidayattulloh 9ef961590d Translated using Weblate (Indonesian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
Jinzhou Huang 9b569615ee Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 75.4% (660 of 875 strings)

Co-authored-by: Jinzhou Huang <2314662431@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
7 months ago
Antonio Sanchez Castellón f48cf2efe4 Translated using Weblate (Spanish)
Currently translated at 98.5% (862 of 875 strings)

Co-authored-by: Antonio Sanchez Castellón <angelfx19@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
7 months ago
Ruthwik 18094a310c Translated using Weblate (Telugu)
Currently translated at 15.4% (135 of 875 strings)

Translated using Weblate (Telugu)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Telugu)

Added translation using Weblate (Telugu)

Co-authored-by: Ruthwik <rtwk03@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/te/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/te/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
Anon 320c49a831 Translated using Weblate (Serbian)
Currently translated at 98.5% (862 of 875 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
7 months ago
return_null 2a971d5dae Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
7 months ago
Dragibus Noir 4467e79ae6 Translated using Weblate (French)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: Dragibus Noir <big.confetti700@aleeas.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
7 months ago
Nataniel Dika Kurniawan c68b180bf6 Translated using Weblate (Malay)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Malay)

Currently translated at 53.1% (465 of 875 strings)

Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
copilot-swe-agent[bot] 5f879f6c83 Fix page position loss when switching reader modes
- Compare content.state with getCurrentState() to detect configuration changes vs intentional updates
- Use content.state when they match (mode switch case) to preserve explicit state updates
- Use getCurrentState() when they differ (rotation case) to restore saved position
- This ensures both screen rotation and mode switching work correctly

Co-authored-by: NathanBap <101987516+NathanBap@users.noreply.github.com>
7 months ago
copilot-swe-agent[bot] aeb3732d75 Fix screen rotation causing reader to jump back to initial page
- Modified BaseReaderFragment to always use getCurrentState() when available
- getCurrentState() contains the most recent reading position saved in onPause/onDestroyView
- content.state may contain the initial state from when content was first loaded
- This ensures the current page position is preserved across configuration changes like screen rotation

Co-authored-by: NathanBap <101987516+NathanBap@users.noreply.github.com>
7 months ago
dragonx943 6292a0fd6b Fix margin 7 months ago
dragonx943 8985b4135d Make button in error dialog centered 7 months ago
Koitharu f8a5397542
Update dependencies 7 months ago
Koitharu 5f51041220
Fix crash: add error handling for scrobbling info 7 months ago
Koitharu 5a14412b62
Fix appying webtoon pull gesture settings 7 months ago
Koitharu be012f631a
Update parsers 7 months ago
Koitharu 0165f43603
Fix crash 7 months ago
Koitharu 55801a1488
Update parsers 7 months ago
Koitharu 77103f016f
Update parsers 7 months ago
DashZero 6b6719a259 Translated using Weblate (Thai)
Currently translated at 50.9% (446 of 875 strings)

Co-authored-by: DashZero <mee_original@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
7 months ago
Joe 822642abb0 Translated using Weblate (Belarusian)
Currently translated at 99.6% (872 of 875 strings)

Co-authored-by: Joe <happenstance@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translation: Kotatsu/Strings
7 months ago
தமிழ்நேரம் 260745fb95 Translated using Weblate (Tamil)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ta/
Translation: Kotatsu/Strings
7 months ago
MuhamadSyabitHidayattulloh 024ec0388f Translated using Weblate (Indonesian)
Currently translated at 100.0% (875 of 875 strings)

Co-authored-by: MuhamadSyabitHidayattulloh <tebepc@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
7 months ago
Draken 5345998eec Translated using Weblate (Vietnamese)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Draken <premieregirl26@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
7 months ago
Nicola Bortoletto 3d56190e71 Translated using Weblate (Italian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
7 months ago
Shams deen 954431d0a5 Added translation using Weblate (Arabic (Egyptian))
Added translation using Weblate (Arabic (Egyptian))

Co-authored-by: Shams deen <shamsdeen84@gmail.com>
7 months ago
return_null afec63b443 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 99.5% (865 of 869 strings)

Co-authored-by: return_null <demolang@dismail.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
7 months ago
Nataniel Dika Kurniawan ac5b29c35a Translated using Weblate (Malay)
Currently translated at 51.7% (453 of 875 strings)

Translated using Weblate (Malay)

Currently translated at 51.6% (452 of 875 strings)

Translated using Weblate (Malay)

Currently translated at 49.5% (431 of 869 strings)

Translated using Weblate (Javanese)

Currently translated at 9.4% (82 of 869 strings)

Translated using Weblate (Javanese)

Currently translated at 100.0% (9 of 9 strings)

Translated using Weblate (Malay)

Currently translated at 40.5% (352 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Javanese)

Added translation using Weblate (Javanese)

Translated using Weblate (Malay)

Currently translated at 38.4% (334 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (869 of 869 strings)

Translated using Weblate (Indonesian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Nataniel Dika Kurniawan <hikawaart2@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/jv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/jv/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ms/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
Frosted 59f5578b66 Translated using Weblate (Turkish)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Frosted <frosted@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
7 months ago
Roel v 391dbb4237 Translated using Weblate (Gothic)
Currently translated at 2.8% (25 of 869 strings)

Translated using Weblate (Gothic)

Currently translated at 44.4% (4 of 9 strings)

Added translation using Weblate (Gothic)

Added translation using Weblate (Gothic)

Co-authored-by: Roel v <roel11112@live.nl>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/got/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/got/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
7 months ago
gekka 7d4505eb78 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 99.5% (865 of 869 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
7 months ago
Максим Горпиніч e6ceb20cf7 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (875 of 875 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (869 of 869 strings)

Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
7 months ago
lenn 8004f8c093 Translated using Weblate (Polish)
Currently translated at 97.8% (849 of 868 strings)

Co-authored-by: lenn <l3ennec@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pl/
Translation: Kotatsu/Strings
7 months ago
Koitharu 61bf2abb6c
Change inset handling in reader 7 months ago
Koitharu d9612f3427
Webtoon pull gesture refactoring 7 months ago
Koitharu 435c3824f7
Remove A5 compatibility code 7 months ago
Koitharu c846693570
Cleanup R8 rules 7 months ago
Koitharu 123937cd01
Fix SearchView closing on back pressed (close #1532, close #1487) 7 months ago
Koitharu 9f56554313
Reduce apk size 7 months ago
Koitharu f8687bb697
Improve background WebView usage 7 months ago
Koitharu 43d3a2cc6a
Fix crash on WebView.stopLoading() 7 months ago
MuhamadSyabitHidayattulloh a95db6ed21
chore: Show description in offline mode (#1597) 8 months ago
Koitharu fd0bb57338
Background captcha resolving 8 months ago
Koitharu 6b94bc2632
Update dependencies 8 months ago
Koitharu c8b91599c6
Fix OkHttp initialization 8 months ago
Draken 3a8b0f9e93
Merge pull request #1586 from MuhamadSyabitHidayattulloh/feat/pull-gesture-navigate-chapter
feat: Add Pull Gesture Navigate Chapter
8 months ago
MuhamadSyabitHidayattulloh 17a0725666 feat: Realtime Favorite and Storage Badges 8 months ago
Draken 3be7848ad9 Revert distributionSha256Sum 8 months ago
dragonx943 08202c11a3 build: migrate to gradle 9 8 months ago
MuhamadSyabitHidayattulloh 5ef907d046 fix: Ui not visible if Control Panel show 8 months ago
MuhamadSyabitHidayattulloh c3776ea3c6 feat: Add Pull Gesture Navigate Chapter 8 months ago
puargs 1d1e49123a feat(reader): Add sensitivity setting for double-page mode
This commit introduces a new setting to control the scroll sensitivity of the double-page reader mode.

- A SeekBar has been added to the reader configuration sheet to adjust the sensitivity.
- The DoublePageSnapHelper now uses this setting to calculate the scroll distance.
- The setting is stored in AppSettings.
8 months ago

2
.idea/.gitignore vendored

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

@ -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>

@ -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'
]
}

@ -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)
webView.evaluateJavascript(script) { result ->
cont.resume(result?.takeUnless { it == "null" })
}
}
} finally {
webView.reset()
}
suspendCoroutine { cont ->
webView.evaluateJavascript(script) { result ->
cont.resume(result?.takeUnless { it == "null" })
}
}
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()
}
}
}
@MainThread
fun getDefaultUserAgent() = runCatching {
obtainWebView().settings.userAgentString.sanitizeHeaderValue().trim().nullIfEmpty()
}.onFailure { e ->
e.printStackTraceDebug()
}.getOrNull()
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
}
if (scroll < 0) {
positionsToMove *= -1
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 (isRTL) {
positionsToMove *= -1
// Apply a minimum threshold
if (positionsToMove == 0 && scroll.absoluteValue > itemSize * 0.2) {
positionsToMove = 1 * scroll.sign
}
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

@ -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"

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

@ -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>

@ -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>

@ -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>

@ -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>

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

@ -832,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">Детаљи о грешци:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Покушај да &lt;a href=%2$s&gt;отвориш мангу у веб прегледачу&lt;/a&gt; да би био сигуран да је доступна на извору&lt;br&gt;2. Увери се да користите &lt;a href=kotatsu://about&gt;latest version of Kotatsu&gt;најновију верзију Котатсу-а&lt;/a&gt;&lt;br&gt;3. Ако је доступно, пошаљи извештај о грешци програмерима.</string>
<string name="state_abandoned">Напуштен</string>
<string name="zoom_mode_fit_height">Уклопи по висини</string>
<string name="not_available">Недоступно</string>
<string name="not_available">Није доступно</string>
<string name="history_shortcuts_summary">Учини недавну мангу доступном дугим притиском на икону апликације</string>
<string name="automatic_scroll">Самостално померање</string>
<string name="download_option_first_n_chapters">Првих %s</string>
@ -837,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

Loading…
Cancel
Save