Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
e4c80b4443
|
|||
|
940d448e00
|
|||
|
5ab48a7545
|
|||
|
cb2bdbdd9a
|
|||
|
|
8fdaf92cc4 | ||
|
|
0416077964 | ||
| 7b60ed6bad | |||
|
|
619be69580 | ||
|
|
9f3c3f8985 | ||
|
|
f345977858 | ||
| 9610caf002 | |||
| b75220a1b7 | |||
| ab2a6f5a17 | |||
| 2aeefc607b | |||
|
|
9af769bc69 | ||
|
|
46b78cfcd7 | ||
|
|
c24324de9a | ||
|
|
48b9c1236d | ||
|
|
c69d293caa | ||
|
|
0f4cca0e07 | ||
|
|
d6500b8fec | ||
|
|
86140cab1e | ||
| 90dfc84119 | |||
| 6a792f8ac3 | |||
| c81e8749b6 | |||
| 5fa260a0c7 | |||
| e0ba4e2686 | |||
| f188d1c0f3 | |||
|
|
6de55afa27 | ||
|
|
21dcb5b754 | ||
|
|
9b3ea57db1 | ||
|
|
032a8607ba | ||
|
|
f7303c5957 | ||
| d696606ef9 | |||
| 0a6e106a1d | |||
| de1a7f0ca8 | |||
| 9d31e76cc7 | |||
|
|
20910ffb5d | ||
|
|
7497ee6364 | ||
|
|
0f2ed50e18 | ||
|
|
ba066b577b | ||
|
|
4496fe876f | ||
|
|
a9f5abebf0 | ||
|
|
bebee2ef27 | ||
|
|
4ec2b0c8fe | ||
| 4a7be70898 | |||
| 2bcba1eb21 | |||
| feca7ba3fc | |||
| 745b349e5e | |||
| 13946783a5 | |||
| 84e5400522 | |||
| 02c9a933d2 | |||
| 92af851d3b |
4
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
4
.github/ISSUE_TEMPLATE/report_bug.yml
vendored
@@ -61,4 +61,6 @@ body:
|
|||||||
label: Acknowledgements
|
label: Acknowledgements
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
|
||||||
required: true
|
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).
|
||||||
|
required: true
|
||||||
|
|||||||
1
.idea/.gitignore
generated
vendored
1
.idea/.gitignore
generated
vendored
@@ -1,3 +1,4 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
/migrations.xml
|
||||||
|
|||||||
3
.idea/gradle.xml
generated
3
.idea/gradle.xml
generated
@@ -4,8 +4,6 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="GRADLE" />
|
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="jbr-17" />
|
<option name="gradleJvm" value="jbr-17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
@@ -14,6 +12,7 @@
|
|||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="resolveExternalAnnotations" value="false" />
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'org.koitharu.kotatsu'
|
applicationId 'org.koitharu.kotatsu'
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
|
//TODO: update as soon as sources becomes available
|
||||||
|
//noinspection OldTargetApi
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 555
|
versionCode 562
|
||||||
versionName '5.2.3'
|
versionName '5.3.5'
|
||||||
generatedDensities = []
|
generatedDensities = []
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
@@ -79,12 +81,12 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.github.KotatsuApp:kotatsu-parsers:86a82970fc') {
|
implementation('com.github.KotatsuApp:kotatsu-parsers:db96a1ff2e') {
|
||||||
exclude group: 'org.json', module: 'json'
|
exclude group: 'org.json', module: 'json'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.22'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.2'
|
||||||
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.core:core-ktx:1.10.1'
|
implementation 'androidx.core:core-ktx:1.10.1'
|
||||||
@@ -107,19 +109,20 @@ dependencies {
|
|||||||
// TODO https://issuetracker.google.com/issues/254846063
|
// TODO https://issuetracker.google.com/issues/254846063
|
||||||
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
implementation 'androidx.work:work-runtime-ktx:2.8.1'
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation('com.google.guava:guava:32.0.0-android') {
|
implementation('com.google.guava:guava:32.0.1-android') {
|
||||||
exclude group: 'com.google.guava', module: 'failureaccess'
|
exclude group: 'com.google.guava', module: 'failureaccess'
|
||||||
exclude group: 'org.checkerframework', module: 'checker-qual'
|
exclude group: 'org.checkerframework', module: 'checker-qual'
|
||||||
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
|
exclude group: 'com.google.j2objc', module: 'j2objc-annotations'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'androidx.room:room-runtime:2.5.1'
|
implementation 'androidx.room:room-runtime:2.5.2'
|
||||||
implementation 'androidx.room:room-ktx:2.5.1'
|
implementation 'androidx.room:room-ktx:2.5.2'
|
||||||
kapt 'androidx.room:room-compiler:2.5.1'
|
//noinspection KaptUsageInsteadOfKsp
|
||||||
|
kapt 'androidx.room:room-compiler:2.5.2'
|
||||||
|
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
|
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
|
||||||
implementation 'com.squareup.okio:okio:3.3.0'
|
implementation 'com.squareup.okio:okio:3.4.0'
|
||||||
|
|
||||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
||||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
||||||
@@ -131,27 +134,27 @@ dependencies {
|
|||||||
|
|
||||||
implementation 'io.coil-kt:coil-base:2.4.0'
|
implementation 'io.coil-kt:coil-base:2.4.0'
|
||||||
implementation 'io.coil-kt:coil-svg:2.4.0'
|
implementation 'io.coil-kt:coil-svg:2.4.0'
|
||||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:1b19231b2f'
|
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:9b1d20be67'
|
||||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||||
implementation 'io.noties.markwon:core:4.6.2'
|
implementation 'io.noties.markwon:core:4.6.2'
|
||||||
|
|
||||||
implementation 'ch.acra:acra-http:5.9.7'
|
implementation 'ch.acra:acra-http:5.10.1'
|
||||||
implementation 'ch.acra:acra-dialog:5.9.7'
|
implementation 'ch.acra:acra-dialog:5.10.1'
|
||||||
|
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
testImplementation 'org.json:json:20230227'
|
testImplementation 'org.json:json:20230618'
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
|
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.test:runner:1.5.2'
|
androidTestImplementation 'androidx.test:runner:1.5.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.5.0'
|
androidTestImplementation 'androidx.test:rules:1.5.0'
|
||||||
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
|
||||||
|
|
||||||
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1'
|
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2'
|
||||||
|
|
||||||
androidTestImplementation 'androidx.room:room-testing:2.5.1'
|
androidTestImplementation 'androidx.room:room-testing:2.5.2'
|
||||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
|
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.0'
|
||||||
|
|
||||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
|
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1'
|
||||||
|
|||||||
@@ -18,6 +18,22 @@
|
|||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="29" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
|
||||||
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
|
<data android:mimeType="text/plain" />
|
||||||
|
</intent>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.speech.action.RECOGNIZE_SPEECH" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name="org.koitharu.kotatsu.KotatsuApp"
|
android:name="org.koitharu.kotatsu.KotatsuApp"
|
||||||
@@ -32,6 +48,7 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales"
|
android:localeConfig="@xml/locales"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Kotatsu"
|
android:theme="@style/Theme.Kotatsu"
|
||||||
@@ -98,6 +115,9 @@
|
|||||||
<data android:host="sync-settings" />
|
<data android:host="sync-settings" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity"
|
||||||
|
android:label="@string/local_manga_directories" />
|
||||||
<activity
|
<activity
|
||||||
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
|
android:name="org.koitharu.kotatsu.browser.BrowserActivity"
|
||||||
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.net.Uri
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.webkit.CookieManager
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
@@ -16,6 +17,7 @@ import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
|
|||||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
|
import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
|
||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
@@ -34,8 +36,9 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
|
|||||||
}
|
}
|
||||||
with(viewBinding.webView.settings) {
|
with(viewBinding.webView.settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = CommonHeadersInterceptor.userAgentChrome
|
userAgentString = UserAgents.CHROME_MOBILE
|
||||||
}
|
}
|
||||||
|
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
|
||||||
viewBinding.webView.webViewClient = BrowserClient(this)
|
viewBinding.webView.webViewClient = BrowserClient(this)
|
||||||
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
|
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
|
||||||
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
|
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
|
|||||||
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
import org.koitharu.kotatsu.core.util.TaggedActivityResult
|
||||||
import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
|
import org.koitharu.kotatsu.core.util.ext.catchingWebViewUnavailability
|
||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@@ -49,10 +50,9 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
|
|||||||
val url = intent?.dataString.orEmpty()
|
val url = intent?.dataString.orEmpty()
|
||||||
with(viewBinding.webView.settings) {
|
with(viewBinding.webView.settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
cacheMode = WebSettings.LOAD_DEFAULT
|
|
||||||
domStorageEnabled = true
|
domStorageEnabled = true
|
||||||
databaseEnabled = true
|
databaseEnabled = true
|
||||||
userAgentString = intent?.getStringExtra(ARG_UA) ?: CommonHeadersInterceptor.userAgentFallback
|
userAgentString = intent?.getStringExtra(ARG_UA) ?: UserAgents.CHROME_MOBILE
|
||||||
}
|
}
|
||||||
viewBinding.webView.webViewClient = CloudFlareClient(cookieJar, this, url)
|
viewBinding.webView.webViewClient = CloudFlareClient(cookieJar, this, url)
|
||||||
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
|
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView).also {
|
||||||
|
|||||||
@@ -33,11 +33,21 @@ abstract class ErrorObserver(
|
|||||||
return resolver != null && ExceptionResolver.canResolve(error)
|
return resolver != null && ExceptionResolver.canResolve(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isAlive(): Boolean {
|
||||||
|
return when {
|
||||||
|
fragment != null -> fragment.view != null
|
||||||
|
activity != null -> !activity.isDestroyed
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected fun resolve(error: Throwable) {
|
protected fun resolve(error: Throwable) {
|
||||||
lifecycleScope.launch {
|
if (isAlive()) {
|
||||||
val isResolved = resolver?.resolve(error) ?: false
|
lifecycleScope.launch {
|
||||||
if (isActive) {
|
val isResolved = resolver?.resolve(error) ?: false
|
||||||
onResolved?.accept(isResolved)
|
if (isActive) {
|
||||||
|
onResolved?.accept(isResolved)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package org.koitharu.kotatsu.core.exceptions.resolve
|
||||||
|
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
|
|
||||||
|
class ToastErrorObserver(
|
||||||
|
host: View,
|
||||||
|
fragment: Fragment?,
|
||||||
|
) : ErrorObserver(host, fragment, null, null) {
|
||||||
|
|
||||||
|
override suspend fun emit(value: Throwable) {
|
||||||
|
val toast = Toast.makeText(host.context, value.getDisplayMessage(host.context.resources), Toast.LENGTH_SHORT)
|
||||||
|
toast.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,14 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
|||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
|
|
||||||
|
@JvmName("mangaIds")
|
||||||
fun Collection<Manga>.ids() = mapToSet { it.id }
|
fun Collection<Manga>.ids() = mapToSet { it.id }
|
||||||
|
|
||||||
fun Collection<Manga>.distinctById() = distinctBy { it.id }
|
fun Collection<Manga>.distinctById() = distinctBy { it.id }
|
||||||
|
|
||||||
|
@JvmName("chaptersIds")
|
||||||
|
fun Collection<MangaChapter>.ids() = mapToSet { it.id }
|
||||||
|
|
||||||
fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
|
fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
|
||||||
if (size <= 1) {
|
if (size <= 1) {
|
||||||
return size
|
return size
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.core.network
|
package org.koitharu.kotatsu.core.network
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
@@ -10,11 +9,11 @@ import okhttp3.Response
|
|||||||
import org.koitharu.kotatsu.BuildConfig
|
import org.koitharu.kotatsu.BuildConfig
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
|
||||||
import org.koitharu.kotatsu.parsers.util.mergeWith
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mergeWith
|
||||||
import java.net.IDN
|
import java.net.IDN
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ class CommonHeadersInterceptor @Inject constructor(
|
|||||||
headersBuilder.mergeWith(it, replaceExisting = false)
|
headersBuilder.mergeWith(it, replaceExisting = false)
|
||||||
}
|
}
|
||||||
if (headersBuilder[CommonHeaders.USER_AGENT] == null) {
|
if (headersBuilder[CommonHeaders.USER_AGENT] == null) {
|
||||||
headersBuilder[CommonHeaders.USER_AGENT] = userAgentFallback
|
headersBuilder[CommonHeaders.USER_AGENT] = UserAgents.CHROME_MOBILE
|
||||||
}
|
}
|
||||||
if (headersBuilder[CommonHeaders.REFERER] == null && repository != null) {
|
if (headersBuilder[CommonHeaders.REFERER] == null && repository != null) {
|
||||||
val idn = IDN.toASCII(repository.domain)
|
val idn = IDN.toASCII(repository.domain)
|
||||||
@@ -62,26 +61,4 @@ class CommonHeadersInterceptor @Inject constructor(
|
|||||||
|
|
||||||
override fun request(): Request = request
|
override fun request(): Request = request
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
val userAgentFallback
|
|
||||||
get() = "Kotatsu/%s (Android %s; %s; %s %s; %s)".format(
|
|
||||||
BuildConfig.VERSION_NAME,
|
|
||||||
Build.VERSION.RELEASE,
|
|
||||||
Build.MODEL,
|
|
||||||
Build.BRAND,
|
|
||||||
Build.DEVICE,
|
|
||||||
Locale.getDefault().language,
|
|
||||||
)
|
|
||||||
|
|
||||||
val userAgentChrome
|
|
||||||
get() = (
|
|
||||||
"Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) " +
|
|
||||||
"Chrome/100.0.4896.127 Mobile Safari/537.36"
|
|
||||||
).format(
|
|
||||||
Build.VERSION.RELEASE,
|
|
||||||
Build.MODEL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.webkit.CookieManager
|
|||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import okhttp3.Cookie
|
import okhttp3.Cookie
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.newBuilder
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@@ -30,6 +31,21 @@ class AndroidCookieJar : MutableCookieJar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun removeCookies(url: HttpUrl) {
|
||||||
|
val cookies = loadForRequest(url)
|
||||||
|
if (cookies.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val urlString = url.toString()
|
||||||
|
for (c in cookies) {
|
||||||
|
val nc = c.newBuilder()
|
||||||
|
.expiresAt(System.currentTimeMillis() - 100000)
|
||||||
|
.build()
|
||||||
|
cookieManager.setCookie(urlString, nc.toString())
|
||||||
|
}
|
||||||
|
check(loadForRequest(url).isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
|
override suspend fun clear() = suspendCoroutine<Boolean> { continuation ->
|
||||||
cookieManager.removeAllCookies(continuation::resume)
|
cookieManager.removeAllCookies(continuation::resume)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,5 +13,8 @@ interface MutableCookieJar : CookieJar {
|
|||||||
@WorkerThread
|
@WorkerThread
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>)
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
fun removeCookies(url: HttpUrl)
|
||||||
|
|
||||||
suspend fun clear(): Boolean
|
suspend fun clear(): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class PreferencesCookieJar(
|
|||||||
private var isLoaded = false
|
private var isLoaded = false
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@Synchronized
|
||||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||||
loadPersistent()
|
loadPersistent()
|
||||||
val expired = HashSet<String>()
|
val expired = HashSet<String>()
|
||||||
@@ -40,6 +41,7 @@ class PreferencesCookieJar(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
|
@Synchronized
|
||||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||||
val wrapped = cookies.map { CookieWrapper(it) }
|
val wrapped = cookies.map { CookieWrapper(it) }
|
||||||
prefs.edit(commit = true) {
|
prefs.edit(commit = true) {
|
||||||
@@ -53,6 +55,22 @@ class PreferencesCookieJar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
@WorkerThread
|
||||||
|
override fun removeCookies(url: HttpUrl) {
|
||||||
|
loadPersistent()
|
||||||
|
val toRemove = HashSet<String>()
|
||||||
|
for ((key, cookie) in cache) {
|
||||||
|
if (cookie.isExpired() || cookie.cookie.matches(url)) {
|
||||||
|
toRemove += key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toRemove.isNotEmpty()) {
|
||||||
|
cache.removeAll(toRemove)
|
||||||
|
removePersistent(toRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun clear(): Boolean {
|
override suspend fun clear(): Boolean {
|
||||||
cache.clear()
|
cache.clear()
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ fun MangaParser(source: MangaSource, loaderContext: MangaLoaderContext): MangaPa
|
|||||||
return if (source == MangaSource.DUMMY) {
|
return if (source == MangaSource.DUMMY) {
|
||||||
DummyParser(loaderContext)
|
DummyParser(loaderContext)
|
||||||
} else {
|
} else {
|
||||||
source.newParser(loaderContext)
|
loaderContext.newParserInstance(source)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ import org.koitharu.kotatsu.core.util.ext.filterToSet
|
|||||||
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
import org.koitharu.kotatsu.core.util.ext.getEnumValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
import org.koitharu.kotatsu.core.util.ext.putEnumValue
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.takeIfReadable
|
||||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
|
import org.koitharu.kotatsu.shelf.domain.model.ShelfSection
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -234,14 +236,28 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
if (key == null) ScreenshotsPolicy.ALLOW else ScreenshotsPolicy.valueOf(key)
|
if (key == null) ScreenshotsPolicy.ALLOW else ScreenshotsPolicy.valueOf(key)
|
||||||
}.getOrDefault(ScreenshotsPolicy.ALLOW)
|
}.getOrDefault(ScreenshotsPolicy.ALLOW)
|
||||||
|
|
||||||
|
var userSpecifiedMangaDirectories: Set<File>
|
||||||
|
get() {
|
||||||
|
val set = prefs.getStringSet(KEY_LOCAL_MANGA_DIRS, emptySet()).orEmpty()
|
||||||
|
return set.mapNotNullToSet { File(it).takeIfReadable() }
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
val set = value.mapToSet { it.absolutePath }
|
||||||
|
prefs.edit { putStringSet(KEY_LOCAL_MANGA_DIRS, set) }
|
||||||
|
}
|
||||||
|
|
||||||
var mangaStorageDir: File?
|
var mangaStorageDir: File?
|
||||||
get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
|
get() = prefs.getString(KEY_LOCAL_STORAGE, null)?.let {
|
||||||
File(it)
|
File(it)
|
||||||
}?.takeIf { it.exists() }
|
}?.takeIf { it.exists() && it in userSpecifiedMangaDirectories }
|
||||||
set(value) = prefs.edit {
|
set(value) = prefs.edit {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
remove(KEY_LOCAL_STORAGE)
|
remove(KEY_LOCAL_STORAGE)
|
||||||
} else {
|
} else {
|
||||||
|
val userDirs = userSpecifiedMangaDirectories
|
||||||
|
if (value !in userDirs) {
|
||||||
|
userSpecifiedMangaDirectories = userDirs + value
|
||||||
|
}
|
||||||
putString(KEY_LOCAL_STORAGE, value.path)
|
putString(KEY_LOCAL_STORAGE, value.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -461,6 +477,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
|||||||
const val KEY_PROXY_LOGIN = "proxy_login"
|
const val KEY_PROXY_LOGIN = "proxy_login"
|
||||||
const val KEY_PROXY_PASSWORD = "proxy_password"
|
const val KEY_PROXY_PASSWORD = "proxy_password"
|
||||||
const val KEY_IMAGES_PROXY = "images_proxy"
|
const val KEY_IMAGES_PROXY = "images_proxy"
|
||||||
|
const val KEY_LOCAL_MANGA_DIRS = "local_manga_dirs"
|
||||||
|
|
||||||
// About
|
// About
|
||||||
const val KEY_APP_UPDATE = "app_update"
|
const val KEY_APP_UPDATE = "app_update"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.appcompat.view.ActionMode
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.appcompat.widget.ActionBarContextView
|
import androidx.appcompat.widget.ActionBarContextView
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
@@ -103,8 +104,7 @@ abstract class BaseActivity<B : ViewBinding> :
|
|||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
// ActivityCompat.recreate(this)
|
ActivityCompat.recreate(this)
|
||||||
error("Test")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ abstract class BaseViewModel : ViewModel() {
|
|||||||
|
|
||||||
val isLoading: StateFlow<Boolean>
|
val isLoading: StateFlow<Boolean>
|
||||||
get() = loadingCounter.map { it > 0 }
|
get() = loadingCounter.map { it > 0 }
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), loadingCounter.value > 0)
|
||||||
|
|
||||||
protected fun launchJob(
|
protected fun launchJob(
|
||||||
context: CoroutineContext = EmptyCoroutineContext,
|
context: CoroutineContext = EmptyCoroutineContext,
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.ui.dialog
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.BaseAdapter
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.koitharu.kotatsu.R
|
|
||||||
import org.koitharu.kotatsu.databinding.ItemStorageBinding
|
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class StorageSelectDialog private constructor(private val delegate: AlertDialog) :
|
|
||||||
DialogInterface by delegate {
|
|
||||||
|
|
||||||
fun show() = delegate.show()
|
|
||||||
|
|
||||||
class Builder(context: Context, storageManager: LocalStorageManager, listener: OnStorageSelectListener) {
|
|
||||||
|
|
||||||
private val adapter = VolumesAdapter(storageManager)
|
|
||||||
private val delegate = MaterialAlertDialogBuilder(context)
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (adapter.isEmpty) {
|
|
||||||
delegate.setMessage(R.string.cannot_find_available_storage)
|
|
||||||
} else {
|
|
||||||
val defaultValue = runBlocking {
|
|
||||||
storageManager.getDefaultWriteableDir()
|
|
||||||
}
|
|
||||||
adapter.selectedItemPosition = adapter.volumes.indexOfFirst {
|
|
||||||
it.first.canonicalPath == defaultValue?.canonicalPath
|
|
||||||
}
|
|
||||||
delegate.setAdapter(adapter) { d, i ->
|
|
||||||
listener.onStorageSelected(adapter.getItem(i).first)
|
|
||||||
d.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitle(@StringRes titleResId: Int): Builder {
|
|
||||||
delegate.setTitle(titleResId)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setTitle(title: CharSequence): Builder {
|
|
||||||
delegate.setTitle(title)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNegativeButton(@StringRes textId: Int): Builder {
|
|
||||||
delegate.setNegativeButton(textId, null)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun create() = StorageSelectDialog(delegate.create())
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VolumesAdapter(storageManager: LocalStorageManager) : BaseAdapter() {
|
|
||||||
|
|
||||||
var selectedItemPosition: Int = -1
|
|
||||||
val volumes = getAvailableVolumes(storageManager)
|
|
||||||
|
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
|
||||||
val view = convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.item_storage, parent, false)
|
|
||||||
val binding = (view.tag as? ItemStorageBinding) ?: ItemStorageBinding.bind(view).also {
|
|
||||||
view.tag = it
|
|
||||||
}
|
|
||||||
val item = volumes[position]
|
|
||||||
binding.imageViewIndicator.isChecked = selectedItemPosition == position
|
|
||||||
binding.textViewTitle.text = item.second
|
|
||||||
binding.textViewSubtitle.text = item.first.path
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItem(position: Int): Pair<File, String> = volumes[position]
|
|
||||||
|
|
||||||
override fun getItemId(position: Int) = position.toLong()
|
|
||||||
|
|
||||||
override fun getCount() = volumes.size
|
|
||||||
|
|
||||||
override fun hasStableIds() = true
|
|
||||||
|
|
||||||
private fun getAvailableVolumes(storageManager: LocalStorageManager): List<Pair<File, String>> {
|
|
||||||
return runBlocking {
|
|
||||||
storageManager.getWriteableDirs().map {
|
|
||||||
it to storageManager.getStorageDisplayName(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface OnStorageSelectListener {
|
|
||||||
|
|
||||||
fun onStorageSelected(file: File)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.core.ui.util
|
package org.koitharu.kotatsu.core.ui.util
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -13,7 +14,7 @@ fun interface ReversibleHandle {
|
|||||||
suspend fun reverse()
|
suspend fun reverse()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default) {
|
fun ReversibleHandle.reverseAsync() = processLifecycleScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC) {
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
reverse()
|
reverse()
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
|||||||
import com.google.android.material.shape.ShapeAppearanceModel
|
import com.google.android.material.shape.ShapeAppearanceModel
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
import org.koitharu.kotatsu.databinding.ViewTwoLinesItemBinding
|
import org.koitharu.kotatsu.databinding.ViewTwoLinesItemBinding
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
@@ -35,6 +36,18 @@ class TwoLinesItemView @JvmOverloads constructor(
|
|||||||
|
|
||||||
private val binding = ViewTwoLinesItemBinding.inflate(LayoutInflater.from(context), this)
|
private val binding = ViewTwoLinesItemBinding.inflate(LayoutInflater.from(context), this)
|
||||||
|
|
||||||
|
var title: CharSequence?
|
||||||
|
get() = binding.title.text
|
||||||
|
set(value) {
|
||||||
|
binding.title.text = value
|
||||||
|
}
|
||||||
|
|
||||||
|
var subtitle: CharSequence?
|
||||||
|
get() = binding.subtitle.textAndVisible
|
||||||
|
set(value) {
|
||||||
|
binding.subtitle.textAndVisible = value
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var textColors: ColorStateList? = null
|
var textColors: ColorStateList? = null
|
||||||
context.withStyledAttributes(
|
context.withStyledAttributes(
|
||||||
@@ -76,8 +89,7 @@ class TwoLinesItemView @JvmOverloads constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setIconResource(@DrawableRes resId: Int) {
|
fun setIconResource(@DrawableRes resId: Int) {
|
||||||
val icon = if (resId != 0) ContextCompat.getDrawable(context, resId) else null
|
binding.icon.setImageResource(resId)
|
||||||
binding.icon.setImageDrawable(icon)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createShapeDrawable(ta: TypedArray): InsetDrawable {
|
private fun createShapeDrawable(ta: TypedArray): InsetDrawable {
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.core.util
|
|
||||||
|
|
||||||
fun interface BufferedObserver<T> {
|
|
||||||
|
|
||||||
fun onChanged(t: T, previous: T?)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package org.koitharu.kotatsu.core.util.ext
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.storage.StorageManager
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import org.koitharu.kotatsu.parsers.util.removeSuffix
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.reflect.Array as ArrayReflect
|
||||||
|
|
||||||
|
private const val PRIMARY_VOLUME_NAME = "primary"
|
||||||
|
|
||||||
|
fun Uri.resolveFile(context: Context): File? {
|
||||||
|
val volumeId = getVolumeIdFromTreeUri(this) ?: return null
|
||||||
|
val volumePath = getVolumePath(volumeId, context)?.removeSuffix(File.separatorChar) ?: return null
|
||||||
|
val documentPath = getDocumentPathFromTreeUri(this)?.removeSuffix(File.separatorChar) ?: return null
|
||||||
|
|
||||||
|
return File(
|
||||||
|
if (documentPath.isNotEmpty()) {
|
||||||
|
if (documentPath.startsWith(File.separator)) {
|
||||||
|
volumePath + documentPath
|
||||||
|
} else {
|
||||||
|
volumePath + File.separator + documentPath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
volumePath
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVolumePath(volumeId: String, context: Context): String? {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
getVolumePathForAndroid11AndAbove(volumeId, context)
|
||||||
|
} else {
|
||||||
|
getVolumePathBeforeAndroid11(volumeId, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getVolumePathBeforeAndroid11(volumeId: String, context: Context): String? = runCatching {
|
||||||
|
val mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
|
||||||
|
val getVolumeList = mStorageManager.javaClass.getMethod("getVolumeList")
|
||||||
|
val getUuid = storageVolumeClazz.getMethod("getUuid")
|
||||||
|
val getPath = storageVolumeClazz.getMethod("getPath")
|
||||||
|
val isPrimary = storageVolumeClazz.getMethod("isPrimary")
|
||||||
|
val result = getVolumeList.invoke(mStorageManager)
|
||||||
|
val length = ArrayReflect.getLength(checkNotNull(result))
|
||||||
|
(0 until length).firstNotNullOfOrNull { i ->
|
||||||
|
val storageVolumeElement = ArrayReflect.get(result, i)
|
||||||
|
val uuid = getUuid.invoke(storageVolumeElement) as String
|
||||||
|
val primary = isPrimary.invoke(storageVolumeElement) as Boolean
|
||||||
|
when {
|
||||||
|
primary && volumeId == PRIMARY_VOLUME_NAME -> getPath.invoke(storageVolumeElement) as String
|
||||||
|
uuid == volumeId -> getPath.invoke(storageVolumeElement) as String
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
it.printStackTraceDebug()
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.R)
|
||||||
|
private fun getVolumePathForAndroid11AndAbove(volumeId: String, context: Context): String? = runCatching {
|
||||||
|
val storageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
|
||||||
|
storageManager.storageVolumes.firstNotNullOfOrNull { volume ->
|
||||||
|
if (volume.isPrimary && volumeId == PRIMARY_VOLUME_NAME) {
|
||||||
|
volume.directory?.path
|
||||||
|
} else {
|
||||||
|
val uuid = volume.uuid
|
||||||
|
if (uuid != null && uuid == volumeId) volume.directory?.path else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
it.printStackTraceDebug()
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
private fun getVolumeIdFromTreeUri(treeUri: Uri): String? {
|
||||||
|
val docId = DocumentsContract.getTreeDocumentId(treeUri)
|
||||||
|
val split = docId.split(":".toRegex())
|
||||||
|
return split.firstOrNull()?.takeUnless { it.isEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDocumentPathFromTreeUri(treeUri: Uri): String? {
|
||||||
|
val docId = DocumentsContract.getTreeDocumentId(treeUri)
|
||||||
|
val split: Array<String?> = docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
return if (split.size >= 2 && split[1] != null) split[1] else File.separator
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.os.SystemClock
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
import kotlinx.coroutines.flow.onCompletion
|
||||||
@@ -62,3 +63,23 @@ fun <T> Flow<T>.zipWithPrevious(): Flow<Pair<T?, T>> = flow {
|
|||||||
emit(result)
|
emit(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun <T1, T2, T3, T4, T5, T6, R> combine(
|
||||||
|
flow: Flow<T1>,
|
||||||
|
flow2: Flow<T2>,
|
||||||
|
flow3: Flow<T3>,
|
||||||
|
flow4: Flow<T4>,
|
||||||
|
flow5: Flow<T5>,
|
||||||
|
flow6: Flow<T6>,
|
||||||
|
transform: suspend (T1, T2, T3, T4, T5, T6) -> R
|
||||||
|
): Flow<R> = combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> ->
|
||||||
|
transform(
|
||||||
|
args[0] as T1,
|
||||||
|
args[1] as T2,
|
||||||
|
args[2] as T3,
|
||||||
|
args[3] as T4,
|
||||||
|
args[4] as T5,
|
||||||
|
args[5] as T6,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.core.util.ext
|
package org.koitharu.kotatsu.core.util.ext
|
||||||
|
|
||||||
|
import okhttp3.Cookie
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
@@ -38,3 +39,23 @@ fun Response.ensureSuccess() = apply {
|
|||||||
throw IllegalStateException(message)
|
throw IllegalStateException(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c ->
|
||||||
|
c.name(name)
|
||||||
|
c.value(value)
|
||||||
|
if (persistent) {
|
||||||
|
c.expiresAt(expiresAt)
|
||||||
|
}
|
||||||
|
if (hostOnly) {
|
||||||
|
c.hostOnlyDomain(domain)
|
||||||
|
} else {
|
||||||
|
c.domain(domain)
|
||||||
|
}
|
||||||
|
c.path(path)
|
||||||
|
if (secure) {
|
||||||
|
c.secure()
|
||||||
|
}
|
||||||
|
if (httpOnly) {
|
||||||
|
c.httpOnly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
|||||||
|
|
||||||
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
||||||
is FileNotFoundException -> resources.getString(R.string.file_not_found)
|
is FileNotFoundException -> resources.getString(R.string.file_not_found)
|
||||||
|
is AccessDeniedException -> resources.getString(R.string.no_access_to_file)
|
||||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||||
is SyncApiException,
|
is SyncApiException,
|
||||||
is ContentUnavailableException,
|
is ContentUnavailableException,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package org.koitharu.kotatsu.core.util.ext
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
|
||||||
|
fun Toolbar.setNavigationIconSafe(@DrawableRes iconRes: Int, retry: Boolean = true) {
|
||||||
|
try {
|
||||||
|
setNavigationIcon(iconRes)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
if (retry) {
|
||||||
|
post { setNavigationIconSafe(iconRes, retry = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ class ChaptersBottomSheetMediator(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onActionModeStarted(mode: ActionMode) {
|
override fun onActionModeStarted(mode: ActionMode) {
|
||||||
|
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||||
lock()
|
lock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
|||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||||
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
|
import org.koitharu.kotatsu.databinding.FragmentChaptersBinding
|
||||||
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter
|
||||||
@@ -66,6 +67,9 @@ class ChaptersFragment :
|
|||||||
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
viewModel.isChaptersEmpty.observe(viewLifecycleOwner) {
|
||||||
binding.textViewHolder.isVisible = it
|
binding.textViewHolder.isVisible = it
|
||||||
}
|
}
|
||||||
|
viewModel.onSelectChapter.observeEvent(viewLifecycleOwner) {
|
||||||
|
selectionController?.onItemLongClick(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import org.koitharu.kotatsu.core.util.ext.measureHeight
|
|||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
|
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.setNavigationIconSafe
|
||||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||||
import org.koitharu.kotatsu.details.service.MangaPrefetchService
|
import org.koitharu.kotatsu.details.service.MangaPrefetchService
|
||||||
@@ -211,7 +212,7 @@ class DetailsActivity :
|
|||||||
}
|
}
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
toolbar.addMenuProvider(chaptersMenuProvider)
|
toolbar.addMenuProvider(chaptersMenuProvider)
|
||||||
toolbar.setNavigationIcon(materialR.drawable.abc_ic_clear_material)
|
toolbar.setNavigationIconSafe(materialR.drawable.abc_ic_clear_material)
|
||||||
} else {
|
} else {
|
||||||
toolbar.removeMenuProvider(chaptersMenuProvider)
|
toolbar.removeMenuProvider(chaptersMenuProvider)
|
||||||
toolbar.navigationIcon = null
|
toolbar.navigationIcon = null
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import androidx.core.view.updatePadding
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
|
import coil.request.SuccessResult
|
||||||
import coil.util.CoilUtils
|
import coil.util.CoilUtils
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -296,7 +297,7 @@ class DetailsFragment :
|
|||||||
private fun loadCover(manga: Manga) {
|
private fun loadCover(manga: Manga) {
|
||||||
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
|
val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }
|
||||||
val lastResult = CoilUtils.result(requireViewBinding().imageViewCover)
|
val lastResult = CoilUtils.result(requireViewBinding().imageViewCover)
|
||||||
if (lastResult?.request?.data == imageUrl) {
|
if (lastResult is SuccessResult && lastResult.request.data == imageUrl) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val request = ImageRequest.Builder(context ?: return)
|
val request = ImageRequest.Builder(context ?: return)
|
||||||
|
|||||||
@@ -16,12 +16,11 @@ import kotlinx.coroutines.launch
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.browser.BrowserActivity
|
import org.koitharu.kotatsu.browser.BrowserActivity
|
||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.util.ShareHelper
|
import org.koitharu.kotatsu.core.util.ShareHelper
|
||||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
|
||||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
|
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesSheet
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
|
||||||
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
|
import org.koitharu.kotatsu.scrobbling.common.ui.selector.ScrobblingSelectorSheet
|
||||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ class DetailsMenuProvider(
|
|||||||
private val viewModel: DetailsViewModel,
|
private val viewModel: DetailsViewModel,
|
||||||
private val snackbarHost: View,
|
private val snackbarHost: View,
|
||||||
private val appShortcutManager: AppShortcutManager,
|
private val appShortcutManager: AppShortcutManager,
|
||||||
) : MenuProvider {
|
) : MenuProvider, OnListItemClickListener<DownloadOption> {
|
||||||
|
|
||||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||||
menuInflater.inflate(R.menu.opt_details, menu)
|
menuInflater.inflate(R.menu.opt_details, menu)
|
||||||
@@ -44,7 +43,7 @@ class DetailsMenuProvider(
|
|||||||
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)
|
menu.findItem(R.id.action_shortcut).isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(activity)
|
||||||
menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable
|
menu.findItem(R.id.action_scrobbling).isVisible = viewModel.isScrobblingAvailable
|
||||||
menu.findItem(R.id.action_favourite).setIcon(
|
menu.findItem(R.id.action_favourite).setIcon(
|
||||||
if (viewModel.favouriteCategories.value == true) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
if (viewModel.favouriteCategories.value) R.drawable.ic_heart else R.drawable.ic_heart_outline,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,15 +79,7 @@ class DetailsMenuProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_save -> {
|
R.id.action_save -> {
|
||||||
viewModel.manga.value?.let {
|
DownloadDialogHelper(snackbarHost, viewModel).show(this)
|
||||||
val chaptersCount = it.chapters?.size ?: 0
|
|
||||||
val branches = viewModel.branches.value.orEmpty()
|
|
||||||
if (chaptersCount > 5 || branches.size > 1) {
|
|
||||||
showSaveConfirmation(it, chaptersCount, branches)
|
|
||||||
} else {
|
|
||||||
viewModel.download(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_browser -> {
|
R.id.action_browser -> {
|
||||||
@@ -125,35 +116,16 @@ class DetailsMenuProvider(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List<MangaBranch>) {
|
override fun onItemClick(item: DownloadOption, view: View) {
|
||||||
val dialogBuilder = MaterialAlertDialogBuilder(activity)
|
val chaptersIds: Set<Long>? = when (item) {
|
||||||
.setTitle(R.string.save_manga)
|
is DownloadOption.WholeManga -> null
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
is DownloadOption.SelectionHint -> {
|
||||||
if (branches.size > 1) {
|
viewModel.startChaptersSelection()
|
||||||
val items = Array(branches.size) { i -> branches[i].name.orEmpty() }
|
return
|
||||||
val currentBranch = branches.indexOfFirst { it.isSelected }
|
|
||||||
val checkedIndices = BooleanArray(branches.size) { i -> i == currentBranch }
|
|
||||||
dialogBuilder.setMultiChoiceItems(items, checkedIndices) { _, i, checked ->
|
|
||||||
checkedIndices[i] = checked
|
|
||||||
}.setPositiveButton(R.string.save) { _, _ ->
|
|
||||||
val selectedBranches = branches.mapIndexedNotNullTo(HashSet()) { i, b ->
|
|
||||||
if (checkedIndices[i]) b.name else null
|
|
||||||
}
|
|
||||||
val chaptersIds = manga.chapters?.mapNotNullToSet { c ->
|
|
||||||
if (c.branch in selectedBranches) c.id else null
|
|
||||||
}
|
|
||||||
viewModel.download(chaptersIds)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogBuilder.setMessage(
|
|
||||||
activity.getString(
|
|
||||||
R.string.large_manga_save_confirm,
|
|
||||||
activity.resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount),
|
|
||||||
),
|
|
||||||
).setPositiveButton(R.string.save) { _, _ ->
|
|
||||||
viewModel.download(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else -> item.chaptersIds
|
||||||
}
|
}
|
||||||
dialogBuilder.show()
|
viewModel.download(chaptersIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,14 @@ import org.koitharu.kotatsu.R
|
|||||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||||
|
import org.koitharu.kotatsu.core.os.NetworkState
|
||||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.combine
|
||||||
import org.koitharu.kotatsu.core.util.ext.computeSize
|
import org.koitharu.kotatsu.core.util.ext.computeSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||||
import org.koitharu.kotatsu.core.util.ext.sanitize
|
import org.koitharu.kotatsu.core.util.ext.sanitize
|
||||||
@@ -72,6 +74,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
||||||
private val doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
private val doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
||||||
|
networkState: NetworkState,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val intent = MangaIntent(savedStateHandle)
|
private val intent = MangaIntent(savedStateHandle)
|
||||||
@@ -81,6 +84,7 @@ class DetailsViewModel @Inject constructor(
|
|||||||
|
|
||||||
val onShowToast = MutableEventFlow<Int>()
|
val onShowToast = MutableEventFlow<Int>()
|
||||||
val onShowTip = MutableEventFlow<Unit>()
|
val onShowTip = MutableEventFlow<Unit>()
|
||||||
|
val onSelectChapter = MutableEventFlow<Long>()
|
||||||
val onDownloadStarted = MutableEventFlow<Unit>()
|
val onDownloadStarted = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
val manga = doubleManga.map { it?.any }
|
val manga = doubleManga.map { it?.any }
|
||||||
@@ -176,8 +180,9 @@ class DetailsViewModel @Inject constructor(
|
|||||||
selectedBranch,
|
selectedBranch,
|
||||||
newChaptersCount,
|
newChaptersCount,
|
||||||
bookmarks,
|
bookmarks,
|
||||||
) { manga, history, branch, news, bookmarks ->
|
networkState,
|
||||||
mapChapters(manga?.remote, manga?.local, history, news, branch, bookmarks)
|
) { manga, history, branch, news, bookmarks, isOnline ->
|
||||||
|
mapChapters(manga?.remote?.takeIf { isOnline }, manga?.local, history, news, branch, bookmarks)
|
||||||
},
|
},
|
||||||
isChaptersReversed,
|
isChaptersReversed,
|
||||||
chaptersQuery,
|
chaptersQuery,
|
||||||
@@ -286,6 +291,14 @@ class DetailsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startChaptersSelection() {
|
||||||
|
val chapters = chapters.value
|
||||||
|
val chapter = chapters.find {
|
||||||
|
it.isUnread && !it.isDownloaded
|
||||||
|
} ?: chapters.firstOrNull() ?: return
|
||||||
|
onSelectChapter.call(chapter.chapter.id)
|
||||||
|
}
|
||||||
|
|
||||||
fun onButtonTipClosed() {
|
fun onButtonTipClosed() {
|
||||||
settings.closeTip(DetailsActivity.TIP_BUTTON)
|
settings.closeTip(DetailsActivity.TIP_BUTTON)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.koitharu.kotatsu.details.ui
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.view.View
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.model.ids
|
||||||
|
import org.koitharu.kotatsu.core.ui.dialog.RecyclerViewAlertDialog
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.download.ui.dialog.DownloadOption
|
||||||
|
import org.koitharu.kotatsu.download.ui.dialog.downloadOptionAD
|
||||||
|
|
||||||
|
class DownloadDialogHelper(
|
||||||
|
private val host: View,
|
||||||
|
private val viewModel: DetailsViewModel,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun show(callback: OnListItemClickListener<DownloadOption>) {
|
||||||
|
val branch = viewModel.selectedBranchValue
|
||||||
|
val allChapters = viewModel.manga.value?.chapters ?: return
|
||||||
|
val branchChapters = viewModel.manga.value?.getChapters(branch).orEmpty()
|
||||||
|
val history = viewModel.history.value
|
||||||
|
|
||||||
|
val options = buildList {
|
||||||
|
add(DownloadOption.WholeManga(allChapters.ids()))
|
||||||
|
if (branch != null && branchChapters.isNotEmpty()) {
|
||||||
|
add(DownloadOption.AllChapters(branch, branchChapters.ids()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (history != null) {
|
||||||
|
val unreadChapters = branchChapters.takeLastWhile { it.id != history.chapterId }
|
||||||
|
if (unreadChapters.isNotEmpty() && unreadChapters.size < branchChapters.size) {
|
||||||
|
add(DownloadOption.AllUnreadChapters(unreadChapters.ids(), branch))
|
||||||
|
if (unreadChapters.size > 5) {
|
||||||
|
add(DownloadOption.NextUnreadChapters(unreadChapters.take(5).ids()))
|
||||||
|
if (unreadChapters.size > 10) {
|
||||||
|
add(DownloadOption.NextUnreadChapters(unreadChapters.take(10).ids()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (branchChapters.size > 5) {
|
||||||
|
add(DownloadOption.FirstChapters(branchChapters.take(5).ids()))
|
||||||
|
if (branchChapters.size > 10) {
|
||||||
|
add(DownloadOption.FirstChapters(branchChapters.take(10).ids()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(DownloadOption.SelectionHint())
|
||||||
|
}
|
||||||
|
var dialog: DialogInterface? = null
|
||||||
|
val listener = OnListItemClickListener<DownloadOption> { item, _ ->
|
||||||
|
callback.onItemClick(item, host)
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
dialog = RecyclerViewAlertDialog.Builder<DownloadOption>(host.context)
|
||||||
|
.addAdapterDelegate(downloadOptionAD(listener))
|
||||||
|
.setCancelable(true)
|
||||||
|
.setTitle(R.string.download)
|
||||||
|
.setNegativeButton(android.R.string.cancel)
|
||||||
|
.setItems(options)
|
||||||
|
.create()
|
||||||
|
.also { it.show() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package org.koitharu.kotatsu.details.ui
|
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
|
||||||
|
|
||||||
class MangaDetailsAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
|
|
||||||
|
|
||||||
override fun getItemCount() = 2
|
|
||||||
|
|
||||||
override fun createFragment(position: Int): Fragment = when (position) {
|
|
||||||
0 -> DetailsFragment()
|
|
||||||
1 -> ChaptersFragment()
|
|
||||||
else -> throw IndexOutOfBoundsException("No fragment for position $position")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.koitharu.kotatsu.download.ui.dialog
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import java.util.Locale
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
sealed interface DownloadOption {
|
||||||
|
|
||||||
|
val chaptersIds: Set<Long>
|
||||||
|
|
||||||
|
@get:DrawableRes
|
||||||
|
val iconResId: Int
|
||||||
|
|
||||||
|
val chaptersCount: Int
|
||||||
|
get() = chaptersIds.size
|
||||||
|
|
||||||
|
fun getLabel(resources: Resources): CharSequence
|
||||||
|
|
||||||
|
class AllChapters(
|
||||||
|
val branch: String,
|
||||||
|
override val chaptersIds: Set<Long>,
|
||||||
|
) : DownloadOption {
|
||||||
|
|
||||||
|
override val iconResId = R.drawable.ic_select_group
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return resources.getString(R.string.download_option_all_chapters, branch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WholeManga(
|
||||||
|
override val chaptersIds: Set<Long>,
|
||||||
|
) : DownloadOption {
|
||||||
|
|
||||||
|
override val iconResId = materialR.drawable.abc_ic_menu_selectall_mtrl_alpha
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return resources.getString(R.string.download_option_whole_manga)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FirstChapters(
|
||||||
|
override val chaptersIds: Set<Long>,
|
||||||
|
) : DownloadOption {
|
||||||
|
|
||||||
|
override val iconResId = R.drawable.ic_list_start
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return resources.getString(
|
||||||
|
R.string.download_option_first_n_chapters,
|
||||||
|
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount)
|
||||||
|
.lowercase(Locale.getDefault()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AllUnreadChapters(
|
||||||
|
override val chaptersIds: Set<Long>,
|
||||||
|
val branch: String?,
|
||||||
|
) : DownloadOption {
|
||||||
|
|
||||||
|
override val iconResId = R.drawable.ic_list_end
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return if (branch == null) {
|
||||||
|
resources.getString(R.string.download_option_all_unread)
|
||||||
|
} else {
|
||||||
|
resources.getString(R.string.download_option_all_unread_b, branch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NextUnreadChapters(
|
||||||
|
override val chaptersIds: Set<Long>,
|
||||||
|
) : DownloadOption {
|
||||||
|
|
||||||
|
override val iconResId = R.drawable.ic_list_next
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return resources.getString(
|
||||||
|
R.string.download_option_next_unread_n_chapters,
|
||||||
|
resources.getQuantityString(R.plurals.chapters, chaptersCount, chaptersCount)
|
||||||
|
.lowercase(Locale.getDefault()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectionHint : DownloadOption {
|
||||||
|
|
||||||
|
override val chaptersIds: Set<Long> = emptySet()
|
||||||
|
override val iconResId = R.drawable.ic_tap
|
||||||
|
|
||||||
|
override fun getLabel(resources: Resources): CharSequence {
|
||||||
|
return resources.getString(R.string.download_option_manual_selection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package org.koitharu.kotatsu.download.ui.dialog
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemDownloadOptionBinding
|
||||||
|
|
||||||
|
fun downloadOptionAD(
|
||||||
|
onClickListener: OnListItemClickListener<DownloadOption>,
|
||||||
|
) = adapterDelegateViewBinding<DownloadOption, DownloadOption, ItemDownloadOptionBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemDownloadOptionBinding.inflate(layoutInflater, parent, false) },
|
||||||
|
) {
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { v -> onClickListener.onItemClick(item, v) }
|
||||||
|
|
||||||
|
bind {
|
||||||
|
with(binding.root) {
|
||||||
|
title = item.getLabel(resources)
|
||||||
|
subtitle = if (item.chaptersCount == 0) null else resources.getQuantityString(
|
||||||
|
R.plurals.chapters,
|
||||||
|
item.chaptersCount,
|
||||||
|
item.chaptersCount,
|
||||||
|
)
|
||||||
|
setIconResource(item.iconResId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,11 +141,13 @@ class DownloadsViewModel @Inject constructor(
|
|||||||
fun remove(ids: Set<Long>) {
|
fun remove(ids: Set<Long>) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val snapshot = works.value ?: return@launchJob
|
val snapshot = works.value ?: return@launchJob
|
||||||
|
val uuids = HashSet<UUID>(ids.size)
|
||||||
for (work in snapshot) {
|
for (work in snapshot) {
|
||||||
if (work.id.mostSignificantBits in ids) {
|
if (work.id.mostSignificantBits in ids) {
|
||||||
workScheduler.delete(work.id)
|
uuids.add(work.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
workScheduler.delete(uuids)
|
||||||
onActionDone.call(ReversibleAction(R.string.downloads_removed, null))
|
onActionDone.call(ReversibleAction(R.string.downloads_removed, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import org.koitharu.kotatsu.core.util.WorkManagerHelper
|
|||||||
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
import org.koitharu.kotatsu.core.util.ext.deleteAwait
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||||
import org.koitharu.kotatsu.core.util.progress.TimeLeftEstimator
|
import org.koitharu.kotatsu.core.util.progress.TimeLeftEstimator
|
||||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||||
@@ -60,7 +61,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
|||||||
import org.koitharu.kotatsu.parsers.util.await
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -322,9 +322,9 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
includedIds: LongArray?,
|
includedIds: LongArray?,
|
||||||
): List<MangaChapter> {
|
): List<MangaChapter> {
|
||||||
val chapters = checkNotNull(manga.chapters?.toMutableList()) {
|
val chapters = checkNotNull(manga.chapters) {
|
||||||
"Chapters list must not be null"
|
"Chapters list must not be null"
|
||||||
}
|
}.toMutableList()
|
||||||
if (includedIds != null) {
|
if (includedIds != null) {
|
||||||
val chaptersIdsSet = includedIds.toMutableSet()
|
val chaptersIdsSet = includedIds.toMutableSet()
|
||||||
chapters.retainAll { x -> chaptersIdsSet.remove(x.id) }
|
chapters.retainAll { x -> chaptersIdsSet.remove(x.id) }
|
||||||
@@ -399,6 +399,13 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
WorkManagerHelper(workManager).deleteWork(id)
|
WorkManagerHelper(workManager).deleteWork(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun delete(ids: Collection<UUID>) {
|
||||||
|
val wm = workManager
|
||||||
|
val helper = WorkManagerHelper(wm)
|
||||||
|
ids.forEach { id -> wm.cancelWorkById(id).await() }
|
||||||
|
helper.deleteWorks(ids)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun removeCompleted() {
|
suspend fun removeCompleted() {
|
||||||
val helper = WorkManagerHelper(workManager)
|
val helper = WorkManagerHelper(workManager)
|
||||||
val finishedWorks = helper.getFinishedWorkInfosByTag(TAG)
|
val finishedWorks = helper.getFinishedWorkInfosByTag(TAG)
|
||||||
@@ -406,10 +413,7 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateConstraints() {
|
suspend fun updateConstraints() {
|
||||||
val constraints = Constraints.Builder()
|
val constraints = createConstraints()
|
||||||
.setRequiresStorageNotLow(true)
|
|
||||||
.setRequiredNetworkType(if (settings.isDownloadsWiFiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
|
||||||
.build()
|
|
||||||
val helper = WorkManagerHelper(workManager)
|
val helper = WorkManagerHelper(workManager)
|
||||||
val works = helper.getWorkInfosByTag(TAG)
|
val works = helper.getWorkInfosByTag(TAG)
|
||||||
for (work in works) {
|
for (work in works) {
|
||||||
@@ -418,6 +422,7 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
val request = OneTimeWorkRequestBuilder<DownloadWorker>()
|
val request = OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
|
.addTag(TAG)
|
||||||
.setId(work.id)
|
.setId(work.id)
|
||||||
.build()
|
.build()
|
||||||
helper.updateWork(request)
|
helper.updateWork(request)
|
||||||
@@ -425,15 +430,15 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun scheduleImpl(data: Collection<Data>) {
|
private suspend fun scheduleImpl(data: Collection<Data>) {
|
||||||
val constraints = Constraints.Builder()
|
if (data.isEmpty()) {
|
||||||
.setRequiresStorageNotLow(true)
|
return
|
||||||
.setRequiredNetworkType(if (settings.isDownloadsWiFiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
}
|
||||||
.build()
|
val constraints = createConstraints()
|
||||||
val requests = data.map { inputData ->
|
val requests = data.map { inputData ->
|
||||||
OneTimeWorkRequestBuilder<DownloadWorker>()
|
OneTimeWorkRequestBuilder<DownloadWorker>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.keepResultsForAtLeast(7, TimeUnit.DAYS)
|
.keepResultsForAtLeast(30, TimeUnit.DAYS)
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.SECONDS)
|
||||||
.setInputData(inputData)
|
.setInputData(inputData)
|
||||||
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
||||||
@@ -441,6 +446,10 @@ class DownloadWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
workManager.enqueue(requests).await()
|
workManager.enqueue(requests).await()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createConstraints() = Constraints.Builder()
|
||||||
|
.setRequiredNetworkType(if (settings.isDownloadsWiFiOnly) NetworkType.UNMETERED else NetworkType.CONNECTED)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.history.domain
|
package org.koitharu.kotatsu.history.domain
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.NonCancellable
|
import kotlinx.coroutines.NonCancellable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -30,7 +31,7 @@ class HistoryUpdateUseCase @Inject constructor(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
readerState: ReaderState,
|
readerState: ReaderState,
|
||||||
percent: Float
|
percent: Float
|
||||||
) = processLifecycleScope.launch(Dispatchers.Default) {
|
) = processLifecycleScope.launch(Dispatchers.Default, CoroutineStart.ATOMIC) {
|
||||||
runCatchingCancellable {
|
runCatchingCancellable {
|
||||||
withContext(NonCancellable) {
|
withContext(NonCancellable) {
|
||||||
invoke(manga, readerState, percent)
|
invoke(manga, readerState, percent)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
|
|||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FilenameFilter
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -192,7 +193,7 @@ class LocalMangaRepository @Inject constructor(
|
|||||||
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
|
||||||
files.map { file ->
|
files.map { file ->
|
||||||
async(dispatcher) {
|
async(dispatcher) {
|
||||||
runCatchingCancellable { LocalMangaInput.of(file).getManga() }.getOrNull()
|
runCatchingCancellable { LocalMangaInput.ofOrNull(file)?.getManga() }.getOrNull()
|
||||||
}
|
}
|
||||||
}.awaitAll()
|
}.awaitAll()
|
||||||
}.filterNotNullTo(ArrayList(files.size))
|
}.filterNotNullTo(ArrayList(files.size))
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ package org.koitharu.kotatsu.local.data
|
|||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
|
import androidx.core.net.toFile
|
||||||
import dagger.Reusable
|
import dagger.Reusable
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -13,11 +16,14 @@ import okhttp3.Cache
|
|||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.util.ext.computeSize
|
import org.koitharu.kotatsu.core.util.ext.computeSize
|
||||||
import org.koitharu.kotatsu.core.util.ext.getStorageName
|
import org.koitharu.kotatsu.core.util.ext.getStorageName
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.resolveFile
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val DIR_NAME = "manga"
|
private const val DIR_NAME = "manga"
|
||||||
|
private const val NOMEDIA = ".nomedia"
|
||||||
private const val CACHE_DISK_PERCENTAGE = 0.02
|
private const val CACHE_DISK_PERCENTAGE = 0.02
|
||||||
private const val CACHE_SIZE_MIN: Long = 10 * 1024 * 1024 // 10MB
|
private const val CACHE_SIZE_MIN: Long = 10 * 1024 * 1024 // 10MB
|
||||||
private const val CACHE_SIZE_MAX: Long = 250 * 1024 * 1024 // 250MB
|
private const val CACHE_SIZE_MAX: Long = 250 * 1024 * 1024 // 250MB
|
||||||
@@ -74,14 +80,42 @@ class LocalStorageManager @Inject constructor(
|
|||||||
preferredDir ?: getFallbackStorageDir()?.takeIf { it.isWriteable() }
|
preferredDir ?: getFallbackStorageDir()?.takeIf { it.isWriteable() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getStorageDisplayName(file: File) = file.getStorageName(context)
|
suspend fun getApplicationStorageDirs(): Set<File> = runInterruptible(Dispatchers.IO) {
|
||||||
|
getAvailableStorageDirs()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun resolveUri(uri: Uri): File? = runInterruptible(Dispatchers.IO) {
|
||||||
|
if (uri.scheme == "file") {
|
||||||
|
uri.toFile()
|
||||||
|
} else {
|
||||||
|
uri.resolveFile(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setDirIsNoMedia(dir: File) = runInterruptible(Dispatchers.IO) {
|
||||||
|
File(dir, NOMEDIA).createNewFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun takePermissions(uri: Uri) {
|
||||||
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getDirectoryDisplayName(dir: File, isFullPath: Boolean): String = runInterruptible(Dispatchers.IO) {
|
||||||
|
val packageName = context.packageName
|
||||||
|
if (dir.absolutePath.contains(packageName)) {
|
||||||
|
dir.getStorageName(context)
|
||||||
|
} else if (isFullPath) {
|
||||||
|
dir.path
|
||||||
|
} else {
|
||||||
|
dir.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private fun getConfiguredStorageDirs(): MutableSet<File> {
|
private fun getConfiguredStorageDirs(): MutableSet<File> {
|
||||||
val set = getAvailableStorageDirs()
|
val set = getAvailableStorageDirs()
|
||||||
settings.mangaStorageDir?.let {
|
set.addAll(settings.userSpecifiedMangaDirectories)
|
||||||
set.add(it)
|
|
||||||
}
|
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.local.data.input
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
|
import org.koitharu.kotatsu.local.data.CbzFilter
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
@@ -30,6 +31,12 @@ sealed class LocalMangaInput(
|
|||||||
else -> LocalMangaZipInput(file)
|
else -> LocalMangaZipInput(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ofOrNull(file: File): LocalMangaInput? = when {
|
||||||
|
file.isDirectory -> LocalMangaDirInput(file)
|
||||||
|
CbzFilter.isFileSupported(file.name) -> LocalMangaZipInput(file)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
protected fun zipUri(file: File, entryName: String): String =
|
protected fun zipUri(file: File, entryName: String): String =
|
||||||
Uri.fromParts("cbz", file.path, entryName).toString()
|
Uri.fromParts("cbz", file.path, entryName).toString()
|
||||||
|
|||||||
@@ -35,18 +35,6 @@ class LocalMangaUtil(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun writeIndex(index: MangaIndex) {
|
|
||||||
newOutput().use { output ->
|
|
||||||
when (output) {
|
|
||||||
is LocalMangaDirOutput -> {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
|
|
||||||
is LocalMangaZipOutput -> TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun newOutput(): LocalMangaOutput = runInterruptible(Dispatchers.IO) {
|
private suspend fun newOutput(): LocalMangaOutput = runInterruptible(Dispatchers.IO) {
|
||||||
val file = manga.url.toUri().toFile()
|
val file = manga.url.toUri().toFile()
|
||||||
if (file.isDirectory) {
|
if (file.isDirectory) {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
|||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_DEFERRED)
|
||||||
.setOngoing(true)
|
.setOngoing(false)
|
||||||
.build()
|
.build()
|
||||||
startForeground(NOTIFICATION_ID, notification)
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
|
|||||||
|
|
||||||
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
|
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
|
||||||
super.onViewBindingCreated(binding, savedInstanceState)
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
addMenuProvider(LocalListMenuProvider(this::onEmptyActionClick))
|
addMenuProvider(LocalListMenuProvider(binding.root.context, this::onEmptyActionClick))
|
||||||
viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }
|
viewModel.onMangaRemoved.observeEvent(viewLifecycleOwner) { onItemRemoved() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class LocalListFragment : MangaListFragment(), FilterOwner {
|
|||||||
FilterSheetFragment.show(childFragmentManager)
|
FilterSheetFragment.show(childFragmentManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onScrolledToEnd() = Unit
|
override fun onScrolledToEnd() = viewModel.loadNextPage()
|
||||||
|
|
||||||
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||||
mode.menuInflater.inflate(R.menu.mode_local, menu)
|
mode.menuInflater.inflate(R.menu.mode_local, menu)
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package org.koitharu.kotatsu.local.ui
|
package org.koitharu.kotatsu.local.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuInflater
|
import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import androidx.core.view.MenuProvider
|
import androidx.core.view.MenuProvider
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
|
||||||
|
|
||||||
class LocalListMenuProvider(
|
class LocalListMenuProvider(
|
||||||
|
private val context: Context,
|
||||||
private val onImportClick: Function0<Unit>,
|
private val onImportClick: Function0<Unit>,
|
||||||
) : MenuProvider {
|
) : MenuProvider {
|
||||||
|
|
||||||
@@ -20,6 +23,12 @@ class LocalListMenuProvider(
|
|||||||
onImportClick()
|
onImportClick()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.action_settings -> {
|
||||||
|
context.startActivity(MangaDirectoriesActivity.newIntent(context))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.local.ui
|
package org.koitharu.kotatsu.local.ui
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -24,7 +25,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
mangaRepositoryFactory: MangaRepository.Factory,
|
mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
filter: FilterCoordinator,
|
filter: FilterCoordinator,
|
||||||
settings: AppSettings,
|
private val settings: AppSettings,
|
||||||
downloadScheduler: DownloadWorker.Scheduler,
|
downloadScheduler: DownloadWorker.Scheduler,
|
||||||
listExtraProvider: ListExtraProvider,
|
listExtraProvider: ListExtraProvider,
|
||||||
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
|
||||||
@@ -36,7 +37,7 @@ class LocalListViewModel @Inject constructor(
|
|||||||
settings,
|
settings,
|
||||||
listExtraProvider,
|
listExtraProvider,
|
||||||
downloadScheduler,
|
downloadScheduler,
|
||||||
) {
|
), SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
val onMangaRemoved = MutableEventFlow<Unit>()
|
val onMangaRemoved = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
@@ -47,6 +48,18 @@ class LocalListViewModel @Inject constructor(
|
|||||||
loadList(filter.snapshot(), append = false).join()
|
loadList(filter.snapshot(), append = false).join()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
settings.subscribe(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
settings.unsubscribe(this)
|
||||||
|
super.onCleared()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
if (key == AppSettings.KEY_LOCAL_MANGA_DIRS) {
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun delete(ids: Set<Long>) {
|
fun delete(ids: Set<Long>) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ enum class ScoreFormat {
|
|||||||
|
|
||||||
POINT_5 -> score / 5f
|
POINT_5 -> score / 5f
|
||||||
POINT_3 -> score / 3f
|
POINT_3 -> score / 3f
|
||||||
}
|
}.coerceIn(0f, 1f)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ class MALRepository @Inject constructor(
|
|||||||
status = json.getString("status"),
|
status = json.getString("status"),
|
||||||
chapter = json.getInt("num_chapters_read"),
|
chapter = json.getInt("num_chapters_read"),
|
||||||
comment = json.getString("comments"),
|
comment = json.getString("comments"),
|
||||||
rating = json.getDouble("score").toFloat() / 10f,
|
rating = (json.getDouble("score").toFloat() / 10f).coerceIn(0f, 1f),
|
||||||
)
|
)
|
||||||
db.scrobblingDao.upsert(entity)
|
db.scrobblingDao.upsert(entity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ class ShikimoriRepository @Inject constructor(
|
|||||||
status = json.getString("status"),
|
status = json.getString("status"),
|
||||||
chapter = json.getInt("chapters"),
|
chapter = json.getInt("chapters"),
|
||||||
comment = json.getString("text"),
|
comment = json.getString("text"),
|
||||||
rating = json.getDouble("score").toFloat() / 10f,
|
rating = (json.getDouble("score").toFloat() / 10f).coerceIn(0f, 1f),
|
||||||
)
|
)
|
||||||
db.scrobblingDao.upsert(entity)
|
db.scrobblingDao.upsert(entity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,18 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.ui.dialog.StorageSelectDialog
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.getStorageName
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.settings.storage.MangaDirectorySelectDialog
|
||||||
import java.io.File
|
import org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class DownloadsSettingsFragment :
|
class DownloadsSettingsFragment :
|
||||||
BasePreferenceFragment(R.string.downloads),
|
BasePreferenceFragment(R.string.downloads),
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener,
|
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
StorageSelectDialog.OnStorageSelectListener {
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var storageManager: LocalStorageManager
|
lateinit var storageManager: LocalStorageManager
|
||||||
@@ -39,6 +37,7 @@ class DownloadsSettingsFragment :
|
|||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
findPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()
|
findPreference<Preference>(AppSettings.KEY_LOCAL_STORAGE)?.bindStorageName()
|
||||||
|
findPreference<Preference>(AppSettings.KEY_LOCAL_MANGA_DIRS)?.bindDirectoriesCount()
|
||||||
settings.subscribe(this)
|
settings.subscribe(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +52,10 @@ class DownloadsSettingsFragment :
|
|||||||
findPreference<Preference>(key)?.bindStorageName()
|
findPreference<Preference>(key)?.bindStorageName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppSettings.KEY_LOCAL_MANGA_DIRS -> {
|
||||||
|
findPreference<Preference>(key)?.bindDirectoriesCount()
|
||||||
|
}
|
||||||
|
|
||||||
AppSettings.KEY_DOWNLOADS_WIFI -> {
|
AppSettings.KEY_DOWNLOADS_WIFI -> {
|
||||||
updateDownloadsConstraints()
|
updateDownloadsConstraints()
|
||||||
}
|
}
|
||||||
@@ -62,12 +65,12 @@ class DownloadsSettingsFragment :
|
|||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
return when (preference.key) {
|
return when (preference.key) {
|
||||||
AppSettings.KEY_LOCAL_STORAGE -> {
|
AppSettings.KEY_LOCAL_STORAGE -> {
|
||||||
val ctx = context ?: return false
|
MangaDirectorySelectDialog.show(childFragmentManager)
|
||||||
StorageSelectDialog.Builder(ctx, storageManager, this)
|
true
|
||||||
.setTitle(preference.title ?: "")
|
}
|
||||||
.setNegativeButton(android.R.string.cancel)
|
|
||||||
.create()
|
AppSettings.KEY_LOCAL_MANGA_DIRS -> {
|
||||||
.show()
|
startActivity(MangaDirectoriesActivity.newIntent(preference.context))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,14 +78,21 @@ class DownloadsSettingsFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStorageSelected(file: File) {
|
|
||||||
settings.mangaStorageDir = file
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Preference.bindStorageName() {
|
private fun Preference.bindStorageName() {
|
||||||
viewLifecycleScope.launch {
|
viewLifecycleScope.launch {
|
||||||
val storage = storageManager.getDefaultWriteableDir()
|
val storage = storageManager.getDefaultWriteableDir()
|
||||||
summary = storage?.getStorageName(context) ?: getString(R.string.not_available)
|
summary = if (storage != null) {
|
||||||
|
storageManager.getDirectoryDisplayName(storage, isFullPath = true)
|
||||||
|
} else {
|
||||||
|
getString(R.string.not_available)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Preference.bindDirectoriesCount() {
|
||||||
|
viewLifecycleScope.launch {
|
||||||
|
val dirs = storageManager.getReadableDirs().size
|
||||||
|
summary = resources.getQuantityString(R.plurals.items, dirs, dirs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import androidx.annotation.WorkerThread
|
|||||||
import androidx.core.os.LocaleListCompat
|
import androidx.core.os.LocaleListCompat
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
@@ -19,18 +22,26 @@ class NewSourcesViewModel @Inject constructor(
|
|||||||
|
|
||||||
private val initialList = settings.newSources
|
private val initialList = settings.newSources
|
||||||
val sources = MutableStateFlow<List<SourceConfigItem>?>(null)
|
val sources = MutableStateFlow<List<SourceConfigItem>?>(null)
|
||||||
|
private var listUpdateJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchJob(Dispatchers.Default) {
|
listUpdateJob = launchJob(Dispatchers.Default) {
|
||||||
sources.value = buildList()
|
sources.value = buildList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||||
if (isEnabled) {
|
val prevJob = listUpdateJob
|
||||||
settings.hiddenSources -= item.source.name
|
listUpdateJob = launchJob(Dispatchers.Default) {
|
||||||
} else {
|
if (isEnabled) {
|
||||||
settings.hiddenSources += item.source.name
|
settings.hiddenSources -= item.source.name
|
||||||
|
} else {
|
||||||
|
settings.hiddenSources += item.source.name
|
||||||
|
}
|
||||||
|
prevJob?.cancelAndJoin()
|
||||||
|
val list = buildList()
|
||||||
|
ensureActive()
|
||||||
|
sources.value = list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,3 +72,4 @@ class NewSourcesViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
|
||||||
import org.koitharu.kotatsu.core.util.ext.withArgs
|
import org.koitharu.kotatsu.core.util.ext.withArgs
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
|
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
|
||||||
@@ -49,10 +48,18 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
getString(R.string.logged_in_as, it)
|
getString(R.string.logged_in_as, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModel.onError.observeEvent(viewLifecycleOwner, ::onError)
|
viewModel.onError.observeEvent(
|
||||||
|
viewLifecycleOwner,
|
||||||
|
SnackbarErrorObserver(
|
||||||
|
listView,
|
||||||
|
this,
|
||||||
|
exceptionResolver,
|
||||||
|
) { viewModel.onResume() },
|
||||||
|
)
|
||||||
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
|
||||||
findPreference<Preference>(KEY_AUTH)?.isEnabled = !isLoading
|
findPreference<Preference>(KEY_AUTH)?.isEnabled = !isLoading
|
||||||
}
|
}
|
||||||
|
viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(listView))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
@@ -61,32 +68,15 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
|
|||||||
startActivity(SourceAuthActivity.newIntent(preference.context, viewModel.source))
|
startActivity(SourceAuthActivity.newIntent(preference.context, viewModel.source))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
AppSettings.KEY_COOKIES_CLEAR -> {
|
||||||
|
viewModel.clearCookies()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onError(error: Throwable) {
|
|
||||||
val snackbar = Snackbar.make(
|
|
||||||
listView ?: return,
|
|
||||||
error.getDisplayMessage(resources),
|
|
||||||
Snackbar.LENGTH_INDEFINITE,
|
|
||||||
)
|
|
||||||
if (ExceptionResolver.canResolve(error)) {
|
|
||||||
snackbar.setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) }
|
|
||||||
}
|
|
||||||
snackbar.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resolveError(error: Throwable) {
|
|
||||||
view ?: return
|
|
||||||
viewLifecycleScope.launch {
|
|
||||||
if (exceptionResolver.resolve(error)) {
|
|
||||||
viewModel.onResume()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private const val KEY_AUTH = "auth"
|
private const val KEY_AUTH = "auth"
|
||||||
|
|||||||
@@ -5,9 +5,15 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
@@ -17,11 +23,13 @@ import javax.inject.Inject
|
|||||||
class SourceSettingsViewModel @Inject constructor(
|
class SourceSettingsViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
mangaRepositoryFactory: MangaRepository.Factory,
|
mangaRepositoryFactory: MangaRepository.Factory,
|
||||||
|
private val cookieJar: MutableCookieJar,
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
val source = savedStateHandle.require<MangaSource>(SourceSettingsFragment.EXTRA_SOURCE)
|
val source = savedStateHandle.require<MangaSource>(SourceSettingsFragment.EXTRA_SOURCE)
|
||||||
val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
|
val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
|
||||||
|
|
||||||
|
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||||
val username = MutableStateFlow<String?>(null)
|
val username = MutableStateFlow<String?>(null)
|
||||||
private var usernameLoadJob: Job? = null
|
private var usernameLoadJob: Job? = null
|
||||||
|
|
||||||
@@ -35,6 +43,18 @@ class SourceSettingsViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun clearCookies() {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
val url = HttpUrl.Builder()
|
||||||
|
.scheme("https")
|
||||||
|
.host(repository.domain)
|
||||||
|
.build()
|
||||||
|
cookieJar.removeCookies(url)
|
||||||
|
onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))
|
||||||
|
loadUsername()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadUsername() {
|
private fun loadUsername() {
|
||||||
launchLoadingJob(Dispatchers.Default) {
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import android.webkit.CookieManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContract
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
@@ -27,6 +28,7 @@ import org.koitharu.kotatsu.core.util.ext.getSerializableExtraCompat
|
|||||||
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
|
||||||
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import com.google.android.material.R as materialR
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
@@ -66,8 +68,11 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
|
|||||||
}
|
}
|
||||||
with(viewBinding.webView.settings) {
|
with(viewBinding.webView.settings) {
|
||||||
javaScriptEnabled = true
|
javaScriptEnabled = true
|
||||||
userAgentString = CommonHeadersInterceptor.userAgentChrome
|
domStorageEnabled = true
|
||||||
|
databaseEnabled = true
|
||||||
|
userAgentString = UserAgents.CHROME_MOBILE
|
||||||
}
|
}
|
||||||
|
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)
|
||||||
viewBinding.webView.webViewClient = BrowserClient(this)
|
viewBinding.webView.webViewClient = BrowserClient(this)
|
||||||
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
|
viewBinding.webView.webChromeClient = ProgressChromeClient(viewBinding.progressBar)
|
||||||
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
|
onBackPressedCallback = WebViewBackPressedCallback(viewBinding.webView)
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemStorageBinding
|
||||||
|
|
||||||
|
fun directoryAD(
|
||||||
|
clickListener: OnListItemClickListener<DirectoryModel>,
|
||||||
|
) = adapterDelegateViewBinding<DirectoryModel, DirectoryModel, ItemStorageBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemStorageBinding.inflate(layoutInflater, parent, false) },
|
||||||
|
) {
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { v -> clickListener.onItemClick(item, v) }
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.textViewTitle.text = item.title ?: getString(item.titleRes)
|
||||||
|
binding.textViewSubtitle.textAndVisible = item.file?.absolutePath
|
||||||
|
binding.imageViewIndicator.isChecked = item.isChecked
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import androidx.recyclerview.widget.DiffUtil.ItemCallback
|
||||||
|
|
||||||
|
class DirectoryDiffCallback : ItemCallback<DirectoryModel>() {
|
||||||
|
|
||||||
|
override fun areItemsTheSame(oldItem: DirectoryModel, newItem: DirectoryModel): Boolean {
|
||||||
|
return oldItem.file == newItem.file
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun areContentsTheSame(oldItem: DirectoryModel, newItem: DirectoryModel): Boolean {
|
||||||
|
return oldItem == newItem
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChangePayload(oldItem: DirectoryModel, newItem: DirectoryModel): Any? {
|
||||||
|
return if (oldItem.isChecked != newItem.isChecked) {
|
||||||
|
Unit
|
||||||
|
} else {
|
||||||
|
super.getChangePayload(oldItem, newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class DirectoryModel(
|
||||||
|
val title: String?,
|
||||||
|
@StringRes val titleRes: Int,
|
||||||
|
val file: File?,
|
||||||
|
val isChecked: Boolean,
|
||||||
|
val isAvailable: Boolean,
|
||||||
|
) : ListModel {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as DirectoryModel
|
||||||
|
|
||||||
|
if (title != other.title) return false
|
||||||
|
if (titleRes != other.titleRes) return false
|
||||||
|
if (file != other.file) return false
|
||||||
|
if (isChecked != other.isChecked) return false
|
||||||
|
return isAvailable == other.isAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = title?.hashCode() ?: 0
|
||||||
|
result = 31 * result + titleRes
|
||||||
|
result = 31 * result + (file?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + isChecked.hashCode()
|
||||||
|
result = 31 * result + isAvailable.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
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.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.ToastErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.ui.AlertDialogFragment
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.showDistinct
|
||||||
|
import org.koitharu.kotatsu.databinding.DialogDirectorySelectBinding
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MangaDirectorySelectDialog : AlertDialogFragment<DialogDirectorySelectBinding>(),
|
||||||
|
OnListItemClickListener<DirectoryModel> {
|
||||||
|
|
||||||
|
private val viewModel: MangaDirectorySelectViewModel by viewModels()
|
||||||
|
private val pickFileTreeLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
||||||
|
if (it != null) viewModel.onCustomDirectoryPicked(it)
|
||||||
|
}
|
||||||
|
private val permissionRequestLauncher = registerForActivityResult(
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
RequestStorageManagerPermissionContract()
|
||||||
|
} else {
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (it) {
|
||||||
|
viewModel.refresh()
|
||||||
|
pickFileTreeLauncher.launch(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateViewBinding(inflater: LayoutInflater, container: ViewGroup?): DialogDirectorySelectBinding {
|
||||||
|
return DialogDirectorySelectBinding.inflate(inflater, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewBindingCreated(binding: DialogDirectorySelectBinding, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewBindingCreated(binding, savedInstanceState)
|
||||||
|
val adapter = AsyncListDifferDelegationAdapter(DirectoryDiffCallback(), directoryAD(this))
|
||||||
|
binding.root.adapter = adapter
|
||||||
|
viewModel.items.observe(viewLifecycleOwner) { adapter.items = it }
|
||||||
|
viewModel.onDismissDialog.observeEvent(viewLifecycleOwner) { dismiss() }
|
||||||
|
viewModel.onPickDirectory.observeEvent(viewLifecycleOwner) { pickCustomDirectory() }
|
||||||
|
viewModel.onError.observeEvent(viewLifecycleOwner, ToastErrorObserver(binding.root, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||||
|
return super.onBuildDialog(builder)
|
||||||
|
.setCancelable(true)
|
||||||
|
.setTitle(R.string.manga_save_location)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(item: DirectoryModel, view: View) {
|
||||||
|
viewModel.onItemClick(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pickCustomDirectory() {
|
||||||
|
permissionRequestLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val TAG = "MangaDirectorySelectDialog"
|
||||||
|
|
||||||
|
fun show(fm: FragmentManager) = MangaDirectorySelectDialog()
|
||||||
|
.showDistinct(fm, TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import okio.FileNotFoundException
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
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.local.data.LocalStorageManager
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class MangaDirectorySelectViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val items = MutableStateFlow(emptyList<DirectoryModel>())
|
||||||
|
val onDismissDialog = MutableEventFlow<Unit>()
|
||||||
|
val onPickDirectory = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onItemClick(item: DirectoryModel) {
|
||||||
|
if (item.file != null) {
|
||||||
|
settings.mangaStorageDir = item.file
|
||||||
|
onDismissDialog.call(Unit)
|
||||||
|
} else {
|
||||||
|
onPickDirectory.call(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCustomDirectoryPicked(uri: Uri) {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
storageManager.takePermissions(uri)
|
||||||
|
val dir = requireNotNull(storageManager.resolveUri(uri)) {
|
||||||
|
"Cannot resolve file name of \"$uri\""
|
||||||
|
}
|
||||||
|
if (!dir.canWrite()) {
|
||||||
|
throw AccessDeniedException(dir)
|
||||||
|
}
|
||||||
|
if (dir !in storageManager.getApplicationStorageDirs()) {
|
||||||
|
settings.mangaStorageDir = dir
|
||||||
|
storageManager.setDirIsNoMedia(dir)
|
||||||
|
}
|
||||||
|
onDismissDialog.call(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
val defaultValue = storageManager.getDefaultWriteableDir()
|
||||||
|
val available = storageManager.getWriteableDirs()
|
||||||
|
items.value = buildList(available.size + 1) {
|
||||||
|
available.mapTo(this) { dir ->
|
||||||
|
DirectoryModel(
|
||||||
|
title = storageManager.getDirectoryDisplayName(dir, isFullPath = false),
|
||||||
|
titleRes = 0,
|
||||||
|
file = dir,
|
||||||
|
isChecked = dir == defaultValue,
|
||||||
|
isAvailable = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
this += DirectoryModel(
|
||||||
|
title = null,
|
||||||
|
titleRes = R.string.pick_custom_directory,
|
||||||
|
file = null,
|
||||||
|
isChecked = false,
|
||||||
|
isAvailable = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.activity.result.contract.ActivityResultContract
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.R)
|
||||||
|
class RequestStorageManagerPermissionContract : ActivityResultContract<String, Boolean>() {
|
||||||
|
|
||||||
|
override fun createIntent(context: Context, input: String): Intent {
|
||||||
|
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
|
||||||
|
intent.addCategory("android.intent.category.DEFAULT")
|
||||||
|
intent.data = "package:${context.packageName}".toUri()
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
|
||||||
|
return Environment.isExternalStorageManager()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getSynchronousResult(context: Context, input: String): SynchronousResult<Boolean>? {
|
||||||
|
return if (Environment.isExternalStorageManager()) {
|
||||||
|
SynchronousResult(true)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage.directories
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||||
|
import org.koitharu.kotatsu.databinding.ItemStorageConfigBinding
|
||||||
|
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||||
|
|
||||||
|
fun directoryConfigAD(
|
||||||
|
clickListener: OnListItemClickListener<DirectoryModel>,
|
||||||
|
) = adapterDelegateViewBinding<DirectoryModel, DirectoryModel, ItemStorageConfigBinding>(
|
||||||
|
{ layoutInflater, parent -> ItemStorageConfigBinding.inflate(layoutInflater, parent, false) },
|
||||||
|
) {
|
||||||
|
|
||||||
|
binding.imageViewRemove.setOnClickListener { v -> clickListener.onItemClick(item, v) }
|
||||||
|
|
||||||
|
bind {
|
||||||
|
binding.textViewTitle.text = item.title ?: getString(item.titleRes)
|
||||||
|
binding.textViewSubtitle.textAndVisible = item.file?.absolutePath
|
||||||
|
binding.imageViewRemove.isVisible = item.isChecked
|
||||||
|
binding.textViewTitle.drawableStart = if (item.isAvailable) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
ContextCompat.getDrawable(context, R.drawable.ic_alert_outline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage.directories
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.core.graphics.Insets
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
|
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
|
import org.koitharu.kotatsu.databinding.ActivityMangaDirectoriesBinding
|
||||||
|
import org.koitharu.kotatsu.settings.storage.DirectoryDiffCallback
|
||||||
|
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||||
|
import org.koitharu.kotatsu.settings.storage.RequestStorageManagerPermissionContract
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MangaDirectoriesActivity : BaseActivity<ActivityMangaDirectoriesBinding>(),
|
||||||
|
OnListItemClickListener<DirectoryModel>, View.OnClickListener {
|
||||||
|
|
||||||
|
private val viewModel: MangaDirectoriesViewModel by viewModels()
|
||||||
|
private val pickFileTreeLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) {
|
||||||
|
if (it != null) viewModel.onCustomDirectoryPicked(it)
|
||||||
|
}
|
||||||
|
private val permissionRequestLauncher = registerForActivityResult(
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
RequestStorageManagerPermissionContract()
|
||||||
|
} else {
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (it) {
|
||||||
|
viewModel.updateList()
|
||||||
|
pickFileTreeLauncher.launch(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(ActivityMangaDirectoriesBinding.inflate(layoutInflater))
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
val adapter = AsyncListDifferDelegationAdapter(DirectoryDiffCallback(), directoryConfigAD(this))
|
||||||
|
viewBinding.recyclerView.adapter = adapter
|
||||||
|
viewBinding.fabAdd.setOnClickListener(this)
|
||||||
|
viewModel.items.observe(this) { adapter.items = it }
|
||||||
|
viewModel.isLoading.observe(this) { viewBinding.progressBar.isVisible = it }
|
||||||
|
viewModel.onError.observeEvent(
|
||||||
|
this,
|
||||||
|
SnackbarErrorObserver(viewBinding.root, null, exceptionResolver) {
|
||||||
|
if (it) viewModel.updateList()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemClick(item: DirectoryModel, view: View) {
|
||||||
|
viewModel.onRemoveClick(item.file ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View?) {
|
||||||
|
permissionRequestLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
|
viewBinding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
rightMargin = topMargin + insets.right
|
||||||
|
leftMargin = topMargin + insets.left
|
||||||
|
bottomMargin = topMargin + insets.bottom
|
||||||
|
}
|
||||||
|
viewBinding.root.updatePadding(
|
||||||
|
left = insets.left,
|
||||||
|
right = insets.right,
|
||||||
|
)
|
||||||
|
viewBinding.recyclerView.updatePadding(
|
||||||
|
bottom = insets.bottom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newIntent(context: Context) = Intent(context, MangaDirectoriesActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.storage.directories
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import okio.FileNotFoundException
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||||
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
|
import org.koitharu.kotatsu.settings.storage.DirectoryModel
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class MangaDirectoriesViewModel @Inject constructor(
|
||||||
|
private val storageManager: LocalStorageManager,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : BaseViewModel() {
|
||||||
|
|
||||||
|
val items = MutableStateFlow(emptyList<DirectoryModel>())
|
||||||
|
private var loadingJob: Job? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateList() {
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCustomDirectoryPicked(uri: Uri) {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
loadingJob?.cancelAndJoin()
|
||||||
|
storageManager.takePermissions(uri)
|
||||||
|
val dir = requireNotNull(storageManager.resolveUri(uri)) {
|
||||||
|
"Cannot resolve file name of \"$uri\""
|
||||||
|
}
|
||||||
|
if (!dir.canWrite()) {
|
||||||
|
throw AccessDeniedException(dir)
|
||||||
|
}
|
||||||
|
if (dir !in storageManager.getApplicationStorageDirs()) {
|
||||||
|
settings.userSpecifiedMangaDirectories += dir
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onRemoveClick(directory: File) {
|
||||||
|
settings.userSpecifiedMangaDirectories -= directory
|
||||||
|
if (settings.mangaStorageDir == directory) {
|
||||||
|
settings.mangaStorageDir = null
|
||||||
|
}
|
||||||
|
loadList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadList() {
|
||||||
|
val prevJob = loadingJob
|
||||||
|
loadingJob = launchJob(Dispatchers.Default) {
|
||||||
|
prevJob?.cancelAndJoin()
|
||||||
|
val applicationDirs = storageManager.getApplicationStorageDirs()
|
||||||
|
val customDirs = settings.userSpecifiedMangaDirectories
|
||||||
|
items.value = buildList(applicationDirs.size + customDirs.size) {
|
||||||
|
applicationDirs.mapTo(this) { dir ->
|
||||||
|
DirectoryModel(
|
||||||
|
title = storageManager.getDirectoryDisplayName(dir, isFullPath = false),
|
||||||
|
titleRes = 0,
|
||||||
|
file = dir,
|
||||||
|
isChecked = false,
|
||||||
|
isAvailable = dir.canRead() && dir.canWrite(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
customDirs.mapTo(this) { dir ->
|
||||||
|
DirectoryModel(
|
||||||
|
title = storageManager.getDirectoryDisplayName(dir, isFullPath = false),
|
||||||
|
titleRes = 0,
|
||||||
|
file = dir,
|
||||||
|
isChecked = true,
|
||||||
|
isAvailable = dir.canRead() && dir.canWrite(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -11,6 +12,7 @@ import kotlinx.coroutines.flow.mapLatest
|
|||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
||||||
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
|
||||||
import org.koitharu.kotatsu.favourites.data.toMangaList
|
import org.koitharu.kotatsu.favourites.data.toMangaList
|
||||||
@@ -32,6 +34,7 @@ class ShelfContentObserveUseCase @Inject constructor(
|
|||||||
private val trackingRepository: TrackingRepository,
|
private val trackingRepository: TrackingRepository,
|
||||||
private val suggestionRepository: SuggestionRepository,
|
private val suggestionRepository: SuggestionRepository,
|
||||||
private val db: MangaDatabase,
|
private val db: MangaDatabase,
|
||||||
|
private val settings: AppSettings,
|
||||||
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -46,7 +49,10 @@ class ShelfContentObserveUseCase @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun observeLocalManga(sortOrder: SortOrder, limit: Int): Flow<List<Manga>> {
|
private fun observeLocalManga(sortOrder: SortOrder, limit: Int): Flow<List<Manga>> {
|
||||||
return localStorageChanges
|
return combine<LocalManga?, String, Any?>(
|
||||||
|
localStorageChanges,
|
||||||
|
settings.observe().filter { it == AppSettings.KEY_LOCAL_MANGA_DIRS }.onStart { emit("") }
|
||||||
|
) { a, b -> a to b }
|
||||||
.onStart { emit(null) }
|
.onStart { emit(null) }
|
||||||
.mapLatest {
|
.mapLatest {
|
||||||
localMangaRepository.getList(0, null, sortOrder).take(limit)
|
localMangaRepository.getList(0, null, sortOrder).take(limit)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class SuggestionsWorker @AssistedInject constructor(
|
|||||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
.setDefaults(0)
|
.setDefaults(0)
|
||||||
.setOngoing(true)
|
.setOngoing(false)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import org.koitharu.kotatsu.core.db.entity.MangaEntity
|
|||||||
entity = MangaEntity::class,
|
entity = MangaEntity::class,
|
||||||
parentColumns = ["manga_id"],
|
parentColumns = ["manga_id"],
|
||||||
childColumns = ["manga_id"],
|
childColumns = ["manga_id"],
|
||||||
onDelete = ForeignKey.CASCADE
|
onDelete = ForeignKey.CASCADE,
|
||||||
)
|
),
|
||||||
]
|
],
|
||||||
)
|
)
|
||||||
class TrackEntity(
|
class TrackEntity(
|
||||||
@PrimaryKey(autoGenerate = false)
|
@PrimaryKey(autoGenerate = false)
|
||||||
@@ -27,4 +27,4 @@ class TrackEntity(
|
|||||||
@ColumnInfo(name = "last_check") val lastCheck: Long,
|
@ColumnInfo(name = "last_check") val lastCheck: Long,
|
||||||
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
|
@get:Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
|
||||||
@ColumnInfo(name = "last_notified_id") val lastNotifiedChapterId: Long
|
@ColumnInfo(name = "last_notified_id") val lastNotifiedChapterId: Long
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ abstract class TracksDao {
|
|||||||
@Query("UPDATE tracks SET chapters_new = 0")
|
@Query("UPDATE tracks SET chapters_new = 0")
|
||||||
abstract suspend fun clearCounters()
|
abstract suspend fun clearCounters()
|
||||||
|
|
||||||
|
@Query("UPDATE tracks SET chapters_new = 0 WHERE manga_id = :mangaId")
|
||||||
|
abstract suspend fun clearCounter(mangaId: Long)
|
||||||
|
|
||||||
@Query("DELETE FROM tracks WHERE manga_id = :mangaId")
|
@Query("DELETE FROM tracks WHERE manga_id = :mangaId")
|
||||||
abstract suspend fun delete(mangaId: Long)
|
abstract suspend fun delete(mangaId: Long)
|
||||||
|
|
||||||
|
|||||||
@@ -126,6 +126,18 @@ class TrackingRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun clearUpdates(ids: Collection<Long>) {
|
||||||
|
when {
|
||||||
|
ids.isEmpty() -> return
|
||||||
|
ids.size == 1 -> db.tracksDao.clearCounter(ids.single())
|
||||||
|
else -> db.withTransaction {
|
||||||
|
for (id in ids) {
|
||||||
|
db.tracksDao.clearCounter(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun syncWithHistory(manga: Manga, chapterId: Long) {
|
suspend fun syncWithHistory(manga: Manga, chapterId: Long) {
|
||||||
val chapters = manga.chapters ?: return
|
val chapters = manga.chapters ?: return
|
||||||
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
|
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
package org.koitharu.kotatsu.tracker.ui.updates
|
package org.koitharu.kotatsu.tracker.ui.updates
|
||||||
|
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -12,6 +17,22 @@ class UpdatesFragment : MangaListFragment() {
|
|||||||
|
|
||||||
override fun onScrolledToEnd() = Unit
|
override fun onScrolledToEnd() = Unit
|
||||||
|
|
||||||
|
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
mode.menuInflater.inflate(R.menu.mode_updates, menu)
|
||||||
|
return super.onCreateActionMode(controller, mode, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
|
||||||
|
return when (item.itemId) {
|
||||||
|
R.id.action_remove -> {
|
||||||
|
viewModel.remove(controller.snapshot())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> super.onActionItemClicked(controller, mode, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun newInstance() = UpdatesFragment()
|
fun newInstance() = UpdatesFragment()
|
||||||
|
|||||||
@@ -59,4 +59,10 @@ class UpdatesViewModel @Inject constructor(
|
|||||||
override fun onRefresh() = Unit
|
override fun onRefresh() = Unit
|
||||||
|
|
||||||
override fun onRetry() = Unit
|
override fun onRetry() = Unit
|
||||||
|
|
||||||
|
fun remove(ids: Set<Long>) {
|
||||||
|
launchJob(Dispatchers.Default) {
|
||||||
|
repository.clearUpdates(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ class TrackWorker @AssistedInject constructor(
|
|||||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
.setPriority(NotificationCompat.PRIORITY_MIN)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
.setDefaults(0)
|
.setDefaults(0)
|
||||||
.setOngoing(true)
|
.setOngoing(false)
|
||||||
.setSilent(true)
|
.setSilent(true)
|
||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class RecentListFactory(
|
|||||||
|
|
||||||
override fun getLoadingView() = null
|
override fun getLoadingView() = null
|
||||||
|
|
||||||
override fun getItemId(position: Int) = dataSet[position].id
|
override fun getItemId(position: Int) = dataSet.getOrNull(position)?.id ?: 0L
|
||||||
|
|
||||||
override fun onDataSetChanged() {
|
override fun onDataSetChanged() {
|
||||||
val data = runBlocking { historyRepository.getList(0, 10) }
|
val data = runBlocking { historyRepository.getList(0, 10) }
|
||||||
@@ -48,7 +48,7 @@ class RecentListFactory(
|
|||||||
|
|
||||||
override fun getViewAt(position: Int): RemoteViews {
|
override fun getViewAt(position: Int): RemoteViews {
|
||||||
val views = RemoteViews(context.packageName, R.layout.item_recent)
|
val views = RemoteViews(context.packageName, R.layout.item_recent)
|
||||||
val item = dataSet[position]
|
val item = dataSet.getOrNull(position) ?: return views
|
||||||
runCatching {
|
runCatching {
|
||||||
coil.executeBlocking(
|
coil.executeBlocking(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ShelfListFactory(
|
|||||||
|
|
||||||
override fun getLoadingView() = null
|
override fun getLoadingView() = null
|
||||||
|
|
||||||
override fun getItemId(position: Int) = dataSet[position].id
|
override fun getItemId(position: Int) = dataSet.getOrNull(position)?.id ?: 0L
|
||||||
|
|
||||||
override fun onDataSetChanged() {
|
override fun onDataSetChanged() {
|
||||||
val data = runBlocking {
|
val data = runBlocking {
|
||||||
@@ -58,7 +58,7 @@ class ShelfListFactory(
|
|||||||
|
|
||||||
override fun getViewAt(position: Int): RemoteViews {
|
override fun getViewAt(position: Int): RemoteViews {
|
||||||
val views = RemoteViews(context.packageName, R.layout.item_shelf)
|
val views = RemoteViews(context.packageName, R.layout.item_shelf)
|
||||||
val item = dataSet[position]
|
val item = dataSet.getOrNull(position) ?: return views
|
||||||
views.setTextViewText(R.id.textView_title, item.title)
|
views.setTextViewText(R.id.textView_title, item.title)
|
||||||
runCatching {
|
runCatching {
|
||||||
coil.executeBlocking(
|
coil.executeBlocking(
|
||||||
|
|||||||
12
app/src/main/res/drawable/ic_list_end.xml
Normal file
12
app/src/main/res/drawable/ic_list_end.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M17,12A5,5 0 0,1 12,17A5,5 0 0,1 7,12C7,9.58 8.72,7.56 11,7.1V3H13V7.1C15.28,7.56 17,9.58 17,12M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" />
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_list_next.xml
Normal file
12
app/src/main/res/drawable/ic_list_next.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12C17,14.42 15.28,16.44 13,16.9V21H11V16.9C8.72,16.44 7,14.42 7,12M12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15M13,3V5H11V3H13Z" />
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_list_start.xml
Normal file
12
app/src/main/res/drawable/ic_list_start.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,7A5,5 0 0,1 17,12C17,14.42 15.28,16.44 13,16.9V21H11V16.9C8.72,16.44 7,14.42 7,12A5,5 0 0,1 12,7M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" />
|
||||||
|
</vector>
|
||||||
11
app/src/main/res/drawable/ic_select_group.xml
Normal file
11
app/src/main/res/drawable/ic_select_group.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M5 3A2 2 0 0 0 3 5H5M7 3V5H9V3M11 3V5H13V3M15 3V5H17V3M19 3V5H21A2 2 0 0 0 19 3M3 7V9H5V7M7 7V11H11V7M13 7V11H17V7M19 7V9H21V7M3 11V13H5V11M19 11V13H21V11M7 13V17H11V13M13 13V17H17V13M3 15V17H5V15M19 15V17H21V15M3 19A2 2 0 0 0 5 21V19M7 19V21H9V19M11 19V21H13V19M15 19V21H17V19M19 19V21A2 2 0 0 0 21 19Z" />
|
||||||
|
</vector>
|
||||||
@@ -58,7 +58,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:contentDescription="@string/add_new_category"
|
android:contentDescription="@string/add_new_category"
|
||||||
android:src="@drawable/ic_add"
|
|
||||||
android:text="@string/create_category"
|
android:text="@string/create_category"
|
||||||
app:fabSize="normal"
|
app:fabSize="normal"
|
||||||
app:icon="@drawable/ic_add"
|
app:icon="@drawable/ic_add"
|
||||||
|
|||||||
57
app/src/main/res/layout/activity_manga_directories.xml
Normal file
57
app/src/main/res/layout/activity_manga_directories.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:layout_scrollFlags="noScroll">
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_anchor="@id/appbar"
|
||||||
|
app:layout_anchorGravity="bottom" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||||
|
android:id="@+id/fab_add"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:contentDescription="@string/pick_custom_directory"
|
||||||
|
android:text="@string/add"
|
||||||
|
app:fabSize="normal"
|
||||||
|
app:icon="@drawable/ic_add"
|
||||||
|
app:layout_anchor="@id/recyclerView"
|
||||||
|
app:layout_anchorGravity="bottom|end"
|
||||||
|
app:layout_behavior="org.koitharu.kotatsu.core.ui.util.ShrinkOnScrollBehavior"
|
||||||
|
app:layout_dodgeInsetEdges="bottom" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -93,8 +93,7 @@
|
|||||||
android:id="@+id/progressBar"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true" />
|
||||||
app:trackCornerRadius="@dimen/mtrl_progress_indicator_full_rounded_corner_radius" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_loading"
|
android:id="@+id/textView_loading"
|
||||||
|
|||||||
13
app/src/main/res/layout/dialog_directory_select.xml
Normal file
13
app/src/main/res/layout/dialog_directory_select.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/recyclerView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:scrollIndicators="top|bottom"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:ignore="UnusedAttribute" />
|
||||||
16
app/src/main/res/layout/item_download_option.xml
Normal file
16
app/src/main/res/layout/item_download_option.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.core.ui.widgets.TwoLinesItemView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/button_file"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
|
android:minHeight="?android:listPreferredItemHeightSmall"
|
||||||
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
|
tools:subtitle="@string/chapters"
|
||||||
|
tools:title="@string/download_option_whole_manga" />
|
||||||
|
|
||||||
@@ -1,48 +1,46 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?selectableItemBackground"
|
android:background="?selectableItemBackground"
|
||||||
android:minHeight="?listPreferredItemHeightLarge"
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?listPreferredItemHeight"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
android:paddingStart="?listPreferredItemPaddingStart"
|
android:paddingStart="?listPreferredItemPaddingStart"
|
||||||
android:paddingTop="16dp"
|
android:paddingEnd="?listPreferredItemPaddingEnd">
|
||||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
|
||||||
android:paddingBottom="16dp">
|
|
||||||
|
|
||||||
<org.koitharu.kotatsu.core.ui.widgets.CheckableImageView
|
<org.koitharu.kotatsu.core.ui.widgets.CheckableImageView
|
||||||
android:id="@+id/imageView_indicator"
|
android:id="@+id/imageView_indicator"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:src="?android:listChoiceIndicatorSingle"
|
android:src="?android:listChoiceIndicatorSingle"
|
||||||
tools:ignore="TouchTargetSizeCheck" />
|
tools:ignore="TouchTargetSizeCheck" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/textView_title"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_marginStart="?listPreferredItemPaddingStart"
|
android:layout_marginStart="?listPreferredItemPaddingStart"
|
||||||
android:layout_toEndOf="@id/imageView_indicator"
|
android:orientation="vertical">
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
|
||||||
tools:text="@tools:sample/lorem[3]" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView_subtitle"
|
android:id="@+id/textView_title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/textView_title"
|
android:ellipsize="end"
|
||||||
android:layout_alignParentEnd="true"
|
android:singleLine="true"
|
||||||
android:layout_marginStart="?listPreferredItemPaddingStart"
|
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||||
android:layout_marginTop="6dp"
|
tools:text="@tools:sample/lorem[3]" />
|
||||||
android:layout_toEndOf="@id/imageView_indicator"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:textAppearance="?attr/textAppearanceBodyMedium"
|
|
||||||
tools:text="@tools:sample/lorem[20]" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
<TextView
|
||||||
|
android:id="@+id/textView_subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||||
|
tools:text="@tools:sample/lorem[20]" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|||||||
53
app/src/main/res/layout/item_storage_config.xml
Normal file
53
app/src/main/res/layout/item_storage_config.xml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
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="wrap_content"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="?listPreferredItemHeight"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:paddingStart="?listPreferredItemPaddingStart">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="6dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||||
|
app:drawableTint="@color/warning"
|
||||||
|
tools:drawableStart="@drawable/ic_alert_outline"
|
||||||
|
tools:text="@tools:sample/lorem[3]" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceBodyMedium"
|
||||||
|
tools:text="@tools:sample/lorem[20]" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView_remove"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/remove"
|
||||||
|
android:padding="?listPreferredItemPaddingEnd"
|
||||||
|
app:srcCompat="@drawable/ic_delete" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
29
app/src/main/res/menu/mode_updates.xml
Normal file
29
app/src/main/res/menu/mode_updates.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_share"
|
||||||
|
android:icon="?actionModeShareDrawable"
|
||||||
|
android:title="@string/share"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_remove"
|
||||||
|
android:icon="@drawable/ic_delete"
|
||||||
|
android:title="@string/delete"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save"
|
||||||
|
android:icon="@drawable/ic_save"
|
||||||
|
android:title="@string/save"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_select_all"
|
||||||
|
android:icon="?actionModeSelectAllDrawable"
|
||||||
|
android:title="@android:string/selectAll"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
</menu>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/action_save"
|
android:id="@+id/action_save"
|
||||||
android:orderInCategory="40"
|
android:orderInCategory="40"
|
||||||
android:title="@string/save"
|
android:title="@string/download"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
|||||||
@@ -9,4 +9,10 @@
|
|||||||
android:title="@string/_import"
|
android:title="@string/_import"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
</menu>
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:orderInCategory="100"
|
||||||
|
android:title="@string/settings"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
|
|||||||
@@ -1,77 +1,77 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="detailed_list">تفاصيل القائمة</string>
|
<string name="detailed_list">تفاصيل القائمة</string>
|
||||||
<string name="error_occurred">حدث خطأ</string>
|
<string name="error_occurred">حدث خطأ</string>
|
||||||
<string name="details">تفاصيل</string>
|
<string name="details">تفاصيل</string>
|
||||||
<string name="grid">شبكة</string>
|
<string name="grid">شبكة</string>
|
||||||
<string name="list_mode">وضع القائمة</string>
|
<string name="list_mode">وضع القائمة</string>
|
||||||
<string name="settings">إعدادات</string>
|
<string name="settings">إعدادات</string>
|
||||||
<string name="remote_sources">المصادر البعيدة</string>
|
<string name="remote_sources">مصادر المانجا</string>
|
||||||
<string name="chapters">فصول</string>
|
<string name="chapters">فصول</string>
|
||||||
<string name="favourites">المفضلة</string>
|
<string name="favourites">المفضلة</string>
|
||||||
<string name="network_error">خطاء في الشبكة</string>
|
<string name="network_error">خطاء في الشبكة</string>
|
||||||
<string name="loading_">جار التحميل…</string>
|
<string name="loading_">جار التحميل…</string>
|
||||||
<string name="chapter_d_of_d">فصل %1$d في %2$d</string>
|
<string name="chapter_d_of_d">فصل %1$d في %2$d</string>
|
||||||
<string name="close">غلق</string>
|
<string name="close">غلق</string>
|
||||||
<string name="try_again">حاول مجدداً</string>
|
<string name="try_again">حاول مجدداً</string>
|
||||||
<string name="computing_">جاري الحوسبة …</string>
|
<string name="computing_">جاري الحوسبة …</string>
|
||||||
<string name="local_storage">التخزين المحلي</string>
|
<string name="local_storage">التخزين المحلي</string>
|
||||||
<string name="history">سجل</string>
|
<string name="history">سجل</string>
|
||||||
<string name="list">قائمة</string>
|
<string name="list">قائمة</string>
|
||||||
<string name="clear_history">محو سجل</string>
|
<string name="clear_history">محو سجل</string>
|
||||||
<string name="add_to_favourites">ضع هذا في المفضلة</string>
|
<string name="add_to_favourites">ضع هذا في المفضلة</string>
|
||||||
<string name="add">أضف</string>
|
<string name="add">أضف</string>
|
||||||
<string name="save">حفظ</string>
|
<string name="save">حفظ</string>
|
||||||
<string name="history_is_empty">لا سجل بعد</string>
|
<string name="history_is_empty">لا سجل بعد</string>
|
||||||
<string name="downloads">التحميلات</string>
|
<string name="downloads">التحميلات</string>
|
||||||
<string name="by_name">اسم</string>
|
<string name="by_name">اسم</string>
|
||||||
<string name="newest">الأحدث</string>
|
<string name="newest">الأحدث</string>
|
||||||
<string name="by_rating">تقييم</string>
|
<string name="by_rating">تقييم</string>
|
||||||
<string name="pages">صفحات</string>
|
<string name="pages">صفحات</string>
|
||||||
<string name="read">اقرأ</string>
|
<string name="read">اقرأ</string>
|
||||||
<string name="share">شارك</string>
|
<string name="share">شارك</string>
|
||||||
<string name="nothing_found">لم يتم عثور على اي شيء</string>
|
<string name="nothing_found">لم يتم عثور على اي شيء</string>
|
||||||
<string name="you_have_not_favourites_yet">لا مفضلة بعد</string>
|
<string name="you_have_not_favourites_yet">لا مفضلة بعد</string>
|
||||||
<string name="search">بحث</string>
|
<string name="search">بحث</string>
|
||||||
<string name="search_manga">البحث في المانجا</string>
|
<string name="search_manga">البحث في المانجا</string>
|
||||||
<string name="manga_downloading_">جاري التنزيل…</string>
|
<string name="manga_downloading_">جاري التنزيل…</string>
|
||||||
<string name="create_shortcut">انشاء اختصار…</string>
|
<string name="create_shortcut">انشاء اختصار…</string>
|
||||||
<string name="theme">مظهر</string>
|
<string name="theme">مظهر</string>
|
||||||
<string name="automatic">حسب النظام</string>
|
<string name="automatic">حسب النظام</string>
|
||||||
<string name="share_s">شارك %s</string>
|
<string name="share_s">شارك %s</string>
|
||||||
<string name="processing_">في طور معالجة…</string>
|
<string name="processing_">في طور معالجة…</string>
|
||||||
<string name="updated">محدث</string>
|
<string name="updated">محدث</string>
|
||||||
<string name="filter">فلتر</string>
|
<string name="filter">فلتر</string>
|
||||||
<string name="sort_order">ترتيب الفرز</string>
|
<string name="sort_order">ترتيب الفرز</string>
|
||||||
<string name="light">ضوء</string>
|
<string name="light">ضوء</string>
|
||||||
<string name="dark">داكن</string>
|
<string name="dark">داكن</string>
|
||||||
<string name="clear">أزل</string>
|
<string name="clear">أزل</string>
|
||||||
<string name="remove">ازالة</string>
|
<string name="remove">ازالة</string>
|
||||||
<string name="popular">شائع</string>
|
<string name="popular">شائع</string>
|
||||||
<string name="add_new_category">قائمة جديدة</string>
|
<string name="add_new_category">قائمة جديدة</string>
|
||||||
<string name="download_complete">تم التنزيل</string>
|
<string name="download_complete">تم التنزيل</string>
|
||||||
<string name="text_clear_history_prompt">هل تريد محو سجل القراءة بالكامل بشكل دائم؟</string>
|
<string name="text_clear_history_prompt">هل تريد محو سجل القراءة بالكامل بشكل دائم؟</string>
|
||||||
<string name="save_page">احفظ الصفحة</string>
|
<string name="save_page">احفظ الصفحة</string>
|
||||||
<string name="page_saved">حفظت</string>
|
<string name="page_saved">حفظت</string>
|
||||||
<string name="standard">اساسي</string>
|
<string name="standard">اساسي</string>
|
||||||
<string name="no_description">لا يوجد وصف</string>
|
<string name="no_description">لا يوجد وصف</string>
|
||||||
<string name="clear_pages_cache">مسح ذاكرة التخزين المؤقت للصفحة</string>
|
<string name="clear_pages_cache">مسح ذاكرة التخزين المؤقت للصفحة</string>
|
||||||
<string name="webtoon">ويبتون</string>
|
<string name="webtoon">ويبتون</string>
|
||||||
<string name="read_mode">وضع القراءة</string>
|
<string name="read_mode">وضع القراءة</string>
|
||||||
<string name="search_on_s">بحث على %s</string>
|
<string name="search_on_s">بحث على %s</string>
|
||||||
<string name="delete_manga">حذف المانغا</string>
|
<string name="delete_manga">حذف المانغا</string>
|
||||||
<string name="text_delete_local_manga">حذف \"%s\" من الجهاز نهائيا؟</string>
|
<string name="text_delete_local_manga">حذف \"%s\" من الجهاز نهائيا؟</string>
|
||||||
<string name="reader_settings">إعدادات القراءة</string>
|
<string name="reader_settings">إعدادات القراءة</string>
|
||||||
<string name="switch_pages">تغییر صفحات</string>
|
<string name="switch_pages">تغییر صفحات</string>
|
||||||
<string name="delete">حذف</string>
|
<string name="delete">حذف</string>
|
||||||
<string name="share_image">شارك الصورة</string>
|
<string name="share_image">شارك الصورة</string>
|
||||||
<string name="text_file_not_supported">إما أن تختار ملف ZIP أو CBZ.</string>
|
<string name="text_file_not_supported">إما أن تختار ملف ZIP أو CBZ.</string>
|
||||||
<string name="_import">استورد</string>
|
<string name="_import">استورد</string>
|
||||||
<string name="operation_not_supported">هذا خيار غير مدعم</string>
|
<string name="operation_not_supported">هذا خيار غير مدعم</string>
|
||||||
<string name="grid_size">حجم الشبكة</string>
|
<string name="grid_size">حجم الشبكة</string>
|
||||||
<string name="volume_buttons">أزرار الصوت</string>
|
<string name="volume_buttons">أزرار الصوت</string>
|
||||||
<string name="taps_on_edges">النقر على حواف الشاشة</string>
|
<string name="taps_on_edges">النقر على حواف الشاشة</string>
|
||||||
<string name="_continue">يكمل</string>
|
<string name="_continue">يكمل</string>
|
||||||
<string name="error">خطاء</string>
|
<string name="error">خطاء</string>
|
||||||
<string name="clear_search_history">مسح تاريخ البحث</string>
|
<string name="clear_search_history">مسح تاريخ البحث</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
<string name="text_history_holder_secondary">Вы можаце знайсці, што пачытаць, у бакавым меню.</string>
|
<string name="text_history_holder_secondary">Вы можаце знайсці, што пачытаць, у бакавым меню.</string>
|
||||||
<string name="text_history_holder_primary">Тут будзе паказана манга, якую вы чытаеце</string>
|
<string name="text_history_holder_primary">Тут будзе паказана манга, якую вы чытаеце</string>
|
||||||
<string name="text_search_holder_secondary">Паспрабуйце перафармуляваць запыт.</string>
|
<string name="text_search_holder_secondary">Паспрабуйце перафармуляваць запыт.</string>
|
||||||
<string name="text_empty_holder_primary">Тут неяк пуста…</string>
|
<string name="text_empty_holder_primary">Неяк тут пуста…</string>
|
||||||
<string name="chapter_is_missing">Глава адсутнічае</string>
|
<string name="chapter_is_missing">Глава адсутнічае</string>
|
||||||
<string name="queued">У чарзе</string>
|
<string name="queued">У чарзе</string>
|
||||||
<string name="about_app_translation_summary">Дапамагчы з перакладам праграмы</string>
|
<string name="about_app_translation_summary">Дапамагчы з перакладам праграмы</string>
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
<string name="state_finished">Завершана</string>
|
<string name="state_finished">Завершана</string>
|
||||||
<string name="state_ongoing">Ангоінг</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="exclude_nsfw_from_history">Выключыць NSFW мангу з гісторыі</string>
|
||||||
<string name="show_pages_numbers">Паказваць нумары старонак</string>
|
<string name="show_pages_numbers">Паказваць нумары старонак</string>
|
||||||
<string name="enabled_sources">Уключаныя крыніцы</string>
|
<string name="enabled_sources">Уключаныя крыніцы</string>
|
||||||
<string name="available_sources">Даступныя крыніцы</string>
|
<string name="available_sources">Даступныя крыніцы</string>
|
||||||
@@ -199,11 +199,11 @@
|
|||||||
<string name="screenshots_allow">Дазваляць</string>
|
<string name="screenshots_allow">Дазваляць</string>
|
||||||
<string name="screenshots_policy">Палітыка скрыншотаў</string>
|
<string name="screenshots_policy">Палітыка скрыншотаў</string>
|
||||||
<string name="screenshots_block_all">Заўсёды блакуйце</string>
|
<string name="screenshots_block_all">Заўсёды блакуйце</string>
|
||||||
<string name="screenshots_block_nsfw">Блок на NSFW</string>
|
<string name="screenshots_block_nsfw">Забараніць для NSFW</string>
|
||||||
<string name="filter_load_error">Немагчыма загрузіць спіс жанраў</string>
|
<string name="filter_load_error">Немагчыма загрузіць спіс жанраў</string>
|
||||||
<string name="disabled">Адключаны</string>
|
<string name="disabled">Адключаны</string>
|
||||||
<string name="enabled">Уключаны</string>
|
<string name="enabled">Уключаны</string>
|
||||||
<string name="exclude_nsfw_from_suggestions">Не прапануйце мангу NSFW</string>
|
<string name="exclude_nsfw_from_suggestions">Ня прапаноўваць NSFW мангу</string>
|
||||||
<string name="text_suggestion_holder">Пачніце чытаць мангу, і вы атрымаеце персаналізаваныя прапановы</string>
|
<string name="text_suggestion_holder">Пачніце чытаць мангу, і вы атрымаеце персаналізаваныя прапановы</string>
|
||||||
<string name="suggestions_info">Усе даныя аналізуюцца толькі лакальна на гэтай прыладзе і нікуды не адпраўляюцца.</string>
|
<string name="suggestions_info">Усе даныя аналізуюцца толькі лакальна на гэтай прыладзе і нікуды не адпраўляюцца.</string>
|
||||||
<string name="suggestions_summary">Прапануеце мангу, заснаваную на вашых перавагах</string>
|
<string name="suggestions_summary">Прапануеце мангу, заснаваную на вашых перавагах</string>
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
<string name="removed_from_history">Выдалена з гісторыі</string>
|
<string name="removed_from_history">Выдалена з гісторыі</string>
|
||||||
<string name="dns_over_https">DNS праз HTTPS</string>
|
<string name="dns_over_https">DNS праз HTTPS</string>
|
||||||
<string name="detect_reader_mode">Аўтавызначэнне рэжыму чытання</string>
|
<string name="detect_reader_mode">Аўтавызначэнне рэжыму чытання</string>
|
||||||
<string name="detect_reader_mode_summary">Аўтаматычна вызначае, ці з’яўляецца манга вэбтунам</string>
|
<string name="detect_reader_mode_summary">Аўтаматычна вызначае, ці з\'яўляецца манга вэб-коміксам</string>
|
||||||
<string name="new_sources_text">Даступныя новыя крыніцы мангі</string>
|
<string name="new_sources_text">Даступныя новыя крыніцы мангі</string>
|
||||||
<string name="download_slowdown">Запавольванне спампоўкі</string>
|
<string name="download_slowdown">Запавольванне спампоўкі</string>
|
||||||
<string name="suggestions_excluded_genres">Выключыць жанры</string>
|
<string name="suggestions_excluded_genres">Выключыць жанры</string>
|
||||||
@@ -299,7 +299,7 @@
|
|||||||
<string name="different_languages">Розныя мовы</string>
|
<string name="different_languages">Розныя мовы</string>
|
||||||
<string name="network_unavailable">Сетка недаступная</string>
|
<string name="network_unavailable">Сетка недаступная</string>
|
||||||
<string name="network_unavailable_hint">Каб чытаць мангу онлайн, уключыце Wi-Fi або мабільную сетку</string>
|
<string name="network_unavailable_hint">Каб чытаць мангу онлайн, уключыце Wi-Fi або мабільную сетку</string>
|
||||||
<string name="webtoon_zoom">Webtoon зум</string>
|
<string name="webtoon_zoom">Маштабаванне ў рэжыме манхвы</string>
|
||||||
<string name="theme_name_dynamic">Дынамічны</string>
|
<string name="theme_name_dynamic">Дынамічны</string>
|
||||||
<string name="color_theme">Каляровая гама</string>
|
<string name="color_theme">Каляровая гама</string>
|
||||||
<string name="language">Мова</string>
|
<string name="language">Мова</string>
|
||||||
@@ -426,9 +426,21 @@
|
|||||||
<string name="show_pages_numbers_summary">Паказаць нумары старонак у ніжнім куце</string>
|
<string name="show_pages_numbers_summary">Паказаць нумары старонак у ніжнім куце</string>
|
||||||
<string name="network">Сетка</string>
|
<string name="network">Сетка</string>
|
||||||
<string name="data_and_privacy">Дадзеныя і канфідэнцыяльнасць</string>
|
<string name="data_and_privacy">Дадзеныя і канфідэнцыяльнасць</string>
|
||||||
<string name="webtoon_zoom_summary">Дазволіць жэст для павелічэння ў рэжыме webtoon</string>
|
<string name="webtoon_zoom_summary">Уключыць жэст павелічэння маштабу ў рэжыме манхвы</string>
|
||||||
<string name="details_button_tip">Націсніце і ўтрымлівайце кнопку \"Чытаць\", каб убачыць дадатковыя параметры</string>
|
<string name="details_button_tip">Націсніце і ўтрымлівайце кнопку \"Чытаць\", каб убачыць дадатковыя параметры</string>
|
||||||
<string name="restore_summary">Аднавіць раней створаную рэзервовую копію</string>
|
<string name="restore_summary">Аднавіць раней створаную рэзервовую копію</string>
|
||||||
<string name="reader_info_bar_summary">Паказаць бягучы час і ход чытання ў верхняй частцы экрана</string>
|
<string name="reader_info_bar_summary">Паказаць бягучы час і ход чытання ў верхняй частцы экрана</string>
|
||||||
<string name="pages_animation_summary">Анімацыя перагортвання старонак</string>
|
<string name="pages_animation_summary">Анімацыя перагортвання старонак</string>
|
||||||
|
<string name="clear_source_cookies_summary">Выдаліць файлы cookie толькі для вызначанага дамена. У большасці выпадкаў гэта робіць аўтарызацыю несапраўднай</string>
|
||||||
|
<string name="download_option_whole_manga">Манга цалкам</string>
|
||||||
|
<string name="local_manga_directories">Лакальныя каталогі мангі</string>
|
||||||
|
<string name="download_option_all_chapters">Усе раздзелы з перакладам %s</string>
|
||||||
|
<string name="download_option_first_n_chapters">Першыя %s</string>
|
||||||
|
<string name="download_option_all_unread_b">Усе непрачытаныя раздзелы (%s)</string>
|
||||||
|
<string name="download_option_all_unread">Усе непрачытаныя раздзелы</string>
|
||||||
|
<string name="download_option_manual_selection">Выбірайце раздзелы ўручную</string>
|
||||||
|
<string name="pick_custom_directory">Выберыце карыстальніцкі каталог</string>
|
||||||
|
<string name="download_option_next_unread_n_chapters">Наступная непрачытаная %s</string>
|
||||||
|
<string name="custom_directory">Карыстацкі каталог</string>
|
||||||
|
<string name="no_access_to_file">У вас няма доступу да гэтага файла або каталога</string>
|
||||||
</resources>
|
</resources>
|
||||||
33
app/src/main/res/values-cs/plurals.xml
Normal file
33
app/src/main/res/values-cs/plurals.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</plurals>
|
||||||
|
</resources>
|
||||||
446
app/src/main/res/values-cs/strings.xml
Normal file
446
app/src/main/res/values-cs/strings.xml
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<string name="network_error">Problém s připojením</string>
|
||||||
|
<string name="details">Detaily</string>
|
||||||
|
<string name="chapters">Kapitoly</string>
|
||||||
|
<string name="list">Seznam</string>
|
||||||
|
<string name="detailed_list">Podrobný seznam</string>
|
||||||
|
<string name="grid">Mřížka</string>
|
||||||
|
<string name="list_mode">Režim seznamu</string>
|
||||||
|
<string name="settings">Nastavení</string>
|
||||||
|
<string name="remote_sources">Zdroje mang</string>
|
||||||
|
<string name="loading_">Načítání…</string>
|
||||||
|
<string name="computing_">Vypočítávání…</string>
|
||||||
|
<string name="chapter_d_of_d">Kapitola %1$d z %2$d</string>
|
||||||
|
<string name="close">Zavřít</string>
|
||||||
|
<string name="try_again">Zkusit znovu</string>
|
||||||
|
<string name="clear_history">Vyčistit historii</string>
|
||||||
|
<string name="nothing_found">Nic nenalezeno</string>
|
||||||
|
<string name="history_is_empty">Zatím žádná historie</string>
|
||||||
|
<string name="read">Číst</string>
|
||||||
|
<string name="you_have_not_favourites_yet">Zatím žádné oblíbené</string>
|
||||||
|
<string name="add_to_favourites">Oblíbit toto</string>
|
||||||
|
<string name="add">Přidat</string>
|
||||||
|
<string name="share">Sdílet</string>
|
||||||
|
<string name="create_shortcut">Vytvořit zkratku…</string>
|
||||||
|
<string name="share_s">Sdílet %s</string>
|
||||||
|
<string name="processing_">Zpracovávání…</string>
|
||||||
|
<string name="download_complete">Staženo</string>
|
||||||
|
<string name="downloads">Stažené</string>
|
||||||
|
<string name="by_name">Název</string>
|
||||||
|
<string name="sort_order">Pořadí řazení</string>
|
||||||
|
<string name="filter">Filtr</string>
|
||||||
|
<string name="theme">Téma</string>
|
||||||
|
<string name="light">Světlé</string>
|
||||||
|
<string name="dark">Tmavé</string>
|
||||||
|
<string name="automatic">Následovat systém</string>
|
||||||
|
<string name="remove">Odstranit</string>
|
||||||
|
<string name="_s_deleted_from_local_storage">\"%s\" smazáno z místního uložiště</string>
|
||||||
|
<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="clear_pages_cache">Vyčistit mezipaměť stran</string>
|
||||||
|
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||||
|
<string name="standard">Standardní</string>
|
||||||
|
<string name="webtoon">Webtoon</string>
|
||||||
|
<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="reader_settings">Nastavení čtečky</string>
|
||||||
|
<string name="switch_pages">Prohodit strany</string>
|
||||||
|
<string name="taps_on_edges">Rohová kliknutí</string>
|
||||||
|
<string name="volume_buttons">Tlačítka hlasitosti</string>
|
||||||
|
<string name="_continue">Pokračovat</string>
|
||||||
|
<string name="error">Chyba</string>
|
||||||
|
<string name="clear_thumbs_cache">Vyčistit mezipamět náhledů</string>
|
||||||
|
<string name="clear_search_history">Vyčisit historii vyhledávání</string>
|
||||||
|
<string name="domain">Doména</string>
|
||||||
|
<string name="app_update_available">Je dostupná nová verze této aplikace</string>
|
||||||
|
<string name="open_in_browser">Otevřít v prohlížeči</string>
|
||||||
|
<string name="large_manga_save_confirm">Tato manga má %s. Uložit všechny\?</string>
|
||||||
|
<string name="save_manga">Uložit</string>
|
||||||
|
<string name="notifications">Oznámení</string>
|
||||||
|
<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">Zjistěte co číst na boční nabídce.</string>
|
||||||
|
<string name="text_shelf_holder_primary">Vaše manga bude zobrazena zde</string>
|
||||||
|
<string name="text_shelf_holder_secondary">Zjistě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="recent_manga">Nedávné</string>
|
||||||
|
<string name="manga_shelf">Polička</string>
|
||||||
|
<string name="pages_animation">Animace stránek</string>
|
||||||
|
<string name="manga_save_location">Složka pro stahování</string>
|
||||||
|
<string name="not_available">Není dostupné</string>
|
||||||
|
<string name="cannot_find_available_storage">Není dostupné uložiště</string>
|
||||||
|
<string name="other_storage">Další uložiště</string>
|
||||||
|
<string name="done">Hotovo</string>
|
||||||
|
<string name="all_favourites">Všechny oblíbené</string>
|
||||||
|
<string name="favourites_category_empty">Prázdná kategorie</string>
|
||||||
|
<string name="text_feed_holder">Nové kapitoly toho co čtete jsou zobrazeny zde</string>
|
||||||
|
<string name="search_results">Výsledky vyhledávání</string>
|
||||||
|
<string name="new_version_s">Nová verze: %s</string>
|
||||||
|
<string name="updates_feed_cleared">Vyčištěno</string>
|
||||||
|
<string name="rotate_screen">Otočit displej</string>
|
||||||
|
<string name="update">Aktualizovat</string>
|
||||||
|
<string name="dont_check">Nekontrolovat</string>
|
||||||
|
<string name="enter_password">Zadejte heslo</string>
|
||||||
|
<string name="wrong_password">Špatné heslo</string>
|
||||||
|
<string name="protect_application">Chránit aplikaci</string>
|
||||||
|
<string name="repeat_password">Zopakujte heslo</string>
|
||||||
|
<string name="about">O</string>
|
||||||
|
<string name="app_version">Verze %s</string>
|
||||||
|
<string name="check_for_updates">Zkontrolovat aktualizace</string>
|
||||||
|
<string name="no_update_available">Nejsou dostupné žádné aktualizace</string>
|
||||||
|
<string name="scale_mode">Režim měřítka</string>
|
||||||
|
<string name="zoom_mode_fit_center">Pasovat do středu</string>
|
||||||
|
<string name="zoom_mode_fit_height">Pasovat na výšku</string>
|
||||||
|
<string name="zoom_mode_fit_width">Pasovat na šířku</string>
|
||||||
|
<string name="zoom_mode_keep_start">Uchovat při zapnutí</string>
|
||||||
|
<string name="black_dark_theme">Černá</string>
|
||||||
|
<string name="black_dark_theme_summary">Využívá méně energie na AMOLED displejích</string>
|
||||||
|
<string name="backup_restore">Zálohovat a obnovit</string>
|
||||||
|
<string name="create_backup">Vytvořit zálohu dat</string>
|
||||||
|
<string name="restore_backup">Obnovit ze zálohy</string>
|
||||||
|
<string name="data_restored">Obnoveno</string>
|
||||||
|
<string name="preparing_">Připravuji…</string>
|
||||||
|
<string name="file_not_found">Soubor nebyl nalezen</string>
|
||||||
|
<string name="data_restored_success">Všechna data byla obnovena</string>
|
||||||
|
<string name="clear_updates_feed">Vyčistit frontu aktualizací</string>
|
||||||
|
<string name="feed_will_update_soon">Aktualizace fronty brzy začne</string>
|
||||||
|
<string name="just_now">Právě teď</string>
|
||||||
|
<string name="long_ago">Dávno</string>
|
||||||
|
<string name="group">Skupina</string>
|
||||||
|
<string name="today">Dnes</string>
|
||||||
|
<string name="tap_to_try_again">Klikněte pro zopakování</string>
|
||||||
|
<string name="reader_mode_hint">Zvolená konfigurace bude pro tuto mangu zapamatována</string>
|
||||||
|
<string name="silent">Tiché</string>
|
||||||
|
<string name="captcha_required">Vyžadována CAPTCHA</string>
|
||||||
|
<string name="captcha_solve">Vyřešit</string>
|
||||||
|
<string name="text_clear_updates_feed_prompt">Vyčistit permanentně všechnu historii aktualizací\?</string>
|
||||||
|
<string name="check_for_new_chapters">Zkontrolovat nové kapitoly</string>
|
||||||
|
<string name="reverse">Obráceně</string>
|
||||||
|
<string name="sign_in">Přihlásit se</string>
|
||||||
|
<string name="auth_required">Abyste mohli vidět tento kontent, je nutné se přihlásit</string>
|
||||||
|
<string name="default_s">Základní: %s</string>
|
||||||
|
<string name="next">Další</string>
|
||||||
|
<string name="protect_application_subtitle">Zadejte heslo pro spuštění aplikace s</string>
|
||||||
|
<string name="password_length_hint">Heslo musí obsahovat nejméně 4 nebo více znaků</string>
|
||||||
|
<string name="text_clear_search_history_prompt">Odstranit permanentně všechny nedávné vyhledávání\?</string>
|
||||||
|
<string name="welcome">Vítejte</string>
|
||||||
|
<string name="backup_saved">Záloha uložena</string>
|
||||||
|
<string name="queued">Ve frontě</string>
|
||||||
|
<string name="chapter_is_missing">Chybí kapitola</string>
|
||||||
|
<string name="about_app_translation">Překlad</string>
|
||||||
|
<string name="auth_complete">Autorizováno</string>
|
||||||
|
<string name="auth_not_supported_by">Přihlášení na %s není podporováno</string>
|
||||||
|
<string name="text_clear_cookies_prompt">Budete odhlášeni ze všech zdrojů</string>
|
||||||
|
<string name="genres">Žánry</string>
|
||||||
|
<string name="state_ongoing">Pokračující</string>
|
||||||
|
<string name="system_default">Základní</string>
|
||||||
|
<string name="exclude_nsfw_from_history">Vynechat všechny NSFW mangy z historie</string>
|
||||||
|
<string name="show_pages_numbers">Očíslované stránky</string>
|
||||||
|
<string name="enabled_sources">Použité zdroje</string>
|
||||||
|
<string name="screenshots_policy">Zásady snímku obrazovky</string>
|
||||||
|
<string name="screenshots_allow">Povolit</string>
|
||||||
|
<string name="screenshots_block_nsfw">Zakázat na NSFW</string>
|
||||||
|
<string name="screenshots_block_all">Vždy blokovat</string>
|
||||||
|
<string name="suggestions">Návrhy</string>
|
||||||
|
<string name="suggestions_enable">Zapnout návrhy</string>
|
||||||
|
<string name="suggestions_summary">Navrhovat mangy dle vašich preferencí</string>
|
||||||
|
<string name="suggestions_info">Všechna data jsou analyzována pouze lokálně na tomto zařízení a nikdy nejsou nikam odesílána.</string>
|
||||||
|
<string name="text_suggestion_holder">Začněte číst mangu a získejte personalizované návrhy</string>
|
||||||
|
<string name="exclude_nsfw_from_suggestions">Nenavrhovat NSFW mangu</string>
|
||||||
|
<string name="enabled">Zapnuto</string>
|
||||||
|
<string name="disabled">Vypnuto</string>
|
||||||
|
<string name="filter_load_error">Nelze načíst seznam žánrů</string>
|
||||||
|
<string name="reset_filter">Resetovat filtr</string>
|
||||||
|
<string name="onboard_text">Vyberte jazyk ve kterém si přejete číst mangu. Můžete jej později změnit v nastavení.</string>
|
||||||
|
<string name="never">Nikdy</string>
|
||||||
|
<string name="only_using_wifi">Pouze na Wi-Fi</string>
|
||||||
|
<string name="always">Vždy</string>
|
||||||
|
<string name="preload_pages">Přednačíst stránky</string>
|
||||||
|
<string name="logged_in_as">Přihlášen jako %s</string>
|
||||||
|
<string name="nsfw">18+</string>
|
||||||
|
<string name="various_languages">Různé jazyky</string>
|
||||||
|
<string name="search_chapters">Najít kapitolu</string>
|
||||||
|
<string name="chapters_empty">Žádné kapitoly v této manze</string>
|
||||||
|
<string name="percent_string_pattern">%1$s%%</string>
|
||||||
|
<string name="appearance">Vzhled</string>
|
||||||
|
<string name="suggestions_excluded_genres_summary">Specifikujte žánry které nechcete vidět v návrzích</string>
|
||||||
|
<string name="text_delete_local_manga_batch">Permanentně odstranit vybrané předměty ze zařízení\?</string>
|
||||||
|
<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="account_already_exists">Účet již existuje</string>
|
||||||
|
<string name="back">Zpět</string>
|
||||||
|
<string name="sync_title">Synchronizujte svá data</string>
|
||||||
|
<string name="email_enter_hint">Pro pokračování zadejte svůj email</string>
|
||||||
|
<string name="hide">Schovat</string>
|
||||||
|
<string name="new_sources_text">Jsou dostupné nové zdroje mang</string>
|
||||||
|
<string name="check_new_chapters_title">Kontrolovat nové kapiroly a upozornit na ně</string>
|
||||||
|
<string name="show_notification_new_chapters_on">Dostanete oznámení o aktualizaci mang které čtete</string>
|
||||||
|
<string name="notifications_enable">Zapnout oznámení</string>
|
||||||
|
<string name="name">Jméno</string>
|
||||||
|
<string name="edit">Upravit</string>
|
||||||
|
<string name="edit_category">Upravit kategorii</string>
|
||||||
|
<string name="tracking">Sledování</string>
|
||||||
|
<string name="empty_favourite_categories">Žádné oblíbené kategorie</string>
|
||||||
|
<string name="logout">Odhlásit se</string>
|
||||||
|
<string name="bookmark_add">Přidat záložku</string>
|
||||||
|
<string name="bookmark_remove">Odstranit záložku</string>
|
||||||
|
<string name="bookmarks">Záložky</string>
|
||||||
|
<string name="bookmark_added">Přidána záložka</string>
|
||||||
|
<string name="undo">Vrátit zpět</string>
|
||||||
|
<string name="removed_from_history">Odstraněno z historie</string>
|
||||||
|
<string name="dns_over_https">DNS přes HTTPS</string>
|
||||||
|
<string name="default_mode">Základní režim</string>
|
||||||
|
<string name="detect_reader_mode">Automaticky detekovat režim čtení</string>
|
||||||
|
<string name="detect_reader_mode_summary">Automaticky detekovat zda je manga webtoon</string>
|
||||||
|
<string name="disable_battery_optimization">Vypnout optimalizaci baterie</string>
|
||||||
|
<string name="disable_battery_optimization_summary">Pomáhá s kontrolou nových kapitol</string>
|
||||||
|
<string name="send">Odeslat</string>
|
||||||
|
<string name="status_planned">Plánované</string>
|
||||||
|
<string name="status_reading">Čtení</string>
|
||||||
|
<string name="status_re_reading">Znovu-čtení</string>
|
||||||
|
<string name="status_completed">Dokončeno</string>
|
||||||
|
<string name="status_on_hold">Pozastaveno</string>
|
||||||
|
<string name="status_dropped">Zahozeno</string>
|
||||||
|
<string name="disable_all">Vypnout vše</string>
|
||||||
|
<string name="use_fingerprint">Pokud lze, použít otisk prstu</string>
|
||||||
|
<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="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="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>
|
||||||
|
<string name="clear_all_history">Vyčistit celou historii</string>
|
||||||
|
<string name="history_cleared">Historie vyčištěna</string>
|
||||||
|
<string name="manage">Spravovat</string>
|
||||||
|
<string name="no_bookmarks_yet">Zatím žádné záložky</string>
|
||||||
|
<string name="bookmarks_removed">Záložky odstraněny</string>
|
||||||
|
<string name="no_manga_sources">Žádné zdroje mang</string>
|
||||||
|
<string name="no_manga_sources_text">Zapnout zdroje mang pro čtení online</string>
|
||||||
|
<string name="categories_delete_confirm">Jste si jisti že chcete smazat zvolené oblíbené kategorie\?
|
||||||
|
\nVšechny mangy v ní budou ztraceny a nelze jej vrátit zpět.</string>
|
||||||
|
<string name="reorder">Přeskupit</string>
|
||||||
|
<string name="empty">Prázdné</string>
|
||||||
|
<string name="explore">Prozkoumat</string>
|
||||||
|
<string name="confirm_exit">Stiskněte Zpět znovu pro ukončení</string>
|
||||||
|
<string name="exit_confirmation_summary">Stiskněte Zpět dvakrát pro ukončení aplikace</string>
|
||||||
|
<string name="exit_confirmation">Potvrzení ukončení</string>
|
||||||
|
<string name="saved_manga">Uložená manga</string>
|
||||||
|
<string name="pages_cache">Mezipaměť stránek</string>
|
||||||
|
<string name="other_cache">Další mezipamět</string>
|
||||||
|
<string name="storage_usage">Využívání uložiště</string>
|
||||||
|
<string name="available">Dostupné</string>
|
||||||
|
<string name="memory_usage_pattern">%s -%s</string>
|
||||||
|
<string name="removed_from_favourites">Odstraněno z oblíbených</string>
|
||||||
|
<string name="options">Možnosti</string>
|
||||||
|
<string name="not_found_404">Kontent nebyl nalezen nebo byl odstraněn</string>
|
||||||
|
<string name="incognito_mode">Režim inkognito</string>
|
||||||
|
<string name="no_chapters">Žádné kapitoly</string>
|
||||||
|
<string name="automatic_scroll">Automatické rolování</string>
|
||||||
|
<string name="reader_info_pattern">Kap. %1$d/%2$d Str. %3$d/%4$d</string>
|
||||||
|
<string name="reader_info_bar">Zobrazovat informační lištu v čtečce</string>
|
||||||
|
<string name="comics_archive">Archív komiksů</string>
|
||||||
|
<string name="folder_with_images">Složka s obrázky</string>
|
||||||
|
<string name="importing_manga">Importuji mangu</string>
|
||||||
|
<string name="import_will_start_soon">Importování brzy začne</string>
|
||||||
|
<string name="feed">Fronta</string>
|
||||||
|
<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">Ovládání ergonomické čtečky</string>
|
||||||
|
<string name="color_correction">Korekce barev</string>
|
||||||
|
<string name="brightness">Jas</string>
|
||||||
|
<string name="contrast">Kontrast</string>
|
||||||
|
<string name="reset">Resetovat</string>
|
||||||
|
<string name="color_correction_hint">Vybrané nastavení barev bude pro tuto mangu zapamatováno</string>
|
||||||
|
<string name="text_unsaved_changes_prompt">Uložit nebo zahodit neuložené změny\?</string>
|
||||||
|
<string name="discard">Zahodit</string>
|
||||||
|
<string name="error_no_space_left">Na zařízení není volné žádné místo</string>
|
||||||
|
<string name="reader_slider">Zobrazovat posuvník změny stran</string>
|
||||||
|
<string name="webtoon_zoom">Webtoon přiblížení</string>
|
||||||
|
<string name="different_languages">Různé jazyky</string>
|
||||||
|
<string name="network_unavailable">Připojení není dostupné</string>
|
||||||
|
<string name="network_unavailable_hint">Zapněte Wi-Fi nebo mobilní data abyste mohli číst mangu online</string>
|
||||||
|
<string name="server_error">Chyba na straně serveru (%1$d). Prosíme zkuste to znovu později</string>
|
||||||
|
<string name="clear_new_chapters_counters">Také vyčistit informace o nových kapitolách</string>
|
||||||
|
<string name="compact">Kompaktní</string>
|
||||||
|
<string name="source_disabled">Zdroj vypnut</string>
|
||||||
|
<string name="prefetch_content">Přednačítání kontentu</string>
|
||||||
|
<string name="mark_as_current">Označit jako aktuální</string>
|
||||||
|
<string name="language">Jazyk</string>
|
||||||
|
<string name="share_logs">Sdílet záznamy</string>
|
||||||
|
<string name="enable_logging">Zapnout zaznamenávání</string>
|
||||||
|
<string name="enable_logging_summary">Zaznamenat některé akce pro spravovací účely</string>
|
||||||
|
<string name="show_suspicious_content">Zobrazovat podezřelý kontent</string>
|
||||||
|
<string name="theme_name_dynamic">Dynamické</string>
|
||||||
|
<string name="color_theme">Schéma barev</string>
|
||||||
|
<string name="show_in_grid_view">Zobrazovat v zobrazení mřížek</string>
|
||||||
|
<string name="theme_name_miku">Miku</string>
|
||||||
|
<string name="theme_name_asuka">Asuka</string>
|
||||||
|
<string name="theme_name_mion">Mion</string>
|
||||||
|
<string name="theme_name_rikka">Rikka</string>
|
||||||
|
<string name="theme_name_sakura">Sakura</string>
|
||||||
|
<string name="theme_name_mamimi">Mamimi</string>
|
||||||
|
<string name="theme_name_kanade">Kanade</string>
|
||||||
|
<string name="nothing_here">Nic tu není</string>
|
||||||
|
<string name="scrobbling_empty_hint">Pro sledování pokroku čtení, vyberte Menu → Sledovat na displeji detailů mangy.</string>
|
||||||
|
<string name="services">Služby</string>
|
||||||
|
<string name="allow_unstable_updates">Povolit nestabilní aktualizace</string>
|
||||||
|
<string name="allow_unstable_updates_summary">Navrhnout beta aktualizace této aplikace</string>
|
||||||
|
<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="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="restore_backup_description">Importovat dřive vytvořenou zálohu uživatelských dat</string>
|
||||||
|
<string name="show_on_shelf">Zobrazovat na poličce</string>
|
||||||
|
<string name="sync_auth_hint">Můžete se přihlásit do již existujícího účtu nebo vytvořit nový</string>
|
||||||
|
<string name="find_similar">Najít podobné</string>
|
||||||
|
<string name="sync_settings">Nastavení synchronizace</string>
|
||||||
|
<string name="server_address">Adresa serveru</string>
|
||||||
|
<string name="ignore_ssl_errors">Ignorovat SSL chyby</string>
|
||||||
|
<string name="mirror_switching">Vybrat zrcadlo automaticky</string>
|
||||||
|
<string name="mirror_switching_summary">Automaticky prohodit doménu pro vzdálené zdroje při chybě pokud jsou zrcadla dostupná</string>
|
||||||
|
<string name="pause">Pozastavit</string>
|
||||||
|
<string name="resume">Vrátit</string>
|
||||||
|
<string name="paused">Pozastaveno</string>
|
||||||
|
<string name="remove_completed">Odstranění dokončeno</string>
|
||||||
|
<string name="downloads_wifi_only_summary">Zastavit stahování při měnění na mobilní data</string>
|
||||||
|
<string name="suggestion_manga">Doporučení: %s</string>
|
||||||
|
<string name="suggestions_notifications_summary">Občas zobrazit oznámení s navrženou mangou</string>
|
||||||
|
<string name="more">Více</string>
|
||||||
|
<string name="enable">Zapnout</string>
|
||||||
|
<string name="no_thanks">Ne, děkuji</string>
|
||||||
|
<string name="cancel_all_downloads_confirm">Všechna aktivní stahování budou zrušena, částečně stažená data budou ztracena</string>
|
||||||
|
<string name="remove_completed_downloads_confirm">Vaše historie stahování bude permanentně odstraněna</string>
|
||||||
|
<string name="text_downloads_list_holder">Nemáte žádná stažení</string>
|
||||||
|
<string name="downloads_resumed">Stahování bylo vráceno</string>
|
||||||
|
<string name="downloads_paused">Stahování bylo pozastaveno</string>
|
||||||
|
<string name="downloads_removed">Stažené soubory byly odstraněny</string>
|
||||||
|
<string name="downloads_cancelled">Stahování bylo zrušeno</string>
|
||||||
|
<string name="suggestions_enable_prompt">Chcete získávat personalizované návrhy mang\?</string>
|
||||||
|
<string name="translations">Překlady</string>
|
||||||
|
<string name="web_view_unavailable">WebView není dostupné: zkontrolovay jestli je provozovatel WebView nainstalovaný</string>
|
||||||
|
<string name="clear_network_cache">Vyčistit mezipaměť sítě</string>
|
||||||
|
<string name="type">Typ</string>
|
||||||
|
<string name="address">Adresa</string>
|
||||||
|
<string name="invalid_value_message">Neplatná hodnota</string>
|
||||||
|
<string name="manga_branch_title_template">%1$s (%2$s)</string>
|
||||||
|
<string name="downloaded">Staženo</string>
|
||||||
|
<string name="images_proxy_title">Proxy pro optimalizaci obrázků</string>
|
||||||
|
<string name="images_procy_description">Použít službu wsrv.nl ke snížení využití provozu a urychlení načítání obrázků, pokud je to možné</string>
|
||||||
|
<string name="invert_colors">Invertovat barvy</string>
|
||||||
|
<string name="username">Uživatelské jméno</string>
|
||||||
|
<string name="password">Heslo</string>
|
||||||
|
<string name="authorization_optional">Autorizace (dobrovolné)</string>
|
||||||
|
<string name="invalid_port_number">Neplatné číslo portu</string>
|
||||||
|
<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="show_pages_numbers_summary">Zobrazovat číslo strany ve spodním rohu</string>
|
||||||
|
<string name="pages_animation_summary">Animovat změny stran</string>
|
||||||
|
<string name="details_button_tip">Stiskněte a podržte tlačítko Číst pro více možností</string>
|
||||||
|
<string name="download_option_all_chapters">Všechny kapitoly s překladem %s</string>
|
||||||
|
<string name="download_option_whole_manga">Celá manga</string>
|
||||||
|
<string name="download_option_first_n_chapters">První %s</string>
|
||||||
|
<string name="download_option_next_unread_n_chapters">Další nepřečtené %s</string>
|
||||||
|
<string name="download_option_all_unread">Všechny nepřečtené kapitoly</string>
|
||||||
|
<string name="download_option_all_unread_b">Všechny nepřečtené kapitoly (%s)</string>
|
||||||
|
<string name="custom_directory">Vlastní adrešář</string>
|
||||||
|
<string name="pick_custom_directory">Vyberte vlastní adresář</string>
|
||||||
|
<string name="no_access_to_file">Nemáte žádný přístup k tomuto souboru nebo adresáři</string>
|
||||||
|
<string name="local_manga_directories">Lokální adresář mang</string>
|
||||||
|
<string name="local_storage">Místní uložiště</string>
|
||||||
|
<string name="favourites">Oblíbené</string>
|
||||||
|
<string name="history">Historie</string>
|
||||||
|
<string name="error_occurred">Vyskytla se chyba</string>
|
||||||
|
<string name="add_new_category">Nová kategorie</string>
|
||||||
|
<string name="save">Uložit</string>
|
||||||
|
<string name="search">Hledat</string>
|
||||||
|
<string name="manga_downloading_">Stahování…</string>
|
||||||
|
<string name="updated">Aktualizováno</string>
|
||||||
|
<string name="by_rating">Hodnocení</string>
|
||||||
|
<string name="search_manga">Hledat mangu</string>
|
||||||
|
<string name="popular">Populární</string>
|
||||||
|
<string name="newest">Nejnovější</string>
|
||||||
|
<string name="text_clear_history_prompt">Permanentně vyčistit celou historii čtení\?</string>
|
||||||
|
<string name="no_description">Žádný popis</string>
|
||||||
|
<string name="pages">Strany</string>
|
||||||
|
<string name="clear">Vyčistit</string>
|
||||||
|
<string name="save_page">Uložit stranu</string>
|
||||||
|
<string name="page_saved">Uloženo</string>
|
||||||
|
<string name="operation_not_supported">Tato operace není podporována</string>
|
||||||
|
<string name="search_on_s">Hledat na %s</string>
|
||||||
|
<string name="internal_storage">Interní uložiště</string>
|
||||||
|
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d z %2$d na</string>
|
||||||
|
<string name="notification_sound">Zvuk oznámení</string>
|
||||||
|
<string name="search_history_cleared">Vyčištěno</string>
|
||||||
|
<string name="gestures_only">Pouze gesta</string>
|
||||||
|
<string name="external_storage">Externí uložiště</string>
|
||||||
|
<string name="new_chapters">Nové kapitoly</string>
|
||||||
|
<string name="download">Stáhnout</string>
|
||||||
|
<string name="notifications_settings">Nastavení oznámení</string>
|
||||||
|
<string name="light_indicator">LED indikátor</string>
|
||||||
|
<string name="vibration">Vibrace</string>
|
||||||
|
<string name="favourites_categories">Oblíbené kategorie</string>
|
||||||
|
<string name="remove_category">Odstranit</string>
|
||||||
|
<string name="read_later">Přečíst později</string>
|
||||||
|
<string name="updates">Aktualizace</string>
|
||||||
|
<string name="size_s">Velikost: %s</string>
|
||||||
|
<string name="passwords_mismatch">Neodpovídající heslo</string>
|
||||||
|
<string name="track_sources">Hledat aktualizace</string>
|
||||||
|
<string name="protect_application_summary">Zeptat se na heslo při zapnutí Kotatsu</string>
|
||||||
|
<string name="yesterday">Včera</string>
|
||||||
|
<string name="right_to_left">Z prava doleva</string>
|
||||||
|
<string name="clear_feed">Vyčistit frontu</string>
|
||||||
|
<string name="create_category">Nová kategorie</string>
|
||||||
|
<string name="clear_cookies">Vyčistit cookies</string>
|
||||||
|
<string name="data_restored_with_errors">Data byla obnovena, ale vyskytly se chyby</string>
|
||||||
|
<string name="backup_information">Můžete vytvořit zálohu vaší historie a oblíbených a obnovit jej</string>
|
||||||
|
<string name="cookies_cleared">Všechny cookies byli odstraněny</string>
|
||||||
|
<string name="suggestions_updating">Aktualizují se návrhy</string>
|
||||||
|
<string name="tracker_warning">Některá zařízení mají jiné systémové chování, to může rozbít procesy na pozadí.</string>
|
||||||
|
<string name="read_more">Číst více</string>
|
||||||
|
<string name="about_app_translation_summary">Přeložte tuto aplikaci</string>
|
||||||
|
<string name="confirm">Potvrdit</string>
|
||||||
|
<string name="available_sources">Dostupné zdroje</string>
|
||||||
|
<string name="suggestions_excluded_genres">Vynechat žánry</string>
|
||||||
|
<string name="download_slowdown_summary">Pomohá předejít zablokování vaší IP adresy</string>
|
||||||
|
<string name="canceled">Zrušeno</string>
|
||||||
|
<string name="sync">Synchronizace</string>
|
||||||
|
<string name="show_notification_new_chapters_off">Nedostanete oznámení ale nové kapitoly budou v seznamu zvýrazněné</string>
|
||||||
|
<string name="bookmark_removed">Záložka odstraněna</string>
|
||||||
|
<string name="crash_text">Něco se pokazilo. Prosím odešlete hlášení o chybě vývojářům aby jste nám ji pomohli opravit.</string>
|
||||||
|
<string name="state_finished">Hotovo</string>
|
||||||
|
<string name="select_range">Vybrat rozsah</string>
|
||||||
|
<string name="last_2_hours">Poslední 2 hodiny</string>
|
||||||
|
<string name="no_bookmarks_summary">Můžete vytvořit záložku při čtení mangy</string>
|
||||||
|
<string name="random">Náhodné</string>
|
||||||
|
<string name="import_completed">Importování dokončeno</string>
|
||||||
|
<string name="import_completed_hint">Můžete odstranit originální soubor z uložiště abyste ušetřili místo</string>
|
||||||
|
<string name="folder_with_images_import_description">Můžete vybrat adresář s archivy nebo obrázky. Každý archiv (nebo podkategorie) bude znám jako kapitola.</string>
|
||||||
|
<string name="speed">Rychlost</string>
|
||||||
|
<string name="downloads_wifi_only">Stahovat pouze přes Wi-Fi</string>
|
||||||
|
<string name="sync_host_description">Můžete použít samostatně hostovaný synchronizační server nebo základní. Neměňte pokud si nejste jisti co děláte.</string>
|
||||||
|
<string name="cancel_all">Zrušit vše</string>
|
||||||
|
<string name="proxy">Proxy</string>
|
||||||
|
<string name="port">Port</string>
|
||||||
|
<string name="webtoon_zoom_summary">Povolit přiblížení v gestu ve webtoon režimu</string>
|
||||||
|
<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>
|
||||||
|
</resources>
|
||||||
@@ -1,82 +1,82 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="local_storage">Εσωτερικός χώρος</string>
|
<string name="local_storage">Εσωτερικός χώρος</string>
|
||||||
<string name="favourites">Αγαπημένα</string>
|
<string name="favourites">Αγαπημένα</string>
|
||||||
<string name="history">Ιστορικό</string>
|
<string name="history">Ιστορικό</string>
|
||||||
<string name="error_occurred">Προέκυψε σφάλμα</string>
|
<string name="error_occurred">Προέκυψε σφάλμα</string>
|
||||||
<string name="try_again">Επανάληψη</string>
|
<string name="try_again">Επανάληψη</string>
|
||||||
<string name="grid">Πλέγμα</string>
|
<string name="grid">Πλέγμα</string>
|
||||||
<string name="list_mode">Εμφάνιση ως λίστα</string>
|
<string name="list_mode">Εμφάνιση ως λίστα</string>
|
||||||
<string name="settings">Ρυθμίσεις</string>
|
<string name="settings">Ρυθμίσεις</string>
|
||||||
<string name="remote_sources">Απομακρυσμένες πηγές</string>
|
<string name="remote_sources">Πηγές μάνγκα</string>
|
||||||
<string name="computing_">Επεξεργασία…</string>
|
<string name="computing_">Επεξεργασία…</string>
|
||||||
<string name="close">Κλείσιμο</string>
|
<string name="close">Κλείσιμο</string>
|
||||||
<string name="clear_history">Εκκαθάριση ιστορικού</string>
|
<string name="clear_history">Εκκαθάριση ιστορικού</string>
|
||||||
<string name="nothing_found">Δεν βρέθηκε τίποτα</string>
|
<string name="nothing_found">Δεν βρέθηκε τίποτα</string>
|
||||||
<string name="history_is_empty">Κενό ιστορικό</string>
|
<string name="history_is_empty">Κενό ιστορικό</string>
|
||||||
<string name="read">Διάβασε</string>
|
<string name="read">Διάβασε</string>
|
||||||
<string name="add_to_favourites">Προσθήκη στα αγαπημένα</string>
|
<string name="add_to_favourites">Προσθήκη στα αγαπημένα</string>
|
||||||
<string name="add_new_category">Νέα κατηγορία</string>
|
<string name="add_new_category">Νέα κατηγορία</string>
|
||||||
<string name="save">Αποθήκευση</string>
|
<string name="save">Αποθήκευση</string>
|
||||||
<string name="share">Κοινοποιήση</string>
|
<string name="share">Κοινοποιήση</string>
|
||||||
<string name="create_shortcut">Δημιουργία συντόμευσης…</string>
|
<string name="create_shortcut">Δημιουργία συντόμευσης…</string>
|
||||||
<string name="share_s">Κοινοποίηση %s</string>
|
<string name="share_s">Κοινοποίηση %s</string>
|
||||||
<string name="search">Αναζήτηση</string>
|
<string name="search">Αναζήτηση</string>
|
||||||
<string name="search_manga">Αναζήτηση μάνγκα</string>
|
<string name="search_manga">Αναζήτηση μάνγκα</string>
|
||||||
<string name="manga_downloading_">Λήψη…</string>
|
<string name="manga_downloading_">Λήψη…</string>
|
||||||
<string name="download_complete">Κατεβασμένο</string>
|
<string name="download_complete">Κατεβασμένο</string>
|
||||||
<string name="downloads">Λήψεις</string>
|
<string name="downloads">Λήψεις</string>
|
||||||
<string name="updated">Ενημερωμένο</string>
|
<string name="updated">Ενημερωμένο</string>
|
||||||
<string name="newest">Νεότερο</string>
|
<string name="newest">Νεότερο</string>
|
||||||
<string name="by_rating">Βαθμολογία</string>
|
<string name="by_rating">Βαθμολογία</string>
|
||||||
<string name="filter">Φίλτρο</string>
|
<string name="filter">Φίλτρο</string>
|
||||||
<string name="dark">Σκοτεινό</string>
|
<string name="dark">Σκοτεινό</string>
|
||||||
<string name="automatic">Όπως στο σύστημα</string>
|
<string name="automatic">Όπως στο σύστημα</string>
|
||||||
<string name="clear">Εκκαθάριση</string>
|
<string name="clear">Εκκαθάριση</string>
|
||||||
<string name="text_clear_history_prompt">Να διαγράψετε μόνιμα όλο το ιστορικό ανάγνωσης;</string>
|
<string name="text_clear_history_prompt">Να διαγράψετε μόνιμα όλο το ιστορικό ανάγνωσης;</string>
|
||||||
<string name="remove">Διαγραφή</string>
|
<string name="remove">Διαγραφή</string>
|
||||||
<string name="save_page">Αποθήκευση σελίδας</string>
|
<string name="save_page">Αποθήκευση σελίδας</string>
|
||||||
<string name="page_saved">Αποθηκευμένα</string>
|
<string name="page_saved">Αποθηκευμένα</string>
|
||||||
<string name="share_image">Κοινή χρήση εικόνας</string>
|
<string name="share_image">Κοινή χρήση εικόνας</string>
|
||||||
<string name="_import">Εισαγωγή</string>
|
<string name="_import">Εισαγωγή</string>
|
||||||
<string name="delete">Διαγραφή</string>
|
<string name="delete">Διαγραφή</string>
|
||||||
<string name="text_file_not_supported">Επιλέξτε ένα αρχείο ZIP ή CBZ.</string>
|
<string name="text_file_not_supported">Επιλέξτε ένα αρχείο ZIP ή CBZ.</string>
|
||||||
<string name="no_description">Χωρίς περιγραφή</string>
|
<string name="no_description">Χωρίς περιγραφή</string>
|
||||||
<string name="clear_pages_cache">Εκκαθάριση μνήμης cache της σελίδας</string>
|
<string name="clear_pages_cache">Εκκαθάριση μνήμης cache της σελίδας</string>
|
||||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||||
<string name="standard">Τυπικό</string>
|
<string name="standard">Τυπικό</string>
|
||||||
<string name="webtoon">Μάνχγουα</string>
|
<string name="webtoon">Μάνχγουα</string>
|
||||||
<string name="search_on_s">Αναζήτηση στο %s</string>
|
<string name="search_on_s">Αναζήτηση στο %s</string>
|
||||||
<string name="delete_manga">Διαγραφή μάνγκα</string>
|
<string name="delete_manga">Διαγραφή μάνγκα</string>
|
||||||
<string name="text_delete_local_manga">Μόνιμη διαγραφή του \"%s\" από τη συσκευή;</string>
|
<string name="text_delete_local_manga">Μόνιμη διαγραφή του \"%s\" από τη συσκευή;</string>
|
||||||
<string name="reader_settings">Ρυθμίσεις λειτουργίας ανάγνωσης</string>
|
<string name="reader_settings">Ρυθμίσεις λειτουργίας ανάγνωσης</string>
|
||||||
<string name="switch_pages">Αλλαγή σελίδων</string>
|
<string name="switch_pages">Αλλαγή σελίδων</string>
|
||||||
<string name="network_error">Αδυναμία σύνδεσης στο ίντερνετ</string>
|
<string name="network_error">Σφάλμα δικτύου</string>
|
||||||
<string name="chapters">Κεφάλαια</string>
|
<string name="chapters">Κεφάλαια</string>
|
||||||
<string name="details">Πληροφορίες</string>
|
<string name="details">Πληροφορίες</string>
|
||||||
<string name="list">Λίστα</string>
|
<string name="list">Λίστα</string>
|
||||||
<string name="detailed_list">Λεπτομερής λίστα</string>
|
<string name="detailed_list">Λεπτομερής λίστα</string>
|
||||||
<string name="loading_">Φόρτωση…</string>
|
<string name="loading_">Φόρτωση…</string>
|
||||||
<string name="chapter_d_of_d">Κεφάλαιο%1$d από %2$d</string>
|
<string name="chapter_d_of_d">Κεφάλαιο%1$d από %2$d</string>
|
||||||
<string name="you_have_not_favourites_yet">Δεν υπάρχουν αγαπημένα</string>
|
<string name="you_have_not_favourites_yet">Δεν υπάρχουν αγαπημένα</string>
|
||||||
<string name="add">Προσθήκη</string>
|
<string name="add">Προσθήκη</string>
|
||||||
<string name="processing_">Επεξεργασία…</string>
|
<string name="processing_">Επεξεργασία…</string>
|
||||||
<string name="by_name">Όνομα</string>
|
<string name="by_name">Όνομα</string>
|
||||||
<string name="popular">Δημοφιλή</string>
|
<string name="popular">Δημοφιλή</string>
|
||||||
<string name="sort_order">Τρόπος Ταξινόμησης</string>
|
<string name="sort_order">Τρόπος Ταξινόμησης</string>
|
||||||
<string name="theme">Θέμα</string>
|
<string name="theme">Θέμα</string>
|
||||||
<string name="light">Φωτεινό</string>
|
<string name="light">Φωτεινό</string>
|
||||||
<string name="pages">Σελίδες</string>
|
<string name="pages">Σελίδες</string>
|
||||||
<string name="_s_deleted_from_local_storage">Το \"%s\" διαγράφηκε από τον τοπικό χώρο αποθήκευσης</string>
|
<string name="_s_deleted_from_local_storage">Το \"%s\" διαγράφηκε από τον τοπικό χώρο αποθήκευσης</string>
|
||||||
<string name="operation_not_supported">Αυτή η λειτουργία δεν υποστηρίζεται</string>
|
<string name="operation_not_supported">Αυτή η λειτουργία δεν υποστηρίζεται</string>
|
||||||
<string name="read_mode">Λειτουργία ανάγνωσης</string>
|
<string name="read_mode">Λειτουργία ανάγνωσης</string>
|
||||||
<string name="grid_size">Μέγεθος πλέγματος</string>
|
<string name="grid_size">Μέγεθος πλέγματος</string>
|
||||||
<string name="theme_name_mamimi">Μαμίμι</string>
|
<string name="theme_name_mamimi">Μαμίμι</string>
|
||||||
<string name="theme_name_kanade">Κανάντε</string>
|
<string name="theme_name_kanade">Κανάντε</string>
|
||||||
<string name="nothing_here">Δεν υπάρχει τίποτα εδώ</string>
|
<string name="nothing_here">Δεν υπάρχει τίποτα εδώ</string>
|
||||||
<string name="theme_name_miku">Μίκου</string>
|
<string name="theme_name_miku">Μίκου</string>
|
||||||
<string name="theme_name_asuka">Άσουκα</string>
|
<string name="theme_name_asuka">Άσουκα</string>
|
||||||
<string name="theme_name_mion">Μιόν</string>
|
<string name="theme_name_mion">Μιόν</string>
|
||||||
<string name="theme_name_rikka">Ρίκκα</string>
|
<string name="theme_name_rikka">Ρίκκα</string>
|
||||||
<string name="theme_name_sakura">Σακούρα</string>
|
<string name="theme_name_sakura">Σακούρα</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -431,4 +431,16 @@
|
|||||||
<string name="details_button_tip">Manten pulsado el botón Leer para ver más opciones</string>
|
<string name="details_button_tip">Manten pulsado el botón Leer para ver más opciones</string>
|
||||||
<string name="restore_summary">Restaurar una copia de seguridad creada anteriormente</string>
|
<string name="restore_summary">Restaurar una copia de seguridad creada anteriormente</string>
|
||||||
<string name="reader_info_bar_summary">Muestra la hora actual y el progreso de la lectura en la parte superior de la pantalla</string>
|
<string name="reader_info_bar_summary">Muestra la hora actual y el progreso de la lectura en la parte superior de la pantalla</string>
|
||||||
|
<string name="clear_source_cookies_summary">Borrar las cookies solo para el dominio especificado. En la mayoría de los casos invalidará la autorización</string>
|
||||||
|
<string name="download_option_whole_manga">El manga completo</string>
|
||||||
|
<string name="download_option_first_n_chapters">Primero %s</string>
|
||||||
|
<string name="download_option_all_unread">Todos los capítulos sin leer</string>
|
||||||
|
<string name="download_option_all_unread_b">Todos los capítulos sin leer (%s)</string>
|
||||||
|
<string name="download_option_manual_selection">Selección manual de los capítulos</string>
|
||||||
|
<string name="download_option_all_chapters">Todos los capítulos con traducción %s</string>
|
||||||
|
<string name="download_option_next_unread_n_chapters">Siguiente %s sin leer</string>
|
||||||
|
<string name="pick_custom_directory">Elegir un directorio personalizado</string>
|
||||||
|
<string name="no_access_to_file">No tienes acceso a este archivo o directorio</string>
|
||||||
|
<string name="custom_directory">Directorio personalizado</string>
|
||||||
|
<string name="local_manga_directories">Directorios locales del manga</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -431,4 +431,16 @@
|
|||||||
<string name="network">Réseau</string>
|
<string name="network">Réseau</string>
|
||||||
<string name="data_and_privacy">Données et confidentialité</string>
|
<string name="data_and_privacy">Données et confidentialité</string>
|
||||||
<string name="show_pages_numbers_summary">Afficher les numéros de page dans le coin inférieur</string>
|
<string name="show_pages_numbers_summary">Afficher les numéros de page dans le coin inférieur</string>
|
||||||
|
<string name="clear_source_cookies_summary">Effacer les cookies pour le domaine spécifié uniquement. Dans la plupart des cas, l\'autorisation sera invalidée</string>
|
||||||
|
<string name="download_option_first_n_chapters">%s premier(s)</string>
|
||||||
|
<string name="download_option_next_unread_n_chapters">%s prochain(s) non lu(s)</string>
|
||||||
|
<string name="download_option_all_unread_b">Tous les chapitres non lus (%s)</string>
|
||||||
|
<string name="custom_directory">Répertoire personnalisé</string>
|
||||||
|
<string name="download_option_all_chapters">Tous les chapitres avec traduction %s</string>
|
||||||
|
<string name="download_option_all_unread">Tous les chapitres non lus</string>
|
||||||
|
<string name="download_option_whole_manga">Tout le manga</string>
|
||||||
|
<string name="download_option_manual_selection">Sélection manuelle des chapitres</string>
|
||||||
|
<string name="no_access_to_file">Vous n\'avez pas accès à ce fichier ou répertoire</string>
|
||||||
|
<string name="pick_custom_directory">Choisir un répertoire personnalisé</string>
|
||||||
|
<string name="local_manga_directories">Annuaires locaux de mangas</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,19 +2,66 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="details">विवरण</string>
|
<string name="details">विवरण</string>
|
||||||
<string name="chapters">अध्याय</string>
|
<string name="chapters">अध्याय</string>
|
||||||
<string name="nothing_found">कुछ भी नहीं मिला</string>
|
<string name="nothing_found">कुछ नहीं मिला</string>
|
||||||
<string name="history_is_empty">अभी तक कोई इतिहास नहीं है</string>
|
<string name="history_is_empty">अभी तक कोई इतिहास नहीं है</string>
|
||||||
<string name="read">पढ़ना</string>
|
<string name="read">पढ़ें</string>
|
||||||
<string name="add_to_favourites">इसे पसंद करें</string>
|
<string name="add_to_favourites">इसे पसंद करें</string>
|
||||||
<string name="add">जोड़ना</string>
|
<string name="add">जोड़ो</string>
|
||||||
<string name="save">बचाना</string>
|
<string name="save">संचय करो</string>
|
||||||
<string name="newest">नवीनतम</string>
|
<string name="newest">नवीनतम</string>
|
||||||
<string name="light">रोशनी</string>
|
<string name="light">उजाला</string>
|
||||||
<string name="dark">अँधेरा</string>
|
<string name="dark">अँधेरा</string>
|
||||||
<string name="close">बंद करना</string>
|
<string name="close">बंद करो</string>
|
||||||
<string name="try_again">पुनः प्रयास करें</string>
|
<string name="try_again">पुनः प्रयास करें</string>
|
||||||
<string name="you_have_not_favourites_yet">अभी तक कोई पसंदीदा नहीं है</string>
|
<string name="you_have_not_favourites_yet">अभी तक कोई पसंदीदा नहीं है</string>
|
||||||
<string name="remove">निकालना</string>
|
<string name="remove">निकालो</string>
|
||||||
<string name="by_name">नाम</string>
|
<string name="by_name">नाम</string>
|
||||||
<string name="popular">लोकप्रिय</string>
|
<string name="popular">लोकप्रिय</string>
|
||||||
|
<string name="local_storage">स्थानीय स्टॉरेज</string>
|
||||||
|
<string name="error_occurred">कोई त्रुटि हुई</string>
|
||||||
|
<string name="network_error">नेटवर्क समस्या</string>
|
||||||
|
<string name="favourites">पसंदीदा</string>
|
||||||
|
<string name="detailed_list">विस्तृत सूची</string>
|
||||||
|
<string name="settings">सेटिंग्स्</string>
|
||||||
|
<string name="list_mode">सूची रुपी</string>
|
||||||
|
<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="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="by_rating">रेटिंग</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="text_file_sizes">B|kB|MB|GB|TB</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="history">इतिहास</string>
|
||||||
|
<string name="grid">ग्रिड</string>
|
||||||
|
<string name="loading_">लोड हो रहा है…</string>
|
||||||
|
<string name="text_file_not_supported">या तो झीप नहीं तो सीबीझेड फाईल को चुनें।</string>
|
||||||
|
<string name="updated">अपडेट हो गया</string>
|
||||||
|
<string name="_s_deleted_from_local_storage">\"%s\", स्थानीय स्टॉरेज में से मिट गईं</string>
|
||||||
|
<string name="text_clear_history_prompt">पढ़ने का इतिहास सदा के लिए मिटाए\?</string>
|
||||||
|
<string name="save_page">पन्ना संचय करो</string>
|
||||||
|
<string name="_import">आयात करें</string>
|
||||||
|
<string name="operation_not_supported">यह कार्य समर्थित नहीं है</string>
|
||||||
|
<string name="sort_order">छंटाई क्रम</string>
|
||||||
|
<string name="list">सूची</string>
|
||||||
|
<string name="filter">फिल्टर</string>
|
||||||
|
<string name="theme">थीम</string>
|
||||||
|
<string name="automatic">फोन जैसा</string>
|
||||||
|
<string name="pages">पन्ने</string>
|
||||||
|
<string name="no_description">कोई विवरण नहीं है</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -431,4 +431,16 @@
|
|||||||
<string name="webtoon_zoom_summary">Izinkan gerakan zoom in dalam mode webtoon</string>
|
<string name="webtoon_zoom_summary">Izinkan gerakan zoom in dalam mode webtoon</string>
|
||||||
<string name="reader_info_bar_summary">Tampilkan waktu saat ini dan kemajuan pembacaan di bagian atas layar</string>
|
<string name="reader_info_bar_summary">Tampilkan waktu saat ini dan kemajuan pembacaan di bagian atas layar</string>
|
||||||
<string name="pages_animation_summary">Animasikan peralihan halaman</string>
|
<string name="pages_animation_summary">Animasikan peralihan halaman</string>
|
||||||
|
<string name="clear_source_cookies_summary">Hapus cookie hanya untuk domain tertentu. Dalam kebanyakan kasus akan membatalkan otorisasi</string>
|
||||||
|
<string name="download_option_whole_manga">Seluruh manga</string>
|
||||||
|
<string name="download_option_first_n_chapters">Pertama %s</string>
|
||||||
|
<string name="download_option_all_unread">Semua bab yang belum dibaca</string>
|
||||||
|
<string name="download_option_all_unread_b">Semua bab yang belum dibaca (%s)</string>
|
||||||
|
<string name="download_option_all_chapters">Semua bab dengan terjemahan %s</string>
|
||||||
|
<string name="download_option_next_unread_n_chapters">Belum dibaca %s</string>
|
||||||
|
<string name="download_option_manual_selection">Pilih bab secara manual</string>
|
||||||
|
<string name="custom_directory">Direktori khusus</string>
|
||||||
|
<string name="pick_custom_directory">Pilih direktori khusus</string>
|
||||||
|
<string name="no_access_to_file">Anda tidak memiliki akses ke file atau direktori ini</string>
|
||||||
|
<string name="local_manga_directories">Direktori manga</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,372 +1,372 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="no_description">Nessuna descrizione</string>
|
<string name="no_description">Nessuna descrizione</string>
|
||||||
<string name="text_file_not_supported">File non valido. Sono supportati solo ZIP e CBZ.</string>
|
<string name="text_file_not_supported">File non valido. Sono supportati solo ZIP e CBZ.</string>
|
||||||
<string name="operation_not_supported">Questa operazione non è supportata</string>
|
<string name="operation_not_supported">Questa operazione non è supportata</string>
|
||||||
<string name="delete">Elimina</string>
|
<string name="delete">Elimina</string>
|
||||||
<string name="_import">Importa</string>
|
<string name="_import">Importa</string>
|
||||||
<string name="share_image">Condividi l\'immagine</string>
|
<string name="share_image">Condividi l\'immagine</string>
|
||||||
<string name="page_saved">Pagina salvata correttamente</string>
|
<string name="page_saved">Pagina salvata correttamente</string>
|
||||||
<string name="save_page">Salva la pagina</string>
|
<string name="save_page">Salva la pagina</string>
|
||||||
<string name="pages">Pagine</string>
|
<string name="pages">Pagine</string>
|
||||||
<string name="automatic">Automatico</string>
|
<string name="automatic">Automatico</string>
|
||||||
<string name="dark">Scuro</string>
|
<string name="dark">Scuro</string>
|
||||||
<string name="light">Chiaro</string>
|
<string name="light">Chiaro</string>
|
||||||
<string name="theme">Tema</string>
|
<string name="theme">Tema</string>
|
||||||
<string name="filter">Filtro</string>
|
<string name="filter">Filtro</string>
|
||||||
<string name="sort_order">Ordinamento</string>
|
<string name="sort_order">Ordinamento</string>
|
||||||
<string name="by_rating">Per valutazione</string>
|
<string name="by_rating">Per valutazione</string>
|
||||||
<string name="newest">Più recente</string>
|
<string name="newest">Più recente</string>
|
||||||
<string name="updated">Aggiornato</string>
|
<string name="updated">Aggiornato</string>
|
||||||
<string name="popular">Popolare</string>
|
<string name="popular">Popolare</string>
|
||||||
<string name="by_name">Per nome</string>
|
<string name="by_name">Per nome</string>
|
||||||
<string name="downloads">Scaricati</string>
|
<string name="downloads">Scaricati</string>
|
||||||
<string name="download_complete">Scaricamento finito</string>
|
<string name="download_complete">Scaricamento finito</string>
|
||||||
<string name="processing_">Elaborazione…</string>
|
<string name="processing_">Elaborazione…</string>
|
||||||
<string name="manga_downloading_">Scaricamento di manga…</string>
|
<string name="manga_downloading_">Scaricamento di manga…</string>
|
||||||
<string name="search_manga">Cerca manga</string>
|
<string name="search_manga">Cerca manga</string>
|
||||||
<string name="search">Cerca</string>
|
<string name="search">Cerca</string>
|
||||||
<string name="share_s">Condividi %s</string>
|
<string name="share_s">Condividi %s</string>
|
||||||
<string name="create_shortcut">Crea una scorciatoia…</string>
|
<string name="create_shortcut">Crea una scorciatoia…</string>
|
||||||
<string name="share">Condividi</string>
|
<string name="share">Condividi</string>
|
||||||
<string name="save">Salva</string>
|
<string name="save">Salva</string>
|
||||||
<string name="add">Aggiungi</string>
|
<string name="add">Aggiungi</string>
|
||||||
<string name="add_new_category">Aggiungi una nuova categoria</string>
|
<string name="add_new_category">Aggiungi una nuova categoria</string>
|
||||||
<string name="add_to_favourites">Aggiungi ai preferiti</string>
|
<string name="add_to_favourites">Aggiungi ai preferiti</string>
|
||||||
<string name="you_have_not_favourites_yet">Non hai ancora preferiti</string>
|
<string name="you_have_not_favourites_yet">Non hai ancora preferiti</string>
|
||||||
<string name="read">Leggi</string>
|
<string name="read">Leggi</string>
|
||||||
<string name="history_is_empty">La cronologia è vuota</string>
|
<string name="history_is_empty">La cronologia è vuota</string>
|
||||||
<string name="nothing_found">Niente di trovato</string>
|
<string name="nothing_found">Niente di trovato</string>
|
||||||
<string name="clear_history">Cancella la cronologia</string>
|
<string name="clear_history">Cancella la cronologia</string>
|
||||||
<string name="try_again">Riprova</string>
|
<string name="try_again">Riprova</string>
|
||||||
<string name="text_clear_updates_feed_prompt">Tutta la cronologia degli aggiornamenti sarà cancellata e questa azione non può essere annullata. Sei sicuro/a\?</string>
|
<string name="text_clear_updates_feed_prompt">Tutta la cronologia degli aggiornamenti sarà cancellata e questa azione non può essere annullata. Sei sicuro/a\?</string>
|
||||||
<string name="close">Chiudi</string>
|
<string name="close">Chiudi</string>
|
||||||
<string name="chapter_d_of_d">Capitolo %1$d di %2$d</string>
|
<string name="chapter_d_of_d">Capitolo %1$d di %2$d</string>
|
||||||
<string name="loading_">Caricamento…</string>
|
<string name="loading_">Caricamento…</string>
|
||||||
<string name="remote_sources">Fonti remote</string>
|
<string name="remote_sources">Fonti manga</string>
|
||||||
<string name="settings">Impostazioni</string>
|
<string name="settings">Impostazioni</string>
|
||||||
<string name="list_mode">Modalità elenco</string>
|
<string name="list_mode">Modalità elenco</string>
|
||||||
<string name="grid">Griglia</string>
|
<string name="grid">Griglia</string>
|
||||||
<string name="detailed_list">Elenco dettagliato</string>
|
<string name="detailed_list">Elenco dettagliato</string>
|
||||||
<string name="text_delete_local_manga">Vuoi davvero eliminare «%s» dalla memoria locale del tuo dispositivo\?</string>
|
<string name="text_delete_local_manga">Vuoi davvero eliminare «%s» dalla memoria locale del tuo dispositivo\?</string>
|
||||||
<string name="_s_deleted_from_local_storage">«%s» eliminato dall\'archiviazione locale</string>
|
<string name="_s_deleted_from_local_storage">«%s» eliminato dall\'archiviazione locale</string>
|
||||||
<string name="list">Lista</string>
|
<string name="list">Lista</string>
|
||||||
<string name="chapters">Capitoli</string>
|
<string name="chapters">Capitoli</string>
|
||||||
<string name="details">Dettagli</string>
|
<string name="details">Dettagli</string>
|
||||||
<string name="network_error">Errore di connessione</string>
|
<string name="network_error">Errore di connessione</string>
|
||||||
<string name="error_occurred">Si è verificato un errore</string>
|
<string name="error_occurred">Si è verificato un errore</string>
|
||||||
<string name="history">Cronologia</string>
|
<string name="history">Cronologia</string>
|
||||||
<string name="favourites">Preferiti</string>
|
<string name="favourites">Preferiti</string>
|
||||||
<string name="local_storage">Archiviazione locale</string>
|
<string name="local_storage">Archiviazione locale</string>
|
||||||
<string name="read_more">Leggi di più</string>
|
<string name="read_more">Leggi di più</string>
|
||||||
<string name="welcome">Benvenuto/a</string>
|
<string name="welcome">Benvenuto/a</string>
|
||||||
<string name="text_clear_search_history_prompt">Vuoi davvero rimuovere tutte le ricerche recenti\?</string>
|
<string name="text_clear_search_history_prompt">Vuoi davvero rimuovere tutte le ricerche recenti\?</string>
|
||||||
<string name="password_length_hint">La password deve essere di almeno 4 caratteri</string>
|
<string name="password_length_hint">La password deve essere di almeno 4 caratteri</string>
|
||||||
<string name="confirm">Conferma</string>
|
<string name="confirm">Conferma</string>
|
||||||
<string name="protect_application_subtitle">Inserisci la password che sarà richiesta all\'avvio dell\'applicazione</string>
|
<string name="protect_application_subtitle">Inserisci la password che sarà richiesta all\'avvio dell\'applicazione</string>
|
||||||
<string name="next">Prossimo</string>
|
<string name="next">Prossimo</string>
|
||||||
<string name="sign_in">Accedi</string>
|
<string name="sign_in">Accedi</string>
|
||||||
<string name="clear_feed">Cancella il flusso</string>
|
<string name="clear_feed">Cancella il flusso</string>
|
||||||
<string name="silent">Silenzioso</string>
|
<string name="silent">Silenzioso</string>
|
||||||
<string name="tap_to_try_again">Tocca per riprovare</string>
|
<string name="tap_to_try_again">Tocca per riprovare</string>
|
||||||
<string name="today">Oggi</string>
|
<string name="today">Oggi</string>
|
||||||
<string name="group">Gruppo</string>
|
<string name="group">Gruppo</string>
|
||||||
<string name="long_ago">Molto tempo fa</string>
|
<string name="long_ago">Molto tempo fa</string>
|
||||||
<string name="yesterday">Ieri</string>
|
<string name="yesterday">Ieri</string>
|
||||||
<string name="just_now">Proprio ora</string>
|
<string name="just_now">Proprio ora</string>
|
||||||
<string name="file_not_found">File non trovato</string>
|
<string name="file_not_found">File non trovato</string>
|
||||||
<string name="preparing_">Preparazione…</string>
|
<string name="preparing_">Preparazione…</string>
|
||||||
<string name="data_restored">Dati ripristinati</string>
|
<string name="data_restored">Dati ripristinati</string>
|
||||||
<string name="restore_backup">Ripristina da un backup</string>
|
<string name="restore_backup">Ripristina da un backup</string>
|
||||||
<string name="create_backup">Crea un backup dei dati</string>
|
<string name="create_backup">Crea un backup dei dati</string>
|
||||||
<string name="backup_restore">Backup e ripristino</string>
|
<string name="backup_restore">Backup e ripristino</string>
|
||||||
<string name="black_dark_theme_summary">Utile per gli schermi AMOLED</string>
|
<string name="black_dark_theme_summary">Utile per gli schermi AMOLED</string>
|
||||||
<string name="black_dark_theme">Tema nero scuro</string>
|
<string name="black_dark_theme">Tema nero scuro</string>
|
||||||
<string name="create_category">Nuova categoria</string>
|
<string name="create_category">Nuova categoria</string>
|
||||||
<string name="right_to_left">Da destra a sinistra</string>
|
<string name="right_to_left">Da destra a sinistra</string>
|
||||||
<string name="no_update_available">Nessun aggiornamento disponibile</string>
|
<string name="no_update_available">Nessun aggiornamento disponibile</string>
|
||||||
<string name="check_for_updates">Controlla gli aggiornamenti</string>
|
<string name="check_for_updates">Controlla gli aggiornamenti</string>
|
||||||
<string name="app_version">Versione %s</string>
|
<string name="app_version">Versione %s</string>
|
||||||
<string name="about">Informazioni</string>
|
<string name="about">Informazioni</string>
|
||||||
<string name="passwords_mismatch">Le password non corrispondono</string>
|
<string name="passwords_mismatch">Le password non corrispondono</string>
|
||||||
<string name="repeat_password">Ripeti la password</string>
|
<string name="repeat_password">Ripeti la password</string>
|
||||||
<string name="protect_application_summary">Chiedi la password all\'avvio dell\'applicazione</string>
|
<string name="protect_application_summary">Chiedi la password all\'avvio dell\'applicazione</string>
|
||||||
<string name="protect_application">Proteggi l\'applicazione</string>
|
<string name="protect_application">Proteggi l\'applicazione</string>
|
||||||
<string name="wrong_password">Password sbagliata</string>
|
<string name="wrong_password">Password sbagliata</string>
|
||||||
<string name="enter_password">Inserisci la password</string>
|
<string name="enter_password">Inserisci la password</string>
|
||||||
<string name="dont_check">Non controllare</string>
|
<string name="dont_check">Non controllare</string>
|
||||||
<string name="track_sources">Controlla gli aggiornamenti per il manga</string>
|
<string name="track_sources">Controlla gli aggiornamenti per il manga</string>
|
||||||
<string name="feed_will_update_soon">L\'aggiornamento del flusso inizierà presto</string>
|
<string name="feed_will_update_soon">L\'aggiornamento del flusso inizierà presto</string>
|
||||||
<string name="update">Aggiorna</string>
|
<string name="update">Aggiorna</string>
|
||||||
<string name="rotate_screen">Ruota lo schermo</string>
|
<string name="rotate_screen">Ruota lo schermo</string>
|
||||||
<string name="updates_feed_cleared">Flusso degli aggiornamenti cancellato</string>
|
<string name="updates_feed_cleared">Flusso degli aggiornamenti cancellato</string>
|
||||||
<string name="clear_updates_feed">Cancella il flusso degli aggiornamenti</string>
|
<string name="clear_updates_feed">Cancella il flusso degli aggiornamenti</string>
|
||||||
<string name="size_s">Dimensioni: %s</string>
|
<string name="size_s">Dimensioni: %s</string>
|
||||||
<string name="new_version_s">Nuova versione: %s</string>
|
<string name="new_version_s">Nuova versione: %s</string>
|
||||||
<string name="search_results">Risultati della ricerca</string>
|
<string name="search_results">Risultati della ricerca</string>
|
||||||
<string name="text_feed_holder">Qui potrai vedere i nuovi capitoli del manga che stai leggendo</string>
|
<string name="text_feed_holder">Qui potrai vedere i nuovi capitoli del manga che stai leggendo</string>
|
||||||
<string name="updates">Aggiornamenti</string>
|
<string name="updates">Aggiornamenti</string>
|
||||||
<string name="read_later">Leggi più tardi</string>
|
<string name="read_later">Leggi più tardi</string>
|
||||||
<string name="favourites_category_empty">Questa categoria è vuota</string>
|
<string name="favourites_category_empty">Questa categoria è vuota</string>
|
||||||
<string name="all_favourites">Tutti i preferiti</string>
|
<string name="all_favourites">Tutti i preferiti</string>
|
||||||
<string name="done">Finito</string>
|
<string name="done">Finito</string>
|
||||||
<string name="other_storage">Altro spazio di archiviazione</string>
|
<string name="other_storage">Altro spazio di archiviazione</string>
|
||||||
<string name="cannot_find_available_storage">Impossibile trovare uno spazio di archiviazione disponibile</string>
|
<string name="cannot_find_available_storage">Impossibile trovare uno spazio di archiviazione disponibile</string>
|
||||||
<string name="not_available">Non disponibile</string>
|
<string name="not_available">Non disponibile</string>
|
||||||
<string name="manga_save_location">Posizione per lo scaricamento dei manga</string>
|
<string name="manga_save_location">Posizione per lo scaricamento dei manga</string>
|
||||||
<string name="pages_animation">Animazione delle pagine</string>
|
<string name="pages_animation">Animazione delle pagine</string>
|
||||||
<string name="recent_manga">Manga recenti</string>
|
<string name="recent_manga">Manga recenti</string>
|
||||||
<string name="manga_shelf">Scaffale da manga</string>
|
<string name="manga_shelf">Scaffale da manga</string>
|
||||||
<string name="text_local_holder_secondary">Puoi salvarlo da fonti in linea o importarlo da un file.</string>
|
<string name="text_local_holder_secondary">Puoi salvarlo da fonti in linea o importarlo da un file.</string>
|
||||||
<string name="text_local_holder_primary">Non hai ancora salvato nessun manga</string>
|
<string name="text_local_holder_primary">Non hai ancora salvato nessun manga</string>
|
||||||
<string name="text_history_holder_secondary">Puoi trovare cosa leggere nel menù laterale.</string>
|
<string name="text_history_holder_secondary">Puoi trovare cosa leggere nel menù laterale.</string>
|
||||||
<string name="text_history_holder_primary">I manga che stai leggendo saranno visualizzati qui</string>
|
<string name="text_history_holder_primary">I manga che stai leggendo saranno visualizzati qui</string>
|
||||||
<string name="text_search_holder_secondary">Prova a riformulare la domanda.</string>
|
<string name="text_search_holder_secondary">Prova a riformulare la domanda.</string>
|
||||||
<string name="text_empty_holder_primary">È un po\' vuoto qui…</string>
|
<string name="text_empty_holder_primary">È un po\' vuoto qui…</string>
|
||||||
<string name="remove_category">Rimuovi la categoria</string>
|
<string name="remove_category">Rimuovi la categoria</string>
|
||||||
<string name="favourites_categories">Categorie di preferiti</string>
|
<string name="favourites_categories">Categorie di preferiti</string>
|
||||||
<string name="vibration">Vibrazione</string>
|
<string name="vibration">Vibrazione</string>
|
||||||
<string name="light_indicator">Indicatore luminoso</string>
|
<string name="light_indicator">Indicatore luminoso</string>
|
||||||
<string name="notification_sound">Suono di notifica</string>
|
<string name="notification_sound">Suono di notifica</string>
|
||||||
<string name="notifications_settings">Impostazioni notifiche</string>
|
<string name="notifications_settings">Impostazioni notifiche</string>
|
||||||
<string name="download">Scarica</string>
|
<string name="download">Scarica</string>
|
||||||
<string name="new_chapters">Nuovi capitoli</string>
|
<string name="new_chapters">Nuovi capitoli</string>
|
||||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Abilitato %1$d di %2$d</string>
|
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Abilitato %1$d di %2$d</string>
|
||||||
<string name="notifications">Notifiche</string>
|
<string name="notifications">Notifiche</string>
|
||||||
<string name="save_manga">Salva il manga</string>
|
<string name="save_manga">Salva il manga</string>
|
||||||
<string name="large_manga_save_confirm">Questo manga ha %s. Vuoi salvare tutto\?</string>
|
<string name="large_manga_save_confirm">Questo manga ha %s. Vuoi salvare tutto\?</string>
|
||||||
<string name="open_in_browser">Apri nel browser</string>
|
<string name="open_in_browser">Apri nel browser</string>
|
||||||
<string name="app_update_available">Un aggiornamento per l\'applicazione è disponibile</string>
|
<string name="app_update_available">Un aggiornamento per l\'applicazione è disponibile</string>
|
||||||
<string name="domain">Dominio</string>
|
<string name="domain">Dominio</string>
|
||||||
<string name="external_storage">Archiviazione esterna</string>
|
<string name="external_storage">Archiviazione esterna</string>
|
||||||
<string name="internal_storage">Archiviazione interna</string>
|
<string name="internal_storage">Archiviazione interna</string>
|
||||||
<string name="gestures_only">Solo gesti</string>
|
<string name="gestures_only">Solo gesti</string>
|
||||||
<string name="search_history_cleared">Storia della ricerca cancellata</string>
|
<string name="search_history_cleared">Storia della ricerca cancellata</string>
|
||||||
<string name="clear_search_history">Cancella la cronologia delle ricerche</string>
|
<string name="clear_search_history">Cancella la cronologia delle ricerche</string>
|
||||||
<string name="clear_thumbs_cache">Svuota la cache delle miniature</string>
|
<string name="clear_thumbs_cache">Svuota la cache delle miniature</string>
|
||||||
<string name="error">Errore</string>
|
<string name="error">Errore</string>
|
||||||
<string name="_continue">Continua</string>
|
<string name="_continue">Continua</string>
|
||||||
<string name="volume_buttons">Pulsanti volume</string>
|
<string name="volume_buttons">Pulsanti volume</string>
|
||||||
<string name="taps_on_edges">Tocchi sui bordi</string>
|
<string name="taps_on_edges">Tocchi sui bordi</string>
|
||||||
<string name="switch_pages">Cambia pagina</string>
|
<string name="switch_pages">Cambia pagina</string>
|
||||||
<string name="reader_settings">Impostazioni del lettore</string>
|
<string name="reader_settings">Impostazioni del lettore</string>
|
||||||
<string name="delete_manga">Elimina il manga</string>
|
<string name="delete_manga">Elimina il manga</string>
|
||||||
<string name="search_on_s">Ricerca su %s</string>
|
<string name="search_on_s">Ricerca su %s</string>
|
||||||
<string name="grid_size">Dimensione della griglia</string>
|
<string name="grid_size">Dimensione della griglia</string>
|
||||||
<string name="read_mode">Modalità lettura</string>
|
<string name="read_mode">Modalità lettura</string>
|
||||||
<string name="webtoon">Webtoon</string>
|
<string name="webtoon">Webtoon</string>
|
||||||
<string name="standard">Standard</string>
|
<string name="standard">Standard</string>
|
||||||
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
|
||||||
<string name="clear_pages_cache">Cancella la cache delle pagine</string>
|
<string name="clear_pages_cache">Cancella la cache delle pagine</string>
|
||||||
<string name="remove">Rimuovi</string>
|
<string name="remove">Rimuovi</string>
|
||||||
<string name="text_clear_history_prompt">Vuoi davvero cancellare tutta la tua cronologia di lettura\?</string>
|
<string name="text_clear_history_prompt">Vuoi davvero cancellare tutta la tua cronologia di lettura\?</string>
|
||||||
<string name="clear">Cancella</string>
|
<string name="clear">Cancella</string>
|
||||||
<string name="tracker_warning">Alcuni produttori possono cambiare il comportamento del sistema, che può interrompere le attività nello sfondo.</string>
|
<string name="tracker_warning">Alcuni produttori possono cambiare il comportamento del sistema, che può interrompere le attività nello sfondo.</string>
|
||||||
<string name="backup_saved">Backup salvato con successo</string>
|
<string name="backup_saved">Backup salvato con successo</string>
|
||||||
<string name="default_s">Predefinito: %s</string>
|
<string name="default_s">Predefinito: %s</string>
|
||||||
<string name="auth_required">Devi autorizzare la visualizzazione di questo contenuto</string>
|
<string name="auth_required">Devi autorizzare la visualizzazione di questo contenuto</string>
|
||||||
<string name="reverse">Inverti</string>
|
<string name="reverse">Inverti</string>
|
||||||
<string name="check_for_new_chapters">Controllo dei nuovi capitoli</string>
|
<string name="check_for_new_chapters">Controllo dei nuovi capitoli</string>
|
||||||
<string name="cookies_cleared">Tutti i cookie sono stati rimossi</string>
|
<string name="cookies_cleared">Tutti i cookie sono stati rimossi</string>
|
||||||
<string name="clear_cookies">Cancella i cookie</string>
|
<string name="clear_cookies">Cancella i cookie</string>
|
||||||
<string name="captcha_solve">Risolvi</string>
|
<string name="captcha_solve">Risolvi</string>
|
||||||
<string name="captcha_required">CAPTCHA è richiesto</string>
|
<string name="captcha_required">CAPTCHA è richiesto</string>
|
||||||
<string name="reader_mode_hint">La configurazione scelta sarà ricordata per questo manga</string>
|
<string name="reader_mode_hint">La configurazione scelta sarà ricordata per questo manga</string>
|
||||||
<string name="backup_information">Puoi creare un backup della tua cronologia e dei tuoi preferiti e ripristinarlo</string>
|
<string name="backup_information">Puoi creare un backup della tua cronologia e dei tuoi preferiti e ripristinarlo</string>
|
||||||
<string name="data_restored_with_errors">I dati sono ripristinati, ma ci sono errori</string>
|
<string name="data_restored_with_errors">I dati sono ripristinati, ma ci sono errori</string>
|
||||||
<string name="data_restored_success">Tutti i dati ripristinati con successo</string>
|
<string name="data_restored_success">Tutti i dati ripristinati con successo</string>
|
||||||
<string name="zoom_mode_keep_start">Tieni all\'inizio</string>
|
<string name="zoom_mode_keep_start">Tieni all\'inizio</string>
|
||||||
<string name="zoom_mode_fit_height">Adatta all\'altezza</string>
|
<string name="zoom_mode_fit_height">Adatta all\'altezza</string>
|
||||||
<string name="zoom_mode_fit_center">Adatta al centro</string>
|
<string name="zoom_mode_fit_center">Adatta al centro</string>
|
||||||
<string name="zoom_mode_fit_width">Adatta alla larghezza</string>
|
<string name="zoom_mode_fit_width">Adatta alla larghezza</string>
|
||||||
<string name="scale_mode">Modalità scala</string>
|
<string name="scale_mode">Modalità scala</string>
|
||||||
<string name="chapter_is_missing">Capitolo mancante</string>
|
<string name="chapter_is_missing">Capitolo mancante</string>
|
||||||
<string name="queued">In coda</string>
|
<string name="queued">In coda</string>
|
||||||
<string name="about_app_translation">Traduzione</string>
|
<string name="about_app_translation">Traduzione</string>
|
||||||
<string name="about_app_translation_summary">Traduci questa applicazione</string>
|
<string name="about_app_translation_summary">Traduci questa applicazione</string>
|
||||||
<string name="genres">Generi</string>
|
<string name="genres">Generi</string>
|
||||||
<string name="text_clear_cookies_prompt">Sarai disconnesso/a da tutte le fonti in cui sei autorizzato/a</string>
|
<string name="text_clear_cookies_prompt">Sarai disconnesso/a da tutte le fonti in cui sei autorizzato/a</string>
|
||||||
<string name="auth_not_supported_by">L\'autorizzazione su %s non è supportata</string>
|
<string name="auth_not_supported_by">L\'autorizzazione su %s non è supportata</string>
|
||||||
<string name="auth_complete">Autorizzazione completa</string>
|
<string name="auth_complete">Autorizzazione completa</string>
|
||||||
<string name="state_finished">Finito</string>
|
<string name="state_finished">Finito</string>
|
||||||
<string name="state_ongoing">In corso</string>
|
<string name="state_ongoing">In corso</string>
|
||||||
<string name="system_default">Predefinito</string>
|
<string name="system_default">Predefinito</string>
|
||||||
<string name="exclude_nsfw_from_history">Escludi i manga NSFW dalla storia</string>
|
<string name="exclude_nsfw_from_history">Escludi i manga NSFW dalla storia</string>
|
||||||
<string name="show_pages_numbers">Mostra i numeri delle pagine</string>
|
<string name="show_pages_numbers">Mostra i numeri delle pagine</string>
|
||||||
<string name="enabled_sources">Fonti abilitate</string>
|
<string name="enabled_sources">Fonti abilitate</string>
|
||||||
<string name="available_sources">Fonti disponibili</string>
|
<string name="available_sources">Fonti disponibili</string>
|
||||||
<string name="computing_">Calcolo…</string>
|
<string name="computing_">Calcolo…</string>
|
||||||
<string name="screenshots_policy">Politica sulle schermate</string>
|
<string name="screenshots_policy">Politica sulle schermate</string>
|
||||||
<string name="screenshots_allow">Permetti</string>
|
<string name="screenshots_allow">Permetti</string>
|
||||||
<string name="screenshots_block_nsfw">Blocca per NSFW</string>
|
<string name="screenshots_block_nsfw">Blocca per NSFW</string>
|
||||||
<string name="screenshots_block_all">Blocca sempre</string>
|
<string name="screenshots_block_all">Blocca sempre</string>
|
||||||
<string name="filter_load_error">Impossibile caricare la lista dei generi</string>
|
<string name="filter_load_error">Impossibile caricare la lista dei generi</string>
|
||||||
<string name="suggestions_enable">Abilita i suggerimenti</string>
|
<string name="suggestions_enable">Abilita i suggerimenti</string>
|
||||||
<string name="suggestions_summary">Suggerisci manga in base alle tue preferenze</string>
|
<string name="suggestions_summary">Suggerisci manga in base alle tue preferenze</string>
|
||||||
<string name="suggestions_info">Tutti i dati sono analizzati localmente su questo dispositivo. Non c\'è trasferimento dei suoi dati personali a nessun servizio</string>
|
<string name="suggestions_info">Tutti i dati vengono analizzati solo localmente su questo dispositivo e mai inviati da nessuna parte.</string>
|
||||||
<string name="text_suggestion_holder">Inizia a leggere manga e riceverai suggerimenti personalizzati</string>
|
<string name="text_suggestion_holder">Inizia a leggere manga e riceverai suggerimenti personalizzati</string>
|
||||||
<string name="suggestions">Suggerimenti</string>
|
<string name="suggestions">Suggerimenti</string>
|
||||||
<string name="enabled">Abilitato</string>
|
<string name="enabled">Abilitato</string>
|
||||||
<string name="disabled">Disabilitato</string>
|
<string name="disabled">Disabilitato</string>
|
||||||
<string name="exclude_nsfw_from_suggestions">Non suggerire manga NSFW</string>
|
<string name="exclude_nsfw_from_suggestions">Non suggerire manga NSFW</string>
|
||||||
<string name="reset_filter">Ripristina il filtro</string>
|
<string name="reset_filter">Ripristina il filtro</string>
|
||||||
<string name="onboard_text">Seleziona le lingue in cui vuoi leggere i manga. Puoi cambiarla in seguito nelle impostazioni.</string>
|
<string name="onboard_text">Seleziona le lingue in cui vuoi leggere i manga. Puoi cambiarla in seguito nelle impostazioni.</string>
|
||||||
<string name="never">Mai</string>
|
<string name="never">Mai</string>
|
||||||
<string name="preload_pages">Precarica le pagine</string>
|
<string name="preload_pages">Precarica le pagine</string>
|
||||||
<string name="only_using_wifi">Solo usando il Wi-Fi</string>
|
<string name="only_using_wifi">Solo usando il Wi-Fi</string>
|
||||||
<string name="always">Sempre</string>
|
<string name="always">Sempre</string>
|
||||||
<string name="logged_in_as">Registrato come %s</string>
|
<string name="logged_in_as">Registrato come %s</string>
|
||||||
<string name="nsfw">18+</string>
|
<string name="nsfw">18+</string>
|
||||||
<string name="various_languages">Varie lingue</string>
|
<string name="various_languages">Varie lingue</string>
|
||||||
<string name="search_chapters">Trova un capitolo</string>
|
<string name="search_chapters">Trova un capitolo</string>
|
||||||
<string name="chapters_empty">Nessun capitolo in questo manga</string>
|
<string name="chapters_empty">Nessun capitolo in questo manga</string>
|
||||||
<string name="percent_string_pattern">%1$s%%</string>
|
<string name="percent_string_pattern">%1$s%%</string>
|
||||||
<string name="appearance">Aspetto</string>
|
<string name="appearance">Aspetto</string>
|
||||||
<string name="suggestions_updating">Aggiornamento dei suggerimenti</string>
|
<string name="suggestions_updating">Aggiornamento dei suggerimenti</string>
|
||||||
<string name="suggestions_excluded_genres_summary">Specifica i generi che non vuoi vedere nei suggerimenti</string>
|
<string name="suggestions_excluded_genres_summary">Specifica i generi che non vuoi vedere nei suggerimenti</string>
|
||||||
<string name="suggestions_excluded_genres">Escludi generi</string>
|
<string name="suggestions_excluded_genres">Escludi generi</string>
|
||||||
<string name="removal_completed">Rimozione completata</string>
|
<string name="removal_completed">Rimozione completata</string>
|
||||||
<string name="text_delete_local_manga_batch">Eliminare gli elementi selezionati dal dispositivo in modo permanente\?</string>
|
<string name="text_delete_local_manga_batch">Eliminare gli elementi selezionati dal dispositivo in modo permanente\?</string>
|
||||||
<string name="download_slowdown">Rallentamento dello scaricamento</string>
|
<string name="download_slowdown">Rallentamento dello scaricamento</string>
|
||||||
<string name="local_manga_processing">Elaborazione dei manga salvati</string>
|
<string name="local_manga_processing">Elaborazione dei manga salvati</string>
|
||||||
<string name="chapters_will_removed_background">I capitoli saranno rimossi in sfondo. Può richiedere un po\' di tempo</string>
|
<string name="chapters_will_removed_background">I capitoli verranno rimossi in background</string>
|
||||||
<string name="download_slowdown_summary">Aiuta ad evitare il blocco del tuo indirizzo IP</string>
|
<string name="download_slowdown_summary">Aiuta ad evitare il blocco del tuo indirizzo IP</string>
|
||||||
<string name="hide">Nascondi</string>
|
<string name="hide">Nascondi</string>
|
||||||
<string name="new_sources_text">Sono disponibili nuove fonti di manga</string>
|
<string name="new_sources_text">Sono disponibili nuove fonti di manga</string>
|
||||||
<string name="show_notification_new_chapters_on">Riceverai notifiche sugli aggiornamenti del manga che stai leggendo</string>
|
<string name="show_notification_new_chapters_on">Riceverai notifiche sugli aggiornamenti del manga che stai leggendo</string>
|
||||||
<string name="notifications_enable">Abilita le notifiche</string>
|
<string name="notifications_enable">Abilita le notifiche</string>
|
||||||
<string name="show_notification_new_chapters_off">Non riceverai notifiche ma i nuovi capitoli saranno evidenziati nelle liste</string>
|
<string name="show_notification_new_chapters_off">Non riceverai notifiche ma i nuovi capitoli saranno evidenziati nelle liste</string>
|
||||||
<string name="empty_favourite_categories">Nessuna categoria preferita</string>
|
<string name="empty_favourite_categories">Nessuna categoria preferita</string>
|
||||||
<string name="name">Nome</string>
|
<string name="name">Nome</string>
|
||||||
<string name="edit">Modifica</string>
|
<string name="edit">Modifica</string>
|
||||||
<string name="edit_category">Modifica la categoria</string>
|
<string name="edit_category">Modifica la categoria</string>
|
||||||
<string name="check_new_chapters_title">Controlla i nuovi capitoli e notificarli</string>
|
<string name="check_new_chapters_title">Controlla i nuovi capitoli e notificarli</string>
|
||||||
<string name="bookmarks">Segnalibri</string>
|
<string name="bookmarks">Segnalibri</string>
|
||||||
<string name="removed_from_history">Rimosso dalla cronologia</string>
|
<string name="removed_from_history">Rimosso dalla cronologia</string>
|
||||||
<string name="bookmark_remove">Rimuovi il segnalibro</string>
|
<string name="bookmark_remove">Rimuovi il segnalibro</string>
|
||||||
<string name="bookmark_add">Aggiungi un segnalibro</string>
|
<string name="bookmark_add">Aggiungi un segnalibro</string>
|
||||||
<string name="bookmark_removed">Segnalibro rimosso</string>
|
<string name="bookmark_removed">Segnalibro rimosso</string>
|
||||||
<string name="bookmark_added">Segnalibro aggiunto</string>
|
<string name="bookmark_added">Segnalibro aggiunto</string>
|
||||||
<string name="undo">Annulla</string>
|
<string name="undo">Annulla</string>
|
||||||
<string name="default_mode">Modalità predefinita</string>
|
<string name="default_mode">Modalità predefinita</string>
|
||||||
<string name="detect_reader_mode">Modalità di lettura a rilevamento automatico</string>
|
<string name="detect_reader_mode">Modalità di lettura a rilevamento automatico</string>
|
||||||
<string name="dns_over_https">DNS su HTTPS</string>
|
<string name="dns_over_https">DNS su HTTPS</string>
|
||||||
<string name="detect_reader_mode_summary">Rileva automaticamente se il manga è un webtoon</string>
|
<string name="detect_reader_mode_summary">Rileva automaticamente se il manga è un webtoon</string>
|
||||||
<string name="disable_battery_optimization">Disattiva l\'ottimizzazione della batteria</string>
|
<string name="disable_battery_optimization">Disattiva l\'ottimizzazione della batteria</string>
|
||||||
<string name="disable_battery_optimization_summary">Contribuisce ai controlli di aggiornamento in sfondo</string>
|
<string name="disable_battery_optimization_summary">Contribuisce ai controlli di aggiornamento in sfondo</string>
|
||||||
<string name="crash_text">Qualcosa è andato storto. Si prega di inviare una segnalazione di bug agli sviluppatori per aiutarci a risolvere il problema.</string>
|
<string name="crash_text">Qualcosa è andato storto. Si prega di inviare una segnalazione di bug agli sviluppatori per aiutarci a risolvere il problema.</string>
|
||||||
<string name="send">Invia</string>
|
<string name="send">Invia</string>
|
||||||
<string name="disable_all">Disabilita tutto</string>
|
<string name="disable_all">Disabilita tutto</string>
|
||||||
<string name="use_fingerprint">Usa le impronte digitali se disponibili</string>
|
<string name="use_fingerprint">Usa le impronte digitali se disponibili</string>
|
||||||
<string name="appwidget_shelf_description">Manga dai preferiti</string>
|
<string name="appwidget_shelf_description">Manga dai preferiti</string>
|
||||||
<string name="appwidget_recent_description">I manga letti di recente</string>
|
<string name="appwidget_recent_description">I manga letti di recente</string>
|
||||||
<string name="report">Segnala</string>
|
<string name="report">Segnala</string>
|
||||||
<string name="tracking">Monitoraggio</string>
|
<string name="tracking">Monitoraggio</string>
|
||||||
<string name="status_reading">Lettura</string>
|
<string name="status_reading">Lettura</string>
|
||||||
<string name="status_re_reading">Rilettura</string>
|
<string name="status_re_reading">Rilettura</string>
|
||||||
<string name="status_on_hold">In attesa</string>
|
<string name="status_on_hold">In attesa</string>
|
||||||
<string name="show_reading_indicators">Mostrare gli indicatori di progresso della lettura</string>
|
<string name="show_reading_indicators">Mostrare gli indicatori di progresso della lettura</string>
|
||||||
<string name="data_deletion">Eliminazione dei dati</string>
|
<string name="data_deletion">Eliminazione dei dati</string>
|
||||||
<string name="show_reading_indicators_summary">Mostra la percentuale di lettura nella cronologia e nei preferiti</string>
|
<string name="show_reading_indicators_summary">Mostra la percentuale di lettura nella cronologia e nei preferiti</string>
|
||||||
<string name="exclude_nsfw_from_history_summary">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i vostri progressi non verranno salvati</string>
|
<string name="exclude_nsfw_from_history_summary">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i vostri progressi non verranno salvati</string>
|
||||||
<string name="clear_cookies_summary">Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate</string>
|
<string name="clear_cookies_summary">Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate</string>
|
||||||
<string name="show_all">Mostra tutto</string>
|
<string name="show_all">Mostra tutto</string>
|
||||||
<string name="logout">Esci</string>
|
<string name="logout">Esci</string>
|
||||||
<string name="status_planned">Pianificato</string>
|
<string name="status_planned">Pianificato</string>
|
||||||
<string name="status_completed">Finito</string>
|
<string name="status_completed">Finito</string>
|
||||||
<string name="status_dropped">Caduto</string>
|
<string name="status_dropped">Caduto</string>
|
||||||
<string name="invalid_domain_message">Dominio non valido</string>
|
<string name="invalid_domain_message">Dominio non valido</string>
|
||||||
<string name="select_range">Seleziona l\'intervallo</string>
|
<string name="select_range">Seleziona l\'intervallo</string>
|
||||||
<string name="not_found_404">Contenuto non trovato o rimosso</string>
|
<string name="not_found_404">Contenuto non trovato o rimosso</string>
|
||||||
<string name="compact">Compatto</string>
|
<string name="compact">Compatto</string>
|
||||||
<string name="source_disabled">Fonte disabilitata</string>
|
<string name="source_disabled">Fonte disabilitata</string>
|
||||||
<string name="text_shelf_holder_primary">I tuoi manga verrano visualizzati qui</string>
|
<string name="text_shelf_holder_primary">I tuoi manga verrano visualizzati qui</string>
|
||||||
<string name="text_shelf_holder_secondary">Scopri cosa leggere nella sezione «Esplora»</string>
|
<string name="text_shelf_holder_secondary">Scopri cosa leggere nella sezione «Esplora»</string>
|
||||||
<string name="canceled">Annullato</string>
|
<string name="canceled">Annullato</string>
|
||||||
<string name="server_error">Errore lato server (%1$d). Riprovare più tardi</string>
|
<string name="server_error">Errore lato server (%1$d). Riprovare più tardi</string>
|
||||||
<string name="clear_new_chapters_counters">Informazioni chiare anche sui nuovi capitoli</string>
|
<string name="clear_new_chapters_counters">Informazioni chiare anche sui nuovi capitoli</string>
|
||||||
<string name="prefetch_content">Precaricamento dei contenuti</string>
|
<string name="prefetch_content">Precaricamento dei contenuti</string>
|
||||||
<string name="mark_as_current">Contrassegna come corrente</string>
|
<string name="mark_as_current">Contrassegna come corrente</string>
|
||||||
<string name="error_no_space_left">Non c\'è più spazio sul dispositivo</string>
|
<string name="error_no_space_left">Non c\'è più spazio sul dispositivo</string>
|
||||||
<string name="different_languages">Lingue diverse</string>
|
<string name="different_languages">Lingue diverse</string>
|
||||||
<string name="network_unavailable">La rete non è disponibile</string>
|
<string name="network_unavailable">La rete non è disponibile</string>
|
||||||
<string name="network_unavailable_hint">Attiva il Wi-Fi o la rete mobile per leggere i manga in linea</string>
|
<string name="network_unavailable_hint">Attiva il Wi-Fi o la rete mobile per leggere i manga in linea</string>
|
||||||
<string name="webtoon_zoom">Zoom Webtoon</string>
|
<string name="webtoon_zoom">Zoom Webtoon</string>
|
||||||
<string name="account_already_exists">Questo account già esiste</string>
|
<string name="account_already_exists">Questo account già esiste</string>
|
||||||
<string name="back">Indietro</string>
|
<string name="back">Indietro</string>
|
||||||
<string name="sync">Sincronizzazione</string>
|
<string name="sync">Sincronizzazione</string>
|
||||||
<string name="clear_all_history">Cancella tutta la cronologia</string>
|
<string name="clear_all_history">Cancella tutta la cronologia</string>
|
||||||
<string name="last_2_hours">Ultime 2 ore</string>
|
<string name="last_2_hours">Ultime 2 ore</string>
|
||||||
<string name="history_cleared">Cronologia cancellata</string>
|
<string name="history_cleared">Cronologia cancellata</string>
|
||||||
<string name="manage">Gestisci</string>
|
<string name="manage">Gestisci</string>
|
||||||
<string name="no_bookmarks_yet">Non ci sono ancora segnalibri</string>
|
<string name="no_bookmarks_yet">Non ci sono ancora segnalibri</string>
|
||||||
<string name="no_bookmarks_summary">È possibile creare segnalibri durante la lettura dei manga</string>
|
<string name="no_bookmarks_summary">È possibile creare segnalibri durante la lettura dei manga</string>
|
||||||
<string name="bookmarks_removed">Segnalibri rimossi</string>
|
<string name="bookmarks_removed">Segnalibri rimossi</string>
|
||||||
<string name="no_manga_sources">Nessuna fonte manga</string>
|
<string name="no_manga_sources">Nessuna fonte manga</string>
|
||||||
<string name="no_manga_sources_text">Abilita le fonti manga per leggere manga in linea</string>
|
<string name="no_manga_sources_text">Abilita le fonti manga per leggere manga in linea</string>
|
||||||
<string name="random">Casuale</string>
|
<string name="random">Casuale</string>
|
||||||
<string name="reorder">Riordina</string>
|
<string name="reorder">Riordina</string>
|
||||||
<string name="empty">Vuoto</string>
|
<string name="empty">Vuoto</string>
|
||||||
<string name="explore">Esplora</string>
|
<string name="explore">Esplora</string>
|
||||||
<string name="incognito_mode">Modalità Incognito</string>
|
<string name="incognito_mode">Modalità Incognito</string>
|
||||||
<string name="import_completed">Importazione completata</string>
|
<string name="import_completed">Importazione completata</string>
|
||||||
<string name="import_completed_hint">È possibile eliminare il file originale dalla memoria per risparmiare spazio</string>
|
<string name="import_completed_hint">È possibile eliminare il file originale dalla memoria per risparmiare spazio</string>
|
||||||
<string name="confirm_exit">Premi nuovamente Indietro per uscire</string>
|
<string name="confirm_exit">Premi nuovamente Indietro per uscire</string>
|
||||||
<string name="exit_confirmation_summary">Premi due volte Indietro per uscire dall\'applicazione</string>
|
<string name="exit_confirmation_summary">Premi due volte Indietro per uscire dall\'applicazione</string>
|
||||||
<string name="exit_confirmation">Conferma di uscita</string>
|
<string name="exit_confirmation">Conferma di uscita</string>
|
||||||
<string name="saved_manga">Manga salvati</string>
|
<string name="saved_manga">Manga salvati</string>
|
||||||
<string name="pages_cache">Cache delle pagine</string>
|
<string name="pages_cache">Cache delle pagine</string>
|
||||||
<string name="other_cache">Altra cache</string>
|
<string name="other_cache">Altra cache</string>
|
||||||
<string name="storage_usage">Utilizzo dello spazio di archiviazione</string>
|
<string name="storage_usage">Utilizzo dello spazio di archiviazione</string>
|
||||||
<string name="available">Disponibile</string>
|
<string name="available">Disponibile</string>
|
||||||
<string name="memory_usage_pattern">%s - %s</string>
|
<string name="memory_usage_pattern">%s - %s</string>
|
||||||
<string name="removed_from_favourites">Rimosso dai preferiti</string>
|
<string name="removed_from_favourites">Rimosso dai preferiti</string>
|
||||||
<string name="options">Opzioni</string>
|
<string name="options">Opzioni</string>
|
||||||
<string name="no_chapters">Nessun capitolo</string>
|
<string name="no_chapters">Nessun capitolo</string>
|
||||||
<string name="automatic_scroll">Scorrimento automatico</string>
|
<string name="automatic_scroll">Scorrimento automatico</string>
|
||||||
<string name="reader_info_pattern">Ca. %1$d/%2$d Pg. %3$d/%4$d</string>
|
<string name="reader_info_pattern">Ca. %1$d/%2$d Pg. %3$d/%4$d</string>
|
||||||
<string name="comics_archive">Archivio fumetti</string>
|
<string name="comics_archive">Archivio fumetti</string>
|
||||||
<string name="folder_with_images">Cartella con immagini</string>
|
<string name="folder_with_images">Cartella con immagini</string>
|
||||||
<string name="importing_manga">Importazione di manga</string>
|
<string name="importing_manga">Importazione di manga</string>
|
||||||
<string name="import_will_start_soon">L\'importazione inizierà presto</string>
|
<string name="import_will_start_soon">L\'importazione inizierà presto</string>
|
||||||
<string name="feed">Flusso</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="contrast">Contrasto</string>
|
||||||
<string name="reset">Ripristina</string>
|
<string name="reset">Ripristina</string>
|
||||||
<string name="reader_slider">Mostra il cursore di cambio pagina</string>
|
<string name="reader_slider">Mostra il cursore di cambio pagina</string>
|
||||||
<string name="color_correction">Correzione del colore</string>
|
<string name="color_correction">Correzione del colore</string>
|
||||||
<string name="email_enter_hint">Inserisci il tuo indirizzo e-mail per continuare</string>
|
<string name="email_enter_hint">Inserisci il tuo indirizzo e-mail per continuare</string>
|
||||||
<string name="sync_title">Sincronizza i tuoi dati</string>
|
<string name="sync_title">Sincronizza i tuoi dati</string>
|
||||||
<string name="history_shortcuts">Mostra i collegamenti ai manga recenti</string>
|
<string name="history_shortcuts">Mostra i collegamenti ai manga recenti</string>
|
||||||
<string name="color_correction_hint">Le impostazioni di colore scelte saranno ricordate per questo manga</string>
|
<string name="color_correction_hint">Le impostazioni di colore scelte saranno ricordate per questo manga</string>
|
||||||
<string name="reader_control_ltr">Controllo ergonomico del lettore</string>
|
<string name="reader_control_ltr">Controllo ergonomico del lettore</string>
|
||||||
<string name="brightness">Luminosità</string>
|
<string name="brightness">Luminosità</string>
|
||||||
<string name="categories_delete_confirm">Sei sicuro/a di voler eliminare le categorie preferite selezionate\?
|
<string name="categories_delete_confirm">Sei sicuro/a di voler eliminare le categorie preferite selezionate\?
|
||||||
\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
|
\n Tutti i manga in esso contenuti andranno persi e questo non può essere annullato.</string>
|
||||||
<string name="history_shortcuts_summary">Rendere disponibili i manga recenti premendo a lungo sull\'icona dell\'applicazione</string>
|
<string name="history_shortcuts_summary">Rendere disponibili i manga recenti premendo a lungo sull\'icona dell\'applicazione</string>
|
||||||
<string name="reader_info_bar">Mostra la barra delle informazioni nel lettore</string>
|
<string name="reader_info_bar">Mostra la barra delle informazioni nel lettore</string>
|
||||||
<string name="manga_error_description_pattern">Dettagli dell\'errore:<br><tt>%1$s</tt><br>1. Prova ad <a href=%2$s>aprire il manga in un browser web</a> per assicurarsi che sia disponibile sulla sua fonte<br>2. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
|
<string name="manga_error_description_pattern">Dettagli dell\'errore:<br><tt>%1$s</tt><br>1. Prova ad <a href=%2$s>aprire il manga in un browser web</a> per assicurarsi che sia disponibile sulla sua fonte<br>2. Se è disponibile, inviare una segnalazione di errore agli sviluppatori.</string>
|
||||||
<string name="text_unsaved_changes_prompt">Salvare o eliminare le modifiche non salvate\?</string>
|
<string name="text_unsaved_changes_prompt">Salvare o eliminare le modifiche non salvate\?</string>
|
||||||
<string name="discard">Abbandona</string>
|
<string name="discard">Abbandona</string>
|
||||||
<string name="language">Lingua</string>
|
<string name="language">Lingua</string>
|
||||||
<string name="share_logs">Condividi i registri</string>
|
<string name="share_logs">Condividi i registri</string>
|
||||||
<string name="enable_logging_summary">Registra alcune azioni a scopo di debug</string>
|
<string name="enable_logging_summary">Registra alcune azioni a scopo di debug</string>
|
||||||
<string name="enable_logging">Abilita la registrazione</string>
|
<string name="enable_logging">Abilita la registrazione</string>
|
||||||
<string name="show_suspicious_content">Mostra il contenuto sospetto</string>
|
<string name="show_suspicious_content">Mostra il contenuto sospetto</string>
|
||||||
<string name="theme_name_dynamic">Dinamico</string>
|
<string name="theme_name_dynamic">Dinamico</string>
|
||||||
<string name="color_theme">Schema colori</string>
|
<string name="color_theme">Schema colori</string>
|
||||||
<string name="show_in_grid_view">Mostra nella vista griglia</string>
|
<string name="show_in_grid_view">Mostra nella vista griglia</string>
|
||||||
<string name="scrobbling_empty_hint">Per tenere traccia dell\'avanzamento della lettura, seleziona Menu → Traccia nella schermata dei dettagli del manga.</string>
|
<string name="scrobbling_empty_hint">Per tenere traccia dell\'avanzamento della lettura, seleziona Menu → Traccia nella schermata dei dettagli del manga.</string>
|
||||||
<string name="nothing_here">Non c\'è niente qui</string>
|
<string name="nothing_here">Non c\'è niente qui</string>
|
||||||
<string name="services">Servizi</string>
|
<string name="services">Servizi</string>
|
||||||
<string name="theme_name_mamimi">Mamimi</string>
|
<string name="theme_name_mamimi">Mamimi</string>
|
||||||
<string name="theme_name_kanade">Kanade</string>
|
<string name="theme_name_kanade">Kanade</string>
|
||||||
<string name="theme_name_asuka">Asuka</string>
|
<string name="theme_name_asuka">Asuka</string>
|
||||||
<string name="theme_name_mion">Mion</string>
|
<string name="theme_name_mion">Mion</string>
|
||||||
<string name="theme_name_rikka">Rikka</string>
|
<string name="theme_name_rikka">Rikka</string>
|
||||||
<string name="theme_name_miku">Miku</string>
|
<string name="theme_name_miku">Miku</string>
|
||||||
<string name="allow_unstable_updates">Permetti aggiornamenti instabili</string>
|
<string name="allow_unstable_updates">Permetti aggiornamenti instabili</string>
|
||||||
<string name="allow_unstable_updates_summary">Proponi aggiornamenti alle versioni beta dell\'app</string>
|
<string name="allow_unstable_updates_summary">Proponi aggiornamenti alle versioni beta dell\'app</string>
|
||||||
<string name="download_started">Scaricamento iniziato</string>
|
<string name="download_started">Scaricamento iniziato</string>
|
||||||
<string name="theme_name_sakura">Sakura</string>
|
<string name="theme_name_sakura">Sakura</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -431,4 +431,16 @@
|
|||||||
<string name="show_pages_numbers_summary">下隅にページ番号を表示する</string>
|
<string name="show_pages_numbers_summary">下隅にページ番号を表示する</string>
|
||||||
<string name="reader_info_bar_summary">画面上部に現在時刻と読書の進行状況を表示する</string>
|
<string name="reader_info_bar_summary">画面上部に現在時刻と読書の進行状況を表示する</string>
|
||||||
<string name="details_button_tip">読むボタンを長押しすると、より多くのオプションが表示されます</string>
|
<string name="details_button_tip">読むボタンを長押しすると、より多くのオプションが表示されます</string>
|
||||||
|
<string name="clear_source_cookies_summary">指定されたドメインのクッキーのみをクリアします。ほとんどの場合、認証は無効になります</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_next_unread_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="custom_directory">カスタムディレクトリ</string>
|
||||||
|
<string name="no_access_to_file">このファイルまたはディレクトリにアクセスできません</string>
|
||||||
|
<string name="pick_custom_directory">カスタムディレクトリを選択</string>
|
||||||
|
<string name="local_manga_directories">ローカルマンガディレクトリ</string>
|
||||||
</resources>
|
</resources>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user