Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d5bde6e60 | ||
|
|
bf740ddc93 | ||
|
|
fddbf35e8c | ||
|
|
a47fea02d1 | ||
|
|
250136cfdc | ||
|
|
597ad01e8f | ||
|
|
f7b44f2b0f | ||
|
|
5aab43ac93 | ||
|
|
2d278159ea | ||
|
|
da61462d79 | ||
|
|
2ab0912880 | ||
|
|
3914616222 | ||
|
|
a73b2703be | ||
|
|
49590f6d02 | ||
|
|
f4a0fcf5ba | ||
|
|
6ab803e682 | ||
|
|
0faa97b08c | ||
|
|
2ae488544b | ||
|
|
a7e2cfc878 | ||
|
|
da6db9c1b4 | ||
|
|
88b3e5cf34 | ||
|
|
7347f0ba10 | ||
|
|
4c55682552 | ||
|
|
324031aa2a | ||
|
|
1355c3d75c | ||
|
|
8533168155 | ||
|
|
51f6ec6e55 | ||
|
|
7e3f67c14d | ||
|
|
c51320f033 | ||
|
|
9c50a47abc | ||
|
|
473d273d18 | ||
|
|
f19b628655 | ||
|
|
fa74d4b27a | ||
|
|
cdb6655e37 | ||
|
|
4f19f7ebdf | ||
|
|
bf8838f943 | ||
|
|
1e1e9fabdc | ||
|
|
745972a717 | ||
|
|
6055776329 | ||
|
|
4074791f9a | ||
|
|
b1ab48e912 | ||
|
|
a71e2dd289 | ||
|
|
b8283acd0d | ||
|
|
bbdf1c756e | ||
|
|
283878879b | ||
|
|
b74ec98d68 | ||
|
|
3691db8e8e | ||
|
|
e25ccf6b25 | ||
|
|
ffebdb0c49 | ||
|
|
6accdbced5 | ||
|
|
2fcb94e1d7 | ||
|
|
6211ef974d | ||
|
|
0eacf7bb98 | ||
|
|
c9b7d650a8 | ||
|
|
a29f7d6533 | ||
|
|
72f8c626d7 | ||
|
|
f05ef5125d | ||
|
|
40b3d8e6fd | ||
|
|
a695bdc565 | ||
|
|
9700fabd9a | ||
|
|
4877db42f9 | ||
|
|
9b418fd63b | ||
|
|
b2eef0df11 | ||
|
|
34462829ff | ||
|
|
2afcbef8d0 | ||
|
|
695becbda0 | ||
|
|
5877d8215d | ||
|
|
48b357dfef | ||
|
|
b20cc7c0d9 | ||
|
|
0f43f02fad | ||
|
|
9b658cf0b8 | ||
|
|
ce705e12a8 |
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -2,4 +2,4 @@ blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⚠️ Source issue
|
||||
url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new
|
||||
about: If you have troubles with a manga parser or want to propose new manga source, please open an issue in the kotatsu-parsers repository instead
|
||||
about: If you have a problem with a specific **manga source** or want to propose a new one, please open an issue in the kotatsu-parsers repository instead
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
4
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
@@ -60,7 +60,7 @@ body:
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||
- label: This is not a duplicate of an existing issue. Please look through the list of [open issues](https://github.com/KotatsuApp/Kotatsu/issues) before creating a new one.
|
||||
required: true
|
||||
- label: If this is an issue with a parser, I should be opening an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new/choose).
|
||||
- label: This is not an issue with a specific manga source. Otherwise, you have to open an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new/choose).
|
||||
required: true
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
4
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@@ -20,5 +20,5 @@ body:
|
||||
label: Acknowledgements
|
||||
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: This is not a duplicate of an existing issue. Please look through the list of [open issues](https://github.com/KotatsuApp/Kotatsu/issues) before creating a new one.
|
||||
required: true
|
||||
|
||||
@@ -16,8 +16,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 641
|
||||
versionName = '7.0'
|
||||
versionCode = 645
|
||||
versionName = '7.1.2'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
|
||||
ksp {
|
||||
@@ -82,7 +82,7 @@ afterEvaluate {
|
||||
}
|
||||
dependencies {
|
||||
//noinspection GradleDependency
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:3e32a6280a') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:26be293f24') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
@@ -93,19 +93,20 @@ dependencies {
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core-ktx:1.13.1'
|
||||
implementation 'androidx.activity:activity-ktx:1.9.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.7.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.7.1'
|
||||
implementation 'androidx.transition:transition-ktx:1.5.0'
|
||||
implementation 'androidx.collection:collection-ktx:1.4.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-service:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-service:2.8.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-process:2.8.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-rc01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.7.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.0'
|
||||
implementation 'androidx.webkit:webkit:1.11.0'
|
||||
|
||||
implementation 'androidx.work:work-runtime:2.9.0'
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
|
||||
<activity
|
||||
android:name=".tracker.ui.debug.TrackerDebugActivity"
|
||||
android:label="@string/check_for_new_chapters" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.Context
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import androidx.core.view.MenuProvider
|
||||
import leakcanary.LeakCanary
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.workinspector.WorkInspector
|
||||
|
||||
class SettingsMenuProvider(
|
||||
private val context: Context,
|
||||
) : MenuProvider {
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_settings, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_leaks -> {
|
||||
context.startActivity(LeakCanary.newLeakDisplayActivityIntent())
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_works -> {
|
||||
context.startActivity(WorkInspector.getIntent(context))
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,9 @@
|
||||
android:title="@string/leak_canary_display_activity_label"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@id/action_tracker"
|
||||
android:title="@string/check_for_new_chapters"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@id/action_works"
|
||||
android:title="Works"
|
||||
android:title="@string/wi_lib_name"
|
||||
app:showAsAction="never" />
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -100,6 +100,13 @@
|
||||
<intent-filter>
|
||||
<action android:name="${applicationId}.action.READ_MANGA" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.samsung.android.support.REMOTE_ACTION" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="com.samsung.android.support.REMOTE_ACTION"
|
||||
android:resource="@xml/remote_action" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.search.ui.SearchActivity"
|
||||
@@ -245,6 +252,12 @@
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.alternatives.ui.AlternativesActivity"
|
||||
android:label="@string/alternatives" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.settings.about.AppUpdateActivity"
|
||||
android:label="@string/app_update_available" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity"
|
||||
android:label="@string/tracker_debug_info" />
|
||||
|
||||
<service
|
||||
android:name="androidx.work.impl.foreground.SystemForegroundService"
|
||||
@@ -357,13 +370,6 @@
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_recent" />
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.settings.about.UpdateDownloadReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name="org.koitharu.kotatsu.core.ErrorReporterReceiver"
|
||||
android:exported="false">
|
||||
|
||||
@@ -108,8 +108,10 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
viewBinding.webView.stopLoading()
|
||||
viewBinding.webView.destroy()
|
||||
if (hasViewBinding()) {
|
||||
viewBinding.webView.stopLoading()
|
||||
viewBinding.webView.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
|
||||
@@ -27,13 +27,14 @@ import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
|
||||
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.util.AcraScreenLogger
|
||||
@@ -70,8 +71,9 @@ interface AppModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkState(
|
||||
@ApplicationContext context: Context
|
||||
) = NetworkState(context.connectivityManager)
|
||||
@ApplicationContext context: Context,
|
||||
settings: AppSettings,
|
||||
) = NetworkState(context.connectivityManager, settings)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
||||
@@ -39,7 +39,7 @@ suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptibl
|
||||
val filename = buildString {
|
||||
append(context.getString(R.string.app_name).replace(' ', '_').lowercase(Locale.ROOT))
|
||||
append('_')
|
||||
append(LocalDate.now().format(DateTimeFormatter.ofPattern("ddMMyyyy")))
|
||||
append(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
|
||||
append(".bk.zip")
|
||||
}
|
||||
BackupZipOutput(File(dir, filename))
|
||||
|
||||
@@ -16,16 +16,16 @@ class MemoryContentCache @Inject constructor(application: Application) : Compone
|
||||
|
||||
private val isLowRam = application.isLowRamDevice()
|
||||
|
||||
init {
|
||||
application.registerComponentCallbacks(this)
|
||||
}
|
||||
|
||||
private val detailsCache = ExpiringLruCache<SafeDeferred<Manga>>(if (isLowRam) 1 else 4, 5, TimeUnit.MINUTES)
|
||||
private val pagesCache =
|
||||
ExpiringLruCache<SafeDeferred<List<MangaPage>>>(if (isLowRam) 1 else 4, 10, TimeUnit.MINUTES)
|
||||
private val relatedMangaCache =
|
||||
ExpiringLruCache<SafeDeferred<List<Manga>>>(if (isLowRam) 1 else 3, 10, TimeUnit.MINUTES)
|
||||
|
||||
init {
|
||||
application.registerComponentCallbacks(this)
|
||||
}
|
||||
|
||||
suspend fun getDetails(source: MangaSource, url: String): Manga? {
|
||||
return detailsCache[Key(source, url)]?.awaitOrNull()
|
||||
}
|
||||
|
||||
@@ -1,36 +1,48 @@
|
||||
package org.koitharu.kotatsu.core.exceptions.resolve
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.collection.MutableScatterMap
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity
|
||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareActivity
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity.BaseActivityEntryPoint
|
||||
import org.koitharu.kotatsu.core.ui.dialog.ErrorDetailsDialog
|
||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
||||
import org.koitharu.kotatsu.core.util.ext.findActivity
|
||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
|
||||
import java.security.cert.CertPathValidatorException
|
||||
import javax.net.ssl.SSLException
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
|
||||
private val continuations = ArrayMap<String, Continuation<Boolean>>(1)
|
||||
private val continuations = MutableScatterMap<String, Continuation<Boolean>>(1)
|
||||
private val activity: FragmentActivity?
|
||||
private val fragment: Fragment?
|
||||
private val sourceAuthContract: ActivityResultLauncher<MangaSource>
|
||||
private val cloudflareContract: ActivityResultLauncher<CloudFlareProtectedException>
|
||||
|
||||
val context: Context?
|
||||
get() = activity ?: fragment?.context
|
||||
|
||||
constructor(activity: FragmentActivity) {
|
||||
this.activity = activity
|
||||
fragment = null
|
||||
@@ -56,6 +68,12 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
suspend fun resolve(e: Throwable): Boolean = when (e) {
|
||||
is CloudFlareProtectedException -> resolveCF(e)
|
||||
is AuthRequiredException -> resolveAuthException(e.source)
|
||||
is SSLException,
|
||||
is CertPathValidatorException -> {
|
||||
showSslErrorDialog()
|
||||
false
|
||||
}
|
||||
|
||||
is NotFoundException -> {
|
||||
openInBrowser(e.url)
|
||||
false
|
||||
@@ -80,13 +98,37 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
}
|
||||
|
||||
private fun openInBrowser(url: String) {
|
||||
val context = activity ?: fragment?.activity ?: return
|
||||
context.startActivity(BrowserActivity.newIntent(context, url, null, null))
|
||||
context?.run {
|
||||
startActivity(BrowserActivity.newIntent(this, url, null, null))
|
||||
}
|
||||
}
|
||||
|
||||
private fun openAlternatives(manga: Manga) {
|
||||
val context = activity ?: fragment?.activity ?: return
|
||||
context.startActivity(AlternativesActivity.newIntent(context, manga))
|
||||
context?.run {
|
||||
startActivity(AlternativesActivity.newIntent(this, manga))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSslErrorDialog() {
|
||||
val ctx = context ?: return
|
||||
val settings = getAppSettings(ctx)
|
||||
if (settings.isSSLBypassEnabled) {
|
||||
Toast.makeText(ctx, R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
MaterialAlertDialogBuilder(ctx)
|
||||
.setTitle(R.string.ignore_ssl_errors)
|
||||
.setMessage(R.string.ignore_ssl_errors_summary)
|
||||
.setPositiveButton(R.string.apply) { _, _ ->
|
||||
settings.isSSLBypassEnabled = true
|
||||
Toast.makeText(ctx, R.string.settings_apply_restart_required, Toast.LENGTH_SHORT).show()
|
||||
ctx.findActivity()?.finishAffinity()
|
||||
}.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun getAppSettings(context: Context): AppSettings {
|
||||
return EntryPointAccessors.fromApplication<BaseActivityEntryPoint>(context).settings
|
||||
}
|
||||
|
||||
private fun getFragmentManager() = checkNotNull(fragment?.childFragmentManager ?: activity?.supportFragmentManager)
|
||||
@@ -99,6 +141,9 @@ class ExceptionResolver : ActivityResultCallback<TaggedActivityResult> {
|
||||
is AuthRequiredException -> R.string.sign_in
|
||||
is NotFoundException -> if (e.url.isNotEmpty()) R.string.open_in_browser else 0
|
||||
is UnsupportedSourceException -> if (e.manga != null) R.string.alternatives else 0
|
||||
is SSLException,
|
||||
is CertPathValidatorException -> R.string.fix
|
||||
|
||||
else -> 0
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,8 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.util.formatSimple
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@JvmName("mangaIds")
|
||||
@@ -119,17 +118,10 @@ val Manga.appUrl: Uri
|
||||
.appendQueryParameter("url", url)
|
||||
.build()
|
||||
|
||||
private val chaptersNumberFormat = DecimalFormat("#.#").also { f ->
|
||||
f.decimalFormatSymbols = DecimalFormatSymbols.getInstance().also {
|
||||
it.decimalSeparator = '.'
|
||||
}
|
||||
}
|
||||
|
||||
fun MangaChapter.formatNumber(): String? {
|
||||
if (number <= 0f) {
|
||||
return null
|
||||
}
|
||||
return chaptersNumberFormat.format(number.toDouble())
|
||||
fun MangaChapter.formatNumber(): String? = if (number > 0f) {
|
||||
number.formatSimple()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun Manga.chaptersCount(): Int {
|
||||
|
||||
@@ -4,8 +4,10 @@ import android.util.Log
|
||||
import dagger.Lazy
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Interceptor.Chain
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
@@ -13,6 +15,7 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mergeWith
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.net.IDN
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -23,7 +26,7 @@ class CommonHeadersInterceptor @Inject constructor(
|
||||
private val mangaLoaderContextLazy: Lazy<MangaLoaderContextImpl>,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
override fun intercept(chain: Chain): Response {
|
||||
val request = chain.request()
|
||||
val source = request.tag(MangaSource::class.java)
|
||||
val repository = if (source != null) {
|
||||
@@ -46,7 +49,7 @@ class CommonHeadersInterceptor @Inject constructor(
|
||||
headersBuilder.trySet(CommonHeaders.REFERER, "https://$idn/")
|
||||
}
|
||||
val newRequest = request.newBuilder().headers(headersBuilder.build()).build()
|
||||
return repository?.intercept(ProxyChain(chain, newRequest)) ?: chain.proceed(newRequest)
|
||||
return repository?.interceptSafe(ProxyChain(chain, newRequest)) ?: chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
private fun Headers.Builder.trySet(name: String, value: String) = try {
|
||||
@@ -55,10 +58,21 @@ class CommonHeadersInterceptor @Inject constructor(
|
||||
e.printStackTraceDebug()
|
||||
}
|
||||
|
||||
private fun Interceptor.interceptSafe(chain: Chain): Response = runCatchingCancellable {
|
||||
intercept(chain)
|
||||
}.getOrElse { e ->
|
||||
if (e is IOException) {
|
||||
throw e
|
||||
} else {
|
||||
// only IOException can be safely thrown from an Interceptor
|
||||
throw IOException("Error in interceptor: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyChain(
|
||||
private val delegate: Interceptor.Chain,
|
||||
private val delegate: Chain,
|
||||
private val request: Request,
|
||||
) : Interceptor.Chain by delegate {
|
||||
) : Chain by delegate {
|
||||
|
||||
override fun request(): Request = request
|
||||
}
|
||||
|
||||
@@ -83,6 +83,11 @@ class DoHManager(
|
||||
tryGetByIp("2a10:50c0::2:ff"),
|
||||
),
|
||||
).build()
|
||||
|
||||
DoHProvider.ZERO_MS -> DnsOverHttps.Builder().client(bootstrapClient)
|
||||
.url("https://0ms.dev/dns-query".toHttpUrl())
|
||||
.resolvePublicAddresses(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun tryGetByIp(ip: String): InetAddress? = try {
|
||||
|
||||
@@ -2,5 +2,5 @@ package org.koitharu.kotatsu.core.network
|
||||
|
||||
enum class DoHProvider {
|
||||
|
||||
NONE, GOOGLE, CLOUDFLARE, ADGUARD
|
||||
}
|
||||
NONE, GOOGLE, CLOUDFLARE, ADGUARD, ZERO_MS
|
||||
}
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.network
|
||||
|
||||
import android.util.Log
|
||||
import androidx.collection.ArraySet
|
||||
import coil.intercept.Interceptor
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageResult
|
||||
import coil.request.SuccessResult
|
||||
import coil.size.Dimension
|
||||
import coil.size.isOriginal
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.isHttpOrHttps
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.util.Collections
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ImageProxyInterceptor @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
) : Interceptor {
|
||||
|
||||
private val blacklist = Collections.synchronizedSet(ArraySet<String>())
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val request = chain.request
|
||||
if (!settings.isImagesProxyEnabled) {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
val url: HttpUrl? = when (val data = request.data) {
|
||||
is HttpUrl -> data
|
||||
is String -> data.toHttpUrlOrNull()
|
||||
else -> null
|
||||
}
|
||||
if (url == null || !url.isHttpOrHttps || url.host in blacklist) {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
val newUrl = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host("wsrv.nl")
|
||||
.addQueryParameter("url", url.toString())
|
||||
.addQueryParameter("we", null)
|
||||
val size = request.sizeResolver.size()
|
||||
if (!size.isOriginal) {
|
||||
newUrl.addQueryParameter("crop", "cover")
|
||||
(size.height as? Dimension.Pixels)?.let { newUrl.addQueryParameter("h", it.toString()) }
|
||||
(size.width as? Dimension.Pixels)?.let { newUrl.addQueryParameter("w", it.toString()) }
|
||||
}
|
||||
|
||||
val newRequest = request.newBuilder()
|
||||
.data(newUrl.build())
|
||||
.build()
|
||||
val result = chain.proceed(newRequest)
|
||||
return if (result is SuccessResult) {
|
||||
result
|
||||
} else {
|
||||
logDebug((result as? ErrorResult)?.throwable)
|
||||
chain.proceed(request).also {
|
||||
if (it is SuccessResult) {
|
||||
blacklist.add(url.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {
|
||||
if (!settings.isImagesProxyEnabled) {
|
||||
return okHttp.newCall(request).await()
|
||||
}
|
||||
val sourceUrl = request.url
|
||||
val targetUrl = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host("wsrv.nl")
|
||||
.addQueryParameter("url", sourceUrl.toString())
|
||||
.addQueryParameter("we", null)
|
||||
val newRequest = request.newBuilder()
|
||||
.url(targetUrl.build())
|
||||
.build()
|
||||
return runCatchingCancellable {
|
||||
okHttp.doCall(newRequest)
|
||||
}.recover {
|
||||
logDebug(it)
|
||||
okHttp.doCall(request).also {
|
||||
blacklist.add(sourceUrl.host)
|
||||
}
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun OkHttpClient.doCall(request: Request): Response {
|
||||
return newCall(request).await().ensureSuccess()
|
||||
}
|
||||
|
||||
private fun logDebug(e: Throwable?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w("ImageProxy", e.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.network.cookies.AndroidCookieJar
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
import org.koitharu.kotatsu.core.network.cookies.PreferencesCookieJar
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.RealImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.assertNotInMainThread
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
@@ -29,6 +31,9 @@ interface NetworkModule {
|
||||
@Binds
|
||||
fun bindCookieJar(androidCookieJar: MutableCookieJar): CookieJar
|
||||
|
||||
@Binds
|
||||
fun bindImageProxyInterceptor(impl: RealImageProxyInterceptor): ImageProxyInterceptor
|
||||
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import android.util.Log
|
||||
import androidx.collection.ArraySet
|
||||
import coil.intercept.Interceptor
|
||||
import coil.network.HttpException
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.ImageResult
|
||||
import coil.request.SuccessResult
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareBlockedException
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.isHttpOrHttps
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.net.HttpURLConnection
|
||||
import java.util.Collections
|
||||
|
||||
abstract class BaseImageProxyInterceptor : ImageProxyInterceptor {
|
||||
|
||||
private val blacklist = Collections.synchronizedSet(ArraySet<String>())
|
||||
|
||||
final override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val request = chain.request
|
||||
val url: HttpUrl? = when (val data = request.data) {
|
||||
is HttpUrl -> data
|
||||
is String -> data.toHttpUrlOrNull()
|
||||
else -> null
|
||||
}
|
||||
if (url == null || !url.isHttpOrHttps || url.host in blacklist) {
|
||||
return chain.proceed(request)
|
||||
}
|
||||
val newRequest = onInterceptImageRequest(request, url)
|
||||
return when (val result = chain.proceed(newRequest)) {
|
||||
is SuccessResult -> result
|
||||
is ErrorResult -> {
|
||||
logDebug(result.throwable, newRequest.data)
|
||||
chain.proceed(request).also {
|
||||
if (it is SuccessResult && result.throwable.isBlockedByServer()) {
|
||||
blacklist.add(url.host)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final override suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {
|
||||
val newRequest = onInterceptPageRequest(request)
|
||||
return runCatchingCancellable {
|
||||
okHttp.doCall(newRequest)
|
||||
}.recover { error ->
|
||||
logDebug(error, newRequest.url)
|
||||
okHttp.doCall(request).also {
|
||||
if (error.isBlockedByServer()) {
|
||||
blacklist.add(request.url.host)
|
||||
}
|
||||
}
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
protected abstract suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest
|
||||
|
||||
protected abstract suspend fun onInterceptPageRequest(request: Request): Request
|
||||
|
||||
private suspend fun OkHttpClient.doCall(request: Request): Response {
|
||||
return newCall(request).await().ensureSuccess()
|
||||
}
|
||||
|
||||
private fun logDebug(e: Throwable, url: Any) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w("ImageProxy", "${e.message}: $url", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Throwable.isBlockedByServer(): Boolean {
|
||||
return this is CloudFlareBlockedException
|
||||
|| (this is HttpException && response.code == HttpURLConnection.HTTP_FORBIDDEN)
|
||||
|| (this is HttpStatusException && statusCode == HttpURLConnection.HTTP_FORBIDDEN)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.intercept.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
interface ImageProxyInterceptor : Interceptor {
|
||||
|
||||
suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.intercept.Interceptor
|
||||
import coil.request.ImageResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.plus
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RealImageProxyInterceptor @Inject constructor(
|
||||
private val settings: AppSettings,
|
||||
) : ImageProxyInterceptor {
|
||||
|
||||
private val delegate = settings.observeAsStateFlow(
|
||||
scope = processLifecycleScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_IMAGES_PROXY,
|
||||
valueProducer = { createDelegate() },
|
||||
)
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
return delegate.value?.intercept(chain) ?: chain.proceed(chain.request)
|
||||
}
|
||||
|
||||
override suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {
|
||||
return delegate.value?.interceptPageRequest(request, okHttp) ?: okHttp.newCall(request).await()
|
||||
}
|
||||
|
||||
private fun createDelegate(): ImageProxyInterceptor? = when (val proxy = settings.imagesProxy) {
|
||||
-1 -> null
|
||||
0 -> WsrvNlProxyInterceptor()
|
||||
1 -> ZeroMsProxyInterceptor()
|
||||
else -> error("Unsupported images proxy $proxy")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Dimension
|
||||
import coil.size.isOriginal
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
class WsrvNlProxyInterceptor : BaseImageProxyInterceptor() {
|
||||
|
||||
override suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest {
|
||||
val newUrl = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host("wsrv.nl")
|
||||
.addQueryParameter("url", url.toString())
|
||||
.addQueryParameter("we", null)
|
||||
val size = request.sizeResolver.size()
|
||||
if (!size.isOriginal) {
|
||||
newUrl.addQueryParameter("crop", "cover")
|
||||
(size.height as? Dimension.Pixels)?.let { newUrl.addQueryParameter("h", it.toString()) }
|
||||
(size.width as? Dimension.Pixels)?.let { newUrl.addQueryParameter("w", it.toString()) }
|
||||
}
|
||||
|
||||
return request.newBuilder()
|
||||
.data(newUrl.build())
|
||||
.build()
|
||||
}
|
||||
|
||||
override suspend fun onInterceptPageRequest(request: Request): Request {
|
||||
val sourceUrl = request.url
|
||||
val targetUrl = HttpUrl.Builder()
|
||||
.scheme("https")
|
||||
.host("wsrv.nl")
|
||||
.addQueryParameter("url", sourceUrl.toString())
|
||||
.addQueryParameter("we", null)
|
||||
return request.newBuilder()
|
||||
.url(targetUrl.build())
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.request.ImageRequest
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
class ZeroMsProxyInterceptor : BaseImageProxyInterceptor() {
|
||||
|
||||
override suspend fun onInterceptImageRequest(request: ImageRequest, url: HttpUrl): ImageRequest {
|
||||
if (url.host == "x.0ms.dev" || url.host == "0ms.dev") {
|
||||
return request
|
||||
}
|
||||
val newUrl = ("https://x.0ms.dev/q70/$url").toHttpUrl()
|
||||
return request.newBuilder()
|
||||
.data(newUrl)
|
||||
.build()
|
||||
}
|
||||
|
||||
override suspend fun onInterceptPageRequest(request: Request): Request {
|
||||
val newUrl = ("https://x.0ms.dev/q70/${request.url}").toHttpUrl()
|
||||
return request.newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,15 @@ import android.net.ConnectivityManager.NetworkCallback
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.MediatorStateFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.isOnline
|
||||
|
||||
class NetworkState(
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
) : MediatorStateFlow<Boolean>(connectivityManager.isOnline()) {
|
||||
private val settings: AppSettings,
|
||||
) : MediatorStateFlow<Boolean>(connectivityManager.isOnline(settings)) {
|
||||
|
||||
private val callback = NetworkCallbackImpl()
|
||||
|
||||
@@ -19,7 +21,10 @@ class NetworkState(
|
||||
override fun onActive() {
|
||||
invalidate()
|
||||
val request = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, callback)
|
||||
}
|
||||
@@ -37,7 +42,7 @@ class NetworkState(
|
||||
}
|
||||
|
||||
private fun invalidate() {
|
||||
publishValue(connectivityManager.isOnline())
|
||||
publishValue(connectivityManager.isOnline(settings))
|
||||
}
|
||||
|
||||
private inner class NetworkCallbackImpl : NetworkCallback() {
|
||||
@@ -48,4 +53,27 @@ class NetworkState(
|
||||
|
||||
override fun onUnavailable() = invalidate()
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
fun ConnectivityManager.isOnline(settings: AppSettings): Boolean {
|
||||
if (settings.isOfflineCheckDisabled) {
|
||||
return true
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activeNetwork?.let { isOnline(it) } ?: false
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
activeNetworkInfo?.isConnected == true
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConnectivityManager.isOnline(network: Network): Boolean {
|
||||
val capabilities = getNetworkCapabilities(network) ?: return false
|
||||
return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
|
||||
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
|
||||
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
|| capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.graphics.Canvas
|
||||
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
|
||||
import org.koitharu.kotatsu.parsers.bitmap.Rect
|
||||
import java.io.OutputStream
|
||||
import android.graphics.Bitmap as AndroidBitmap
|
||||
import android.graphics.Rect as AndroidRect
|
||||
|
||||
class BitmapWrapper private constructor(
|
||||
private val androidBitmap: AndroidBitmap,
|
||||
) : Bitmap {
|
||||
|
||||
private val canvas by lazy { Canvas(androidBitmap) } // is not always used, so initialized lazily
|
||||
|
||||
override val height: Int
|
||||
get() = androidBitmap.height
|
||||
|
||||
override val width: Int
|
||||
get() = androidBitmap.width
|
||||
|
||||
override fun drawBitmap(sourceBitmap: Bitmap, src: Rect, dst: Rect) {
|
||||
val androidSourceBitmap = (sourceBitmap as BitmapWrapper).androidBitmap
|
||||
canvas.drawBitmap(androidSourceBitmap, src.toAndroidRect(), dst.toAndroidRect(), null)
|
||||
}
|
||||
|
||||
fun compressTo(output: OutputStream) {
|
||||
androidBitmap.compress(AndroidBitmap.CompressFormat.PNG, 100, output)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(width: Int, height: Int): Bitmap = BitmapWrapper(
|
||||
AndroidBitmap.createBitmap(width, height, AndroidBitmap.Config.ARGB_8888),
|
||||
)
|
||||
|
||||
fun create(bitmap: AndroidBitmap): Bitmap = BitmapWrapper(
|
||||
if (bitmap.isMutable) bitmap else bitmap.copy(AndroidBitmap.Config.ARGB_8888, true),
|
||||
)
|
||||
|
||||
private fun Rect.toAndroidRect() = AndroidRect(left, top, right, bottom)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import android.webkit.WebView
|
||||
import androidx.annotation.MainThread
|
||||
@@ -10,15 +11,21 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||
import okio.Buffer
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||
import org.koitharu.kotatsu.core.prefs.SourceSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.configureForParser
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.requireBody
|
||||
import org.koitharu.kotatsu.core.util.ext.sanitizeHeaderValue
|
||||
import org.koitharu.kotatsu.core.util.ext.toList
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.bitmap.Bitmap
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
@@ -68,6 +75,27 @@ class MangaLoaderContextImpl @Inject constructor(
|
||||
return LocaleListCompat.getAdjustedDefault().toList()
|
||||
}
|
||||
|
||||
override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response {
|
||||
val image = response.requireBody().byteStream()
|
||||
|
||||
val opts = BitmapFactory.Options()
|
||||
opts.inMutable = true
|
||||
val bitmap = BitmapFactory.decodeStream(image, null, opts) ?: error("Cannot decode bitmap")
|
||||
val result = redraw(BitmapWrapper.create(bitmap)) as BitmapWrapper
|
||||
|
||||
val body = Buffer().also {
|
||||
result.compressTo(it.outputStream())
|
||||
}.asResponseBody("image/jpeg".toMediaType())
|
||||
|
||||
return response.newBuilder()
|
||||
.body(body)
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun createBitmap(width: Int, height: Int): Bitmap {
|
||||
return BitmapWrapper.create(width, height)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun obtainWebView(): WebView {
|
||||
return webViewCached?.get() ?: WebView(androidContext).also {
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.requireBody
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
@@ -150,10 +151,6 @@ class FaviconFetcher(
|
||||
return if (networkResponse != null) DataSource.NETWORK else DataSource.DISK
|
||||
}
|
||||
|
||||
private fun Response.requireBody(): ResponseBody {
|
||||
return checkNotNull(body) { "response body == null" }
|
||||
}
|
||||
|
||||
private fun Size.toCacheKey() = buildString {
|
||||
append(width.toString())
|
||||
append('x')
|
||||
|
||||
@@ -136,6 +136,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
get() = prefs.getBoolean(KEY_TRAFFIC_WARNING, true)
|
||||
set(value) = prefs.edit { putBoolean(KEY_TRAFFIC_WARNING, value) }
|
||||
|
||||
val isOfflineCheckDisabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_OFFLINE_DISABLED, false)
|
||||
|
||||
var isAllFavouritesVisible: Boolean
|
||||
get() = prefs.getBoolean(KEY_ALL_FAVOURITES_VISIBLE, true)
|
||||
set(value) = prefs.edit { putBoolean(KEY_ALL_FAVOURITES_VISIBLE, value) }
|
||||
@@ -152,6 +155,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val isTrackerNotificationsEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_TRACKER_NOTIFICATIONS, true)
|
||||
|
||||
val isTrackerNsfwDisabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_TRACKER_NO_NSFW, false)
|
||||
|
||||
var notificationSound: Uri
|
||||
get() = prefs.getString(KEY_NOTIFICATIONS_SOUND, null)?.toUriOrNull()
|
||||
?: Settings.System.DEFAULT_NOTIFICATION_URI
|
||||
@@ -377,14 +383,18 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
val isImagesProxyEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_IMAGES_PROXY, false)
|
||||
val imagesProxy: Int
|
||||
get() {
|
||||
val raw = prefs.getString(KEY_IMAGES_PROXY, null)?.toIntOrNull()
|
||||
return raw ?: if (prefs.getBoolean(KEY_IMAGES_PROXY_OLD, false)) 0 else -1
|
||||
}
|
||||
|
||||
val dnsOverHttps: DoHProvider
|
||||
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
|
||||
|
||||
val isSSLBypassEnabled: Boolean
|
||||
var isSSLBypassEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_SSL_BYPASS, false)
|
||||
set(value) = prefs.edit { putBoolean(KEY_SSL_BYPASS, value) }
|
||||
|
||||
val proxyType: Proxy.Type
|
||||
get() {
|
||||
@@ -544,8 +554,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
|
||||
companion object {
|
||||
|
||||
const val PAGE_SWITCH_VOLUME_KEYS = "volume"
|
||||
|
||||
const val TRACK_HISTORY = "history"
|
||||
const val TRACK_FAVOURITES = "favourites"
|
||||
|
||||
@@ -557,6 +565,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_COLOR_THEME = "color_theme"
|
||||
const val KEY_THEME_AMOLED = "amoled_theme"
|
||||
const val KEY_TRAFFIC_WARNING = "traffic_warning"
|
||||
const val KEY_OFFLINE_DISABLED = "no_offline"
|
||||
const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear"
|
||||
const val KEY_HTTP_CACHE_CLEAR = "http_cache_clear"
|
||||
const val KEY_COOKIES_CLEAR = "cookies_clear"
|
||||
@@ -581,6 +590,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_TRACK_CATEGORIES = "track_categories"
|
||||
const val KEY_TRACK_WARNING = "track_warning"
|
||||
const val KEY_TRACKER_NOTIFICATIONS = "tracker_notifications"
|
||||
const val KEY_TRACKER_NO_NSFW = "tracker_no_nsfw"
|
||||
const val KEY_NOTIFICATIONS_SETTINGS = "notifications_settings"
|
||||
const val KEY_NOTIFICATIONS_SOUND = "notifications_sound"
|
||||
const val KEY_NOTIFICATIONS_VIBRATE = "notifications_vibrate"
|
||||
@@ -593,7 +603,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_APP_PASSWORD_NUMERIC = "app_password_num"
|
||||
const val KEY_PROTECT_APP = "protect_app"
|
||||
const val KEY_PROTECT_APP_BIOMETRIC = "protect_app_bio"
|
||||
const val KEY_APP_VERSION = "app_version"
|
||||
const val KEY_ZOOM_MODE = "zoom_mode"
|
||||
const val KEY_BACKUP = "backup"
|
||||
const val KEY_RESTORE = "restore"
|
||||
@@ -643,7 +652,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_PREFETCH_CONTENT = "prefetch_content"
|
||||
const val KEY_APP_LOCALE = "app_locale"
|
||||
const val KEY_LOGGING_ENABLED = "logging"
|
||||
const val KEY_LOGS_SHARE = "logs_share"
|
||||
const val KEY_SOURCES_GRID = "sources_grid"
|
||||
const val KEY_SOURCES_NEW = "sources_new"
|
||||
const val KEY_UPDATES_UNSTABLE = "updates_unstable"
|
||||
@@ -658,7 +666,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_PROXY_AUTH = "proxy_auth"
|
||||
const val KEY_PROXY_LOGIN = "proxy_login"
|
||||
const val KEY_PROXY_PASSWORD = "proxy_password"
|
||||
const val KEY_IMAGES_PROXY = "images_proxy"
|
||||
const val KEY_IMAGES_PROXY = "images_proxy_2"
|
||||
const val KEY_LOCAL_MANGA_DIRS = "local_manga_dirs"
|
||||
const val KEY_DISABLE_NSFW = "no_nsfw"
|
||||
const val KEY_RELATED_MANGA = "related_manga"
|
||||
@@ -672,7 +680,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_CF_CONTRAST = "cf_contrast"
|
||||
const val KEY_CF_INVERTED = "cf_inverted"
|
||||
const val KEY_CF_GRAYSCALE = "cf_grayscale"
|
||||
const val KEY_IGNORE_DOZE = "ignore_dose"
|
||||
const val KEY_PAGES_TAB = "pages_tab"
|
||||
const val KEY_DETAILS_TAB = "details_tab"
|
||||
const val KEY_DETAILS_LAST_TAB = "details_last_tab"
|
||||
@@ -680,9 +687,18 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_PAGES_SAVE_DIR = "pages_dir"
|
||||
const val KEY_PAGES_SAVE_ASK = "pages_dir_ask"
|
||||
const val KEY_STATS_ENABLED = "stats_on"
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
const val KEY_APP_TRANSLATION = "about_app_translation"
|
||||
const val KEY_FEED_HEADER = "feed_header"
|
||||
const val KEY_SEARCH_SUGGESTION_TYPES = "search_suggest_types"
|
||||
|
||||
// keys for non-persistent preferences
|
||||
const val KEY_APP_VERSION = "app_version"
|
||||
const val KEY_IGNORE_DOZE = "ignore_dose"
|
||||
const val KEY_TRACKER_DEBUG = "tracker_debug"
|
||||
const val KEY_LOGS_SHARE = "logs_share"
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
const val KEY_APP_TRANSLATION = "about_app_translation"
|
||||
|
||||
// old keys are for migration only
|
||||
private const val KEY_IMAGES_PROXY_OLD = "images_proxy"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.core.prefs
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import androidx.core.content.edit
|
||||
import okhttp3.internal.isSensitiveHeader
|
||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
||||
@@ -12,6 +11,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.settings.utils.validation.DomainValidator
|
||||
|
||||
class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig {
|
||||
|
||||
@@ -31,7 +31,11 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
|
||||
.ifNullOrEmpty { key.defaultValue }
|
||||
.sanitizeHeaderValue()
|
||||
|
||||
is ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue).ifNullOrEmpty { key.defaultValue }
|
||||
is ConfigKey.Domain -> prefs.getString(key.key, key.defaultValue)
|
||||
?.trim()
|
||||
?.takeIf { DomainValidator.isValidDomain(it) }
|
||||
?: key.defaultValue
|
||||
|
||||
is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)
|
||||
is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue)
|
||||
} as T
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.ui
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
@@ -92,11 +93,20 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
}
|
||||
|
||||
override fun onSupportNavigateUp(): Boolean {
|
||||
if (supportFragmentManager.backStackEntryCount > 0) {
|
||||
supportFragmentManager.popBackStack()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
// TODO fix behavior on Android 14
|
||||
dispatchNavigateUp()
|
||||
return true
|
||||
}
|
||||
val fm = supportFragmentManager
|
||||
if (fm.isStateSaved) {
|
||||
return false
|
||||
}
|
||||
dispatchNavigateUp()
|
||||
if (fm.backStackEntryCount > 0) {
|
||||
fm.popBackStack()
|
||||
} else {
|
||||
dispatchNavigateUp()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -160,6 +170,8 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
}
|
||||
}
|
||||
|
||||
protected fun hasViewBinding() = ::viewBinding.isInitialized
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface BaseActivityEntryPoint {
|
||||
|
||||
@@ -63,7 +63,7 @@ abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :
|
||||
)
|
||||
}
|
||||
|
||||
protected fun setTitle(title: CharSequence?) {
|
||||
protected open fun setTitle(title: CharSequence?) {
|
||||
(activity as? SettingsActivity)?.setSectionTitle(title)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,34 @@
|
||||
package org.koitharu.kotatsu.core.ui.sheet
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.BackEventCompat
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN
|
||||
|
||||
class BottomSheetCollapseCallback(
|
||||
private val behavior: BottomSheetBehavior<*>,
|
||||
) : OnBackPressedCallback(behavior.state == STATE_EXPANDED) {
|
||||
private val sheet: ViewGroup,
|
||||
private val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(sheet),
|
||||
) : OnBackPressedCallback(behavior.state == STATE_EXPANDED || behavior.state == STATE_HALF_EXPANDED) {
|
||||
|
||||
init {
|
||||
behavior.addBottomSheetCallback(
|
||||
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
@SuppressLint("SwitchIntDef")
|
||||
override fun onStateChanged(view: View, state: Int) {
|
||||
isEnabled = state == STATE_EXPANDED || state == STATE_HALF_EXPANDED
|
||||
when (state) {
|
||||
STATE_EXPANDED,
|
||||
STATE_HALF_EXPANDED -> isEnabled = true
|
||||
|
||||
STATE_COLLAPSED,
|
||||
STATE_HIDDEN -> isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(p0: View, p1: Float) = Unit
|
||||
@@ -24,7 +36,11 @@ class BottomSheetCollapseCallback(
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
behavior.state = STATE_COLLAPSED
|
||||
}
|
||||
override fun handleOnBackPressed() = behavior.handleBackInvoked()
|
||||
|
||||
override fun handleOnBackCancelled() = behavior.cancelBackProgress()
|
||||
|
||||
override fun handleOnBackProgressed(backEvent: BackEventCompat) = behavior.updateBackProgress(backEvent)
|
||||
|
||||
override fun handleOnBackStarted(backEvent: BackEventCompat) = behavior.startBackProgress(backEvent)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.content.pm.ResolveInfo
|
||||
import android.database.SQLException
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -75,6 +76,9 @@ val Context.activityManager: ActivityManager?
|
||||
val Context.powerManager: PowerManager?
|
||||
get() = getSystemService(POWER_SERVICE) as? PowerManager
|
||||
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
|
||||
|
||||
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okhttp3.internal.isSensitiveHeader
|
||||
import okio.IOException
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.HttpStatusException
|
||||
@@ -42,6 +41,8 @@ fun Response.ensureSuccess() = apply {
|
||||
}
|
||||
}
|
||||
|
||||
fun Response.requireBody(): ResponseBody = checkNotNull(body) { "Response body is null" }
|
||||
|
||||
fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c ->
|
||||
c.name(name)
|
||||
c.value(value)
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
|
||||
val Context.connectivityManager: ConnectivityManager
|
||||
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
fun ConnectivityManager.isOnline(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
activeNetwork?.let { isOnline(it) } ?: false
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
activeNetworkInfo?.isConnected == true
|
||||
}
|
||||
}
|
||||
|
||||
private fun ConnectivityManager.isOnline(network: Network): Boolean {
|
||||
val capabilities = getNetworkCapabilities(network)
|
||||
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
}
|
||||
@@ -60,8 +60,7 @@ class DetailsLoadUseCase @Inject constructor(
|
||||
} catch (e: IOException) {
|
||||
local?.await()?.manga?.also { localManga ->
|
||||
send(MangaDetails(localManga, null, localManga.description?.parseAsHtml(withImages = false), true))
|
||||
}
|
||||
throw e
|
||||
} ?: close(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -153,8 +152,8 @@ class DetailsActivity :
|
||||
viewBinding.textViewDescription.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
viewBinding.chipsTags.onChipClickListener = this
|
||||
TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView)
|
||||
viewBinding.containerBottomSheet?.let { BottomSheetBehavior.from(it) }?.let { behavior ->
|
||||
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(behavior))
|
||||
viewBinding.containerBottomSheet?.let { sheet ->
|
||||
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
|
||||
}
|
||||
|
||||
viewModel.details.filterNotNull().observe(this, ::onMangaUpdated)
|
||||
@@ -667,7 +666,7 @@ class DetailsActivity :
|
||||
|
||||
companion object {
|
||||
|
||||
private const val FAV_LABEL_LIMIT = 10
|
||||
private const val FAV_LABEL_LIMIT = 16
|
||||
private const val AUTHOR_LABEL_LIMIT = 16
|
||||
|
||||
fun newIntent(context: Context, manga: Manga): Intent {
|
||||
|
||||
@@ -19,8 +19,8 @@ import okhttp3.OkHttpClient
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.local.data.isFileUri
|
||||
|
||||
@@ -13,7 +13,7 @@ fun downloadChapterAD() = adapterDelegateViewBinding<DownloadChapter, DownloadCh
|
||||
val iconDone = ContextCompat.getDrawable(context, R.drawable.ic_check)
|
||||
|
||||
bind {
|
||||
binding.textViewNumber.text = item.number.toString()
|
||||
binding.textViewNumber.text = item.number
|
||||
binding.textViewTitle.text = item.name
|
||||
binding.textViewTitle.drawableEnd = if (item.isDownloaded) iconDone else null
|
||||
}
|
||||
|
||||
@@ -27,15 +27,20 @@ abstract class FavouritesDao {
|
||||
@Query("SELECT * FROM favourites WHERE deleted_at = 0 GROUP BY manga_id ORDER BY created_at DESC LIMIT :limit")
|
||||
abstract suspend fun findLast(limit: Int): List<FavouriteManga>
|
||||
|
||||
fun observeAll(order: ListSortOrder): Flow<List<FavouriteManga>> {
|
||||
fun observeAll(order: ListSortOrder, limit: Int): Flow<List<FavouriteManga>> {
|
||||
val orderBy = getOrderBy(order)
|
||||
|
||||
@Language("RoomSql")
|
||||
val query = SimpleSQLiteQuery(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE favourites.deleted_at = 0 GROUP BY favourites.manga_id ORDER BY $orderBy",
|
||||
)
|
||||
return observeAllImpl(query)
|
||||
val query = buildString {
|
||||
append(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE favourites.deleted_at = 0 GROUP BY favourites.manga_id ORDER BY ",
|
||||
)
|
||||
append(orderBy)
|
||||
if (limit > 0) {
|
||||
append(" LIMIT ")
|
||||
append(limit)
|
||||
}
|
||||
}
|
||||
return observeAllImpl(SimpleSQLiteQuery(query))
|
||||
}
|
||||
|
||||
@Transaction
|
||||
@@ -52,16 +57,21 @@ abstract class FavouritesDao {
|
||||
)
|
||||
abstract suspend fun findAll(categoryId: Long): List<FavouriteManga>
|
||||
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder): Flow<List<FavouriteManga>> {
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder, limit: Int): Flow<List<FavouriteManga>> {
|
||||
val orderBy = getOrderBy(order)
|
||||
val query = buildString {
|
||||
append(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE category_id = ? AND deleted_at = 0 GROUP BY favourites.manga_id ORDER BY ",
|
||||
)
|
||||
append(orderBy)
|
||||
if (limit > 0) {
|
||||
append(" LIMIT ")
|
||||
append(limit)
|
||||
}
|
||||
}
|
||||
|
||||
@Language("RoomSql")
|
||||
val query = SimpleSQLiteQuery(
|
||||
"SELECT * FROM favourites LEFT JOIN manga ON favourites.manga_id = manga.manga_id " +
|
||||
"WHERE category_id = ? AND deleted_at = 0 GROUP BY favourites.manga_id ORDER BY $orderBy",
|
||||
arrayOf<Any>(categoryId),
|
||||
)
|
||||
return observeAllImpl(query)
|
||||
return observeAllImpl(SimpleSQLiteQuery(query, arrayOf<Any>(categoryId)))
|
||||
}
|
||||
|
||||
suspend fun findCovers(categoryId: Long, order: ListSortOrder): List<Cover> {
|
||||
|
||||
@@ -38,8 +38,8 @@ class FavouritesRepository @Inject constructor(
|
||||
return entities.toMangaList()
|
||||
}
|
||||
|
||||
fun observeAll(order: ListSortOrder): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(order)
|
||||
fun observeAll(order: ListSortOrder, limit: Int): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(order, limit)
|
||||
.mapItems { it.toManga() }
|
||||
}
|
||||
|
||||
@@ -48,14 +48,14 @@ class FavouritesRepository @Inject constructor(
|
||||
return entities.toMangaList()
|
||||
}
|
||||
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(categoryId, order)
|
||||
fun observeAll(categoryId: Long, order: ListSortOrder, limit: Int): Flow<List<Manga>> {
|
||||
return db.getFavouritesDao().observeAll(categoryId, order, limit)
|
||||
.mapItems { it.toManga() }
|
||||
}
|
||||
|
||||
fun observeAll(categoryId: Long): Flow<List<Manga>> {
|
||||
fun observeAll(categoryId: Long, limit: Int): Flow<List<Manga>> {
|
||||
return observeOrder(categoryId)
|
||||
.flatMapLatest { order -> observeAll(categoryId, order) }
|
||||
.flatMapLatest { order -> observeAll(categoryId, order, limit) }
|
||||
}
|
||||
|
||||
fun observeMangaCount(): Flow<Int> {
|
||||
@@ -63,12 +63,6 @@ class FavouritesRepository @Inject constructor(
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
suspend fun getCategories(): List<FavouriteCategory> {
|
||||
return db.getFavouriteCategoriesDao().findAll().map {
|
||||
it.toFavouriteCategory()
|
||||
}
|
||||
}
|
||||
|
||||
fun observeCategories(): Flow<List<FavouriteCategory>> {
|
||||
return db.getFavouriteCategoriesDao().observeAll().mapItems {
|
||||
it.toFavouriteCategory()
|
||||
|
||||
@@ -33,7 +33,7 @@ class FavouritesListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickLis
|
||||
binding.recyclerView.isVP2BugWorkaroundEnabled = true
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
override fun onScrolledToEnd() = viewModel.requestMoreItems()
|
||||
|
||||
override fun onFilterClick(view: View?) {
|
||||
val menu = PopupMenu(view?.context ?: return, view)
|
||||
|
||||
@@ -32,8 +32,11 @@ import org.koitharu.kotatsu.list.ui.model.LoadingState
|
||||
import org.koitharu.kotatsu.list.ui.model.toErrorState
|
||||
import org.koitharu.kotatsu.list.ui.model.toUi
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 20
|
||||
|
||||
@HiltViewModel
|
||||
class FavouritesListViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
@@ -46,6 +49,8 @@ class FavouritesListViewModel @Inject constructor(
|
||||
|
||||
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
|
||||
private val refreshTrigger = MutableStateFlow(Any())
|
||||
private val limit = MutableStateFlow(PAGE_SIZE)
|
||||
private val isReady = AtomicBoolean(false)
|
||||
|
||||
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
|
||||
@@ -61,13 +66,7 @@ class FavouritesListViewModel @Inject constructor(
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
override val content = combine(
|
||||
if (categoryId == NO_ID) {
|
||||
sortOrder.filterNotNull().flatMapLatest {
|
||||
repository.observeAll(it)
|
||||
}
|
||||
} else {
|
||||
repository.observeAll(categoryId)
|
||||
},
|
||||
observeFavorites(),
|
||||
listMode,
|
||||
refreshTrigger,
|
||||
) { list, mode, _ ->
|
||||
@@ -85,7 +84,10 @@ class FavouritesListViewModel @Inject constructor(
|
||||
),
|
||||
)
|
||||
|
||||
else -> list.toUi(mode, listExtraProvider)
|
||||
else -> {
|
||||
isReady.set(true)
|
||||
list.toUi(mode, listExtraProvider)
|
||||
}
|
||||
}
|
||||
}.catch {
|
||||
emit(listOf(it.toErrorState(canRetry = false)))
|
||||
@@ -126,4 +128,19 @@ class FavouritesListViewModel @Inject constructor(
|
||||
repository.setCategoryOrder(categoryId, order)
|
||||
}
|
||||
}
|
||||
|
||||
fun requestMoreItems() {
|
||||
if (isReady.compareAndSet(true, false)) {
|
||||
limit.value += PAGE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeFavorites() = if (categoryId == NO_ID) {
|
||||
combine(sortOrder.filterNotNull(), limit, ::Pair)
|
||||
.flatMapLatest { repository.observeAll(it.first, it.second) }
|
||||
} else {
|
||||
limit.flatMapLatest {
|
||||
repository.observeAll(categoryId, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.room.Transaction
|
||||
import androidx.sqlite.db.SimpleSQLiteQuery
|
||||
import androidx.sqlite.db.SupportSQLiteQuery
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.intellij.lang.annotations.Language
|
||||
import org.koitharu.kotatsu.core.db.entity.TagEntity
|
||||
import org.koitharu.kotatsu.list.domain.ListSortOrder
|
||||
|
||||
@@ -28,8 +27,7 @@ abstract class HistoryDao {
|
||||
@Query("SELECT * FROM history WHERE deleted_at = 0 ORDER BY updated_at DESC LIMIT :limit")
|
||||
abstract fun observeAll(limit: Int): Flow<List<HistoryWithManga>>
|
||||
|
||||
// TODO pagination
|
||||
fun observeAll(order: ListSortOrder): Flow<List<HistoryWithManga>> {
|
||||
fun observeAll(order: ListSortOrder, limit: Int): Flow<List<HistoryWithManga>> {
|
||||
val orderBy = when (order) {
|
||||
ListSortOrder.LAST_READ -> "history.updated_at DESC"
|
||||
ListSortOrder.LONG_AGO_READ -> "history.updated_at ASC"
|
||||
@@ -43,13 +41,18 @@ abstract class HistoryDao {
|
||||
ListSortOrder.UPDATED -> "IFNULL((SELECT last_chapter_date FROM tracks WHERE tracks.manga_id = manga.manga_id), 0) DESC"
|
||||
else -> throw IllegalArgumentException("Sort order $order is not supported")
|
||||
}
|
||||
|
||||
@Language("RoomSql")
|
||||
val query = SimpleSQLiteQuery(
|
||||
"SELECT * FROM history LEFT JOIN manga ON history.manga_id = manga.manga_id " +
|
||||
"WHERE history.deleted_at = 0 GROUP BY history.manga_id ORDER BY $orderBy",
|
||||
)
|
||||
return observeAllImpl(query)
|
||||
val query = buildString {
|
||||
append(
|
||||
"SELECT * FROM history LEFT JOIN manga ON history.manga_id = manga.manga_id " +
|
||||
"WHERE history.deleted_at = 0 GROUP BY history.manga_id ORDER BY ",
|
||||
)
|
||||
append(orderBy)
|
||||
if (limit > 0) {
|
||||
append(" LIMIT ")
|
||||
append(limit)
|
||||
}
|
||||
}
|
||||
return observeAllImpl(SimpleSQLiteQuery(query))
|
||||
}
|
||||
|
||||
@Query("SELECT manga_id FROM history WHERE deleted_at = 0")
|
||||
|
||||
@@ -74,8 +74,8 @@ class HistoryRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun observeAllWithHistory(order: ListSortOrder): Flow<List<MangaWithHistory>> {
|
||||
return db.getHistoryDao().observeAll(order).mapItems {
|
||||
fun observeAllWithHistory(order: ListSortOrder, limit: Int): Flow<List<MangaWithHistory>> {
|
||||
return db.getHistoryDao().observeAll(order, limit).mapItems {
|
||||
MangaWithHistory(
|
||||
it.manga.toManga(it.tags.toMangaTags()),
|
||||
it.history.toMangaHistory(),
|
||||
|
||||
@@ -32,7 +32,7 @@ class HistoryListFragment : MangaListFragment() {
|
||||
viewModel.isStatsEnabled.observe(viewLifecycleOwner, MenuInvalidator(requireActivity()))
|
||||
}
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
override fun onScrolledToEnd() = viewModel.requestMoreItems()
|
||||
|
||||
override fun onEmptyActionClick() {
|
||||
startActivity(NetworkManageIntent())
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.history.ui
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
@@ -43,8 +44,11 @@ import org.koitharu.kotatsu.list.ui.model.toListModel
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 20
|
||||
|
||||
@HiltViewModel
|
||||
class HistoryListViewModel @Inject constructor(
|
||||
private val repository: HistoryRepository,
|
||||
@@ -62,8 +66,11 @@ class HistoryListViewModel @Inject constructor(
|
||||
valueProducer = { historySortOrder },
|
||||
)
|
||||
|
||||
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_HISTORY) { historyListMode }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.historyListMode)
|
||||
override val listMode = settings.observeAsStateFlow(
|
||||
scope = viewModelScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_LIST_MODE_HISTORY,
|
||||
valueProducer = { historyListMode },
|
||||
)
|
||||
|
||||
private val isGroupingEnabled = settings.observeAsFlow(
|
||||
key = AppSettings.KEY_HISTORY_GROUPING,
|
||||
@@ -72,6 +79,9 @@ class HistoryListViewModel @Inject constructor(
|
||||
g && s.isGroupingSupported()
|
||||
}
|
||||
|
||||
private val limit = MutableStateFlow(PAGE_SIZE)
|
||||
private val isReady = AtomicBoolean(false)
|
||||
|
||||
val isStatsEnabled = settings.observeAsStateFlow(
|
||||
scope = viewModelScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_STATS_ENABLED,
|
||||
@@ -79,7 +89,7 @@ class HistoryListViewModel @Inject constructor(
|
||||
)
|
||||
|
||||
override val content = combine(
|
||||
sortOrder.flatMapLatest { repository.observeAllWithHistory(it) },
|
||||
observeHistory(),
|
||||
isGroupingEnabled,
|
||||
listMode,
|
||||
networkState,
|
||||
@@ -95,7 +105,10 @@ class HistoryListViewModel @Inject constructor(
|
||||
),
|
||||
)
|
||||
|
||||
else -> mapList(list, grouped, mode, online, incognito)
|
||||
else -> {
|
||||
isReady.set(true)
|
||||
mapList(list, grouped, mode, online, incognito)
|
||||
}
|
||||
}
|
||||
}.onStart {
|
||||
loadingCounter.increment()
|
||||
@@ -138,6 +151,15 @@ class HistoryListViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun requestMoreItems() {
|
||||
if (isReady.compareAndSet(true, false)) {
|
||||
limit.value += PAGE_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeHistory() = combine(sortOrder, limit, ::Pair)
|
||||
.flatMapLatest { repository.observeAllWithHistory(it.first, it.second) }
|
||||
|
||||
private suspend fun mapList(
|
||||
list: List<MangaWithHistory>,
|
||||
grouped: Boolean,
|
||||
|
||||
@@ -7,11 +7,12 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ErrorResult
|
||||
@@ -20,17 +21,26 @@ import coil.request.SuccessResult
|
||||
import coil.target.ViewTarget
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.ui.util.PopupMenuMediator
|
||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listener, View.OnClickListener {
|
||||
@@ -39,27 +49,45 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private var errorBinding: ItemErrorStateBinding? = null
|
||||
private val viewModel: ImageViewModel by viewModels()
|
||||
private lateinit var menuMediator: PopupMenuMediator
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityImageBinding.inflate(layoutInflater))
|
||||
viewBinding.buttonBack.setOnClickListener(this)
|
||||
loadImage(intent.data)
|
||||
viewBinding.buttonMenu.setOnClickListener(this)
|
||||
val imageUrl = requireNotNull(intent.data)
|
||||
|
||||
val menuProvider = ImageMenuProvider(
|
||||
activity = this,
|
||||
snackbarHost = viewBinding.root,
|
||||
viewModel = viewModel,
|
||||
)
|
||||
menuMediator = PopupMenuMediator(menuProvider)
|
||||
viewModel.isLoading.observe(this, ::onLoadingStateChanged)
|
||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.root, null))
|
||||
viewModel.onImageSaved.observeEvent(this, ::onImageSaved)
|
||||
loadImage(imageUrl)
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
with(viewBinding.buttonBack) {
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top + marginBottom
|
||||
leftMargin = insets.left + marginBottom
|
||||
rightMargin = insets.right + marginBottom
|
||||
}
|
||||
viewBinding.buttonBack.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top + bottomMargin
|
||||
leftMargin = insets.left + bottomMargin
|
||||
rightMargin = insets.right + bottomMargin
|
||||
}
|
||||
viewBinding.buttonMenu.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top + bottomMargin
|
||||
leftMargin = insets.left + bottomMargin
|
||||
rightMargin = insets.right + bottomMargin
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_back -> dispatchNavigateUp()
|
||||
R.id.button_menu -> menuMediator.onLongClick(v)
|
||||
else -> loadImage(intent.data)
|
||||
}
|
||||
}
|
||||
@@ -92,11 +120,34 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.lifecycle(this)
|
||||
.listener(this)
|
||||
.tag(intent.getSerializableExtraCompat<MangaSource>(EXTRA_SOURCE))
|
||||
.source(intent.getSerializableExtraCompat<MangaSource>(EXTRA_SOURCE))
|
||||
.target(SsivTarget(viewBinding.ssiv))
|
||||
.enqueueWith(coil)
|
||||
}
|
||||
|
||||
private fun onImageSaved(uri: Uri) {
|
||||
Snackbar.make(viewBinding.root, R.string.page_saved, Snackbar.LENGTH_LONG)
|
||||
.setAction(R.string.share) {
|
||||
ShareHelper(this).shareImage(uri)
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
val button = viewBinding.buttonMenu
|
||||
button.isClickable = !isLoading
|
||||
if (isLoading) {
|
||||
button.setImageDrawable(
|
||||
CircularProgressDrawable(this).also {
|
||||
it.setStyle(CircularProgressDrawable.LARGE)
|
||||
it.setColorSchemeColors(getThemeColor(com.google.android.material.R.attr.colorControlNormal))
|
||||
it.start()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
button.setImageResource(materialR.drawable.abc_ic_menu_overflow_material)
|
||||
}
|
||||
}
|
||||
|
||||
private class SsivTarget(
|
||||
override val view: SubsamplingScaleImageView,
|
||||
) : ViewTarget<SubsamplingScaleImageView> {
|
||||
@@ -124,7 +175,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_SOURCE = "source"
|
||||
const val EXTRA_SOURCE = "source"
|
||||
|
||||
fun newIntent(context: Context, url: String, source: MangaSource?): Intent {
|
||||
return Intent(context, ImageActivity::class.java)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.koitharu.kotatsu.image.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||
import org.koitharu.kotatsu.local.data.isZipUri
|
||||
|
||||
class ImageMenuProvider(
|
||||
private val activity: ComponentActivity,
|
||||
private val snackbarHost: View,
|
||||
private val viewModel: ImageViewModel,
|
||||
) : MenuProvider {
|
||||
|
||||
private val permissionLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
saveImage()
|
||||
}
|
||||
}
|
||||
|
||||
private val saveLauncher = activity.registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("image/png"),
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
viewModel.saveImage(uri)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(R.menu.opt_image, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
|
||||
R.id.action_save -> {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
permissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
saveImage()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun saveImage() {
|
||||
val name = activity.intent.data?.let {
|
||||
if (it.isZipUri()) {
|
||||
it.fragment
|
||||
} else {
|
||||
it.lastPathSegment
|
||||
}?.substringBeforeLast('.')?.plus(".png")
|
||||
}
|
||||
if (name == null || !saveLauncher.tryLaunch(name)) {
|
||||
Snackbar.make(snackbarHost, R.string.operation_not_supported, Snackbar.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.koitharu.kotatsu.image.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import coil.ImageLoader
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ImageViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
private val coil: ImageLoader,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val onImageSaved = MutableEventFlow<Uri>()
|
||||
|
||||
fun saveImage(destination: Uri) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val request = ImageRequest.Builder(context)
|
||||
.memoryCachePolicy(CachePolicy.READ_ONLY)
|
||||
.data(savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA))
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.source(savedStateHandle[ImageActivity.EXTRA_SOURCE])
|
||||
.build()
|
||||
val bitmap = coil.execute(request).getDrawableOrThrow().toBitmap()
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
context.contentResolver.openOutputStream(destination)?.use { output ->
|
||||
check(bitmap.compress(Bitmap.CompressFormat.PNG, 100, output))
|
||||
} ?: error("Cannot open output stream")
|
||||
}
|
||||
onImageSaved.call(destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.koitharu.kotatsu.local.data
|
||||
|
||||
import androidx.collection.MutableLongObjectMap
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.local.data.input.LocalMangaInput
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.io.File
|
||||
|
||||
class LocalMangaMappingCache {
|
||||
|
||||
private val map = MutableLongObjectMap<File>()
|
||||
|
||||
suspend fun get(mangaId: Long): LocalManga? {
|
||||
val file = synchronized(this) {
|
||||
map[mangaId]
|
||||
} ?: return null
|
||||
return runCatchingCancellable {
|
||||
LocalMangaInput.of(file).getManga()
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
operator fun set(mangaId: Long, localManga: LocalManga?) = synchronized(this) {
|
||||
if (localManga == null) {
|
||||
map.remove(mangaId)
|
||||
} else {
|
||||
map[mangaId] = localManga.file
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ class LocalMangaRepository @Inject constructor(
|
||||
|
||||
override val source = MangaSource.LOCAL
|
||||
private val locks = MultiMutex<Long>()
|
||||
private val localMappingCache = LocalMangaMappingCache()
|
||||
|
||||
override val isMultipleTagsSupported: Boolean = true
|
||||
override val isTagsExclusionSupported: Boolean = true
|
||||
@@ -141,6 +142,10 @@ class LocalMangaRepository @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun findSavedManga(remoteManga: Manga): LocalManga? = runCatchingCancellable {
|
||||
// very fast path
|
||||
localMappingCache.get(remoteManga.id)?.let {
|
||||
return@runCatchingCancellable it
|
||||
}
|
||||
// fast path
|
||||
LocalMangaInput.find(storageManager.getReadableDirs(), remoteManga)?.let {
|
||||
return it.getManga()
|
||||
@@ -162,6 +167,8 @@ class LocalMangaRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
}.firstOrNull()?.getManga()
|
||||
}.onSuccess { x: LocalManga? ->
|
||||
localMappingCache[remoteManga.id] = x
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -66,7 +67,7 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateActivity
|
||||
import javax.inject.Inject
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@@ -84,7 +85,6 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
private val viewModel by viewModels<MainViewModel>()
|
||||
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
|
||||
private val closeSearchCallback = CloseSearchCallback()
|
||||
private val appUpdateDialog = AppUpdateDialog(this)
|
||||
private lateinit var navigationDelegate: MainNavigationDelegate
|
||||
private lateinit var appUpdateBadge: OptionsMenuBadgeHelper
|
||||
|
||||
@@ -190,9 +190,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
}
|
||||
|
||||
R.id.action_app_update -> {
|
||||
viewModel.appUpdate.value?.also {
|
||||
appUpdateDialog.show(it)
|
||||
} != null
|
||||
startActivity(Intent(this, AppUpdateActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
||||
@@ -7,8 +7,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.core.model.findChapter
|
||||
import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
@@ -27,8 +27,8 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.use
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.network.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
@@ -36,6 +36,17 @@ class ReaderControlDelegate(
|
||||
}
|
||||
|
||||
fun onKeyDown(keyCode: Int): Boolean = when (keyCode) {
|
||||
|
||||
KeyEvent.KEYCODE_R -> {
|
||||
listener.switchPageBy(1)
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_L -> {
|
||||
listener.switchPageBy(-1)
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) {
|
||||
listener.switchPageBy(-1)
|
||||
true
|
||||
|
||||
@@ -43,6 +43,12 @@ class RootSettingsFragment : BasePreferenceFragment(0) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
if (!resources.getBoolean(R.bool.is_tablet)) {
|
||||
super.setTitle(title)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindPreferenceSummary(key: String, @StringRes vararg items: Int) {
|
||||
findPreference<Preference>(key)?.summary = items.joinToString { getString(it) }
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
package org.koitharu.kotatsu.settings
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.preference.Preference
|
||||
@@ -21,15 +17,12 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.isScrolledToTop
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
|
||||
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.about.AppUpdateDialog
|
||||
import org.koitharu.kotatsu.settings.sources.SourceSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment
|
||||
import org.koitharu.kotatsu.settings.sources.manage.SourcesManageFragment
|
||||
@@ -40,19 +33,21 @@ import org.koitharu.kotatsu.settings.userdata.UserDataSettingsFragment
|
||||
class SettingsActivity :
|
||||
BaseActivity<ActivitySettingsBinding>(),
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
|
||||
AppBarOwner,
|
||||
FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
val appUpdateDialog = AppUpdateDialog(this)
|
||||
AppBarOwner {
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
get() = viewBinding.appbar
|
||||
|
||||
private val isMasterDetails
|
||||
get() = viewBinding.containerMaster != null
|
||||
|
||||
private var screenPadding = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySettingsBinding.inflate(layoutInflater))
|
||||
screenPadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val isMasterDetails = viewBinding.containerMaster != null
|
||||
val fm = supportFragmentManager
|
||||
val currentFragment = fm.findFragmentById(R.id.container)
|
||||
if (currentFragment == null || (isMasterDetails && currentFragment is RootSettingsFragment)) {
|
||||
@@ -64,61 +59,7 @@ class SettingsActivity :
|
||||
replace(R.id.container_master, RootSettingsFragment())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTitleChanged(title: CharSequence?, color: Int) {
|
||||
super.onTitleChanged(title, color)
|
||||
viewBinding.collapsingToolbarLayout?.title = title
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
supportFragmentManager.addOnBackStackChangedListener(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
supportFragmentManager.removeOnBackStackChangedListener(this)
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.opt_settings, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
|
||||
R.id.action_leaks -> {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(this, "leakcanary.internal.activity.LeakActivity")
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_tracker -> {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(this, "org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity")
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
R.id.action_works -> {
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(this, "org.koitharu.workinspector.WorkInspectorActivity")
|
||||
startActivity(intent)
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onBackStackChanged() {
|
||||
val fragment = supportFragmentManager.findFragmentById(R.id.container) as? RecyclerViewOwner ?: return
|
||||
val recyclerView = fragment.recyclerView
|
||||
recyclerView.post {
|
||||
viewBinding.appbar.setExpanded(recyclerView.isScrolledToTop, false)
|
||||
}
|
||||
addMenuProvider(SettingsMenuProvider(this))
|
||||
}
|
||||
|
||||
override fun onPreferenceStartFragment(
|
||||
@@ -137,8 +78,8 @@ class SettingsActivity :
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
viewBinding.cardDetails?.updateLayoutParams<MarginLayoutParams> {
|
||||
bottomMargin = marginStart + insets.bottom
|
||||
viewBinding.textViewHeader?.updateLayoutParams<MarginLayoutParams> {
|
||||
topMargin = screenPadding + insets.top
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,19 +91,17 @@ class SettingsActivity :
|
||||
|
||||
fun openFragment(fragment: Fragment, isFromRoot: Boolean) {
|
||||
val hasFragment = supportFragmentManager.findFragmentById(R.id.container) != null
|
||||
val isMasterDetail = viewBinding.containerMaster != null
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(R.id.container, fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
|
||||
if (!isMasterDetail || (hasFragment && !isFromRoot)) {
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
if (!isMasterDetails || (hasFragment && !isFromRoot)) {
|
||||
addToBackStack(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openDefaultFragment() {
|
||||
val hasMaster = viewBinding.containerMaster != null
|
||||
val fragment = when (intent?.action) {
|
||||
ACTION_READER -> ReaderSettingsFragment()
|
||||
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
|
||||
@@ -184,7 +123,7 @@ class SettingsActivity :
|
||||
}
|
||||
|
||||
else -> null
|
||||
} ?: if (hasMaster) AppearanceSettingsFragment() else RootSettingsFragment()
|
||||
} ?: if (isMasterDetails) AppearanceSettingsFragment() else RootSettingsFragment()
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(R.id.container, fragment)
|
||||
|
||||
@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.tracker.ui.debug.TrackerDebugActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -41,6 +41,12 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
isEnabled = VersionId(BuildConfig.VERSION_NAME).isStable
|
||||
if (!isEnabled) isChecked = true
|
||||
}
|
||||
if (!settings.isTrackerEnabled) {
|
||||
findPreference<Preference>(AppSettings.KEY_TRACKER_DEBUG)?.run {
|
||||
isEnabled = false
|
||||
setSummary(R.string.check_for_new_chapters_disabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
@@ -68,6 +74,12 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_TRACKER_DEBUG -> {
|
||||
startActivity(Intent(preference.context, TrackerDebugActivity::class.java))
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
@@ -77,7 +89,7 @@ class AboutSettingsFragment : BasePreferenceFragment(R.string.about) {
|
||||
Snackbar.make(listView, R.string.no_update_available, Snackbar.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
(activity as SettingsActivity).appUpdateDialog.show(version)
|
||||
startActivity(Intent(requireContext(), AppUpdateActivity::class.java))
|
||||
}
|
||||
|
||||
private fun openLink(url: String, title: CharSequence?) {
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.buildSpannedString
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.noties.markwon.Markwon
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
import org.koitharu.kotatsu.core.util.ext.showOrHide
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ActivityAppUpdateBinding
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppUpdateActivity : BaseActivity<ActivityAppUpdateBinding>(), View.OnClickListener {
|
||||
|
||||
private val viewModel: AppUpdateViewModel by viewModels()
|
||||
private lateinit var downloadReceiver: UpdateDownloadReceiver
|
||||
|
||||
private val permissionRequest = registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) {
|
||||
if (it) {
|
||||
viewModel.startDownload()
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityAppUpdateBinding.inflate(layoutInflater))
|
||||
downloadReceiver = UpdateDownloadReceiver(viewModel)
|
||||
viewModel.nextVersion.observe(this, ::onNextVersionChanged)
|
||||
viewBinding.buttonCancel.setOnClickListener(this)
|
||||
viewBinding.buttonUpdate.setOnClickListener(this)
|
||||
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
downloadReceiver,
|
||||
IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||
ContextCompat.RECEIVER_EXPORTED,
|
||||
)
|
||||
combine(viewModel.isLoading, viewModel.downloadProgress, ::Pair)
|
||||
.observe(this, ::onProgressChanged)
|
||||
viewModel.downloadState.observe(this, ::onDownloadStateChanged)
|
||||
viewModel.onError.observeEvent(this, ::onError)
|
||||
viewModel.onDownloadDone.observeEvent(this) { intent ->
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTraceDebug()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(downloadReceiver)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_cancel -> finishAfterTransition()
|
||||
R.id.button_update -> doUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||
viewBinding.root.setPadding(
|
||||
basePadding + insets.left,
|
||||
basePadding + insets.top,
|
||||
basePadding + insets.right,
|
||||
basePadding + insets.bottom,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun onNextVersionChanged(version: AppVersion?) {
|
||||
viewBinding.buttonUpdate.isEnabled = version != null && !viewModel.isLoading.value
|
||||
if (version == null) {
|
||||
viewBinding.textViewContent.setText(R.string.loading_)
|
||||
return
|
||||
}
|
||||
val message = withContext(Dispatchers.Default) {
|
||||
buildSpannedString {
|
||||
append(getString(R.string.new_version_s, version.name))
|
||||
appendLine()
|
||||
append(getString(R.string.size_s, FileSize.BYTES.format(this@AppUpdateActivity, version.apkSize)))
|
||||
appendLine()
|
||||
appendLine()
|
||||
append(Markwon.create(this@AppUpdateActivity).toMarkdown(version.description))
|
||||
}
|
||||
}
|
||||
viewBinding.textViewContent.setText(message, TextView.BufferType.SPANNABLE)
|
||||
}
|
||||
|
||||
private fun doUpdate() {
|
||||
viewModel.installIntent.value?.let { intent ->
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
onError(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
permissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
viewModel.startDownload()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openInBrowser() {
|
||||
val latestVersion = viewModel.nextVersion.value ?: return
|
||||
val intent = Intent(Intent.ACTION_VIEW, latestVersion.url.toUri())
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.open_in_browser)))
|
||||
}
|
||||
|
||||
private fun onProgressChanged(value: Pair<Boolean, Float>) {
|
||||
val (isLoading, downloadProgress) = value
|
||||
val indicator = viewBinding.progressBar
|
||||
indicator.showOrHide(isLoading)
|
||||
indicator.isIndeterminate = downloadProgress <= 0f
|
||||
if (downloadProgress > 0f) {
|
||||
indicator.setProgressCompat((indicator.max * downloadProgress).toInt(), true)
|
||||
}
|
||||
viewBinding.buttonUpdate.isEnabled = !isLoading && viewModel.nextVersion.value != null
|
||||
}
|
||||
|
||||
private fun onDownloadStateChanged(state: Int) {
|
||||
val message = when (state) {
|
||||
DownloadManager.STATUS_FAILED -> R.string.error_occurred
|
||||
DownloadManager.STATUS_PAUSED -> R.string.downloads_paused
|
||||
else -> 0
|
||||
}
|
||||
viewBinding.textViewError.setTextAndVisible(message)
|
||||
}
|
||||
|
||||
private fun onError(e: Throwable) {
|
||||
viewBinding.textViewError.textAndVisible = e.getDisplayMessage(resources)
|
||||
}
|
||||
|
||||
private class UpdateDownloadReceiver(
|
||||
private val viewModel: AppUpdateViewModel,
|
||||
) : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||
viewModel.onDownloadComplete(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.Manifest
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.text.buildSpannedString
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.noties.markwon.Markwon
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppVersion
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
|
||||
class AppUpdateDialog(private val activity: AppCompatActivity) {
|
||||
|
||||
private lateinit var latestVersion: AppVersion
|
||||
|
||||
private val permissionRequest = activity.registerForActivityResult(
|
||||
ActivityResultContracts.RequestPermission(),
|
||||
) {
|
||||
if (it) {
|
||||
downloadUpdateImpl()
|
||||
} else {
|
||||
openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
fun show(version: AppVersion) {
|
||||
latestVersion = version
|
||||
val message = buildSpannedString {
|
||||
append(activity.getString(R.string.new_version_s, version.name))
|
||||
appendLine()
|
||||
append(activity.getString(R.string.size_s, FileSize.BYTES.format(activity, version.apkSize)))
|
||||
appendLine()
|
||||
appendLine()
|
||||
append(Markwon.create(activity).toMarkdown(version.description))
|
||||
}
|
||||
MaterialAlertDialogBuilder(activity, DIALOG_THEME_CENTERED)
|
||||
.setTitle(R.string.app_update_available)
|
||||
.setMessage(message)
|
||||
.setIcon(R.drawable.ic_app_update)
|
||||
.setNeutralButton(R.string.open_in_browser) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, version.url.toUri())
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
|
||||
}.setPositiveButton(R.string.update) { _, _ ->
|
||||
downloadUpdate()
|
||||
}.setNegativeButton(android.R.string.cancel, null)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun downloadUpdate() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
permissionRequest.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
} else {
|
||||
downloadUpdateImpl()
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadUpdateImpl() = runCatching {
|
||||
val version = latestVersion
|
||||
val url = version.apkUrl.toUri()
|
||||
val dm = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
val request = DownloadManager.Request(url)
|
||||
.setTitle("${activity.getString(R.string.app_name)} v${version.name}")
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url.lastPathSegment)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/vnd.android.package-archive")
|
||||
dm.enqueue(request)
|
||||
}.onSuccess {
|
||||
Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show()
|
||||
}.onFailure { e ->
|
||||
Toast.makeText(activity, e.getDisplayMessage(activity.resources), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
private fun openInBrowser() {
|
||||
val intent = Intent(Intent.ACTION_VIEW, latestVersion.url.toUri())
|
||||
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Environment
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.github.AppUpdateRepository
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
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(
|
||||
private val repository: AppUpdateRepository,
|
||||
@ApplicationContext context: Context,
|
||||
) : BaseViewModel() {
|
||||
|
||||
val nextVersion = repository.observeAvailableUpdate()
|
||||
val downloadProgress = MutableStateFlow(-1f)
|
||||
val downloadState = MutableStateFlow(DownloadManager.STATUS_PENDING)
|
||||
val installIntent = MutableStateFlow<Intent?>(null)
|
||||
val onDownloadDone = MutableEventFlow<Intent>()
|
||||
|
||||
private val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
private val appName = context.getString(R.string.app_name)
|
||||
|
||||
init {
|
||||
if (nextVersion.value == null) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
repository.fetchUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startDownload() {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val version = nextVersion.requireValue()
|
||||
val url = version.apkUrl.toUri()
|
||||
val request = DownloadManager.Request(url)
|
||||
.setTitle("$appName v${version.name}")
|
||||
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, url.lastPathSegment)
|
||||
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||
.setMimeType("application/vnd.android.package-archive")
|
||||
val downloadId = downloadManager.enqueue(request)
|
||||
observeDownload(downloadId)
|
||||
}
|
||||
}
|
||||
|
||||
fun onDownloadComplete(intent: Intent) {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
if (downloadId == 0L) {
|
||||
return@launchLoadingJob
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
val installerIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
installerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
installerIntent.setDataAndType(
|
||||
downloadManager.getUriForDownloadedFile(downloadId),
|
||||
downloadManager.getMimeTypeForDownloadedFile(downloadId),
|
||||
)
|
||||
installerIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
installIntent.value = installerIntent
|
||||
onDownloadDone.call(installerIntent)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun observeDownload(id: Long) {
|
||||
val query = DownloadManager.Query()
|
||||
query.setFilterById(id)
|
||||
while (coroutineContext.isActive) {
|
||||
downloadManager.query(query).use { cursor ->
|
||||
if (cursor.moveToFirst()) {
|
||||
val bytesDownloaded = cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR),
|
||||
)
|
||||
val bytesTotal = cursor.getLong(
|
||||
cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES),
|
||||
)
|
||||
downloadProgress.value = bytesDownloaded.toFloat() / bytesTotal
|
||||
val state = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS))
|
||||
downloadState.value = state
|
||||
if (state == DownloadManager.STATUS_SUCCESSFUL || state == DownloadManager.STATUS_FAILED) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.about
|
||||
|
||||
import android.app.DownloadManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
|
||||
|
||||
class UpdateDownloadReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
DownloadManager.ACTION_DOWNLOAD_COMPLETE -> {
|
||||
val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0L)
|
||||
if (downloadId == 0L) {
|
||||
return
|
||||
}
|
||||
val dm = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
installIntent.setDataAndType(
|
||||
dm.getUriForDownloadedFile(downloadId),
|
||||
dm.getMimeTypeForDownloadedFile(downloadId),
|
||||
)
|
||||
installIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
|
||||
try {
|
||||
context.startActivity(installIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
e.printStackTraceDebug()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -41,7 +40,13 @@ class MangaDirectorySelectDialog : AlertDialogFragment<DialogDirectorySelectBind
|
||||
) {
|
||||
if (it) {
|
||||
viewModel.refresh()
|
||||
pickFileTreeLauncher.launch(null)
|
||||
if (!pickFileTreeLauncher.tryLaunch(null)) {
|
||||
Toast.makeText(
|
||||
context ?: return@registerForActivityResult,
|
||||
R.string.operation_not_supported,
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,24 @@ class DomainValidator : EditTextValidator() {
|
||||
if (trimmed.isEmpty()) {
|
||||
return ValidationResult.Success
|
||||
}
|
||||
return if (!checkCharacters(trimmed)) {
|
||||
return if (!isValidDomain(trimmed)) {
|
||||
ValidationResult.Failed(context.getString(R.string.invalid_domain_message))
|
||||
} else {
|
||||
ValidationResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkCharacters(value: String): Boolean = runCatching {
|
||||
val parts = value.split(':')
|
||||
require(parts.size <= 2)
|
||||
val urlBuilder = HttpUrl.Builder()
|
||||
urlBuilder.host(parts.first())
|
||||
if (parts.size == 2) {
|
||||
urlBuilder.port(parts[1].toInt())
|
||||
}
|
||||
}.isSuccess
|
||||
companion object {
|
||||
|
||||
fun isValidDomain(value: String): Boolean = runCatching {
|
||||
require(value.isNotEmpty())
|
||||
val parts = value.split(':')
|
||||
require(parts.size <= 2)
|
||||
val urlBuilder = HttpUrl.Builder()
|
||||
urlBuilder.host(parts.first())
|
||||
if (parts.size == 2) {
|
||||
urlBuilder.port(parts[1].toInt())
|
||||
}
|
||||
}.isSuccess
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,6 @@ class Tracker @Inject constructor(
|
||||
return repository.getTracks(offset = 0, limit = limit)
|
||||
}
|
||||
|
||||
suspend fun gc() {
|
||||
repository.gc()
|
||||
}
|
||||
|
||||
suspend fun fetchUpdates(
|
||||
track: MangaTracking,
|
||||
commit: Boolean
|
||||
|
||||
@@ -32,6 +32,7 @@ class TrackerDebugActivity : BaseActivity<ActivityTrackerDebugBinding>(), OnList
|
||||
val tracksAdapter = BaseListAdapter<TrackDebugItem>()
|
||||
.addDelegate(ListItemType.FEED, trackDebugAD(this, coil, this))
|
||||
with(viewBinding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = tracksAdapter
|
||||
addItemDecoration(TypedListSpacingDecoration(context, false))
|
||||
}
|
||||
@@ -49,6 +49,9 @@ class TrackerNotificationHelper @Inject constructor(
|
||||
if (newChapters.isEmpty() || !applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
return null
|
||||
}
|
||||
if (manga.isNsfw && settings.isTrackerNsfwDisabled) {
|
||||
return null
|
||||
}
|
||||
val id = manga.url.hashCode()
|
||||
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
val summary = applicationContext.resources.getQuantityString(
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:elevation="0dp"
|
||||
android:fitsSystemWindows="true"
|
||||
app:elevation="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/container_master"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:liftOnScroll="false">
|
||||
@@ -30,50 +30,46 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/card_details"
|
||||
app:layout_constraintEnd_toStartOf="@id/container"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar"
|
||||
app:layout_constraintWidth_max="400dp"
|
||||
app:layout_constraintWidth_min="320dp"
|
||||
app:layout_constraintWidth_percent="0.35" />
|
||||
app:layout_constraintWidth_percent="0.4" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card_details"
|
||||
<TextView
|
||||
android:id="@+id/textView_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?listPreferredItemPaddingStart"
|
||||
android:layout_marginTop="@dimen/screen_padding"
|
||||
android:layout_marginEnd="?listPreferredItemPaddingEnd"
|
||||
android:gravity="center_vertical|start"
|
||||
android:padding="8dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/container_master"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/appearance" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@id/container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/side_card_offset"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="@dimen/side_card_offset"
|
||||
android:layout_marginBottom="@dimen/side_card_offset"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/container_master"
|
||||
app:layout_constraintTop_toBottomOf="@id/appbar">
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_header"
|
||||
tools:layout="@layout/fragment_settings_sources" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="?listPreferredItemPaddingStart"
|
||||
android:layout_marginEnd="?listPreferredItemPaddingEnd"
|
||||
android:gravity="center_vertical|start"
|
||||
android:padding="8dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
|
||||
tools:text="@string/appearance" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?colorSurfaceDim"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/container_master"
|
||||
app:layout_constraintStart_toEndOf="@id/container_master"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
95
app/src/main/res/layout/activity_app_update.xml
Normal file
95
app/src/main/res/layout/activity_app_update.xml
Normal file
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/screen_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:drawablePadding="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/app_update_available"
|
||||
android:textAppearance="?textAppearanceHeadline5"
|
||||
app:drawableTint="?colorPrimary"
|
||||
app:drawableTopCompat="@drawable/ic_app_update"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/screen_padding"
|
||||
android:max="100"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_error"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textColor="?colorError"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/progressBar"
|
||||
tools:text="@string/error_corrupted_file"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginVertical="@dimen/screen_padding"
|
||||
app:layout_constraintBottom_toTopOf="@id/barrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_error">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_cancel"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@android:string/cancel"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_update"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:text="@string/update"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="button_cancel,button_update" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -24,6 +24,18 @@
|
||||
android:scaleType="center"
|
||||
app:srcCompat="?homeAsUpIndicator" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_menu"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="end"
|
||||
android:layout_margin="@dimen/screen_padding"
|
||||
android:background="@drawable/bg_circle_button"
|
||||
android:contentDescription="@string/back"
|
||||
android:elevation="@dimen/m3_sys_elevation_level1"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/abc_ic_menu_overflow_material" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -9,23 +9,14 @@
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
android:fitsSystemWindows="true"
|
||||
app:liftOnScroll="false">
|
||||
|
||||
<com.google.android.material.appbar.CollapsingToolbarLayout
|
||||
android:id="@+id/collapsingToolbarLayout"
|
||||
style="?collapsingToolbarLayoutMediumStyle"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?collapsingToolbarLayoutMediumSize"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
|
||||
app:toolbarId="@id/toolbar">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</com.google.android.material.appbar.CollapsingToolbarLayout>
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_collapseMode="pin" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
||||
android:layout_marginBottom="16dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
9
app/src/main/res/menu/opt_image.xml
Normal file
9
app/src/main/res/menu/opt_image.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:title="@string/save" />
|
||||
|
||||
</menu>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu />
|
||||
@@ -334,4 +334,4 @@
|
||||
<string name="import_completed_hint">يمكنك حذف الملف الأصلي من التخزين لتوفير مساحة</string>
|
||||
<string name="import_will_start_soon">الإستيراد سيبدأ عن قريب</string>
|
||||
<string name="history_shortcuts">إظهار اختصارات المانجا الحديثة</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -530,8 +530,8 @@
|
||||
<string name="rating_adult">Дарослы</string>
|
||||
<string name="default_tab">Ўкладка па змаўчанні</string>
|
||||
<string name="state_upcoming">Чакаецца</string>
|
||||
<string name="volume_">Гучнасць %d</string>
|
||||
<string name="volume_unknown">Невядомая гучнасць</string>
|
||||
<string name="volume_">Том %d</string>
|
||||
<string name="volume_unknown">Невядомы том</string>
|
||||
<string name="incognito_mode_hint">Ваш прагрэс чытання не будзе захаваны</string>
|
||||
<string name="vertical">Вертыкальны</string>
|
||||
<string name="category_hidden_done">Гэтая катэгорыя была схаваная з галоўнага экрана і даступная праз Меню → Кіраванне катэгорыямі</string>
|
||||
@@ -632,4 +632,13 @@
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
<string name="pin_navigation_ui">Замацаваць інтэрфейс навігацыі</string>
|
||||
<string name="blocked_by_server_message">Вы заблакаваныя серверам. Паспрабуйце выкарыстоўваць іншае сеткавае падлучэнне (VPN, проксі і т. д.)</string>
|
||||
<string name="ignore_ssl_errors_summary">Вы можаце адключыць праверку SSL-сертыфіката, калі пры доступе да сеткавых рэсурсаў узнікаюць праблемы, звязаныя з SSL. Гэта можа паўплываць на вашую бяспеку. Пасля змены гэтага параметра запатрабуецца перазагрузка праграмы.</string>
|
||||
<string name="disable_nsfw_notifications_summary">Не паказваць апавяшчэння аб абнаўленнях мангі NSFW</string>
|
||||
<string name="tracker_debug_info">Часопіс праверкі новых раздзелаў</string>
|
||||
<string name="tracker_debug_info_summary">Адладкавая інфармацыя аб фонавай праверцы наяўнасці новых раздзелаў</string>
|
||||
<string name="disable_connectivity_check">Адключыць праверку падключэння</string>
|
||||
<string name="disable_connectivity_check_summary">Прапусціць праверкі падключэння ў выпадку праблем з падключэннем (напрыклад, пераход у аўтаномны рэжым, нават калі сетка падключана)</string>
|
||||
<string name="disable_nsfw_notifications">Адключыць апавяшчэння NSFW</string>
|
||||
<string name="disable">Адкл.</string>
|
||||
<string name="sources_disabled">Крыніцы адключаны</string>
|
||||
</resources>
|
||||
@@ -3,31 +3,46 @@
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d předmět</item>
|
||||
<item quantity="few">%1$d předměty</item>
|
||||
<item quantity="other">%1$d předměty</item>
|
||||
<item quantity="other">%1$d předmětů</item>
|
||||
</plurals>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nová kapitola</item>
|
||||
<item quantity="few">%1$d nové kapitoly</item>
|
||||
<item quantity="other">%1$d nové kapitoly</item>
|
||||
<item quantity="other">%1$d nových kapitol</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d kapitola</item>
|
||||
<item quantity="few">%1$d kapitoly</item>
|
||||
<item quantity="other">%1$d kapitoly</item>
|
||||
<item quantity="other">%1$d kapitol</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d před minutou</item>
|
||||
<item quantity="few">%1$d před pár minutami</item>
|
||||
<item quantity="other">%1$d před několika minutami</item>
|
||||
<item quantity="one">před %1$d minutou</item>
|
||||
<item quantity="few">před %1$d minutami</item>
|
||||
<item quantity="other">před %1$d minutami</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d před hodinou</item>
|
||||
<item quantity="few">%1$d před pár hodinamy</item>
|
||||
<item quantity="other">%1$d před několika hodinamy</item>
|
||||
<item quantity="one">před %1$d hodinou</item>
|
||||
<item quantity="few">před %1$d hodinami</item>
|
||||
<item quantity="other">před %1$d hodinami</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d před dnem</item>
|
||||
<item quantity="few">%1$d před pár dny</item>
|
||||
<item quantity="other">%1$d před několika dny</item>
|
||||
<item quantity="one">před %1$d dnem</item>
|
||||
<item quantity="few">před %1$d dny</item>
|
||||
<item quantity="other">před %1$d dny</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">před %1$d měsícem</item>
|
||||
<item quantity="few">před %1$d měsíci</item>
|
||||
<item quantity="other">před %1$d měsíci</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="one">%1$d hodina</item>
|
||||
<item quantity="few">%1$d hodiny</item>
|
||||
<item quantity="other">%1$d hodin</item>
|
||||
</plurals>
|
||||
<plurals name="minutes">
|
||||
<item quantity="one">%1$d minuta</item>
|
||||
<item quantity="few">%1$d minuty</item>
|
||||
<item quantity="other">%1$d minut</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -39,7 +39,7 @@
|
||||
<string name="share_image">Sdílet obrázek</string>
|
||||
<string name="_import">Importovat</string>
|
||||
<string name="delete">Smazat</string>
|
||||
<string name="text_file_not_supported">Vyberte buď soubor ZIP či CBZ.</string>
|
||||
<string name="text_file_not_supported">Vyberte soubor ZIP či CBZ.</string>
|
||||
<string name="clear_pages_cache">Vyčistit mezipaměť stran</string>
|
||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||
<string name="standard">Standardní</string>
|
||||
@@ -47,7 +47,7 @@
|
||||
<string name="read_mode">Režim čtení</string>
|
||||
<string name="grid_size">Velikost mřížky</string>
|
||||
<string name="delete_manga">Smazat mangu</string>
|
||||
<string name="text_delete_local_manga">Smazat \"%s\" permanentně z tohoto zařízení\?</string>
|
||||
<string name="text_delete_local_manga">Odstranit trvale \"%s\" z tohoto zařízení?</string>
|
||||
<string name="reader_settings">Nastavení čtečky</string>
|
||||
<string name="switch_pages">Prohodit strany</string>
|
||||
<string name="_continue">Pokračovat</string>
|
||||
@@ -61,9 +61,9 @@
|
||||
<string name="text_empty_holder_primary">Je tu nějak prázdno…</string>
|
||||
<string name="text_search_holder_secondary">Zkuste přeformulovat dotaz.</string>
|
||||
<string name="text_history_holder_primary">To co čtete se zobrazí zde</string>
|
||||
<string name="text_history_holder_secondary">Co si přečíst, najdete v sekci «Prozkoumat»</string>
|
||||
<string name="text_history_holder_secondary">Najděte co číst v sekci « Prozkoumat»</string>
|
||||
<string name="text_local_holder_primary">Nejdříve něco uložte</string>
|
||||
<string name="text_local_holder_secondary">Uložte to z online zdrojů nebo importujte soubor.</string>
|
||||
<string name="text_local_holder_secondary">Uložte něco z online zdrojů nebo importujte soubor.</string>
|
||||
<string name="recent_manga">Nedávné</string>
|
||||
<string name="manga_shelf">Polička</string>
|
||||
<string name="pages_animation">Animace stránek</string>
|
||||
@@ -167,7 +167,7 @@
|
||||
<string name="removal_completed">Odstraňování dokončeno</string>
|
||||
<string name="download_slowdown">Zpomalení stahování</string>
|
||||
<string name="local_manga_processing">Zpracovávání uložených mang</string>
|
||||
<string name="chapters_will_removed_background">Kapitoly budou na pozadí odstraněny</string>
|
||||
<string name="chapters_will_removed_background">Kapitoly budou odstraněny na pozadí</string>
|
||||
<string name="account_already_exists">Účet již existuje</string>
|
||||
<string name="back">Zpět</string>
|
||||
<string name="sync_title">Synchronizujte svá data</string>
|
||||
@@ -207,10 +207,10 @@
|
||||
<string name="appwidget_shelf_description">Manga z vašich oblíbených</string>
|
||||
<string name="appwidget_recent_description">Vaše nedávno čtená manga</string>
|
||||
<string name="report">Hlášení</string>
|
||||
<string name="show_reading_indicators">Zobrazovat idikátory pokroku</string>
|
||||
<string name="show_reading_indicators">Zobrazovat indikátory pokroku ve čtení</string>
|
||||
<string name="data_deletion">Smazání dat</string>
|
||||
<string name="show_reading_indicators_summary">Zobrazovat procento čtení v historii a oblíbených</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Manga označená jako NSFW nebude nikdy přidána do historie a nebude ukládán váš pokrok</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Manga označená jako NSFW nebude nikdy přidána do historie a nebude ukládán Váš pokrok</string>
|
||||
<string name="clear_cookies_summary">Může pomoci v případě nekterých problémů. Všechny autorizace budou neplatné</string>
|
||||
<string name="show_all">Zobrazit vše</string>
|
||||
<string name="invalid_domain_message">Neplatná doména</string>
|
||||
@@ -251,7 +251,7 @@
|
||||
<string name="manga_error_description_pattern">Podrobnosti chyby:<br><tt>%1$s</tt><br><br>1. Zkuste <a href=%2$s>otveřít mangu v prohlížeči</a> abyste se ujistili že je dostupná na zdroji<br>2. Ujistěte se že používáte <a href=kotatsu://about>nejnovější verzi Kotatsu</a><br>3. Pokud je dostupná, pošlete hlášení o chybě vývojářům.</string>
|
||||
<string name="history_shortcuts">Zobrazovat zkratky nedávných mang</string>
|
||||
<string name="history_shortcuts_summary">Udělejte nedávné mangy dostupné dlouhým kliknutím na ikonu aplikace</string>
|
||||
<string name="reader_control_ltr_summary">Klikněte do pravého rohu nebo stisknutí pravého tlačítka vždy změní na další stranu</string>
|
||||
<string name="reader_control_ltr_summary">Kliknutím do pravého rohu nebo stisknutím pravého tlačítka vždy zobrazí následující stranu.</string>
|
||||
<string name="reader_control_ltr">Ovládání ergonomické čtečky</string>
|
||||
<string name="color_correction">Korekce barev</string>
|
||||
<string name="brightness">Jas</string>
|
||||
@@ -293,7 +293,7 @@
|
||||
<string name="download_started">Stahování začalo</string>
|
||||
<string name="got_it">Mám to</string>
|
||||
<string name="sources_reorder_tip">Klikněte a přidržte na předmětu pro přeskupení</string>
|
||||
<string name="user_agent">UserAgent header</string>
|
||||
<string name="user_agent">Záhlaví UserAgent</string>
|
||||
<string name="settings_apply_restart_required">Prosíme restartujte aplikaci pro aplikování těchto změn</string>
|
||||
<string name="comics_archive_import_description">Můžete vybrat jeden nebo vícs .cbz nebo .zip souborů, každý soubor bude znám jako samostatná manga.</string>
|
||||
<string name="show_on_shelf">Zobrazovat na poličce</string>
|
||||
@@ -338,7 +338,7 @@
|
||||
<string name="network">Síť</string>
|
||||
<string name="data_and_privacy">Data a soukromí</string>
|
||||
<string name="restore_summary">Obnovit nedávno vytvořenou zálohu</string>
|
||||
<string name="reader_info_bar_summary">Zobrazovat aktuální čas a pokrok čtení na vršku displeje</string>
|
||||
<string name="reader_info_bar_summary">Zobrazovat aktuální čas a pokrok ve čtení na vršku displeje</string>
|
||||
<string name="show_pages_numbers_summary">Zobrazovat číslo strany ve spodním rohu</string>
|
||||
<string name="download_option_all_chapters">Všechny kapitoly s překladem %s</string>
|
||||
<string name="download_option_whole_manga">Celá manga</string>
|
||||
@@ -425,4 +425,211 @@
|
||||
<string name="clear_source_cookies_summary">Vyčistit cookies pouze pro specifikované domény. Ve většině případech bude neplatná autorizace</string>
|
||||
<string name="download_option_manual_selection">Vyberte kapitoly manuálně</string>
|
||||
<string name="description">Popis</string>
|
||||
</resources>
|
||||
<string name="state_upcoming">Již brzy</string>
|
||||
<string name="no_manga_sources_found">Na Váš dotaz nebyly nalezeny žádné zdroje mang</string>
|
||||
<string name="downloads_settings_info">Pokud máte problémy s blokováním ze strany serveru, můžete v nastavení zdrojů povolit zpomalení stahování pro jednotlivé zdroje mang</string>
|
||||
<string name="content_rating">Klasifikace obsahu</string>
|
||||
<string name="rating_safe">Bezpečný</string>
|
||||
<string name="chapters_grid_view">Zobrazení v mřížce</string>
|
||||
<string name="categories">Kategorie</string>
|
||||
<string name="online_variant">Online varianta</string>
|
||||
<string name="content_type_hentai">Hentai</string>
|
||||
<string name="disable_nsfw">Vypnout NSFW</string>
|
||||
<string name="email_password_enter_hint">Pro pokračování zadejte svůj e-mail a heslo</string>
|
||||
<string name="this_month">Tento měsíc</string>
|
||||
<string name="voice_search">Hlasové vyhledávání</string>
|
||||
<string name="related_manga">Související manga</string>
|
||||
<string name="color_light">Světlý</string>
|
||||
<string name="color_dark">Tmavý</string>
|
||||
<string name="color_white">Bílá</string>
|
||||
<string name="background">Pozadí</string>
|
||||
<string name="color_black">Černá</string>
|
||||
<string name="data_not_restored">Data nebyla obnovena</string>
|
||||
<string name="in_progress">Probíhá</string>
|
||||
<string name="captcha_required_summary">%s vyžaduje vyřešení captcha aby fungovalo správně</string>
|
||||
<string name="languages">Jazyky</string>
|
||||
<string name="unknown">Neznámé</string>
|
||||
<string name="too_many_requests_message">Příliš mnoho dotazů. Zkuste to později</string>
|
||||
<string name="related_manga_summary">Zobrazí seznam souvisejících mang. V některých případech může být nepřesný nebo chybět</string>
|
||||
<string name="advanced">Pokročilý</string>
|
||||
<string name="manga_list">Seznam mang</string>
|
||||
<string name="main_screen_sections">Oddíly hlavní obrazovky</string>
|
||||
<string name="on_device">Na zařízení</string>
|
||||
<string name="directories">Rejstříky</string>
|
||||
<string name="items_limit_exceeded">Nelze přidávat žádné další položky</string>
|
||||
<string name="to_top">Úplně nahoru</string>
|
||||
<string name="moved_to_top">Přesunuto na začátek</string>
|
||||
<string name="zoom_out">Oddálit</string>
|
||||
<string name="zoom_in">Přiblížit</string>
|
||||
<string name="reader_zoom_buttons_summary">Zobrazit či nezobrazit tlačítka ovládání přiblížení v pravém dolním rohu</string>
|
||||
<string name="enhanced_colors_summary">Snižuje banding, ale může ovlivnit výkon</string>
|
||||
<string name="state_abandoned">Upuštěný</string>
|
||||
<string name="enhanced_colors">32-bitový režim barev</string>
|
||||
<string name="suggest_new_sources_summary">Výzva k povolení nově přidaných zdrojů po aktualizaci aplikace</string>
|
||||
<string name="suggest_new_sources">Navrhnout nové zdroje po aktualizaci aplikace</string>
|
||||
<string name="periodic_backups">Pravidelné zálohy</string>
|
||||
<string name="backup_frequency">Frekvence vytváření záloh</string>
|
||||
<string name="frequency_every_day">Každý den</string>
|
||||
<string name="frequency_every_2_days">Každé 2 dny</string>
|
||||
<string name="frequency_once_per_week">Jednou týdně</string>
|
||||
<string name="frequency_twice_per_month">Dvakrát měsíčně</string>
|
||||
<string name="frequency_once_per_month">Jednou měsíčně</string>
|
||||
<string name="backups_output_directory">Výstupní adresář záloh</string>
|
||||
<string name="periodic_backups_enable">Povolit pravidelné zálohy</string>
|
||||
<string name="last_successful_backup">Poslední úspěšná záloha: %s</string>
|
||||
<string name="lock_screen_rotation">Uzamknout otáčení obrazovky</string>
|
||||
<string name="content_type_other">Jiné</string>
|
||||
<string name="sources_catalog">Katalog zdrojů</string>
|
||||
<string name="source_summary_pattern">%1$s, %2$s</string>
|
||||
<string name="source_enabled">Zdroj povolen</string>
|
||||
<string name="catalog">Katalog</string>
|
||||
<string name="manage_sources">Spravovat zdroje</string>
|
||||
<string name="manual">Ručně</string>
|
||||
<string name="available_d">Dostupný: %1$d</string>
|
||||
<string name="disable_nsfw_summary">Zakázat NSFW zdroje a skrýt mangy pro dospělé ze seznamu, je-li to možné</string>
|
||||
<string name="error_multiple_genres_not_supported">Filtrování podle více žánrů tento zdroj mang nepodporuje</string>
|
||||
<string name="error_multiple_states_not_supported">Filtrování podle více stavů tento zdroj mang nepodporuje</string>
|
||||
<string name="error_search_not_supported">Hledání není v tomto zdroji mang podporovaný</string>
|
||||
<string name="skip">Přeskočit</string>
|
||||
<string name="grayscale">Odstíny šedi</string>
|
||||
<string name="data_not_restored_text">Ujistěte se, že jste vybrali ten správný zálohový soubor</string>
|
||||
<string name="manage_categories">Spravovat kategorie</string>
|
||||
<string name="show">Ukázat</string>
|
||||
<string name="apply">Použít</string>
|
||||
<string name="speed_value">x%.1f</string>
|
||||
<string name="search_hint">Zadejte název mangy, žánr nebo název zdroje</string>
|
||||
<string name="progress">Pokrok</string>
|
||||
<string name="order_added">Přidáno</string>
|
||||
<string name="error_corrupted_file">Vrácená data jsou neplatná nebo je soubor poškozen</string>
|
||||
<string name="reader_zoom_buttons">Zobrazit tlačítka pro přiblížení</string>
|
||||
<string name="keep_screen_on">Ponechat obrazovku zapnutou</string>
|
||||
<string name="keep_screen_on_summary">Nevypínat obrazovku během čtení mangy</string>
|
||||
<string name="list_options">Seznam možností</string>
|
||||
<string name="by_relevance">Relevance</string>
|
||||
<string name="state_paused">Pozastavený</string>
|
||||
<string name="reader_optimize">Snížit spotřebu paměti (beta)</string>
|
||||
<string name="reader_optimize_summary">Snížit kvalitu stránek mimo obrazovku pro snížení spotřeby paměti</string>
|
||||
<string name="state">Stav</string>
|
||||
<string name="globally">Globálně</string>
|
||||
<string name="this_manga">Tato manga</string>
|
||||
<string name="error_filter_locale_genre_not_supported">Filtrování podle žánrů nebo lokalizace tento zdroj nepodporuje</string>
|
||||
<string name="error_filter_states_genre_not_supported">Filtrování podle žánrů nebo stavů tento zdroj nepodporuje</string>
|
||||
<string name="welcome_text">Vyberte, které zdroje obsahu chcete povolit. To lze také později změnit v nastavení</string>
|
||||
<string name="genres_search_hint">Začněte psát název žánru</string>
|
||||
<string name="sync_auth">Přihlášení k synchronizačnímu účtu</string>
|
||||
<string name="restore">Obnovit</string>
|
||||
<string name="backup_date_">Datum obnovení: %s</string>
|
||||
<string name="by_name_reverse">Název obráceně</string>
|
||||
<string name="suggestions_wifi_only_summary">Neaktualizujte návrhy pomocí síťových připojení s účtovaným objemem dat</string>
|
||||
<string name="tracker_wifi_only_summary">Nevyhledávejte nové kapitoly pomocí síťových připojení s účtovaným objemem dat</string>
|
||||
<string name="color_correction_apply_text">Tato nastavení lze použít globálně nebo pouze pro aktuální manga. Pokud bude použito globálně, individuální nastavení nebudou přepsána.</string>
|
||||
<string name="content_type_comics">Komiks</string>
|
||||
<string name="no_manga_sources_catalog_text">V této sekci nejsou k dispozici žádné zdroje, případně již byly všechny přidány.
|
||||
\nZůstaňte v kontaktu</string>
|
||||
<string name="content_type_manga">Manga</string>
|
||||
<string name="disable_battery_optimization_summary_downloads">Mohlo by to pomoci se zahájením stahování, pokud s tím máte nějaké problémy</string>
|
||||
<string name="genres_exclude">Vyloučit žánry</string>
|
||||
<string name="unsupported_backup_message">Vyberte prosím správný zálohový soubor Kotatsu</string>
|
||||
<string name="last_used">Naposledy používané</string>
|
||||
<string name="volume_">Díl %d</string>
|
||||
<string name="remove_from_history">Odstranit z historie</string>
|
||||
<string name="incognito_mode_hint">Váš pokrok ve čtení nebude uložen</string>
|
||||
<string name="last_read">Poslední přečtený</string>
|
||||
<string name="minutes_short">%d m</string>
|
||||
<string name="hours_minutes_short">%1$d h %2$d m</string>
|
||||
<string name="vertical">Svisle</string>
|
||||
<string name="ask_for_dest_dir_every_time">Pokaždý se ptát na cílovou složku</string>
|
||||
<string name="show_menu">Zobrazit menu</string>
|
||||
<string name="mark_as_completed_prompt">Označit vybranou mangu jako kompletně přečtenou?
|
||||
\n
|
||||
\nPozor: Stávající pokrok ve čtení se ztratí.</string>
|
||||
<string name="remaining_time_pattern">%1$s %2$s</string>
|
||||
<string name="volume_unknown">Neznámý díl</string>
|
||||
<string name="prev_chapter">Předchozí kapitola</string>
|
||||
<string name="next_chapter">Následující kapitola</string>
|
||||
<string name="prev_page">Předchozí stránka</string>
|
||||
<string name="next_page">Následující stránka</string>
|
||||
<string name="reader_actions">Akce čtečky</string>
|
||||
<string name="reader_actions_summary">Konfigurace akcí pro oblasti obrazovky, na které lze klepnout</string>
|
||||
<string name="switch_pages_volume_buttons">Povolit tlačítka ovládání hlasitosti</string>
|
||||
<string name="switch_pages_volume_buttons_summary">Používání tlačítek ovládání hlasitosti k přetáčení stránek</string>
|
||||
<string name="tap_action">Akce klepnutím</string>
|
||||
<string name="long_tap_action">Akce dlouhým klepnutím</string>
|
||||
<string name="none">Žádný</string>
|
||||
<string name="use_two_pages_landscape">Použít rozvržení dvou stránek na šířku (beta)</string>
|
||||
<string name="default_webtoon_zoom_out">Výchozí zvětšení pro webtoon</string>
|
||||
<string name="reading_time_estimation">Zobrazit odhadovanou dobu čtení</string>
|
||||
<string name="reading_time_estimation_summary">Odhadovaná doba čtení může být nepřesná</string>
|
||||
<string name="pages_saving">Ukládání stránek</string>
|
||||
<string name="default_page_save_dir">Výchozí složka k ukládání stránek</string>
|
||||
<string name="single_cbz_file">Jediný soubor CBZ</string>
|
||||
<string name="preferred_download_format">Upřednostňovaný formát stahování</string>
|
||||
<string name="multiple_cbz_files">Vícero souborů CBZ</string>
|
||||
<string name="clear_stats_confirm">Opravdu chcete vymazat veškeré statistiky čtení? Tato akci nelze vrátit zpět.</string>
|
||||
<string name="pages_read_s">Přečtené stránky: %s</string>
|
||||
<string name="migrate_confirmation">Manga \"%1$s\" z \"%2$s\" bude ve Vaší historii a oblíbených položkách nahrazena \"%3$s\" z \"%4$s\" (je-li k dispozici)</string>
|
||||
<string name="alternatives">Jiné možnosti</string>
|
||||
<string name="migrate">Přesunout</string>
|
||||
<string name="manga_migration">Přesun mangy</string>
|
||||
<string name="migration_completed">Přesun dokončen</string>
|
||||
<string name="delete_read_chapters">Odstranit přečtené kapitoly</string>
|
||||
<string name="delete_read_chapters_prompt">Toto trvale odstraní veškeré přečtené kapitoly z místního uložiště. Můžete je stáhnout znovu později, ale importované kapitoly budou ztraceny navždy</string>
|
||||
<string name="reading_stats">Statistické údaje čtení</string>
|
||||
<string name="other_manga">Jiné mangy</string>
|
||||
<string name="less_than_minute">Méně než minutu</string>
|
||||
<string name="clear_stats">Vymazat statistiky</string>
|
||||
<string name="week">Týden</string>
|
||||
<string name="month">Měsíc</string>
|
||||
<string name="all_time">Po celou dobu</string>
|
||||
<string name="day">Den</string>
|
||||
<string name="three_months">Tři měsíce</string>
|
||||
<string name="empty_stats_text">Pro zvolené období nejsou k dispozici žádné statistiky</string>
|
||||
<string name="show_pages_thumbs_summary">Povolit záložku \"Stránky\" na obrazovce podrobností</string>
|
||||
<string name="show_pages_thumbs">Zobrazit náhledy stránek</string>
|
||||
<string name="fullscreen_mode">Režim celé obrazovky</string>
|
||||
<string name="reader_fullscreen_summary">Skrýt systémový stavový a navigační řádek</string>
|
||||
<string name="category_hidden_done">Tato kategorie byla skryta z hlavní obrazovky a je přístupná přes Menu → Spravovat kategorie</string>
|
||||
<string name="automatic">Automatický</string>
|
||||
<string name="show_updated">Zobrazit aktualizované</string>
|
||||
<string name="split_by_translations_summary">Zobrazit kapitoly s různými překlady samostatně, nikoli v jednom seznamu</string>
|
||||
<string name="order_oldest">Nejstarší</string>
|
||||
<string name="long_ago_read">Přečteno před dlouhou dobou</string>
|
||||
<string name="unread">Nepřečteno</string>
|
||||
<string name="suggestions_unavailable_text">Funkce návrhů je vypnutá</string>
|
||||
<string name="check_for_new_chapters_disabled">Zjišťování nových kapitol je vypnuté</string>
|
||||
<string name="rating_suggestive">Sugestivní</string>
|
||||
<string name="default_tab">Výchozí záložka</string>
|
||||
<string name="mark_as_completed">Označit jako dokončený</string>
|
||||
<string name="toggle_ui">Zobrazit/skrýt uživatelské rozhraní</string>
|
||||
<string name="statistics">Statistiky</string>
|
||||
<string name="no_chapters_deleted">Nebyly odstraněny žádné kapitoly</string>
|
||||
<string name="chapters_deleted_pattern">Odstraněn %1$s, smazán %2$s</string>
|
||||
<string name="delete_read_chapters_auto">Odstraňovat přečtené kapitoly automaticky</string>
|
||||
<string name="runs_on_app_start">Spouští se při spuštění aplikace</string>
|
||||
<string name="webtoon_gaps">Mezery v režimu webtoon</string>
|
||||
<string name="webtoon_gaps_summary">Zobrazit svislé mezery mezi stránkami v režimu webtoon</string>
|
||||
<string name="location">Umístění</string>
|
||||
<string name="config_reset_confirm">Obnovit nastavení na výchozí hodnoty? Tuto akci nelze vrátit zpět.</string>
|
||||
<string name="search_suggestions">Návrhy na vyhledávání</string>
|
||||
<string name="recent_queries">Nedávné dotazy</string>
|
||||
<string name="suggested_queries">Navrhované dotazy</string>
|
||||
<string name="error_no_data_received">Ze serveru nebyla přijata žádná data</string>
|
||||
<string name="rating_adult">Dospělý</string>
|
||||
<string name="show_labels_in_navbar">Zobrazit štítky na navigačním panelu</string>
|
||||
<string name="stats_cleared">Statistiky smazány</string>
|
||||
<string name="delete_read_chapters_summary">Odstranit již přečtené kapitoly z místního uložiště k uvolnění paměti</string>
|
||||
<string name="split_by_translations">Rozdělení podle překladů</string>
|
||||
<string name="hours_short">%d h</string>
|
||||
<string name="missing_storage_permission">Přístup k manze na externím úložišti není povolen</string>
|
||||
<string name="authors">Autoři</string>
|
||||
<string name="enable_source">Povolit zdroj</string>
|
||||
<string name="unsupported_source">Tento zdroj mang není podporovaný</string>
|
||||
<string name="blocked_by_server_message">Jste zablokováni serverem. Zkuste použít jiné síťové připojení (VPN, proxy atd.)</string>
|
||||
<string name="less_frequently">Méně často</string>
|
||||
<string name="more_frequently">Častěji</string>
|
||||
<string name="frequency_of_check">Četnost kontrol</string>
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
<string name="pin_navigation_ui">Upevnit uživatelské rozhraní pro navigaci</string>
|
||||
<string name="fix">Upevnit</string>
|
||||
<string name="pin_navigation_ui_summary">Neskrývat navigační panel a zobrazení vyhledávání při posouvání</string>
|
||||
</resources>
|
||||
@@ -535,4 +535,4 @@
|
||||
<string name="appwidget_recent_description">Τα πρόσφατα διαβασμένα manga σου</string>
|
||||
<string name="clear_cookies_summary">Μπορεί να βοηθήσει σε περίπτωση κάποιων προβλημάτων. Όλες οι εξουσιοδοτήσεις θα ανακληθούν</string>
|
||||
<string name="category_hidden_done">Αυτή η κατηγορία αποκρύφτηκε από την αρχική οθόνη και είναι προσβάσιμη μέσω του Μενού → Διαχείριση κατηγοριών</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -301,7 +301,7 @@
|
||||
<string name="categories_delete_confirm">¿Estás seguro de que quieres eliminar las categorías favoritas seleccionadas\?
|
||||
\nTodos los mangas en ella se perderán y esto no se puede deshacer.</string>
|
||||
<string name="explore">Explorar</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="exit_confirmation_summary">Pulse dos veces «Atrás» para salir de la aplicación</string>
|
||||
<string name="exit_confirmation">Confirmación de salida</string>
|
||||
<string name="pages_cache">Caché de páginas</string>
|
||||
@@ -560,22 +560,22 @@
|
||||
<string name="default_webtoon_zoom_out">Alejar el zoom del webtoon predeterminado</string>
|
||||
<string name="fullscreen_mode">Modo de pantalla completa</string>
|
||||
<string name="reader_fullscreen_summary">Ocultar las barras de estado y navegación del sistema</string>
|
||||
<string name="reading_time_estimation">Mostrar el iempo estimado de lectura</string>
|
||||
<string name="reading_time_estimation">Mostrar el tiempo estimado de lectura</string>
|
||||
<string name="reading_time_estimation_summary">El valor estimado puede ser inexacto</string>
|
||||
<string name="check_for_new_chapters_disabled">La búsqueda de nuevos capítulos está desactivada</string>
|
||||
<string name="suggestions_unavailable_text">Sugerencias desactivadas</string>
|
||||
<string name="show_labels_in_navbar">Mostrar etiquetas en la barra de navegación</string>
|
||||
<string name="ask_for_dest_dir_every_time">Preguntar siempre por el directorio de destino</string>
|
||||
<string name="remove_from_history">Eliminar del historial</string>
|
||||
<string name="pages_saving">Guardar páginas</string>
|
||||
<string name="pages_saving">Guardando páginas</string>
|
||||
<string name="default_page_save_dir">Directorio predeterminado para guardar páginas</string>
|
||||
<string name="location">Ubicación</string>
|
||||
<string name="automatic">Automático</string>
|
||||
<string name="single_cbz_file">Un único archivo CBZ</string>
|
||||
<string name="multiple_cbz_files">Varios archivos CBZ</string>
|
||||
<string name="preferred_download_format">Formato preferido para la descarga</string>
|
||||
<string name="reading_stats">Leyendo las estadísticas</string>
|
||||
<string name="other_manga">Otro manga</string>
|
||||
<string name="reading_stats">Estadística de lectura</string>
|
||||
<string name="other_manga">Otros mangas</string>
|
||||
<string name="less_than_minute">Menos de un minuto</string>
|
||||
<string name="statistics">Estadísticas</string>
|
||||
<string name="stats_cleared">Estadísticas borradas</string>
|
||||
@@ -591,7 +591,7 @@
|
||||
<string name="delete_read_chapters">Borrar capítulos leídos</string>
|
||||
<string name="no_chapters_deleted">No se han eliminado capítulos</string>
|
||||
<string name="chapters_deleted_pattern">Eliminado %1$s, borrado %2$s</string>
|
||||
<string name="delete_read_chapters_summary">Borra los capítulos que ya has leído de la memoria local para liberar espacio</string>
|
||||
<string name="delete_read_chapters_summary">Borrar los capítulos que ya has leído de la memoria local para liberar espacio</string>
|
||||
<string name="delete_read_chapters_prompt">Esto borrará permanentemente todos los capítulos marcados como leídos de tu almacenamiento local. Puede volver a descargarlo más tarde, pero los capítulos importados se perderán para siempre</string>
|
||||
<string name="alternatives">Alternativas</string>
|
||||
<string name="migrate">Migrar</string>
|
||||
@@ -632,4 +632,13 @@
|
||||
<string name="pin_navigation_ui_summary">No ocultar la barra de navegación y la vista de búsqueda al desplazarse</string>
|
||||
<string name="pin_navigation_ui">Pin de la interfaz de usuario</string>
|
||||
<string name="blocked_by_server_message">Estás bloqueado por el servidor. Intente utilizar una conexión de red diferente (VPN, Proxy, etc.)</string>
|
||||
<string name="disable">Desactivar</string>
|
||||
<string name="sources_disabled">Fuentes deshabilitadas</string>
|
||||
<string name="disable_connectivity_check">Desactivar el control de conectividad</string>
|
||||
<string name="ignore_ssl_errors_summary">Puede desactivar la verificación de certificados SSL en caso de que tenga problemas relacionados con SSL al acceder a recursos de red. Esto puede afectar a su seguridad. Es necesario reiniciar la aplicación después de cambiar esta configuración.</string>
|
||||
<string name="disable_connectivity_check_summary">Omitir la comprobación de la conectividad en caso de que tenga problemas con ella (por ejemplo, si pasa al modo sin conexión aunque la red esté conectada)</string>
|
||||
<string name="disable_nsfw_notifications">Deshabilitar notificaciones NSFW</string>
|
||||
<string name="disable_nsfw_notifications_summary">No mostrar notificaciones sobre actualizaciones de manga NSFW</string>
|
||||
<string name="tracker_debug_info">Comprobando el registro de nuevos capítulos</string>
|
||||
<string name="tracker_debug_info_summary">Información de depuración sobre verificaciones de antecedentes para nuevos capítulos</string>
|
||||
</resources>
|
||||
@@ -422,4 +422,4 @@
|
||||
<string name="downloads_resumed">Allalaadimised on jätkanud</string>
|
||||
<string name="invert_colors">Värvide ümberpööramine</string>
|
||||
<string name="proxy">Puhverserver</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -182,7 +182,7 @@
|
||||
<string name="screenshots_block_nsfw">I-block sa NSFW</string>
|
||||
<string name="suggestions_summary">Magmungkahi ng manga batay sa iyong mga kagustuhan</string>
|
||||
<string name="suggestions_info">Ang lahat ng data ay lokal lamang na sinusuri sa device na ito at hindi kailanman ipinadala kahit saan.</string>
|
||||
<string name="disabled">Hindi pinagana</string>
|
||||
<string name="disabled">Di pinagana</string>
|
||||
<string name="reset_filter">I-reset ang filter</string>
|
||||
<string name="text_feed_holder">Ang mga bagong kabanata ng iyong binabasa ay ipinapakita dito</string>
|
||||
<string name="rotate_screen">I-rotate ang screen</string>
|
||||
@@ -250,7 +250,7 @@
|
||||
<string name="other_cache">Iba pang cache</string>
|
||||
<string name="storage_usage">Paggamit ng storage</string>
|
||||
<string name="available">Magagamit na</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="removed_from_favourites">Inalis sa mga paborito</string>
|
||||
<string name="options">Mga pagpipilian</string>
|
||||
<string name="incognito_mode">Incognito mode</string>
|
||||
@@ -322,12 +322,12 @@
|
||||
\nAng lahat ng manga sa loob nito ay mawawala at hindi na ito mababawi.</string>
|
||||
<string name="bookmark_added">Idinagdag ang bookmark</string>
|
||||
<string name="detect_reader_mode_summary">Awtomatikong matukoy kung ang manga ay webtoon</string>
|
||||
<string name="disable_battery_optimization">Huwag paganahin ang pag-optimize ng baterya</string>
|
||||
<string name="disable_battery_optimization">Di paganahin ang pag-optimize ng baterya</string>
|
||||
<string name="disable_battery_optimization_summary">Tumutulong sa mga pagsusuri sa mga update sa background</string>
|
||||
<string name="status_planned">Nakaplano</string>
|
||||
<string name="status_completed">Nakumpleto na</string>
|
||||
<string name="status_on_hold">Naka-hold</string>
|
||||
<string name="disable_all">Huwag paganahin ang lahat</string>
|
||||
<string name="disable_all">Di paganahin lahat</string>
|
||||
<string name="use_fingerprint">Gumamit ng fingerprint kung magagamit</string>
|
||||
<string name="report">Ulat</string>
|
||||
<string name="reset">I-reset</string>
|
||||
@@ -338,7 +338,7 @@
|
||||
<string name="reader_slider">Ipakita ang slider ng paglipat ng pahina</string>
|
||||
<string name="manga_error_description_pattern">Mga detalye ng error:<br><tt>%1$s</tt><br><br>1. Subukang <a href=%2$s>magbukas ng manga sa isang web browser</a> upang matiyak na available ito sa souce<br>2. Tiyaking ginagamit mo ang <a href=kotatsu://about>pinakabagong bersyon ng Kotatsu</a><br>3. Kung available ito, magpadala ng ulat ng error sa mga developer.</string>
|
||||
<string name="enable_logging">Paganahin ang pag-log</string>
|
||||
<string name="source_disabled">Hindi pinagana ang source</string>
|
||||
<string name="source_disabled">Di pinagana ang source</string>
|
||||
<string name="importing_manga">Pag-import ng manga</string>
|
||||
<string name="import_completed_hint">Maaari mong tanggalin ang orihinal na file mula sa storage upang makatipid ng espasyo</string>
|
||||
<string name="compact">Compact</string>
|
||||
@@ -446,7 +446,7 @@
|
||||
<string name="show">Ipakita</string>
|
||||
<string name="unknown">Di-kilala</string>
|
||||
<string name="in_progress">Isinasagawa</string>
|
||||
<string name="disable_nsfw">I-disable ang NSFW</string>
|
||||
<string name="disable_nsfw">Di paganahin ang NSFW</string>
|
||||
<string name="error_corrupted_file">Nabalik ang di-wastong data o na-corrupt ang file</string>
|
||||
<string name="related_manga_summary">Magpakita ng listahan ng mga kaugnay na manga. Sa ilang mga kaso, ito ay maaaring may mali o nawawala</string>
|
||||
<string name="advanced">Advanced</string>
|
||||
@@ -495,7 +495,7 @@
|
||||
<string name="lock_screen_rotation">Rotation ng lock screen</string>
|
||||
<string name="manual">Manu-mano</string>
|
||||
<string name="source_enabled">Napaganang source</string>
|
||||
<string name="disable_nsfw_summary">Huwag paganahin ang mga source na NSFW at itago ang manga na pang-adulto mula sa listahan kung maaari</string>
|
||||
<string name="disable_nsfw_summary">Di paganahin ang mga source na NSFW at itago ang manga na pang-matanda mula sa listahan kung maaari</string>
|
||||
<string name="no_manga_sources_catalog_text">Walang mga sources na maaring gamitin sa seksyong ito, o maaring na-add na lahat.
|
||||
\nManatiling nakatutok para sa iba pang mga source</string>
|
||||
<string name="available_d">Available: %1$d</string>
|
||||
@@ -565,8 +565,8 @@
|
||||
<string name="reader_actions_summary">Ayusin ang mga pagkilos para sa mga nata-tap na lugar ng screen</string>
|
||||
<string name="switch_pages_volume_buttons">Paganahin ang mga volume button</string>
|
||||
<string name="switch_pages_volume_buttons_summary">Gumamit ng mga volume button para sa paglipat ng mga pahina</string>
|
||||
<string name="suggestions_unavailable_text">Naka-disable ang feature na Mga suhestiyon</string>
|
||||
<string name="check_for_new_chapters_disabled">Naka-disable ang pagsuri para sa mga bagong kabanata</string>
|
||||
<string name="suggestions_unavailable_text">Di pinagana ang feature na Mga suhestiyon</string>
|
||||
<string name="check_for_new_chapters_disabled">Di pinagana ang pagsusuri para sa mga bagong kabanata</string>
|
||||
<string name="reading_time_estimation">Ipakita ang tinantyang oras ng pagbabasa</string>
|
||||
<string name="reading_time_estimation_summary">Maaaring hindi tumpak ang halaga ng pagtatantya ng oras</string>
|
||||
<string name="location">Lokasyon</string>
|
||||
@@ -611,7 +611,7 @@
|
||||
<string name="show_pages_thumbs">Ipakita ang mga thumbnail ng pahina</string>
|
||||
<string name="show_pages_thumbs_summary">Paganahin ang tab na \"Mga Pahina\" sa screen ng mga detalye</string>
|
||||
<string name="error_no_data_received">Walang natanggap na data mula sa server</string>
|
||||
<string name="unsupported_backup_message">Mangyaring pumili ng wastong backup file ng Kotatsu</string>
|
||||
<string name="unsupported_backup_message">Pumili ng wastong backup file ng Kotatsu</string>
|
||||
<string name="hours_short">%d o</string>
|
||||
<string name="minutes_short">%d m</string>
|
||||
<string name="hours_minutes_short">%1$d o %2$d m</string>
|
||||
@@ -632,4 +632,9 @@
|
||||
<string name="pin_navigation_ui">I-pin ang UI ng nabigasyon</string>
|
||||
<string name="pin_navigation_ui_summary">Huwag itago ang navigation bar at ang view ng paghahanap sa pag-scroll</string>
|
||||
<string name="blocked_by_server_message">Hinarangan ka ng server. Subukang gumamit ng ibang koneksyon sa network (VPN, Proxy, atbp.)</string>
|
||||
<string name="disable">Di paganahin</string>
|
||||
<string name="sources_disabled">Di pinagana ang mga source</string>
|
||||
<string name="disable_connectivity_check">Di paganahin ang pagtingin sa koneksyon</string>
|
||||
<string name="disable_connectivity_check_summary">Laktawan ang pagsuri sa koneksyon kung sakaling mayroon kang isyu rito (hal. pagpunta sa offline mode kahit na nakakonekta sa network)</string>
|
||||
<string name="ignore_ssl_errors_summary">Maaaring di paganahin ang pag-verify ng mga SSL certificate kung sakaling makaharap ka ng mga isyu na nauugnay sa SSL kapag nag-a-access ng mga network resource. Ito ay makaapekto sa iyong seguridad. Kinakailangang mag-restart ang aplikasyon pagkatapos baguhin ang setting na ito.</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="nothing_found">कुछ नहीं मिला</string>
|
||||
<string name="history_is_empty">अभी तक कोई इतिहास नहीं है</string>
|
||||
<string name="read">पढ़ें</string>
|
||||
<string name="add_to_favourites">इसे पसंद करें</string>
|
||||
<string name="add_to_favourites">इसे पसंदीदा बनाएं</string>
|
||||
<string name="add">जोड़ें</string>
|
||||
<string name="save">सहेजें</string>
|
||||
<string name="newest">नवीनतम</string>
|
||||
@@ -14,7 +14,7 @@
|
||||
<string name="close">बंद करे</string>
|
||||
<string name="try_again">पुनः प्रयास करें</string>
|
||||
<string name="you_have_not_favourites_yet">अभी तक कोई पसंदीदा नहीं है</string>
|
||||
<string name="remove">हटाएँ</string>
|
||||
<string name="remove">हटाएं</string>
|
||||
<string name="by_name">नाम</string>
|
||||
<string name="popular">लोकप्रिय</string>
|
||||
<string name="local_storage">स्थानीय स्टॉरेज</string>
|
||||
@@ -27,45 +27,45 @@
|
||||
<string name="chapter_d_of_d">अध्याय %1$d, %2$d में से</string>
|
||||
<string name="computing_">गणना हो रही है…</string>
|
||||
<string name="add_new_category">नई श्रेणी</string>
|
||||
<string name="clear_history">इतिहास मिटाए</string>
|
||||
<string name="share">शेयर</string>
|
||||
<string name="clear_history">इतिहास साफ करें</string>
|
||||
<string name="share">साझा करें</string>
|
||||
<string name="create_shortcut">शॉर्टकट बनाएं…</string>
|
||||
<string name="share_s">%s साझा करें</string>
|
||||
<string name="search">खोजें</string>
|
||||
<string name="search_manga">मंगा खोजें</string>
|
||||
<string name="manga_downloading_">डाउनलोड हो रहा है…</string>
|
||||
<string name="downloads">डाउनलोड्स</string>
|
||||
<string name="downloads">डाउनलोड</string>
|
||||
<string name="by_rating">रेटिंग</string>
|
||||
<string name="clear">साफ करें</string>
|
||||
<string name="clear">साफ करे</string>
|
||||
<string name="page_saved">सहेजा गया</string>
|
||||
<string name="share_image">छवि साझा करें</string>
|
||||
<string name="delete">मिटाएं</string>
|
||||
<string name="clear_pages_cache">पेज कैश साफ़ करें</string>
|
||||
<string name="clear_pages_cache">पृष्ठ कैशे साफ करें</string>
|
||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||
<string name="standard">सामान्य</string>
|
||||
<string name="webtoon">Webtoon</string>
|
||||
<string name="standard">मानक</string>
|
||||
<string name="webtoon">वेबटून</string>
|
||||
<string name="remote_sources">मांगा स्रोत</string>
|
||||
<string name="download_complete">डाउनलोड हो गया</string>
|
||||
<string name="processing_">प्रक्रिया चल रही है…</string>
|
||||
<string name="download_complete">डाउनलोड किया गया</string>
|
||||
<string name="processing_">प्रसंस्करण…</string>
|
||||
<string name="history">इतिहास</string>
|
||||
<string name="grid">ग्रिड</string>
|
||||
<string name="loading_">लोड हो रहा है…</string>
|
||||
<string name="text_file_not_supported">ZIP या CBZ मैं से एक फ़ाइल चुनें।</string>
|
||||
<string name="updated">अपडेट हो गया</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" को स्थानीय स्टोरेज से हटा दिया गया</string>
|
||||
<string name="save_page">पेज सहेजें</string>
|
||||
<string name="text_file_not_supported">ZIP या CBZ मैं से एक फाइल चुनें।</string>
|
||||
<string name="updated">अद्यतित</string>
|
||||
<string name="_s_deleted_from_local_storage">\"%s\" को स्थानीय स्टोरेज से मिटा दिया गया</string>
|
||||
<string name="save_page">पृष्ठ सहेजें</string>
|
||||
<string name="_import">आयात</string>
|
||||
<string name="operation_not_supported">यह कार्य समर्थित नहीं है</string>
|
||||
<string name="sort_order">क्रमबद्धता क्रम</string>
|
||||
<string name="sort_order">छंटाई क्रम</string>
|
||||
<string name="list">सूची</string>
|
||||
<string name="filter">फिल्टर</string>
|
||||
<string name="theme">थीम</string>
|
||||
<string name="follow_system">सिस्टम की पालन करें</string>
|
||||
<string name="follow_system">सिस्टम की पालना</string>
|
||||
<string name="pages">पन्ने</string>
|
||||
<string name="no_description">कोई विवरण नहीं है</string>
|
||||
<string name="updates">अपडेट</string>
|
||||
<string name="manga_shelf">शेल्फ</string>
|
||||
<string name="text_history_holder_secondary">\"एक्सप्लोर करें\" अनुभाग में जानें कि क्या पढ़ना है</string>
|
||||
<string name="updates">अद्यतन</string>
|
||||
<string name="manga_shelf">दराज</string>
|
||||
<string name="text_history_holder_secondary">«अन्वेषण करें» अनुभाग में जानें कि क्या पढ़ना है</string>
|
||||
<string name="all_favourites">सभी पसंदीदा</string>
|
||||
<string name="light_indicator">LED सूचक</string>
|
||||
<string name="favourites_categories">पसंदीदा श्रेणियां</string>
|
||||
@@ -73,7 +73,7 @@
|
||||
<string name="switch_pages">पन्नो को बदले</string>
|
||||
<string name="rotate_screen">स्क्रीन घुमायें</string>
|
||||
<string name="vibration">कंपन</string>
|
||||
<string name="remove_category">हटाएँ</string>
|
||||
<string name="remove_category">हटाएं</string>
|
||||
<string name="read_mode">पढ़ने की विधि</string>
|
||||
<string name="internal_storage">आंतरिक स्टोरेज</string>
|
||||
<string name="read_later">बाद में पढ़े</string>
|
||||
@@ -81,46 +81,46 @@
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%2$d में से %1$d</string>
|
||||
<string name="text_feed_holder">आप जो पढ़ रहे हैं उसके नए अध्याय यहां दिखाए गए हैं</string>
|
||||
<string name="favourites_category_empty">रिक्त श्रेणी</string>
|
||||
<string name="manga_save_location">डाउनलोड फ़ोल्डर</string>
|
||||
<string name="updates_feed_cleared">साफ हो गया</string>
|
||||
<string name="update">अपडेट</string>
|
||||
<string name="feed_will_update_soon">फीड अपडेट शीघ्र ही आरंभ होगा</string>
|
||||
<string name="manga_save_location">डाउनलोड फोल्डर</string>
|
||||
<string name="updates_feed_cleared">साफ किया गया</string>
|
||||
<string name="update">अद्यतन</string>
|
||||
<string name="feed_will_update_soon">फीड अद्यतन शीघ्र ही आरंभ होगा</string>
|
||||
<string name="app_update_available">इस ऐप का नया संस्करण उपलब्ध हैं</string>
|
||||
<string name="new_version_s">नया संस्करण: %s</string>
|
||||
<string name="text_delete_local_manga">डिवाइस से \"%s\" को स्थायी रूप से हटाएं?</string>
|
||||
<string name="text_delete_local_manga">डिवाइस से \"%s\" को स्थायी रूप से मिटाएं?</string>
|
||||
<string name="text_history_holder_primary">आप जो पढ़ेंगे वह यहां प्रदर्शित किया जाएगा</string>
|
||||
<string name="delete_manga">मंगा हटाएं</string>
|
||||
<string name="delete_manga">मंगा मिटाएं</string>
|
||||
<string name="notification_sound">सूचना की ध्वनि</string>
|
||||
<string name="search_history_cleared">साफ हो गया</string>
|
||||
<string name="open_in_browser">वेब ब्राउज़र में खोलें</string>
|
||||
<string name="notifications">सूचनाएं</string>
|
||||
<string name="not_available">उपल्ब्ध नहीं हैं</string>
|
||||
<string name="track_sources">अपडेट देखें</string>
|
||||
<string name="clear_search_history">खोजा हुवा इतिहास को साफ करे</string>
|
||||
<string name="track_sources">अद्यतन देखें</string>
|
||||
<string name="clear_search_history">खोज इतिहास साफ करें</string>
|
||||
<string name="download">डाउनलोड</string>
|
||||
<string name="size_s">आकार: %s</string>
|
||||
<string name="new_chapters">नये अध्याय</string>
|
||||
<string name="clear_updates_feed">अपडेट फ़ीड साफ़ करें</string>
|
||||
<string name="clear_updates_feed">अद्यतन फीड साफ करें</string>
|
||||
<string name="notifications_settings">सुचना के सेटिंग</string>
|
||||
<string name="domain">डोमेन</string>
|
||||
<string name="reader_settings">रीडर के सेटिंग</string>
|
||||
<string name="reader_settings">पाठक सेटिंग्स</string>
|
||||
<string name="text_search_holder_secondary">क्वेरी को पुन: तैयार करने का प्रयास करें।</string>
|
||||
<string name="error">त्रुटि</string>
|
||||
<string name="grid_size">ग्रिड का आकार</string>
|
||||
<string name="_continue">जारी रखें</string>
|
||||
<string name="recent_manga">हालिया</string>
|
||||
<string name="search_on_s">%s पर खोजें</string>
|
||||
<string name="search_results">खोज के परिणाम</string>
|
||||
<string name="pages_animation">पेज एनीमेशन</string>
|
||||
<string name="search_results">खोज परिणाम</string>
|
||||
<string name="pages_animation">पृष्ठ सजीवता</string>
|
||||
<string name="other_storage">अन्य स्टोरेज</string>
|
||||
<string name="external_storage">बाहरी स्टोरेज</string>
|
||||
<string name="text_local_holder_secondary">किसी ऑनलाइन कैटलॉग से कुछ सहेजें या किसी फ़ाइल से आयात करें।</string>
|
||||
<string name="text_local_holder_secondary">किसी ऑनलाइन कैटलॉग से कुछ सहेजें या किसी फाइल से आयात करें।</string>
|
||||
<string name="text_local_holder_primary">पहले कुछ सहेजें</string>
|
||||
<string name="text_empty_holder_primary">यहाँ कुछ खाली सा है…</string>
|
||||
<string name="done">हो गया</string>
|
||||
<string name="dont_check">जाँच मत करो</string>
|
||||
<string name="text_empty_holder_primary">यहां कुछ खाली सा है…</string>
|
||||
<string name="done">संपन्न</string>
|
||||
<string name="dont_check">जांच मत करें</string>
|
||||
<string name="enter_password">पासवर्ड दर्ज करें</string>
|
||||
<string name="advanced">विकसित</string>
|
||||
<string name="advanced">उन्नत</string>
|
||||
<string name="catalog">कैटलॉग</string>
|
||||
<string name="manage_sources">स्रोत प्रबंधित करें</string>
|
||||
<string name="screenshots_policy">स्क्रीनशॉट नीति</string>
|
||||
@@ -129,35 +129,35 @@
|
||||
<string name="suggestions_enable">सुझाव सक्षम करें</string>
|
||||
<string name="suggestions_summary">अपनी प्राथमिकताओं के आधार पर मंगा का सुझाव दें</string>
|
||||
<string name="suggestions_info">सभी डेटा का विश्लेषण केवल इस डिवाइस पर स्थानीय रूप से किया जाता है और कभी भी कहीं नहीं भेजा जाता है।</string>
|
||||
<string name="reset_filter">फ़िल्टर रीसेट करें</string>
|
||||
<string name="reset_filter">फिल्टर रीसेट करें</string>
|
||||
<string name="onboard_text">उन भाषाओं का चयन करें जिन्हें आप मंगा पढ़ना चाहते हैं। आप इसे बाद में सेटिंग में बदल सकते हैं।</string>
|
||||
<string name="chapters_empty">इस मंगा में कोई अध्याय नहीं</string>
|
||||
<string name="suggestions_excluded_genres">शैलियों को छोड़ें</string>
|
||||
<string name="removal_completed">निष्कासन पूरा हुआ</string>
|
||||
<string name="removal_completed">निष्कासन पूर्ण हो गया</string>
|
||||
<string name="download_slowdown_summary">आपके IP पते को ब्लॉक होने से बचाने में मदद करता है</string>
|
||||
<string name="local_manga_processing">सहेजे गए मंगा का प्रसंस्करण</string>
|
||||
<string name="chapters_will_removed_background">अध्याय पृष्ठभूमि में हटा दिए जाएंगे</string>
|
||||
<string name="comics_archive">कॉमिक्स संग्रह</string>
|
||||
<string name="webtoon_zoom">वेबटून ज़ूम</string>
|
||||
<string name="repeat_password">पासवर्ड दोहराएँ</string>
|
||||
<string name="webtoon_zoom">वेबटून जूम</string>
|
||||
<string name="repeat_password">पासवर्ड दोहराएं</string>
|
||||
<string name="passwords_mismatch">बेमेल पासवर्ड</string>
|
||||
<string name="app_version">संस्करण %s</string>
|
||||
<string name="check_for_updates">अपडेट के लिए जांचें</string>
|
||||
<string name="no_update_available">कोई अपडेट उपलब्ध नहीं</string>
|
||||
<string name="check_for_updates">अद्यतनों के लिए जांचें</string>
|
||||
<string name="no_update_available">कोई अद्यतन उपलब्ध नहीं</string>
|
||||
<string name="scale_mode">स्केल मोड</string>
|
||||
<string name="black_dark_theme">काली</string>
|
||||
<string name="black_dark_theme_summary">AMOLED स्क्रीन पर कम पावर का उपयोग होता है</string>
|
||||
<string name="black_dark_theme_summary">AMOLED स्क्रीन पर कम बिजली का उपयोग होता है</string>
|
||||
<string name="create_backup">डेटा बैकअप बनाएं</string>
|
||||
<string name="restore_backup">बैकअप से पुनर्स्थापित करें</string>
|
||||
<string name="data_restored">पुनर्स्थापित किया गया</string>
|
||||
<string name="data_restored_with_errors">डेटा पुनर्स्थापित कर दिया गया था, लेकिन त्रुटियाँ हैं</string>
|
||||
<string name="just_now">अभी</string>
|
||||
<string name="data_restored_with_errors">डेटा पुनर्स्थापित कर दिया गया था, लेकिन त्रुटियां हैं</string>
|
||||
<string name="just_now">बस अभी</string>
|
||||
<string name="yesterday">कल</string>
|
||||
<string name="long_ago">बहुत पहले</string>
|
||||
<string name="group">समूह</string>
|
||||
<string name="today">आज</string>
|
||||
<string name="tap_to_try_again">दोबारा प्रयास करने के लिए टैप करें</string>
|
||||
<string name="reader_mode_hint">इस मंगा के लिए चुना गया कॉन्फ़िगरेशन याद रखा जाएगा</string>
|
||||
<string name="reader_mode_hint">इस मंगा के लिए चुना गया विन्यास याद रखा जाएगा</string>
|
||||
<string name="silent">खामोश</string>
|
||||
<string name="captcha_required">CAPTCHA आवश्यक है</string>
|
||||
<string name="captcha_solve">हल करें</string>
|
||||
@@ -165,15 +165,15 @@
|
||||
<string name="backup_saved">बैकअप सहेजा गया</string>
|
||||
<string name="welcome">स्वागत है</string>
|
||||
<string name="tracker_warning">कुछ डिवाइसों में अलग-अलग सिस्टम व्यवहार होता है, जो पृष्ठभूमि कार्यों को बाधित कर सकता है।</string>
|
||||
<string name="read_more">और पढ़ें</string>
|
||||
<string name="read_more">अधिक पढ़ें</string>
|
||||
<string name="chapter_is_missing">अध्याय गायब है</string>
|
||||
<string name="about_app_translation_summary">इस ऐप का अनुवाद करें</string>
|
||||
<string name="auth_complete">अधिकृत</string>
|
||||
<string name="auth_not_supported_by">%s पर लॉग इन करना समर्थित नहीं है</string>
|
||||
<string name="auth_not_supported_by">%s पर लॉगिन करना समर्थित नहीं है</string>
|
||||
<string name="text_clear_cookies_prompt">आप सभी स्रोतों से लॉग आउट हो जायेंगे</string>
|
||||
<string name="state_finished">समाप्त</string>
|
||||
<string name="state_ongoing">चल रही है</string>
|
||||
<string name="system_default">डिफ़ॉल्ट</string>
|
||||
<string name="system_default">तयशुदा</string>
|
||||
<string name="exclude_nsfw_from_history">इतिहास से NSFW मंगा को बाहर करें</string>
|
||||
<string name="show_pages_numbers">क्रमांकित पन्ने</string>
|
||||
<string name="screenshots_block_nsfw">NSFW पर रोक लगाएं</string>
|
||||
@@ -185,11 +185,11 @@
|
||||
<string name="only_using_wifi">केवल Wi-Fi पर</string>
|
||||
<string name="always">हमेशा</string>
|
||||
<string name="nsfw">18+</string>
|
||||
<string name="various_languages">विभिन्न भाषाएँ</string>
|
||||
<string name="various_languages">विभिन्न भाषाएं</string>
|
||||
<string name="search_chapters">अध्याय खोजें</string>
|
||||
<string name="percent_string_pattern">%1$s%%</string>
|
||||
<string name="appearance">दिखावट</string>
|
||||
<string name="preload_pages">पन्ने प्रीलोड करें</string>
|
||||
<string name="preload_pages">पन्ने पहले से लोड करें</string>
|
||||
<string name="edit">संपादित करें</string>
|
||||
<string name="download_slowdown">धीमी गति से डाउनलोड करें</string>
|
||||
<string name="edit_category">श्रेणी संपादित करें</string>
|
||||
@@ -197,20 +197,20 @@
|
||||
<string name="empty_favourite_categories">कोई पसंदीदा श्रेणियां नहीं</string>
|
||||
<string name="logout">लॉग आउट</string>
|
||||
<string name="bookmark_add">बुकमार्क जोड़ें</string>
|
||||
<string name="bookmark_remove">बुकमार्क हटाएँ</string>
|
||||
<string name="bookmarks">बुकमार्क्स</string>
|
||||
<string name="bookmark_remove">बुकमार्क हटाएं</string>
|
||||
<string name="bookmarks">बुकमार्क</string>
|
||||
<string name="bookmark_removed">बुकमार्क हटा दिया गया</string>
|
||||
<string name="bookmark_added">बुकमार्क जोड़ा गया</string>
|
||||
<string name="undo">पूर्ववत</string>
|
||||
<string name="undo">पूर्ववत करें</string>
|
||||
<string name="removed_from_history">इतिहास से हटा दिया गया</string>
|
||||
<string name="detect_reader_mode">ऑटोडिटेक्ट रीडर मोड</string>
|
||||
<string name="detect_reader_mode">पाठक मोड का स्वत: पता लगाएं</string>
|
||||
<string name="detect_reader_mode_summary">स्वचालित रूप से पता लगाएं कि मंगा वेबटून है या नहीं</string>
|
||||
<string name="disable_battery_optimization">बैटरी अनुकूलन अक्षम करें</string>
|
||||
<string name="send">भेजें</string>
|
||||
<string name="disable_all">सब अक्षम करें</string>
|
||||
<string name="use_fingerprint">यदि उपलब्ध हो तो फ़िंगरप्रिंट का उपयोग करें</string>
|
||||
<string name="use_fingerprint">यदि उपलब्ध हो तो फिंगरप्रिंट का उपयोग करें</string>
|
||||
<string name="appwidget_shelf_description">आपके पसंदीदा में से मंगा</string>
|
||||
<string name="appwidget_recent_description">आपने हाल ही में पढ़ा मंगा</string>
|
||||
<string name="appwidget_recent_description">आपके हालिया पढ़ें मंगा</string>
|
||||
<string name="report">रिपोर्ट</string>
|
||||
<string name="status_planned">योजना बनाई</string>
|
||||
<string name="status_reading">पढ़ रहा हूँ</string>
|
||||
@@ -218,44 +218,44 @@
|
||||
<string name="status_completed">पूरा किया हुआ</string>
|
||||
<string name="status_on_hold">होल्ड पर</string>
|
||||
<string name="status_dropped">गिरा दिया गया</string>
|
||||
<string name="show_reading_indicators">पढ़ने की प्रगति संकेतक दिखाएँ</string>
|
||||
<string name="show_reading_indicators">पठन प्रगति संकेतक दिखाएं</string>
|
||||
<string name="data_deletion">डेटा विलोपन</string>
|
||||
<string name="exclude_nsfw_from_history_summary">NSFW के रूप में चिह्नित मंगा को इतिहास में कभी नहीं जोड़ा जाएगा और आपकी प्रगति सहेजी नहीं जाएगी</string>
|
||||
<string name="clear_cookies_summary">कुछ समस्या होने पर मदद मिल सकती है. सभी प्राधिकरण अमान्य कर दिए जाएंगे</string>
|
||||
<string name="clear_cookies_summary">कुछ समस्या होने पर मदद मिल सकती है। सभी प्राधिकरण अमान्य कर दिए जाएंगे</string>
|
||||
<string name="invalid_domain_message">अमान्य डोमेन</string>
|
||||
<string name="select_range">रेंज चुनें</string>
|
||||
<string name="clear_all_history">सारा इतिहास साफ़ करें</string>
|
||||
<string name="select_range">दायरा चुनें</string>
|
||||
<string name="clear_all_history">सारा इतिहास साफ करें</string>
|
||||
<string name="last_2_hours">पिछले 2 घंटे</string>
|
||||
<string name="history_cleared">इतिहास साफ़ हो गया</string>
|
||||
<string name="history_cleared">इतिहास साफ हो गया</string>
|
||||
<string name="manage">प्रबंधित करें</string>
|
||||
<string name="explore">अन्वेषण करें</string>
|
||||
<string name="confirm_exit">बाहर निकलने के लिए फिर से वापस दबाएँ</string>
|
||||
<string name="confirm_exit">बाहर निकलने के लिए फिर से वापस दबाएं</string>
|
||||
<string name="exit_confirmation">बाहर निकलने की पुष्टि</string>
|
||||
<string name="saved_manga">सहेजा गया मंगा</string>
|
||||
<string name="pages_cache">पन्नो का कैश</string>
|
||||
<string name="other_cache">अन्य कैश</string>
|
||||
<string name="pages_cache">पन्नो का कैशे</string>
|
||||
<string name="other_cache">अन्य कैशे</string>
|
||||
<string name="available">उपलब्ध</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="removed_from_favourites">पसंदीदा से हटाया गया</string>
|
||||
<string name="options">विकल्प</string>
|
||||
<string name="not_found_404">सामग्री नहीं मिली या हटाई गई</string>
|
||||
<string name="incognito_mode">गुप्त मोड</string>
|
||||
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">रीडर में सूचना पट्टी दिखाएं</string>
|
||||
<string name="folder_with_images">छवियों वाला फ़ोल्डर</string>
|
||||
<string name="reader_info_pattern">अध्. %1$d/%2$d पृ. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">पाठक में सूचना पट्टी दिखाएं</string>
|
||||
<string name="folder_with_images">छवियों वाला फोल्डर</string>
|
||||
<string name="import_completed">आयात पूरा हुआ</string>
|
||||
<string name="history_shortcuts_summary">एप्लिकेशन आइकन पर लंबे समय तक दबाकर हालिया मंगा को उपलब्ध कराएं</string>
|
||||
<string name="reader_control_ltr">एर्गोनोमिक रीडर नियंत्रण</string>
|
||||
<string name="reader_control_ltr">सुविधापूर्ण पाठक नियंत्रण</string>
|
||||
<string name="color_correction">रंग सुधार</string>
|
||||
<string name="brightness">चमक</string>
|
||||
<string name="storage_usage">स्टोरेज उपयोग</string>
|
||||
<string name="contrast">कंट्रास्ट</string>
|
||||
<string name="reset">रीसेट</string>
|
||||
<string name="text_unsaved_changes_prompt">सहेजे न गए परिवर्तन सहेजें या हटाएँ?</string>
|
||||
<string name="contrast">वैषम्य</string>
|
||||
<string name="reset">रीसेट करें</string>
|
||||
<string name="text_unsaved_changes_prompt">असहेजे परिवर्तन सहेजें या त्यागें?</string>
|
||||
<string name="error_no_space_left">डिवाइस पर जगह समाप्त</string>
|
||||
<string name="reader_slider">पेज स्विचिंग स्लाइडर दिखाएँ</string>
|
||||
<string name="reader_slider">पृष्ठ स्विचिंग स्लाइडर दिखाएं</string>
|
||||
<string name="network_unavailable">नेटवर्क उपलब्ध नहीं है</string>
|
||||
<string name="server_error">सर्वर साइड त्रुटि (%1$d). कृपया बाद में पुन: प्रयास करें</string>
|
||||
<string name="server_error">सर्वर साइड त्रुटि (%1$d)। कृपया बाद में पुन: प्रयास करें</string>
|
||||
<string name="clear_new_chapters_counters">नए अध्यायों के बारे में भी स्पष्ट जानकारी</string>
|
||||
<string name="compact">सघन</string>
|
||||
<string name="mark_as_current">वर्तमान के रूप में चिह्नित करें</string>
|
||||
@@ -264,23 +264,23 @@
|
||||
<string name="show_suspicious_content">संदिग्ध सामग्री दिखाएं</string>
|
||||
<string name="theme_name_dynamic">गतिशील</string>
|
||||
<string name="color_theme">रंग योजना</string>
|
||||
<string name="show_in_grid_view">ग्रिड दृश्य में दिखाएँ</string>
|
||||
<string name="show_in_grid_view">ग्रिड दृश्य में दिखाएं</string>
|
||||
<string name="theme_name_miku">Miku</string>
|
||||
<string name="theme_name_rikka">Rikka</string>
|
||||
<string name="theme_name_kanade">Kanade</string>
|
||||
<string name="scrobbling_empty_hint">पढ़ने की प्रगति को ट्रैक करने के लिए, मंगा विवरण स्क्रीन पर मेनू → ट्रैक का चयन करें।</string>
|
||||
<string name="services">सेवाएं</string>
|
||||
<string name="settings_apply_restart_required">कृपया इन परिवर्तनों को लागू करने के लिए एप्लिकेशन को पुनः आरंभ करें</string>
|
||||
<string name="comics_archive_import_description">आप एक या अधिक .cbz या .zip फ़ाइलों का चयन कर सकते हैं, प्रत्येक फ़ाइल को एक अलग मंगा के रूप में पहचाना जाएगा।</string>
|
||||
<string name="user_agent">UserAgent हेडर</string>
|
||||
<string name="comics_archive_import_description">आप एक या अधिक .cbz या .zip फाइलों का चयन कर सकते हैं, प्रत्येक फाइल को एक अलग मंगा के रूप में पहचाना जाएगा।</string>
|
||||
<string name="user_agent">UserAgent शीर्षलेख</string>
|
||||
<string name="speed">गति</string>
|
||||
<string name="show_on_shelf">शेल्फ पर दिखाएँ</string>
|
||||
<string name="show_on_shelf">दराज पर दिखाएं</string>
|
||||
<string name="sync_auth_hint">आप किसी मौजूदा खाते में साइन इन कर सकते हैं या एक नया खाता बना सकते हैं</string>
|
||||
<string name="mirror_switching_summary">यदि मिरर उपलब्ध हैं तो त्रुटियों पर मंगा स्रोतों के लिए स्वचालित रूप से डोमेन स्विच करें</string>
|
||||
<string name="find_similar">समान खोजें</string>
|
||||
<string name="pause">विराम</string>
|
||||
<string name="resume">फिर से शुरू करें</string>
|
||||
<string name="paused">रुका हुआ</string>
|
||||
<string name="resume">पुनः आरम्भ</string>
|
||||
<string name="paused">विरामित</string>
|
||||
<string name="cancel_all">सभी रद्द करें</string>
|
||||
<string name="mirror_switching">स्वचालित रूप से मिरर चुनें</string>
|
||||
<string name="downloads_wifi_only_summary">मोबाइल नेटवर्क पर स्विच करते समय डाउनलोड करना बंद कर दें</string>
|
||||
@@ -297,24 +297,24 @@
|
||||
<string name="downloads_removed">डाउनलोड हटा दिए गए हैं</string>
|
||||
<string name="suggestions_enable_prompt">क्या आप वैयक्तिकृत मंगा सुझाव प्राप्त करना चाहते हैं?</string>
|
||||
<string name="web_view_unavailable">WebView उपलब्ध नहीं है: जांचें कि WebView प्रदाता स्थापित है या नहीं</string>
|
||||
<string name="clear_network_cache">नेटवर्क कैश साफ़ करें</string>
|
||||
<string name="clear_network_cache">नेटवर्क कैशे साफ करें</string>
|
||||
<string name="type">प्रकार</string>
|
||||
<string name="address">पता</string>
|
||||
<string name="port">पोर्ट</string>
|
||||
<string name="proxy">प्रॉक्सी</string>
|
||||
<string name="invalid_value_message">अमान्य मान</string>
|
||||
<string name="password">पासवर्ड</string>
|
||||
<string name="invert_colors">रंगों को उलटा करें</string>
|
||||
<string name="invert_colors">रंगों को पलटें</string>
|
||||
<string name="invalid_port_number">अमान्य पोर्ट नंबर</string>
|
||||
<string name="network">नेटवर्क</string>
|
||||
<string name="show_pages_numbers_summary">निचले कोने में पेज संख्याएँ दिखाएँ</string>
|
||||
<string name="show_pages_numbers_summary">निचले कोने में पृष्ठ संख्याएं दिखाएं</string>
|
||||
<string name="restore_summary">पहले बनाए गए बैकअप को पुनर्स्थापित करें</string>
|
||||
<string name="webtoon_zoom_summary">वेबटून मोड में ज़ूम इन जेस्चर की अनुमति दें</string>
|
||||
<string name="webtoon_zoom_summary">वेबटून मोड में जूम इन जेस्चर की अनुमति दें</string>
|
||||
<string name="reader_info_bar_summary">स्क्रीन के शीर्ष पर वर्तमान समय और पढ़ने की प्रगति दिखाएं</string>
|
||||
<string name="volume_">वॉल्यूम %d</string>
|
||||
<string name="volume_unknown">अज्ञात वॉल्यूम</string>
|
||||
<string name="downloads_settings_info">यदि आपको सर्वर-साइड ब्लॉकिंग की समस्या हो रही है तो आप स्रोत सेटिंग्स में प्रत्येक मंगा स्रोत के लिए व्यक्तिगत रूप से डाउनलोड मंदी को सक्षम कर सकते हैं</string>
|
||||
<string name="remove_from_history">इतिहास से हटा दें</string>
|
||||
<string name="remove_from_history">इतिहास से हटाएं</string>
|
||||
<string name="skip">छोड़ें</string>
|
||||
<string name="incognito_mode_hint">आपकी पढ़ने की प्रगति सहेजी नहीं जाएगी</string>
|
||||
<string name="content_rating">सामग्री मूल्यांकन</string>
|
||||
@@ -328,45 +328,45 @@
|
||||
<string name="download_started">डाउनलोड प्रारंभ हुआ</string>
|
||||
<string name="manga_list">मंगा सूची</string>
|
||||
<string name="disable_nsfw">NSFW अक्षम करें</string>
|
||||
<string name="images_proxy_title">छवियाँ अनुकूलन प्रॉक्सी</string>
|
||||
<string name="images_proxy_title">छवियां अनुकूलन प्रॉक्सी</string>
|
||||
<string name="data_and_privacy">डेटा और गोपनीयता</string>
|
||||
<string name="email_password_enter_hint">जारी रखने के लिए अपना ईमेल और पासवर्ड डालें</string>
|
||||
<string name="clear_source_cookies_summary">केवल निर्दिष्ट डोमेन के लिए कुकीज़ साफ़ करें। अधिकांश मामलों में प्राधिकरण अमान्य हो जाएगा</string>
|
||||
<string name="clear_source_cookies_summary">केवल निर्दिष्ट डोमेन के लिए कुकीज़ साफ करें। अधिकांश मामलों में प्राधिकरण अमान्य हो जाएगा</string>
|
||||
<string name="download_option_next_unread_n_chapters">अगला अपठित %s</string>
|
||||
<string name="no_access_to_file">आपके पास इस फ़ाइल या डॉयरेक्टरी तक कोई पहुंच नहीं है</string>
|
||||
<string name="no_access_to_file">आपके पास इस फाइल या निर्देशिका तक कोई पहुंच नहीं है</string>
|
||||
<string name="voice_search">ध्वनि खोज</string>
|
||||
<string name="related_manga">संबंधित मंगा</string>
|
||||
<string name="description">विवरण</string>
|
||||
<string name="this_month">इस महीने</string>
|
||||
<string name="background">पृष्ठभूमि</string>
|
||||
<string name="local_manga_directories">स्थानीय मंगा डॉयरेक्टरी</string>
|
||||
<string name="data_not_restored_text">सुनिश्चित करें कि आपने सही बैकअप फ़ाइल का चयन किया है</string>
|
||||
<string name="local_manga_directories">स्थानीय मंगा निर्देशिकाएं</string>
|
||||
<string name="data_not_restored_text">सुनिश्चित करें कि आपने सही बैकअप फाइल का चयन किया है</string>
|
||||
<string name="data_not_restored">डेटा पुनर्स्थापित नहीं किया गया</string>
|
||||
<string name="suggestions_wifi_only_summary">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके सुझावों को अपडेट न करें</string>
|
||||
<string name="tracker_wifi_only_summary">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके नए अध्यायों की जाँच न करें</string>
|
||||
<string name="suggestions_wifi_only_summary">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके सुझावों को अद्यतन न करें</string>
|
||||
<string name="tracker_wifi_only_summary">मीटर्ड नेटवर्क कनेक्शन का उपयोग करके नए अध्यायों की जांच न करें</string>
|
||||
<string name="search_hint">मंगा शीर्षक, शैली या स्रोत का नाम दर्ज करें</string>
|
||||
<string name="progress">प्रगति</string>
|
||||
<string name="order_added">जोड़ा गया</string>
|
||||
<string name="show">दिखाएँ</string>
|
||||
<string name="show">दिखाएं</string>
|
||||
<string name="languages">भाषाएं</string>
|
||||
<string name="unknown">अज्ञात</string>
|
||||
<string name="in_progress">प्रगति पर है</string>
|
||||
<string name="error_corrupted_file">अमान्य डेटा लौटाया गया है या फ़ाइल दूषित है</string>
|
||||
<string name="items_limit_exceeded">कोई और आइटम नहीं जोड़ा जा सकता</string>
|
||||
<string name="error_corrupted_file">अमान्य डेटा लौटाया गया है या फाइल दूषित है</string>
|
||||
<string name="items_limit_exceeded">कोई और वस्तुएं नहीं जोड़ा जा सकता</string>
|
||||
<string name="on_device">डिवाइस पर</string>
|
||||
<string name="main_screen_sections">मुख्य स्क्रीन अनुभाग</string>
|
||||
<string name="directories">डॉयरेक्टरी</string>
|
||||
<string name="directories">निर्देशिकाएं</string>
|
||||
<string name="to_top">शीर्ष पर</string>
|
||||
<string name="moved_to_top">शीर्ष पर ले जाया गया</string>
|
||||
<string name="zoom_out">ज़ूम आउट</string>
|
||||
<string name="zoom_in">ज़ूम इन</string>
|
||||
<string name="reader_zoom_buttons">ज़ूम बटन दिखाएँ</string>
|
||||
<string name="reader_zoom_buttons_summary">निचले दाएं कोने में ज़ूम नियंत्रण बटन दिखाना है या नहीं</string>
|
||||
<string name="zoom_out">जूम आउट</string>
|
||||
<string name="zoom_in">जूम इन</string>
|
||||
<string name="reader_zoom_buttons">जूम बटन दिखाएं</string>
|
||||
<string name="reader_zoom_buttons_summary">निचले दाएं कोने में जूम नियंत्रण बटन दिखाना है या नहीं</string>
|
||||
<string name="keep_screen_on">स्क्रीन चालू रखें</string>
|
||||
<string name="keep_screen_on_summary">जब आप मंगा पढ़ रहे हों तो स्क्रीन बंद न करें</string>
|
||||
<string name="enhanced_colors_summary">बैंडिंग को कम करता है, लेकिन प्रदर्शन को प्रभावित कर सकता है</string>
|
||||
<string name="enhanced_colors">32-बिट रंग मोड</string>
|
||||
<string name="suggest_new_sources">ऐप अपडेट के बाद नए स्रोत सुझाएं</string>
|
||||
<string name="suggest_new_sources">ऐप अद्यतन के बाद नए स्रोत सुझाएं</string>
|
||||
<string name="suggest_new_sources_summary">एप्लिकेशन को अपडेट करने के बाद नए जोड़े गए स्रोतों को सक्षम करने का संकेत दें</string>
|
||||
<string name="online_variant">ऑनलाइन संस्करण</string>
|
||||
<string name="periodic_backups">आवधिक बैकअप</string>
|
||||
@@ -388,54 +388,54 @@
|
||||
<string name="source_enabled">स्रोत सक्षम</string>
|
||||
<string name="sources_catalog">स्रोत कैटलॉग</string>
|
||||
<string name="no_manga_sources_catalog_text">इस अनुभाग में कोई स्रोत उपलब्ध नहीं है, या यह सब पहले ही जोड़ा जा चुका होगा।
|
||||
\nबने रहें</string>
|
||||
\nहमारे साथ बने रहें</string>
|
||||
<string name="no_manga_sources_found">आपकी क्वेरी से कोई उपलब्ध मंगा स्रोत नहीं मिला</string>
|
||||
<string name="manual">मैन्युअल</string>
|
||||
<string name="manual">हस्तचालित</string>
|
||||
<string name="available_d">उपलब्ध: %1$d</string>
|
||||
<string name="disable_nsfw_summary">यदि संभव हो तो NSFW स्रोतों को अक्षम करें और वयस्क मंगा को सूची से छिपाएँ</string>
|
||||
<string name="state_paused">रोके गए</string>
|
||||
<string name="reader_optimize">मेमोरी खपत कम करें (beta)</string>
|
||||
<string name="disable_nsfw_summary">यदि संभव हो तो NSFW स्रोतों को अक्षम करें और वयस्क मंगा को सूची से छिपाएं</string>
|
||||
<string name="state_paused">विरामित</string>
|
||||
<string name="reader_optimize">मेमोरी खपत कम करें (बीटा)</string>
|
||||
<string name="state">अवस्था</string>
|
||||
<string name="error_multiple_genres_not_supported">अनेक शैलियों द्वारा फ़िल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="error_multiple_genres_not_supported">अनेक शैलियों द्वारा फिल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="error_search_not_supported">खोज इस मंगा स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="genres_search_hint">शैली का नाम लिखना प्रारंभ करें</string>
|
||||
<string name="disable_battery_optimization_summary_downloads">यदि आपको इसमें कोई समस्या है तो डाउनलोड शुरू करने में मदद मिल सकती है</string>
|
||||
<string name="restore">पुनर्स्थापित करें</string>
|
||||
<string name="backup_date_">बैकअप दिनांक: %s</string>
|
||||
<string name="state_upcoming">आगामी</string>
|
||||
<string name="by_name_reverse">नाम उलटा</string>
|
||||
<string name="mark_as_completed">पूर्ण के रूप में चिह्नित करें</string>
|
||||
<string name="by_name_reverse">नाम उलट दिया गया</string>
|
||||
<string name="mark_as_completed">पूर्ण चिह्नित करें</string>
|
||||
<string name="mark_as_completed_prompt">चयनित मंगा को पूरी तरह से पढ़ा गया के रूप में चिह्नित करें?
|
||||
\n
|
||||
\nचेतावनी: वर्तमान पठन प्रगति नष्ट हो जाएगी।</string>
|
||||
<string name="remaining_time_pattern">%1$s %2$s</string>
|
||||
<string name="show_menu">मेन्यू दिखाएँ</string>
|
||||
<string name="show_menu">मेनू दिखाएं</string>
|
||||
<string name="long_tap_action">लंबे टैप पर कार्रवाई</string>
|
||||
<string name="tap_action">टैप पर कार्रवाई</string>
|
||||
<string name="tap_action">टैप कार्रवाई</string>
|
||||
<string name="none">कोई नहीं</string>
|
||||
<string name="config_reset_confirm">सेटिंग्स को डिफ़ॉल्ट मानों पर रीसेट करें? इस एक्शन को वापस नहीं किया जा सकता।</string>
|
||||
<string name="use_two_pages_landscape">लैंडस्केप ओरिएंटेशन पर दो पेज लेआउट का उपयोग करें (beta)</string>
|
||||
<string name="config_reset_confirm">सेटिंग्स को तयशुदा मानों पर रीसेट करें? इस कार्रवाई को वापस नहीं किया जा सकता।</string>
|
||||
<string name="use_two_pages_landscape">परिदृश्य उन्मुखीकरण पर दो पृष्ठ अभिन्यास का उपयोग करें (बीटा)</string>
|
||||
<string name="got_it">समझ गया</string>
|
||||
<string name="default_tab">डिफ़ॉल्ट टैब</string>
|
||||
<string name="default_tab">तयशुदा टैब</string>
|
||||
<string name="download_option_all_chapters">अनुवाद सहित सभी अध्याय %s</string>
|
||||
<string name="download_option_whole_manga">संपूर्ण मंगा</string>
|
||||
<string name="download_option_first_n_chapters">प्रथम %s</string>
|
||||
<string name="download_option_all_unread">सभी अपठित अध्याय</string>
|
||||
<string name="download_option_all_unread_b">सभी अपठित अध्याय (%s)</string>
|
||||
<string name="download_option_manual_selection">अध्यायों का चयन मैन्युअल रूप से करें</string>
|
||||
<string name="download_option_manual_selection">अध्यायों का चयन हस्तचालित रूप से करें</string>
|
||||
<string name="color_light">हल्का</string>
|
||||
<string name="color_dark">गहरा</string>
|
||||
<string name="color_white">सफ़ेद</string>
|
||||
<string name="color_white">सफेद</string>
|
||||
<string name="color_black">काला</string>
|
||||
<string name="manage_categories">श्रेणी व्यवस्थित करें</string>
|
||||
<string name="downloaded">डाउनलोड किया गया</string>
|
||||
<string name="too_many_requests_message">बहुत सारे अनुरोध. बाद में पुन: प्रयास</string>
|
||||
<string name="related_manga_summary">संबंधित मंगा की एक सूची दिखाएं. कुछ मामलों में यह ग़लत या गायब हो सकता है</string>
|
||||
<string name="pick_custom_directory">कस्टम डायरेक्टरी चुनें</string>
|
||||
<string name="default_webtoon_zoom_out">डिफ़ॉल्ट वेबटून ज़ूम आउट</string>
|
||||
<string name="too_many_requests_message">बहुत सारे अनुरोध। बाद में पुन: प्रयास</string>
|
||||
<string name="related_manga_summary">संबंधित मंगा की एक सूची दिखाएं। कुछ मामलों में यह गलत या गायब हो सकता है</string>
|
||||
<string name="pick_custom_directory">तदनुकूल डायरेक्टरी चुनें</string>
|
||||
<string name="default_webtoon_zoom_out">तयशुदा वेबटून जूम आउट</string>
|
||||
<string name="captcha_required_summary">%s को ठीक से काम करने के लिए कैप्चा को हल करने की आवश्यकता है</string>
|
||||
<string name="fullscreen_mode">पूर्ण स्क्रीन मोड</string>
|
||||
<string name="reader_fullscreen_summary">सिस्टम स्थिति और नेविगेशन बार छिपाएँ</string>
|
||||
<string name="fullscreen_mode">पूर्णस्क्रीन मोड</string>
|
||||
<string name="reader_fullscreen_summary">सिस्टम स्थिति और नेविगेशन बार छिपाएं</string>
|
||||
<string name="username">उपयोक्तानाम</string>
|
||||
<string name="authorization_optional">प्राधिकरण (वैकल्पिक)</string>
|
||||
<string name="category_hidden_done">यह श्रेणी मुख्य स्क्रीन से छिपी हुई थी और मेनू → श्रेणियों को प्रबंधित करें के माध्यम से पहुंच योग्य है</string>
|
||||
@@ -444,192 +444,201 @@
|
||||
<string name="apply">लागू करें</string>
|
||||
<string name="ignore_ssl_errors">SSL त्रुटियों को नजरअंदाज करें</string>
|
||||
<string name="downloads_wifi_only">केवल Wi-Fi के ज़रिए डाउनलोड करें</string>
|
||||
<string name="show_notification_new_chapters_off">आपको सूचनाएं प्राप्त नहीं होंगी लेकिन नए अध्याय सूचियों में हाइलाइट किए जाएंगे</string>
|
||||
<string name="show_notification_new_chapters_off">आपको सूचनाएं प्राप्त नहीं होंगी लेकिन नए अध्याय सूचियों में चिन्हांकित किए जाएंगे</string>
|
||||
<string name="notifications_enable">सूचनाएं सक्षम करें</string>
|
||||
<string name="name">नाम</string>
|
||||
<string name="bookmarks_removed">बुकमार्क हटा दिए गए</string>
|
||||
<string name="no_manga_sources">कोई मंगा स्रोत नहीं</string>
|
||||
<string name="no_manga_sources_text">मंगा को ऑनलाइन पढ़ने के लिए मंगा स्रोतों को सक्षम करें</string>
|
||||
<string name="random">यादृच्छिक</string>
|
||||
<string name="reorder">पुन: व्यवस्थित करें</string>
|
||||
<string name="reorder">पुनः क्रमित करें</string>
|
||||
<string name="empty">खाली</string>
|
||||
<string name="import_will_start_soon">आयात जल्द शुरू होगा</string>
|
||||
<string name="feed">फ़ीड</string>
|
||||
<string name="history_shortcuts">हाल के मंगा शॉर्टकट दिखाएँ</string>
|
||||
<string name="discard">खारिज</string>
|
||||
<string name="sources_reorder_tip">किसी आइटम को पुन: व्यवस्थित करने के लिए उस पर टैप करके रखें</string>
|
||||
<string name="feed">फीड</string>
|
||||
<string name="history_shortcuts">हाल के मंगा शॉर्टकट दिखाएं</string>
|
||||
<string name="discard">त्यागें</string>
|
||||
<string name="sources_reorder_tip">किसी वस्तु को पुन: व्यवस्थित करने के लिए उस पर टैप करके रखें</string>
|
||||
<string name="wrong_password">गलत पासवर्ड</string>
|
||||
<string name="protect_application">ऐप को सुरक्षित रखें</string>
|
||||
<string name="protect_application_summary">Kotatsu शुरू करते समय पासवर्ड मांगें</string>
|
||||
<string name="right_to_left">दाएं-से-बाएं</string>
|
||||
<string name="create_category">नई श्रेणी</string>
|
||||
<string name="zoom_mode_fit_center">केंद्र फिट</string>
|
||||
<string name="zoom_mode_fit_center">केंद्र के अनुरूप</string>
|
||||
<string name="zoom_mode_fit_height">ऊंचाई के अनुरूप</string>
|
||||
<string name="zoom_mode_fit_width">चौड़ाई के अनुरूप</string>
|
||||
<string name="zoom_mode_keep_start">प्रारंभ में रखें</string>
|
||||
<string name="clear_cookies">कूकीज साफ़ करें</string>
|
||||
<string name="clear_feed">फ़ीड साफ़ करें</string>
|
||||
<string name="text_clear_updates_feed_prompt">सभी अपडेट इतिहास स्थायी रूप से साफ़ करें?</string>
|
||||
<string name="check_for_new_chapters">नए अध्यायों की जाँच करें</string>
|
||||
<string name="clear_cookies">कुकीज़ साफ करें</string>
|
||||
<string name="clear_feed">फीड साफ करें</string>
|
||||
<string name="text_clear_updates_feed_prompt">सभी अद्यतन इतिहास स्थायी रूप से साफ करें?</string>
|
||||
<string name="check_for_new_chapters">नए अध्यायों की जांच करें</string>
|
||||
<string name="reverse">उलटा</string>
|
||||
<string name="sign_in">साइन इन</string>
|
||||
<string name="canceled">रद्द किया गया</string>
|
||||
<string name="account_already_exists">खाता पहले से मौजूद है</string>
|
||||
<string name="back">पीछे</string>
|
||||
<string name="sync">सिंक्रनाइज़ेशन</string>
|
||||
<string name="sync_title">अपना डेटा सिंक करें</string>
|
||||
<string name="sync">समन्वयन</string>
|
||||
<string name="sync_title">अपना डेटा समन्वयित करें</string>
|
||||
<string name="email_enter_hint">जारी रखने के लिए अपना ईमेल दर्ज करें</string>
|
||||
<string name="hide">छुपाएं</string>
|
||||
<string name="new_sources_text">नए मंगा स्रोत उपलब्ध हैं</string>
|
||||
<string name="check_new_chapters_title">नए अध्यायों की जाँच करें और इसके बारे में सूचित करें</string>
|
||||
<string name="check_new_chapters_title">नए अध्यायों की जांच करें और इसके बारे में सूचित करें</string>
|
||||
<string name="remove_completed">पूर्ण हटा दें</string>
|
||||
<string name="toggle_ui">UI दिखाएँ/छिपाएँ</string>
|
||||
<string name="toggle_ui">UI दिखाएं/छिपाएं</string>
|
||||
<string name="next_chapter">अगला अध्याय</string>
|
||||
<string name="reader_actions">पाठक क्रियाएँ</string>
|
||||
<string name="reader_actions">पाठक कार्रवाई</string>
|
||||
<string name="switch_pages_volume_buttons">वॉल्यूम बटन सक्षम करें</string>
|
||||
<string name="next_page">अगला पेज</string>
|
||||
<string name="reading_time_estimation">पढ़ने का अनुमानित समय दिखाएँ</string>
|
||||
<string name="reading_time_estimation_summary">समय अनुमान मान ग़लत हो सकता है</string>
|
||||
<string name="location">जगह</string>
|
||||
<string name="next_page">अगला पृष्ठ</string>
|
||||
<string name="reading_time_estimation">पढ़ने का अनुमानित समय दिखाएं</string>
|
||||
<string name="reading_time_estimation_summary">समय अनुमान मान गलत हो सकता है</string>
|
||||
<string name="location">स्थान</string>
|
||||
<string name="queued">कतारबद्ध</string>
|
||||
<string name="about_app_translation">अनुवाद</string>
|
||||
<string name="enabled">सक्रिय</string>
|
||||
<string name="enabled">सक्षम</string>
|
||||
<string name="auth_required">इस सामग्री को देखने के लिए साइन इन करें</string>
|
||||
<string name="default_s">डिफ़ॉल्ट: %s</string>
|
||||
<string name="default_s">तयशुदा: %s</string>
|
||||
<string name="next">अगला</string>
|
||||
<string name="genres">शैलियां</string>
|
||||
<string name="logged_in_as">%s के रूप में लॉग इन किया गया</string>
|
||||
<string name="protect_application_subtitle">ऐप शुरू करने के लिए एक पासवर्ड दर्ज करें</string>
|
||||
<string name="logged_in_as">%s के रूप में लॉगिन किया गया</string>
|
||||
<string name="protect_application_subtitle">ऐप शुरू करने के लिए पासवर्ड दर्ज करें</string>
|
||||
<string name="suggestions_updating">सुझाव अपडेट हो रहे हैं</string>
|
||||
<string name="confirm">पुष्टि करें</string>
|
||||
<string name="suggestions_excluded_genres_summary">वे शैलियाँ निर्दिष्ट करें जिन्हें आप सुझावों में नहीं देखना चाहते</string>
|
||||
<string name="password_length_hint">पासवर्ड 4 अक्षर या अधिक का होना चाहिए</string>
|
||||
<string name="text_delete_local_manga_batch">डिवाइस से चयनित आइटम स्थायी रूप से हटाएं?</string>
|
||||
<string name="suggestions_excluded_genres_summary">वे शैलियां निर्दिष्ट करें जिन्हें आप सुझावों में नहीं देखना चाहते</string>
|
||||
<string name="password_length_hint">पासवर्ड 4 वर्णों या अधिक का होना चाहिए</string>
|
||||
<string name="text_delete_local_manga_batch">डिवाइस से चयनित वस्तुएं स्थायी रूप से मिटाएं?</string>
|
||||
<string name="text_clear_search_history_prompt">हाल की सभी खोज क्वेरी को स्थायी रूप से हटा दें?</string>
|
||||
<string name="about">बारे में</string>
|
||||
<string name="about">ऐप के बारे में</string>
|
||||
<string name="backup_restore">बैकअप और पुनर्स्थापना</string>
|
||||
<string name="preparing_">तैयार कर रहे हैं…</string>
|
||||
<string name="file_not_found">फाइल नहीं मिली</string>
|
||||
<string name="data_restored_success">सारा डेटा पुनर्स्थापित कर दिया गया</string>
|
||||
<string name="backup_information">आप अपने इतिहास और पसंदीदा का बैकअप बना सकते हैं और उसे पुनर्स्थापित कर सकते हैं</string>
|
||||
<string name="show_notification_new_chapters_on">आप जो मंगा पढ़ रहे हैं उसके अपडेट के बारे में आपको सूचनाएं प्राप्त होंगी</string>
|
||||
<string name="show_notification_new_chapters_on">आप जो मंगा पढ़ रहे हैं उसके अद्यतन के बारे में आपको सूचनाएं प्राप्त होंगी</string>
|
||||
<string name="dns_over_https">HTTPS पर DNS</string>
|
||||
<string name="default_mode">डिफ़ॉल्ट मोड</string>
|
||||
<string name="disable_battery_optimization_summary">बैकग्राउंड अपडेट जांच में मदद करता है</string>
|
||||
<string name="default_mode">तयशुदा मोड</string>
|
||||
<string name="disable_battery_optimization_summary">बैकग्राउंड अद्यतन जांच में मदद करता है</string>
|
||||
<string name="crash_text">कुछ गलत हो गया। कृपया इसे ठीक करने में हमारी सहायता के लिए डेवलपर्स को एक बग रिपोर्ट सबमिट करें।</string>
|
||||
<string name="show_reading_indicators_summary">इतिहास और पसंदीदा में पढ़ा गया प्रतिशत दिखाएँ</string>
|
||||
<string name="show_reading_indicators_summary">इतिहास और पसंदीदा में पढ़ा गया प्रतिशत दिखाएं</string>
|
||||
<string name="show_all">सब दिखाएं</string>
|
||||
<string name="downloads_cancelled">डाउनलोड रद्द कर दिए गए हैं</string>
|
||||
<string name="no_bookmarks_yet">अभी तक कोई बुकमार्क नहीं</string>
|
||||
<string name="no_bookmarks_summary">आप मंगा पढ़ते समय बुकमार्क बना सकते हैं</string>
|
||||
<string name="sync_settings">सिंक्रोनाइज़ेशन सेटिंग्स</string>
|
||||
<string name="exit_confirmation_summary">ऐप से बाहर निकलने के लिए बैक को दो बार दबाएँ</string>
|
||||
<string name="sync_settings">समन्वयन सेटिंग्स</string>
|
||||
<string name="exit_confirmation_summary">ऐप से बाहर निकलने के लिए \"पीछे\" को दो बार दबाएं</string>
|
||||
<string name="server_address">सर्वर पता</string>
|
||||
<string name="sync_host_description">आप स्व-होस्टेड सिंक्रनाइज़ेशन सर्वर या डिफ़ॉल्ट सर्वर का उपयोग कर सकते हैं। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे न बदलें।</string>
|
||||
<string name="sync_host_description">आप स्वयं-होस्ट किये गये समन्वयन सर्वर या तयशुदा सर्वर का उपयोग कर सकते हैं। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे न बदलें।</string>
|
||||
<string name="no_chapters">कोई अध्याय नहीं</string>
|
||||
<string name="automatic_scroll">स्वचालित स्क्रॉल</string>
|
||||
<string name="importing_manga">मंगा आयात किया जा रहा है</string>
|
||||
<string name="import_completed_hint">स्थान बचाने के लिए आप मूल फ़ाइल को स्टोरेज से हटा सकते हैं</string>
|
||||
<string name="import_completed_hint">स्थान बचाने के लिए आप मूल फाइल को स्टोरेज से हटा सकते हैं</string>
|
||||
<string name="network_unavailable_hint">मंगा को ऑनलाइन पढ़ने के लिए Wi-Fi या मोबाइल नेटवर्क चालू करें</string>
|
||||
<string name="source_disabled">स्रोत अक्षम किया गया</string>
|
||||
<string name="prefetch_content">सामग्री प्रीलोड हो रही है</string>
|
||||
<string name="share_logs">लॉग साझा करें</string>
|
||||
<string name="enable_logging_summary">डिबग उद्देश्यों के लिए कुछ क्रियाएँ रिकॉर्ड करें। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे चालू न करें</string>
|
||||
<string name="enable_logging_summary">डिबग उद्देश्यों के लिए कुछ क्रियाएं रिकॉर्ड करें। यदि आप निश्चित नहीं हैं कि आप क्या कर रहे हैं तो इसे चालू न करें</string>
|
||||
<string name="theme_name_asuka">Asuka</string>
|
||||
<string name="theme_name_mion">Mion</string>
|
||||
<string name="theme_name_sakura">Sakura</string>
|
||||
<string name="theme_name_mamimi">Mamimi</string>
|
||||
<string name="allow_unstable_updates">अस्थिर अपडेट की अनुमति दें</string>
|
||||
<string name="allow_unstable_updates">अस्थिर अद्यतन की अनुमति दें</string>
|
||||
<string name="nothing_here">यहां कुछ नहीं है</string>
|
||||
<string name="allow_unstable_updates_summary">अस्थिर बिल्ड के बारे में सूचनाएं प्राप्त करें</string>
|
||||
<string name="categories_delete_confirm">क्या आप वाकई चयनित पसंदीदा श्रेणियां हटाना चाहते हैं?
|
||||
<string name="categories_delete_confirm">क्या आप वाकई चयनित पसंदीदा श्रेणियां मिटाना चाहते हैं?
|
||||
\nइसमें मौजूद सारा मंगा नष्ट हो जाएगा और इसे पूर्ववत नहीं किया जा सकता।</string>
|
||||
<string name="manga_error_description_pattern">त्रुटि विवरण:<br><tt>%1$s</tt><br><br>1. यह सुनिश्चित करने के लिए कि यह अपने स्रोत पर उपलब्ध है <a href=%2$s>मंगा को वेब ब्राउज़र में खोलने का प्रयास करें</a><br>2। सुनिश्चित करें कि आप <a href=kotatsu://about>Kotatsu के नवीनतम संस्करण</a><br>3 का उपयोग कर रहे हैं। यदि यह उपलब्ध है, तो डेवलपर्स को एक त्रुटि रिपोर्ट भेजें।</string>
|
||||
<string name="folder_with_images_import_description">आप अभिलेखों या छवियों वाली एक डॉयरेक्टरी का चयन कर सकते हैं। प्रत्येक संग्रह (या उपडॉयरेक्टरी) को एक अध्याय के रूप में पहचाना जाएगा।</string>
|
||||
<string name="images_procy_description">यदि संभव हो तो ट्रैफ़िक उपयोग को कम करने और छवि लोडिंग को तेज़ करने के लिए wsrv.nl सेवा का उपयोग करें</string>
|
||||
<string name="manga_error_description_pattern">त्रुटि विवरण:<br><tt>%1$s</tt><br><br>1। यह सुनिश्चित करने के लिए कि यह अपने स्रोत पर उपलब्ध है <a href=%2$s>मंगा को वेब ब्राउज़र में खोलने का प्रयास करें</a><br>2। सुनिश्चित करें कि आप <a href=kotatsu://about>Kotatsu के नवीनतम संस्करण</a><br>3 का उपयोग कर रहे हैं। यदि यह उपलब्ध है, तो डेवलपर्स को एक त्रुटि रिपोर्ट भेजें।</string>
|
||||
<string name="folder_with_images_import_description">आप अभिलेखों या छवियों वाली एक निर्देशिका का चयन कर सकते हैं। प्रत्येक संग्रह (या उपनिर्देशिका) को एक अध्याय के रूप में पहचाना जाएगा।</string>
|
||||
<string name="images_procy_description">यदि संभव हो तो ट्रैफिक उपयोग को कम करने और छवि लोडिंग को तेज़ करने के लिए wsrv.nl सेवा का उपयोग करें</string>
|
||||
<string name="state_abandoned">गिरा दिया गया</string>
|
||||
<string name="list_options">विकल्पों की सूची बनाएं</string>
|
||||
<string name="by_relevance">प्रासंगिकता</string>
|
||||
<string name="categories">श्रेणियाँ</string>
|
||||
<string name="reader_optimize_summary">कम मेमोरी का उपयोग करने के लिए ऑफस्क्रीन पन्नो की गुणवत्ता कम करें</string>
|
||||
<string name="error_multiple_states_not_supported">एकाधिक राज्यों द्वारा फ़िल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="color_correction_apply_text">ये सेटिंग्स विश्व स्तर पर या केवल वर्तमान मंगा पर लागू की जा सकती हैं। यदि विश्व स्तर पर लागू किया जाता है, तो व्यक्तिगत सेटिंग्स को ओवरराइड नहीं किया जाएगा।</string>
|
||||
<string name="categories">श्रेणियां</string>
|
||||
<string name="reader_optimize_summary">कम मेमोरी का उपयोग करने के लिए छुपे पन्नो की गुणवत्ता कम करें</string>
|
||||
<string name="error_multiple_states_not_supported">एकाधिक राज्यों द्वारा फिल्टर करना इस मंगा स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="color_correction_apply_text">ये सेटिंग्स विश्व स्तर पर या केवल वर्तमान मंगा पर लागू की जा सकती हैं। यदि विश्व स्तर पर लागू किया जाता है, तो व्यक्तिगत सेटिंग्स को अध्यारोहण नहीं किया जाएगा।</string>
|
||||
<string name="this_manga">यह मंगा</string>
|
||||
<string name="error_filter_locale_genre_not_supported">शैलियों और स्थान दोनों के आधार पर फ़िल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="welcome_text">कृपया चुनें कि आप कौन से सामग्री स्रोत सक्षम करना चाहते हैं। इसे बाद में सेटिंग्स में भी कॉन्फ़िगर किया जा सकता है</string>
|
||||
<string name="sync_auth">खाता सिंक करने के लिए लॉगिन करें</string>
|
||||
<string name="error_filter_states_genre_not_supported">शैलियों और राज्यों दोनों द्वारा फ़िल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="error_filter_locale_genre_not_supported">शैलियों और स्थान दोनों के आधार पर फिल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="welcome_text">कृपया चुनें कि आप कौन से सामग्री स्रोत सक्षम करना चाहते हैं। इसे बाद में सेटिंग्स में भी विन्यस्त किया जा सकता है</string>
|
||||
<string name="sync_auth">खाता समन्वयन करने के लिए लॉगिन करें</string>
|
||||
<string name="error_filter_states_genre_not_supported">शैलियों और राज्यों दोनों द्वारा फिल्टर करना इस स्रोत द्वारा समर्थित नहीं है</string>
|
||||
<string name="prev_chapter">पिछला अध्याय</string>
|
||||
<string name="default_page_save_dir">डिफॉल्ट पेज सेव डायरेक्टरी</string>
|
||||
<string name="reader_actions_summary">टैप करने योग्य स्क्रीन क्षेत्रों के लिए क्रियाएँ कॉन्फ़िगर करें</string>
|
||||
<string name="reader_control_ltr_summary">दाएँ किनारे पर टैप करने या दाएँ कुंजी दबाने से हमेशा अगले पेज पर स्विच हो जाता है।</string>
|
||||
<string name="prev_page">पिछला पेज</string>
|
||||
<string name="default_page_save_dir">तयशुदा पृष्ठ सहेजने की डायरेक्टरी</string>
|
||||
<string name="reader_actions_summary">टैप करने योग्य स्क्रीन क्षेत्रों के लिए कार्रवाइयां विन्यस्त करें</string>
|
||||
<string name="reader_control_ltr_summary">दाएं किनारे पर टैप करने या दाएं कुंजी दबाने से हमेशा अगले पृष्ठ पर स्विच हो जाता है।</string>
|
||||
<string name="prev_page">पिछला पृष्ठ</string>
|
||||
<string name="switch_pages_volume_buttons_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="check_for_new_chapters_disabled">नए अध्यायों की जांच अक्षम है</string>
|
||||
<string name="show_labels_in_navbar">नेविगेशन बार में लेबल दिखाएं</string>
|
||||
<string name="pages_saving">पन्ने सहेजा जा रहा है</string>
|
||||
<string name="ask_for_dest_dir_every_time">हर बार गंतव्य स्थान के लिए पूछें</string>
|
||||
<string name="preferred_download_format">वरीय डाउनलोड प्रारूप</string>
|
||||
<string name="multiple_cbz_files">अनेक CBZ फ़ाइलें</string>
|
||||
<string name="automatic">ऑटोमैटिक</string>
|
||||
<string name="single_cbz_file">एकल CBZ फ़ाइल</string>
|
||||
<string name="reading_stats">पठन आँकड़े</string>
|
||||
<string name="stats_cleared">आंकड़े साफ़ हो गए</string>
|
||||
<string name="multiple_cbz_files">अनेक CBZ फाइलें</string>
|
||||
<string name="automatic">स्वचालित</string>
|
||||
<string name="single_cbz_file">एकल CBZ फाइल</string>
|
||||
<string name="reading_stats">पठन आंकड़े</string>
|
||||
<string name="stats_cleared">आंकड़े साफ हो गए</string>
|
||||
<string name="week">हफ्ता</string>
|
||||
<string name="all_time">पूरा समय</string>
|
||||
<string name="other_manga">अन्य मंगा</string>
|
||||
<string name="less_than_minute">एक मिनट से कम</string>
|
||||
<string name="statistics">आंकड़े</string>
|
||||
<string name="clear_stats_confirm">क्या आप सचमुच सभी पठन आँकड़े साफ़ करना चाहते हैं? इस क्रिया को पूर्ववत नहीं किया जा सकता है।</string>
|
||||
<string name="clear_stats_confirm">क्या आप सचमुच सभी पठन आंकड़े साफ करना चाहते हैं? इस क्रिया को पूर्ववत नहीं किया जा सकता है।</string>
|
||||
<string name="three_months">तीन महीने</string>
|
||||
<string name="clear_stats">आँकड़े साफ़ करें</string>
|
||||
<string name="clear_stats">आंकड़े साफ करें</string>
|
||||
<string name="month">महीना</string>
|
||||
<string name="day">दिन</string>
|
||||
<string name="empty_stats_text">चयनित अवधि के लिए कोई आँकड़े नहीं हैं</string>
|
||||
<string name="pages_read_s">पेज पढ़ें गए: %s</string>
|
||||
<string name="manga_migration">मंगा माइग्रेशन</string>
|
||||
<string name="migration_completed">माइग्रेशन पूरा हुआ</string>
|
||||
<string name="migrate">माइग्रेशन</string>
|
||||
<string name="empty_stats_text">चयनित अवधि के लिए कोई आंकड़े नहीं हैं</string>
|
||||
<string name="pages_read_s">पठित पृष्ठ: %s</string>
|
||||
<string name="manga_migration">मंगा प्रवासन</string>
|
||||
<string name="migration_completed">प्रवासन पूरा हुआ</string>
|
||||
<string name="migrate">प्रवासन</string>
|
||||
<string name="alternatives">वैकल्पिक</string>
|
||||
<string name="migrate_confirmation">आपके इतिहास और पसंदीदा में \"%2$s\" से मंगा \"%1$s\" को \"%4$s\" से \"%3$s\" से बदल दिया जाएगा (यदि मौजूद है)</string>
|
||||
<string name="delete_read_chapters">पढ़े हुए अध्याय हटाएँ</string>
|
||||
<string name="no_chapters_deleted">कोई अध्याय नहीं हटाया गया है</string>
|
||||
<string name="chapters_deleted_pattern">%1$s को हटाया गया, %2$s को साफ़ किया गया</string>
|
||||
<string name="delete_read_chapters_summary">स्थान खाली करने के लिए स्थानीय स्टोरेज से उन अध्यायों को हटा दें जिन्हें आप पढ़ चुके हैं</string>
|
||||
<string name="delete_read_chapters_prompt">यह आपके स्थानीय स्टोरेज से पढ़े गए के रूप में चिह्नित सभी अध्यायों को स्थायी रूप से हटा देगा। आप इसे बाद में पुनः डाउनलोड कर सकते हैं, लेकिन आयातित अध्याय हमेशा के लिए खो सकते हैं</string>
|
||||
<string name="delete_read_chapters_auto">पढ़े गए अध्याय स्वचालित रूप से हटाएं</string>
|
||||
<string name="delete_read_chapters">पढ़े हुए अध्याय मिटाएं</string>
|
||||
<string name="no_chapters_deleted">कोई अध्याय नहीं मिटाया गया है</string>
|
||||
<string name="chapters_deleted_pattern">%1$s को हटाया गया, %2$s को साफ किया गया</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">एप्लिकेशन प्रारंभ होने पर चलता है</string>
|
||||
<string name="chapters_grid_view">ग्रिड दृश्य</string>
|
||||
<string name="split_by_translations">अनुवाद द्वारा विभाजित</string>
|
||||
<string name="split_by_translations_summary">विभिन्न अनुवादों वाले अध्यायों को एक सूची के बजाय अलग-अलग दिखाएँ</string>
|
||||
<string name="split_by_translations_summary">विभिन्न अनुवादों वाले अध्यायों को एक सूची के बजाय अलग-अलग दिखाएं</string>
|
||||
<string name="order_oldest">सबसे पुराना</string>
|
||||
<string name="long_ago_read">बहुत पहले पढ़ा था</string>
|
||||
<string name="unread">अपठित</string>
|
||||
<string name="unsupported_source">यह मंगा स्रोत समर्थित नहीं है</string>
|
||||
<string name="enable_source">स्रोत सक्षम करें</string>
|
||||
<string name="show_pages_thumbs_summary">विवरण स्क्रीन पर \"पेज\" टैब सक्षम करें</string>
|
||||
<string name="show_pages_thumbs">पेज थंबनेल दिखाएँ</string>
|
||||
<string name="show_pages_thumbs_summary">विवरण स्क्रीन पर \"पृष्ठ\" टैब सक्षम करें</string>
|
||||
<string name="show_pages_thumbs">पृष्ठ थंबनेल दिखाएं</string>
|
||||
<string name="error_no_data_received">सर्वर से कोई डेटा प्राप्त नहीं हुआ</string>
|
||||
<string name="unsupported_backup_message">कृपया उचित कोटात्सु बैकअप फाइल चुनें</string>
|
||||
<string name="unsupported_backup_message">कृपया उचित Kotatsu बैकअप फाइल चुनें</string>
|
||||
<string name="hours_short">%d घं</string>
|
||||
<string name="minutes_short">%d मि</string>
|
||||
<string name="hours_minutes_short">%1$d घं %2$d मि</string>
|
||||
<string name="fix">ठीक करें</string>
|
||||
<string name="missing_storage_permission">बाहरी स्टोरेज पर मंगा तक पहुंचने की कोई अनुमति नहीं है</string>
|
||||
<string name="last_used">अंतिम प्रयोग</string>
|
||||
<string name="show_updated">अद्यतन दिखाएँ</string>
|
||||
<string name="show_updated">अद्यतित दिखाएं</string>
|
||||
<string name="webtoon_gaps">वेबटून मोड में अंतराल</string>
|
||||
<string name="webtoon_gaps_summary">वेबटून मोड में पृष्ठों के बीच लंबवत अंतराल दिखाएं</string>
|
||||
<string name="less_frequently">कम बार</string>
|
||||
<string name="more_frequently">अधिक बार</string>
|
||||
<string name="frequency_of_check">जांच की आवृत्ति</string>
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
<string name="pin_navigation_ui">पिन नेविगेशन UI</string>
|
||||
<string name="pin_navigation_ui">नेविगेशन UI पिन करें</string>
|
||||
<string name="pin_navigation_ui_summary">स्क्रॉल पर नेविगेशन बार और खोज दृश्य को न छिपाएं</string>
|
||||
<string name="search_suggestions">सुझाव खोजें</string>
|
||||
<string name="recent_queries">हाल के प्रश्न</string>
|
||||
<string name="suggested_queries">सुझाए गए प्रश्न</string>
|
||||
<string name="authors">लेखक</string>
|
||||
<string name="blocked_by_server_message">आपको सर्वर द्वारा ब्लॉक कर दिया गया है. किसी भिन्न नेटवर्क कनेक्शन (VPN, प्रॉक्सी, आदि) का उपयोग करने का प्रयास करें</string>
|
||||
</resources>
|
||||
<string name="authors">रचयिता</string>
|
||||
<string name="blocked_by_server_message">आपको सर्वर द्वारा अवरुद्ध कर दिया गया है। किसी भिन्न नेटवर्क कनेक्शन (VPN, प्रॉक्सी, आदि) का उपयोग करने का प्रयास करें</string>
|
||||
<string name="disable">अक्षम करें</string>
|
||||
<string name="sources_disabled">स्रोत अक्षम</string>
|
||||
<string name="disable_connectivity_check">कनेक्टिविटी जांच अक्षम करें</string>
|
||||
<string name="ignore_ssl_errors_summary">यदि नेटवर्क संसाधनों तक पहुँचने के दौरान आपको SSL से संबंधित समस्याओं का सामना करना पड़ता है तो आप SSL प्रमाणपत्र सत्यापन को अक्षम कर सकते हैं। इससे आपकी सुरक्षा प्रभावित हो सकती है। इस सेटिंग को बदलने के बाद एप्लिकेशन को पुनरारंभ करना आवश्यक है।</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="tracker_debug_info">नए अध्याय लॉग की जांच की जा रही है</string>
|
||||
<string name="tracker_debug_info_summary">नए अध्यायों के लिए पृष्ठभूमि जांच के बारे में जानकारी डीबग करें</string>
|
||||
</resources>
|
||||
@@ -614,4 +614,4 @@
|
||||
<string name="runs_on_app_start">Futtatás amikor az alkalmazás elindul</string>
|
||||
<string name="enable_source">Forrás engedélyezése</string>
|
||||
<string name="unsupported_source">Ez a manga forrás nem támogatott</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -307,7 +307,7 @@
|
||||
<string name="other_cache">Altra cache</string>
|
||||
<string name="storage_usage">Utilizzo dello spazio di archiviazione</string>
|
||||
<string name="available">Disponibile</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="removed_from_favourites">Rimosso dai preferiti</string>
|
||||
<string name="options">Opzioni</string>
|
||||
<string name="no_chapters">Nessun capitolo</string>
|
||||
@@ -318,7 +318,7 @@
|
||||
<string name="importing_manga">Importazione di manga</string>
|
||||
<string name="import_will_start_soon">L\'importazione inizierà presto</string>
|
||||
<string name="feed">Flusso</string>
|
||||
<string name="reader_control_ltr_summary">Toccando il bordo destro o premendo il tasto destro si passa sempre alla pagina successiva</string>
|
||||
<string name="reader_control_ltr_summary">Toccando il bordo destro o premendo il tasto destro si passa sempre alla pagina successiva.</string>
|
||||
<string name="contrast">Contrasto</string>
|
||||
<string name="reset">Ripristina</string>
|
||||
<string name="reader_slider">Mostra il cursore di cambio pagina</string>
|
||||
@@ -538,4 +538,59 @@
|
||||
<string name="volume_">Volume %d</string>
|
||||
<string name="category_hidden_done">Questa categoria è stata nascosta dalla schermata principale ed è accessibile tramite Menù → Gestisci categorie</string>
|
||||
<string name="remaining_time_pattern">%1$s %2$s</string>
|
||||
</resources>
|
||||
<string name="last_used">Ultimo utilizzo</string>
|
||||
<string name="incognito_mode_hint">I tuoi progressi di lettura non saranno salvati</string>
|
||||
<string name="vertical">Verticale</string>
|
||||
<string name="last_read">Ultima lettura</string>
|
||||
<string name="reading_time_estimation_summary">Il tempo di lettura stimato potrebbe essere inaccurato</string>
|
||||
<string name="reading_time_estimation">Mostra il tempo di lettura stimato</string>
|
||||
<string name="email_password_enter_hint">Inserisci la tua email e password per continuare</string>
|
||||
<string name="show_menu">Mostra menu</string>
|
||||
<string name="switch_pages_volume_buttons_summary">Usa i pulsanti del volume per cambiare pagine</string>
|
||||
<string name="tap_action">Azioni tap</string>
|
||||
<string name="long_tap_action">Azioni tap prolungato</string>
|
||||
<string name="none">Nessuno</string>
|
||||
<string name="fullscreen_mode">Modalità schermo interno</string>
|
||||
<string name="chapters_grid_view">Vista griglia</string>
|
||||
<string name="prev_chapter">Capitolo precedente</string>
|
||||
<string name="reader_actions_summary">Configura le azioni per le aree di schermo cliccabili</string>
|
||||
<string name="suggestions_unavailable_text">La funzione di suggerimento è disabilitata</string>
|
||||
<string name="show_labels_in_navbar">Mostra le etichette nella barra di navigazione</string>
|
||||
<string name="delete_read_chapters_summary">Rimuovi i capitoli già letti dalla memoria locale per liberare spazio</string>
|
||||
<string name="delete_read_chapters_prompt">Questo eliminerà in modo permanente tutti i capitoli segnati come già letti dalla memoria locale. Puoi scaricarli di nuovo, ma i capitoli importati potrebbero essere persi per sempre</string>
|
||||
<string name="order_oldest">Meno recenti</string>
|
||||
<string name="long_ago_read">Letto molto tempo fa</string>
|
||||
<string name="unsupported_source">Questa fonte manga non è supportata</string>
|
||||
<string name="hours_short">%d ore</string>
|
||||
<string name="minutes_short">%d m</string>
|
||||
<string name="show_updated">Mostra aggiornato</string>
|
||||
<string name="toggle_ui">Mostra/nascondi UI</string>
|
||||
<string name="next_chapter">Prossimo capitolo</string>
|
||||
<string name="prev_page">Pagina precedente</string>
|
||||
<string name="next_page">Prossima pagina</string>
|
||||
<string name="reader_actions">Azioni del lettore</string>
|
||||
<string name="switch_pages_volume_buttons">Abilita pulsanti del volume</string>
|
||||
<string name="reader_fullscreen_summary">Nascondi le barre di stato e di notifica</string>
|
||||
<string name="check_for_new_chapters_disabled">Il controllo di nuovi capitoli è disabilitato</string>
|
||||
<string name="delete_read_chapters_auto">Elimina i capitoli già letti automaticamente</string>
|
||||
<string name="runs_on_app_start">Esegui quando l\'applicazione viene avviata</string>
|
||||
<string name="split_by_translations">Dividi per traduzioni</string>
|
||||
<string name="split_by_translations_summary">Mostra separatamente i capitoli con diverse traduzioni, invece che in un\'unica lista</string>
|
||||
<string name="unread">Non letto</string>
|
||||
<string name="show_pages_thumbs">Mostra le anteprime delle pagine</string>
|
||||
<string name="show_pages_thumbs_summary">Abilita la tab \"Pagine\" nella schermata di dettaglio</string>
|
||||
<string name="unsupported_backup_message">Si prega di selezione un file backup di Kotatsu corretto</string>
|
||||
<string name="hours_minutes_short">%1$d h %2$d m</string>
|
||||
<string name="fix">Aggiustamenti</string>
|
||||
<string name="missing_storage_permission">Non sono presenti i permessi per accedere al manga nella memoria esterna</string>
|
||||
<string name="webtoon_gaps">Gap in modalità webtoon</string>
|
||||
<string name="webtoon_gaps_summary">Mostra gap verticali tra le pagine in modalità webotoon</string>
|
||||
<string name="config_reset_confirm">Ripristinare le impostazioni ai valori predefiniti? Questo procedimento è irreversibile.</string>
|
||||
<string name="use_two_pages_landscape">Usa il layout a due pagine con l\'orientamento orizzontale (beta)</string>
|
||||
<string name="error_no_data_received">Non è stato ricevuto nessun dato dal server</string>
|
||||
<string name="enable_source">Attiva fonte</string>
|
||||
<string name="less_frequently">Meno frequente</string>
|
||||
<string name="more_frequently">Più frequente</string>
|
||||
<string name="frequency_of_check">Frequenza di controllo</string>
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
</resources>
|
||||
@@ -462,4 +462,4 @@
|
||||
<string name="reader_zoom_buttons_summary">右下にズームコントロールボタンを表示するかどうか</string>
|
||||
<string name="reader_zoom_buttons">ズームボタンを表示</string>
|
||||
<string name="zoom_out">ズームアウト</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -514,4 +514,4 @@
|
||||
<string name="this_manga">Бұл маңга</string>
|
||||
<string name="error_filter_locale_genre_not_supported">Бұл дереккөзде жанр бойынша және локал файлдар бойынша сүзуге болмайды</string>
|
||||
<string name="error_multiple_genres_not_supported">Бұл дереккөзде бірнеше жанр бойынша сүзуге болмайды</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -337,4 +337,4 @@
|
||||
<string name="reset">초기화</string>
|
||||
<string name="text_unsaved_changes_prompt">저장되지 않은 변경 사항을 저장하거나 삭제하시겠습니까\?</string>
|
||||
<string name="manga_error_description_pattern">오류 세부정보:<br><tt>%1$s</tt><br><br>1. <a href=%2$s>웹 브라우저에서 만화를 열어</a> 소스에서 사용할 수 있는지 확인하세요<br>2. <a href=kotatsu://about>최신 버전의 Kotatsu</a><br>를 사용하고 있는지 확인하세요.3. 사용 가능한 경우 개발자에게 오류 보고서를 보냅니다.</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -303,4 +303,4 @@
|
||||
<string name="error_corrupted_file">Data tidak sah dikembalikan atau fail rosak</string>
|
||||
<string name="on_device">Pada peranti</string>
|
||||
<string name="directories">Panduan</string>
|
||||
</resources>
|
||||
</resources>
|
||||
30
app/src/main/res/values-my/plurals.xml
Normal file
30
app/src/main/res/values-my/plurals.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="other">%1$d စာမျက်နှာအသစ်</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="other">%1$d စာမျက်နှာ</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="other">%1$d မိနစ်အကြာက</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="other">%1$d နာရီအကြာက</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="other">%1$d အချက်အလက်</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="other">%1$d ရက်အကြာက</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="other">%1$d လအကြာက</item>
|
||||
</plurals>
|
||||
<plurals name="hours">
|
||||
<item quantity="other">%1$d နာရီ</item>
|
||||
</plurals>
|
||||
<plurals name="minutes">
|
||||
<item quantity="other">%1$d မိနစ်</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -387,4 +387,4 @@
|
||||
<string name="downloads_paused">Stansa hentingane</string>
|
||||
<string name="sync_auth_hint">Du kan logge inn på ein konto du alt har, eller lage ein ny ein</string>
|
||||
<string name="invalid_value_message">Ugild verdi</string>
|
||||
</resources>
|
||||
</resources>
|
||||
@@ -236,7 +236,7 @@
|
||||
<string name="invalid_domain_message">Nieważna domena</string>
|
||||
<string name="reorder">Zmień kolejność</string>
|
||||
<string name="exit_confirmation">Potwierdzenie wyjścia</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="reader_info_pattern">Rozdz. %1$d/%2$d Str. %3$d/%4$d</string>
|
||||
<string name="network_unavailable_hint">Włącz Wi-Fi lub sieć komórkową, aby czytać mangę online</string>
|
||||
<string name="_import">Importuj</string>
|
||||
@@ -632,4 +632,13 @@
|
||||
<string name="pin_navigation_ui_summary">Nie ukrywaj paska nawigacji i paska wyszukiwania podczas przewijania</string>
|
||||
<string name="search_suggestions">Sugestie wyszukiwania</string>
|
||||
<string name="blocked_by_server_message">Jesteś zablokowany przez serwer. Spróbuj użyć innego połączenia sieciowego (VPN, proxy itp.)</string>
|
||||
<string name="sources_disabled">Źródła wyłączone</string>
|
||||
<string name="disable">Wyłączone</string>
|
||||
<string name="disable_connectivity_check">Wyłącz sprawdzanie łączności</string>
|
||||
<string name="ignore_ssl_errors_summary">Możesz wyłączyć weryfikację certyfikatów SSL w przypadku wystąpienia problemów związanych z SSL podczas uzyskiwania dostępu do zasobów sieciowych. Może to mieć wpływ na Twoje bezpieczeństwo. Po zmianie tego ustawienia wymagane jest ponowne uruchomienie aplikacji.</string>
|
||||
<string name="disable_connectivity_check_summary">Pomiń sprawdzanie łączności w przypadku problemów z nią (np. przejście do trybu offline, mimo że sieć jest podłączona)</string>
|
||||
<string name="disable_nsfw_notifications">Wyłącz powiadomienia NSFW</string>
|
||||
<string name="disable_nsfw_notifications_summary">Nie pokazuj powiadomień o aktualizacjach mangi NSFW</string>
|
||||
<string name="tracker_debug_info_summary">Debuguj informacje o sprawdzaniu dostępności nowych rozdziałów</string>
|
||||
<string name="tracker_debug_info">Dziennik sprawdzania nowych rozdziałów</string>
|
||||
</resources>
|
||||
@@ -308,7 +308,7 @@
|
||||
<string name="reader_info_pattern">Cap. %1$d/%2$d Pág. %3$d/%4$d</string>
|
||||
<string name="reader_info_bar">Mostrar barra de informações no leitor</string>
|
||||
<string name="folder_with_images">Pasta com imagens</string>
|
||||
<string name="memory_usage_pattern">%s - %s</string>
|
||||
<string name="memory_usage_pattern">%1$s - %2$s</string>
|
||||
<string name="comics_archive">Arquivo de quadrinhos</string>
|
||||
<string name="importing_manga">Importando mangá(s)</string>
|
||||
<string name="import_completed">Importação completa</string>
|
||||
@@ -619,4 +619,22 @@
|
||||
<string name="delete_read_chapters_prompt">Isso irá apagar permanentemente todos os capítulos marcados como lidos do armazenamento local. Você pode baixá-los novamente mais tarde, mas os capítulos importados podem ser perdidos para sempre</string>
|
||||
<string name="last_used">Usado pela última vez</string>
|
||||
<string name="show_updated">Mostrar atualização</string>
|
||||
</resources>
|
||||
<string name="disable_connectivity_check">Desativar a verificação de conectividade</string>
|
||||
<string name="disable_connectivity_check_summary">Ignore a verificação de conectividade caso tenha problemas com ela (por exemplo, entrar no modo off-line mesmo que a rede esteja conectada)</string>
|
||||
<string name="webtoon_gaps">Lacunas no modo webtoon</string>
|
||||
<string name="webtoon_gaps_summary">Mostrar espaços verticais entre as páginas no modo webtoon</string>
|
||||
<string name="authors">Autores</string>
|
||||
<string name="ignore_ssl_errors_summary">Você pode desativar a verificação de certificados SSL caso tenha problemas relacionados a SSL ao acessar recursos de rede. Isso pode afetar sua segurança. É necessário reiniciar o aplicativo após alterar essa configuração.</string>
|
||||
<string name="search_suggestions">Sugestões de Pesquisa</string>
|
||||
<string name="recent_queries">Consultas recentes</string>
|
||||
<string name="suggested_queries">Consultas sugeridas</string>
|
||||
<string name="disable">Desativar</string>
|
||||
<string name="sources_disabled">Fontes desativadas</string>
|
||||
<string name="blocked_by_server_message">Você está bloqueado pelo servidor. Tente usar uma conexão de internet diferente (VPN, Proxy, etc.)</string>
|
||||
<string name="less_frequently">Com menos frequência</string>
|
||||
<string name="more_frequently">Com mais frequência</string>
|
||||
<string name="frequency_of_check">Frequência de verificação</string>
|
||||
<string name="new_chapters_pattern">%1$s: %2$d</string>
|
||||
<string name="pin_navigation_ui">Fixar interface de navegação</string>
|
||||
<string name="pin_navigation_ui_summary">Não esconder barra de navegação e visualização de pesquisa ao rolar</string>
|
||||
</resources>
|
||||
@@ -614,4 +614,20 @@
|
||||
<string name="enable_source">Ativar fonte</string>
|
||||
<string name="unsupported_source">Esta fonte de mangá não é suportada</string>
|
||||
<string name="delete_read_chapters_prompt">Isso irá apagar permanentemente todos os capítulos marcados como lidos do armazenamento local. Você pode baixá-los novamente mais tarde, mas os capítulos importados podem ser perdidos para sempre</string>
|
||||
<string name="last_used">Usado por último</string>
|
||||
<string name="show_updated">Mostrar atualização</string>
|
||||
<string name="webtoon_gaps">Lacunas no modo webtoon</string>
|
||||
<string name="authors">Autores</string>
|
||||
<string name="disable">Desativar</string>
|
||||
<string name="sources_disabled">Fontes desativadas</string>
|
||||
<string name="webtoon_gaps_summary">Mostrar espaços verticais entre páginas no modo webtoon</string>
|
||||
<string name="recent_queries">Consultas recentes</string>
|
||||
<string name="search_suggestions">Sugestões de pesquisa</string>
|
||||
<string name="suggested_queries">Consultas sugeridas</string>
|
||||
<string name="blocked_by_server_message">Você está bloqueado pelo servidor. Tente usar uma conexão de rede diferente (VPN, Proxy, etc.)</string>
|
||||
<string name="less_frequently">Menos frequência</string>
|
||||
<string name="more_frequently">Mais frequência</string>
|
||||
<string name="frequency_of_check">Frequência de verificação</string>
|
||||
<string name="pin_navigation_ui">Fixar interface de navegação</string>
|
||||
<string name="pin_navigation_ui_summary">Ao rolar, não esconda a barra de navegação e a visualização de busca</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user