Compare commits

...

42 Commits

Author SHA1 Message Date
Koitharu
7135902100 Update parsers 2023-11-10 14:55:28 +02:00
Nayuki
969947ef71 Translated using Weblate (Thai)
Currently translated at 73.2% (373 of 509 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
GpixeL
806e4eade6 Translated using Weblate (Indonesian)
Currently translated at 99.4% (506 of 509 strings)

Co-authored-by: GpixeL <gamesfire313@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Abay Emes
063cfbe6b9 Translated using Weblate (Kazakh)
Currently translated at 100.0% (7 of 7 strings)

Translated using Weblate (Kazakh)

Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/kk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-11-10 14:48:38 +02:00
InfinityDouki56
7cb94a3baa Translated using Weblate (Filipino)
Currently translated at 88.8% (452 of 509 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Oğuz Ersen
894c584c78 Translated using Weblate (Turkish)
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
gallegonovato
2f65e7776a Translated using Weblate (Spanish)
Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
Макар Разин
76c56c9119 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (509 of 509 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (509 of 509 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (509 of 509 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-10 14:48:38 +02:00
InfinityDouki56
e0a803399c Translated using Weblate (Filipino)
Currently translated at 88.9% (452 of 508 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
kenewjr
7803f42486 Translated using Weblate (Indonesian)
Currently translated at 96.4% (490 of 508 strings)

Co-authored-by: kenewjr <kenelewatan@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Макар Разин
39713b3cf6 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (508 of 508 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Nayuki
8ebf5cea62 Translated using Weblate (Thai)
Currently translated at 68.7% (349 of 508 strings)

Co-authored-by: Nayuki <me@nayuki.cyou>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/th/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Abay Emes
663dabe218 Added translation using Weblate (Kazakh)
Translated using Weblate (Kazakh)

Currently translated at 57.4% (292 of 508 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Tommy12pl
3a5d0120bf Translated using Weblate (Chinese (Simplified))
Currently translated at 99.4% (505 of 508 strings)

Co-authored-by: Tommy12pl <tommy12pl@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
gallegonovato
a773f932d4 Translated using Weblate (Spanish)
Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-11-07 18:49:34 +02:00
Koitharu
2a5812735f Cubic reader scroll speed 2023-11-05 08:54:07 +02:00
Koitharu
06ec145802 Update parsers 2023-11-02 08:56:43 +02:00
Koitharu
6624778f7f Fix periodical backups 2023-11-02 08:50:51 +02:00
Koitharu
1af1f071ad Fix crashes 2023-11-01 17:25:55 +02:00
Koitharu
f87db4e6d3 Update dependencies 2023-11-01 16:38:10 +02:00
Crono
07bd66fb39 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (508 of 508 strings)

Co-authored-by: Crono <cronoreader@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2023-11-01 16:32:38 +02:00
Koitharu
4bb0d52217 Fix downloading 2023-10-28 16:39:43 +03:00
Koitharu
66de4bd49e Translated using Weblate (Russian)
Currently translated at 100.0% (508 of 508 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Bai
ff12d63696 Translated using Weblate (Turkish)
Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: Bai <batuhanakkurt000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
InfinityDouki56
c168a841f3 Translated using Weblate (Filipino)
Currently translated at 88.9% (451 of 507 strings)

Co-authored-by: InfinityDouki56 <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
pro maxime
8bfb676e6a Translated using Weblate (Arabic)
Currently translated at 36.0% (183 of 507 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: pro maxime <promaxime45@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-10-28 16:16:39 +03:00
gallegonovato
d5c0ce280e Translated using Weblate (Spanish)
Currently translated at 100.0% (507 of 507 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Vinícius Saturnino
b34627c361 Translated using Weblate (Portuguese)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Vinícius Saturnino <saturninodepaulavinicius62@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Paulo Oliveira
cbc3be056a Translated using Weblate (Portuguese)
Currently translated at 100.0% (498 of 498 strings)

Co-authored-by: Paulo Oliveira <junior.literasas@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2023-10-28 16:16:39 +03:00
Koitharu
d9acc4ec18 Fix periodical backups to external directory 2023-10-28 16:14:47 +03:00
Koitharu
577cc848ee Scroll lists to top atomatically 2023-10-28 15:26:22 +03:00
Koitharu
8a64c88a07 (Temporary) remove chapters list from downloads 2023-10-28 14:44:58 +03:00
Koitharu
1cd7745e38 Update parsers 2023-10-28 13:26:02 +03:00
Koitharu
395b3f7200 Fix proguard rules 2023-10-27 17:27:40 +03:00
Koitharu
b8db4c81d8 Handle up navigation from reader 2023-10-27 16:44:40 +03:00
Koitharu
98bd42f3ae Remove deletions from sync process 2023-10-27 15:02:10 +03:00
Koitharu
db8835a7b8 Fix history restoring 2023-10-27 14:18:14 +03:00
Koitharu
afe50a9ed6 Fixes 2023-10-27 13:58:04 +03:00
Koitharu
beba818f57 Periodic backups 2023-10-26 17:24:11 +03:00
Koitharu
beb17ef442 Pause autoscroll while touch down 2023-10-26 16:13:30 +03:00
Koitharu
24f1546019 Fix pagination 2023-10-26 12:45:32 +03:00
ngocanhtve
1b0fed5c56 Translated using Weblate (Vietnamese)
Currently translated at 84.1% (419 of 498 strings)

Translated using Weblate (Vietnamese)

Currently translated at 100.0% (7 of 7 strings)

Co-authored-by: ngocanhtve <ngocanh.tve@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/vi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/vi/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2023-10-26 12:29:29 +03:00
63 changed files with 1239 additions and 298 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 590
versionName = '6.2.3'
versionCode = 595
versionName = '6.2.7'
generatedDensities = []
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
ksp {
@@ -82,17 +82,17 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:6bf0ae92e4') {
implementation('com.github.KotatsuApp:kotatsu-parsers:02463e5833') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.10'
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.20'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.activity:activity-ktx:1.8.0'
implementation 'androidx.fragment:fragment-ktx:1.6.1'
implementation 'androidx.fragment:fragment-ktx:1.6.2'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
implementation 'androidx.lifecycle:lifecycle-service:2.6.2'
@@ -119,8 +119,8 @@ dependencies {
implementation 'androidx.room:room-ktx:2.6.0'
ksp 'androidx.room:room-compiler:2.6.0'
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.11.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0'
implementation 'com.squareup.okio:okio:3.6.0'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
@@ -128,22 +128,22 @@ dependencies {
implementation 'com.google.dagger:hilt-android:2.48.1'
kapt 'com.google.dagger:hilt-compiler:2.48.1'
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'androidx.hilt:hilt-work:1.1.0'
kapt 'androidx.hilt:hilt-compiler:1.1.0'
implementation 'io.coil-kt:coil-base:2.4.0'
implementation 'io.coil-kt:coil-svg:2.4.0'
implementation 'io.coil-kt:coil-base:2.5.0'
implementation 'io.coil-kt:coil-svg:2.5.0'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:cf089a264d'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
implementation 'ch.acra:acra-http:5.11.2'
implementation 'ch.acra:acra-dialog:5.11.2'
implementation 'ch.acra:acra-http:5.11.3'
implementation 'ch.acra:acra-dialog:5.11.3'
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20230618'
testImplementation 'org.json:json:20231013'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
androidTestImplementation 'androidx.test:runner:1.5.2'

View File

@@ -18,3 +18,4 @@
-keep class org.koitharu.kotatsu.core.exceptions.* { *; }
-keep class org.koitharu.kotatsu.settings.NotificationSettingsLegacyFragment
-keep class org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy { *; }
-keep class org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment { *; }

View File

@@ -48,8 +48,8 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
if (!catchingWebViewUnavailability {
setContentView(
ActivityBrowserBinding.inflate(
layoutInflater
)
layoutInflater,
),
)
}) {
return
@@ -82,9 +82,11 @@ class CloudFlareActivity : BaseActivity<ActivityBrowserBinding>(), CloudFlareCal
}
override fun onDestroy() {
viewBinding.webView.run {
stopLoading()
destroy()
runCatching {
viewBinding.webView
}.onSuccess {
it.stopLoading()
it.destroy()
}
super.onDestroy()
}

View File

@@ -29,7 +29,7 @@ class BackupZipOutput(val file: File) : Closeable {
}
}
private const val DIR_BACKUPS = "backups"
const val DIR_BACKUPS = "backups"
suspend fun BackupZipOutput(context: Context): BackupZipOutput = runInterruptible(Dispatchers.IO) {
val dir = context.run {

View File

@@ -354,6 +354,16 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val is32BitColorsEnabled: Boolean
get() = prefs.getBoolean(KEY_32BIT_COLOR, false)
val isPeriodicalBackupEnabled: Boolean
get() = prefs.getBoolean(KEY_BACKUP_PERIODICAL_ENABLED, false)
val periodicalBackupFrequency: Long
get() = prefs.getString(KEY_BACKUP_PERIODICAL_FREQUENCY, null)?.toLongOrNull() ?: 7L
var periodicalBackupOutput: Uri?
get() = prefs.getString(KEY_BACKUP_PERIODICAL_OUTPUT, null)?.toUriOrNull()
set(value) = prefs.edit { putString(KEY_BACKUP_PERIODICAL_OUTPUT, value?.toString()) }
fun isTipEnabled(tip: String): Boolean {
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
}
@@ -458,6 +468,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_ZOOM_MODE = "zoom_mode"
const val KEY_BACKUP = "backup"
const val KEY_RESTORE = "restore"
const val KEY_BACKUP_PERIODICAL_ENABLED = "backup_periodic"
const val KEY_BACKUP_PERIODICAL_FREQUENCY = "backup_periodic_freq"
const val KEY_BACKUP_PERIODICAL_OUTPUT = "backup_periodic_output"
const val KEY_BACKUP_PERIODICAL_LAST = "backup_periodic_last"
const val KEY_HISTORY_GROUPING = "history_grouping"
const val KEY_READING_INDICATORS = "reading_indicators"
const val KEY_REVERSE_CHAPTERS = "reverse_chapters"

View File

@@ -6,7 +6,6 @@ import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.annotation.CallSuper
@@ -96,11 +95,10 @@ abstract class BaseActivity<B : ViewBinding> :
insetsDelegate.onViewCreated(binding.root)
}
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
onBackPressedDispatcher.onBackPressed()
// TODO: navigateUpTo
true
} else super.onOptionsItemSelected(item)
override fun onSupportNavigateUp(): Boolean {
dispatchNavigateUp()
return true
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
@@ -151,6 +149,17 @@ abstract class BaseActivity<B : ViewBinding> :
window.statusBarColor = defaultStatusBarColor
}
protected open fun dispatchNavigateUp() {
val upIntent = parentActivityIntent
if (upIntent != null) {
if (!navigateUpTo(upIntent)) {
startActivity(upIntent)
}
} else {
finishAfterTransition()
}
}
private fun putDataToExtras(intent: Intent?) {
intent?.putExtra(EXTRA_DATA, intent.data)
}

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.core.ui.list
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
class RecyclerScrollKeeper(
private val rv: RecyclerView,
) : AdapterDataObserver() {
private val scrollUpRunnable = Runnable {
(rv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(0, 0)
}
fun attach() {
rv.adapter?.registerAdapterDataObserver(this)
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
if (positionStart == 0 && isScrolledToTop()) {
postScrollUp()
}
}
override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount)
if (toPosition == 0 && isScrolledToTop()) {
postScrollUp()
}
}
private fun postScrollUp() {
rv.postDelayed(scrollUpRunnable, 500L)
}
private fun isScrolledToTop(): Boolean {
return (rv.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition() == 0
}
}

View File

@@ -0,0 +1,74 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.util.ArrayMap
import android.util.AttributeSet
import com.google.android.material.slider.Slider
import kotlin.math.cbrt
import kotlin.math.pow
class CubicSlider @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : Slider(context, attrs) {
private val changeListeners = ArrayMap<OnChangeListener, OnChangeListenerMapper>(1)
override fun setValue(value: Float) {
super.setValue(value.unmap())
}
override fun getValue(): Float {
return super.getValue().map()
}
override fun getValueFrom(): Float {
return super.getValueFrom().map()
}
override fun setValueFrom(valueFrom: Float) {
super.setValueFrom(valueFrom.unmap())
}
override fun getValueTo(): Float {
return super.getValueTo().map()
}
override fun setValueTo(valueTo: Float) {
super.setValueTo(valueTo.unmap())
}
override fun addOnChangeListener(listener: OnChangeListener) {
val mapper = OnChangeListenerMapper(listener)
super.addOnChangeListener(mapper)
changeListeners[listener] = mapper
}
override fun removeOnChangeListener(listener: OnChangeListener) {
changeListeners.remove(listener)?.let {
super.removeOnChangeListener(it)
}
}
override fun clearOnChangeListeners() {
super.clearOnChangeListeners()
changeListeners.clear()
}
private fun Float.map(): Float {
return this.pow(3)
}
private fun Float.unmap(): Float {
return cbrt(this)
}
private inner class OnChangeListenerMapper(
private val delegate: OnChangeListener,
) : OnChangeListener {
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
delegate.onValueChange(slider, value.map(), fromUser)
}
}
}

View File

@@ -26,7 +26,7 @@ class CompositeMutex<T : Any> : Set<T> {
}
override fun isEmpty(): Boolean {
return state.isEmpty
return state.isEmpty()
}
override fun iterator(): Iterator<T> {

View File

@@ -19,7 +19,7 @@ class CompositeMutex2<T : Any> : Set<T> {
}
override fun isEmpty(): Boolean {
return delegates.isEmpty
return delegates.isEmpty()
}
override fun iterator(): Iterator<T> {

View File

@@ -83,7 +83,7 @@ fun <I> ActivityResultLauncher<I>.tryLaunch(
e.printStackTraceDebug()
}.isSuccess
fun SharedPreferences.observe() = callbackFlow<String?> {
fun SharedPreferences.observe(): Flow<String?> = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
trySendBlocking(key)
}

View File

@@ -23,7 +23,7 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
return null
}
}
disposeImageRequest()
// disposeImageRequest()
return ImageRequest.Builder(context)
.data(data)
.lifecycle(lifecycleOwner)

View File

@@ -87,5 +87,5 @@ class DetailsInteractor @Inject constructor(
}
}
suspend fun findLocal(seed: Manga) = localMangaRepository.getRemoteManga(seed)
suspend fun findRemote(seed: Manga) = localMangaRepository.getRemoteManga(seed)
}

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.details.domain
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.model.findChapter
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.local.data.LocalMangaRepository
@@ -13,6 +14,7 @@ class ProgressUpdateUseCase @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val database: MangaDatabase,
private val localMangaRepository: LocalMangaRepository,
private val networkState: NetworkState,
) {
suspend operator fun invoke(manga: Manga): Float {
@@ -22,6 +24,9 @@ class ProgressUpdateUseCase @Inject constructor(
} else {
manga
}
if (!seed.isLocal && !networkState.value) {
return PROGRESS_NONE
}
val repo = mangaRepositoryFactory.create(seed.source)
val details = if (manga.source != seed.source || seed.chapters.isNullOrEmpty()) {
repo.getDetails(seed)

View File

@@ -149,15 +149,13 @@ class DetailsViewModel @Inject constructor(
val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
val relatedManga: StateFlow<List<MangaItemModel>> = manga
.mapLatest {
if (it != null && settings.isRelatedMangaEnabled) {
relatedMangaUseCase.invoke(it)?.toUi(ListMode.GRID, extraProvider).orEmpty()
} else {
emptyList()
}
val relatedManga: StateFlow<List<MangaItemModel>> = manga.mapLatest {
if (it != null && settings.isRelatedMangaEnabled) {
relatedMangaUseCase.invoke(it)?.toUi(ListMode.GRID, extraProvider).orEmpty()
} else {
emptyList()
}
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
val branches: StateFlow<List<MangaBranch>> = combine(
details,
@@ -217,7 +215,7 @@ class DetailsViewModel @Inject constructor(
}
launchJob(Dispatchers.Default) {
val manga = details.firstOrNull { it != null && it.isLocal } ?: return@launchJob
remoteManga.value = interactor.findLocal(manga.toManga())
remoteManga.value = interactor.findRemote(manga.toManga())
}
}

View File

@@ -18,8 +18,7 @@ data class DownloadState(
val currentPage: Int = 0,
val eta: Long = -1L,
val localManga: LocalManga? = null,
val downloadedChapters: LongArray = LongArray(0),
val scheduledChapters: LongArray = LongArray(0),
val downloadedChapters: Int = 0,
val timestamp: Long = System.currentTimeMillis(),
) {
@@ -42,68 +41,17 @@ data class DownloadState(
.putLong(DATA_ETA, eta)
.putLong(DATA_TIMESTAMP, timestamp)
.putString(DATA_ERROR, error)
.putLongArray(DATA_CHAPTERS, downloadedChapters)
.putLongArray(DATA_CHAPTERS_SRC, scheduledChapters)
.putInt(DATA_CHAPTERS, downloadedChapters)
.putBoolean(DATA_INDETERMINATE, isIndeterminate)
.putBoolean(DATA_PAUSED, isPaused)
.build()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as DownloadState
if (manga != other.manga) return false
if (isIndeterminate != other.isIndeterminate) return false
if (isPaused != other.isPaused) return false
if (isStopped != other.isStopped) return false
if (error != other.error) return false
if (totalChapters != other.totalChapters) return false
if (currentChapter != other.currentChapter) return false
if (totalPages != other.totalPages) return false
if (currentPage != other.currentPage) return false
if (eta != other.eta) return false
if (localManga != other.localManga) return false
if (!downloadedChapters.contentEquals(other.downloadedChapters)) return false
if (!scheduledChapters.contentEquals(other.scheduledChapters)) return false
if (timestamp != other.timestamp) return false
if (max != other.max) return false
if (progress != other.progress) return false
if (percent != other.percent) return false
return true
}
override fun hashCode(): Int {
var result = manga.hashCode()
result = 31 * result + isIndeterminate.hashCode()
result = 31 * result + isPaused.hashCode()
result = 31 * result + isStopped.hashCode()
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + totalChapters
result = 31 * result + currentChapter
result = 31 * result + totalPages
result = 31 * result + currentPage
result = 31 * result + eta.hashCode()
result = 31 * result + (localManga?.hashCode() ?: 0)
result = 31 * result + downloadedChapters.contentHashCode()
result = 31 * result + scheduledChapters.contentHashCode()
result = 31 * result + timestamp.hashCode()
result = 31 * result + max
result = 31 * result + progress
result = 31 * result + percent.hashCode()
return result
}
companion object {
private const val DATA_MANGA_ID = "manga_id"
private const val DATA_MAX = "max"
private const val DATA_PROGRESS = "progress"
private const val DATA_CHAPTERS = "chapter"
private const val DATA_CHAPTERS_SRC = "chapters_src"
private const val DATA_CHAPTERS = "chapter_cnt"
private const val DATA_ETA = "eta"
private const val DATA_TIMESTAMP = "timestamp"
private const val DATA_ERROR = "error"
@@ -126,8 +74,6 @@ data class DownloadState(
fun getTimestamp(data: Data): Date = Date(data.getLong(DATA_TIMESTAMP, 0L))
fun getDownloadedChapters(data: Data): LongArray = data.getLongArray(DATA_CHAPTERS) ?: LongArray(0)
fun getScheduledChapters(data: Data): LongArray = data.getLongArray(DATA_CHAPTERS_SRC) ?: LongArray(0)
fun getDownloadedChapters(data: Data): Int = data.getInt(DATA_CHAPTERS, 0)
}
}

View File

@@ -2,28 +2,22 @@ package org.koitharu.kotatsu.download.ui.list
import android.transition.TransitionManager
import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkInfo
import coil.ImageLoader
import coil.request.SuccessResult
import coil.util.CoilUtils
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.util.ext.drawableEnd
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemDownloadBinding
import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter
import org.koitharu.kotatsu.download.ui.list.chapters.downloadChapterAD
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.util.format
@@ -36,9 +30,7 @@ fun downloadItemAD(
) {
val percentPattern = context.resources.getString(R.string.percent_string_pattern)
val expandIcon = ContextCompat.getDrawable(context, R.drawable.ic_expand_collapse)
val chaptersAdapter = BaseListAdapter<DownloadChapter>()
.addDelegate(ListItemType.CHAPTER, downloadChapterAD())
// val expandIcon = ContextCompat.getDrawable(context, R.drawable.ic_expand_collapse)
val clickListener = object : View.OnClickListener, View.OnLongClickListener {
override fun onClick(v: View) {
@@ -59,27 +51,26 @@ fun downloadItemAD(
binding.buttonResume.setOnClickListener(clickListener)
itemView.setOnClickListener(clickListener)
itemView.setOnLongClickListener(clickListener)
binding.recyclerViewChapters.addItemDecoration(DividerItemDecoration(context, RecyclerView.VERTICAL))
binding.recyclerViewChapters.adapter = chaptersAdapter
bind { payloads ->
if (ListModelDiffCallback.PAYLOAD_CHECKED_CHANGED in payloads && context.isAnimationsEnabled) {
TransitionManager.beginDelayedTransition(binding.constraintLayout)
}
binding.textViewTitle.text = item.manga.title
binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.apply {
placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
transformations(TrimTransformation())
source(item.manga.source)
enqueueWith(coil)
if ((CoilUtils.result(binding.imageViewCover) as? SuccessResult)?.memoryCacheKey != item.coverCacheKey) {
binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.apply {
placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder)
allowRgb565(true)
transformations(TrimTransformation())
memoryCacheKey(item.coverCacheKey)
source(item.manga.source)
enqueueWith(coil)
}
}
binding.textViewTitle.isChecked = item.isExpanded
binding.textViewTitle.drawableEnd = if (item.isExpandable) expandIcon else null
binding.cardDetails.isVisible = item.isExpanded
chaptersAdapter.items = item.chapters
// binding.textViewTitle.isChecked = item.isExpanded
// binding.textViewTitle.drawableEnd = if (item.isExpandable) expandIcon else null
when (item.workState) {
WorkInfo.State.ENQUEUED,
WorkInfo.State.BLOCKED -> {
@@ -117,11 +108,11 @@ fun downloadItemAD(
binding.progressBar.isVisible = false
binding.progressBar.isEnabled = true
binding.textViewPercent.isVisible = false
if (item.totalChapters > 0) {
if (item.chaptersDownloaded > 0) {
binding.textViewDetails.text = context.resources.getQuantityString(
R.plurals.chapters,
item.totalChapters,
item.totalChapters,
item.chaptersDownloaded,
item.chaptersDownloaded,
)
binding.textViewDetails.isVisible = true
} else {

View File

@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.download.ui.list
import android.text.format.DateUtils
import androidx.work.WorkInfo
import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter
import coil.memory.MemoryCache
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
@@ -17,14 +17,15 @@ data class DownloadItemModel(
val manga: Manga,
val error: String?,
val max: Int,
val totalChapters: Int,
val progress: Int,
val eta: Long,
val timestamp: Date,
val chapters: List<DownloadChapter>,
val chaptersDownloaded: Int,
val isExpanded: Boolean,
) : ListModel, Comparable<DownloadItemModel> {
val coverCacheKey = MemoryCache.Key(manga.coverUrl, mapOf("dl" to "1"))
val percent: Float
get() = if (max > 0) progress / max.toFloat() else 0f
@@ -38,7 +39,7 @@ data class DownloadItemModel(
get() = workState == WorkInfo.State.RUNNING && isPaused
val isExpandable: Boolean
get() = chapters.isNotEmpty()
get() = false // TODO
fun getEtaString(): CharSequence? = if (hasEta) {
DateUtils.getRelativeTimeSpanString(

View File

@@ -15,6 +15,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.observe
@@ -53,6 +54,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
addItemDecoration(decoration)
adapter = downloadsAdapter
selectionController.attachToRecyclerView(this)
RecyclerScrollKeeper(this).attach()
}
addMenuProvider(DownloadsMenuProvider(this, viewModel))
viewModel.items.observe(this) {

View File

@@ -28,7 +28,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.daysDiff
import org.koitharu.kotatsu.download.domain.DownloadState
import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListHeader
@@ -239,8 +238,6 @@ class DownloadsViewModel @Inject constructor(
val mangaId = DownloadState.getMangaId(workData)
if (mangaId == 0L) return null
val manga = getManga(mangaId) ?: return null
val downloadedChapters = DownloadState.getDownloadedChapters(workData)
val scheduledChapters = DownloadState.getScheduledChapters(workData).toSet()
return DownloadItemModel(
id = id,
workState = state,
@@ -252,19 +249,8 @@ class DownloadsViewModel @Inject constructor(
progress = DownloadState.getProgress(workData),
eta = DownloadState.getEta(workData),
timestamp = DownloadState.getTimestamp(workData),
totalChapters = downloadedChapters.size,
chaptersDownloaded = DownloadState.getDownloadedChapters(workData),
isExpanded = isExpanded,
chapters = manga.chapters?.mapNotNull {
if (it.id in scheduledChapters) {
DownloadChapter(
number = it.number,
name = it.name,
isDownloaded = it.id in downloadedChapters,
)
} else {
null
}
}.orEmpty(),
)
}

View File

@@ -38,6 +38,7 @@ import okio.buffer
import okio.sink
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
import org.koitharu.kotatsu.core.model.ids
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.parser.MangaDataRepository
@@ -46,7 +47,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.Throttler
import org.koitharu.kotatsu.core.util.ext.awaitFinishedWorkInfosByTag
import org.koitharu.kotatsu.core.util.ext.awaitUpdateWork
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfoById
import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.core.util.ext.deleteWork
@@ -105,11 +105,12 @@ class DownloadWorker @AssistedInject constructor(
setForeground(getForegroundInfo())
val mangaId = inputData.getLong(MANGA_ID, 0L)
val manga = mangaDataRepository.findMangaById(mangaId) ?: return Result.failure()
val chaptersIds = inputData.getLongArray(CHAPTERS_IDS)?.takeUnless { it.isEmpty() }
val downloadedIds = getDoneChapters()
lastPublishedState = DownloadState(manga, isIndeterminate = true)
publishState(DownloadState(manga, isIndeterminate = true))
val chaptersIds = inputData.getLongArray(CHAPTERS_IDS)?.takeUnless { it.isEmpty() }
val downloadedIds = getDoneChapters(manga)
return try {
downloadMangaImpl(chaptersIds, downloadedIds)
downloadMangaImpl(manga, chaptersIds, downloadedIds)
Result.success(currentState.toWorkData())
} catch (e: CancellationException) {
withContext(NonCancellable) {
@@ -147,10 +148,11 @@ class DownloadWorker @AssistedInject constructor(
}
private suspend fun downloadMangaImpl(
subject: Manga,
includedIds: LongArray?,
excludedIds: LongArray,
excludedIds: Set<Long>,
) {
var manga = currentState.manga
var manga = subject
val chaptersToSkip = excludedIds.toMutableSet()
withMangaLock(manga) {
ContextCompat.registerReceiver(
@@ -178,16 +180,9 @@ class DownloadWorker @AssistedInject constructor(
}
}
val chapters = getChapters(mangaDetails, includedIds)
publishState(
currentState.copy(scheduledChapters = LongArray(chapters.size) { i -> chapters[i].id }),
)
for ((chapterIndex, chapter) in chapters.withIndex()) {
if (chaptersToSkip.remove(chapter.id)) {
publishState(
currentState.copy(
downloadedChapters = currentState.downloadedChapters + chapter.id,
),
)
publishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))
continue
}
val pages = runFailsafe(pausingHandle) {
@@ -225,11 +220,7 @@ class DownloadWorker @AssistedInject constructor(
localStorageChanges.emit(LocalMangaInput.of(output.rootFile).getManga())
}.onFailure(Throwable::printStackTraceDebug)
}
publishState(
currentState.copy(
downloadedChapters = currentState.downloadedChapters + chapter.id,
),
)
publishState(currentState.copy(downloadedChapters = currentState.downloadedChapters + 1))
}
publishState(currentState.copy(isIndeterminate = true, eta = -1L))
output.mergeWithExisting()
@@ -336,11 +327,9 @@ class DownloadWorker @AssistedInject constructor(
setProgress(state.toWorkData())
}
private suspend fun getDoneChapters(): LongArray {
val work = WorkManager.getInstance(applicationContext).awaitWorkInfoById(id)
?: return LongArray(0)
return DownloadState.getDownloadedChapters(work.progress)
}
private suspend fun getDoneChapters(manga: Manga) = runCatchingCancellable {
localMangaRepository.getDetails(manga).chapters?.ids()
}.getOrNull().orEmpty()
private fun getChapters(
manga: Manga,

View File

@@ -169,6 +169,7 @@ abstract class FavouritesDao {
ListSortOrder.NEWEST -> "favourites.created_at DESC"
ListSortOrder.ALPHABETIC -> "manga.title ASC"
ListSortOrder.NEW_CHAPTERS -> "(SELECT chapters_new FROM tracks WHERE tracks.manga_id = manga.manga_id) DESC"
ListSortOrder.UPDATED, // for legacy support
ListSortOrder.PROGRESS -> "(SELECT percent FROM history WHERE history.manga_id = manga.manga_id) DESC"
else -> throw IllegalArgumentException("Sort order $sortOrder is not supported")
}

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.db.entity.toMangaTag
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
import org.koitharu.kotatsu.core.util.ext.mapItems
@@ -185,7 +186,7 @@ class HistoryRepository @Inject constructor(
private suspend fun HistoryEntity.recoverIfNeeded(manga: Manga): HistoryEntity {
val chapters = manga.chapters
if (chapters.isNullOrEmpty() || chapters.findById(chapterId) != null) {
if (manga.isLocal || chapters.isNullOrEmpty() || chapters.findById(chapterId) != null) {
return this
}
val newChapterId = chapters.getOrNull(

View File

@@ -9,6 +9,7 @@ import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkManageIntent
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.list.ui.MangaListFragment
@@ -23,6 +24,7 @@ class HistoryListFragment : MangaListFragment() {
override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) {
super.onViewBindingCreated(binding, savedInstanceState)
RecyclerScrollKeeper(binding.recyclerView).attach()
addMenuProvider(HistoryListMenuProvider(binding.root.context, viewModel))
}

View File

@@ -183,7 +183,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> if (isSearchOpened()) {
super.onOptionsItemSelected(item)
closeSearchCallback.handleOnBackPressed()
true
} else {
viewBinding.searchView.requestFocus()
true

View File

@@ -52,6 +52,7 @@ import org.koitharu.kotatsu.core.util.ext.postDelayed
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.zipWithPrevious
import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
@@ -147,6 +148,11 @@ class ReaderActivity :
}
}
override fun getParentActivityIntent(): Intent? {
val manga = viewModel.manga?.toManga() ?: return null
return DetailsActivity.newIntent(this, manga)
}
override fun onUserInteraction() {
super.onUserInteraction()
scrollTimer.onUserInteraction()
@@ -249,6 +255,7 @@ class ReaderActivity :
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
touchHelper.dispatchTouchEvent(ev)
scrollTimer.onTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.reader.ui
import android.view.MotionEvent
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import dagger.assisted.Assisted
@@ -8,11 +9,14 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import kotlin.math.roundToLong
@@ -33,6 +37,7 @@ class ScrollTimer @AssistedInject constructor(
private var delayMs: Long = 10L
private var pageSwitchDelay: Long = 100L
private var resumeAt = 0L
private var isTouchDown = MutableStateFlow(false)
var isEnabled: Boolean = false
set(value) {
@@ -55,6 +60,19 @@ class ScrollTimer @AssistedInject constructor(
resumeAt = System.currentTimeMillis() + INTERACTION_SKIP_MS
}
fun onTouchEvent(event: MotionEvent) {
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
isTouchDown.value = true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
isTouchDown.value = false
}
}
}
private fun onSpeedChanged(speed: Float) {
if (speed <= 0f) {
delayMs = 0L
@@ -108,12 +126,18 @@ class ScrollTimer @AssistedInject constructor(
}
private fun isPaused(): Boolean {
return resumeAt > System.currentTimeMillis()
return isTouchDown.value || resumeAt > System.currentTimeMillis()
}
private suspend fun delayUntilResumed() {
while (isPaused()) {
delay(resumeAt - System.currentTimeMillis())
val delayTime = resumeAt - System.currentTimeMillis()
if (delayTime > 0) {
delay(delayTime)
} else {
yield()
}
isTouchDown.first { !it }
}
}

View File

@@ -128,7 +128,7 @@ class ReaderConfigSheet :
when (buttonView.id) {
R.id.switch_scroll_timer -> {
findCallback()?.isAutoScrollEnabled = isChecked
requireViewBinding().labelTimer.isVisible = isChecked
requireViewBinding().layoutTimer.isVisible = isChecked
requireViewBinding().sliderTimer.isVisible = isChecked
}
}
@@ -159,6 +159,7 @@ class ReaderConfigSheet :
if (fromUser) {
settings.readerAutoscrollSpeed = value
}
(viewBinding ?: return).labelTimerValue.text = getString(R.string.speed_value, value * 10f)
}
override fun onActivityResult(result: Uri?) {

View File

@@ -12,11 +12,11 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.distinctById
@@ -134,15 +134,17 @@ open class RemoteListViewModel @Inject constructor(
sortOrder = filterState.sortOrder,
tags = filterState.tags,
)
mangaList.update { oldList ->
val oldList = mangaList.getAndUpdate { oldList ->
if (!append || oldList.isNullOrEmpty()) {
list
} else {
oldList + list
}
}
if (append) {
hasNextPage.value = list.isNotEmpty()
}.orEmpty()
hasNextPage.value = if (append) {
list.isNotEmpty()
} else {
list.size > oldList.size || hasNextPage.value
}
} catch (e: CancellationException) {
throw e
@@ -152,6 +154,7 @@ open class RemoteListViewModel @Inject constructor(
if (!mangaList.value.isNullOrEmpty()) {
errorEvent.call(e)
}
hasNextPage.value = false
}
}.also { loadingJob = it }
}

View File

@@ -139,6 +139,7 @@ class AppearanceSettingsFragment :
private val deviceLocales = LocaleManagerCompat.getSystemLocales(context)
.map { it.language }
.distinct()
override fun compare(a: Locale, b: Locale): Int {
return if (a === b) {

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.core.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.databinding.DialogProgressBinding
import java.io.File
import java.io.FileOutputStream
@@ -28,7 +29,7 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
private var backup: File? = null
private val saveFileContract = registerForActivityResult(
ActivityResultContracts.CreateDocument("*/*"),
ActivityResultContracts.CreateDocument("application/zip"),
) { uri ->
val file = backup
if (uri != null && file != null) {
@@ -81,7 +82,10 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
private fun onBackupDone(file: File) {
this.backup = file
saveFileContract.launch(file.name)
if (!saveFileContract.tryLaunch(file.name)) {
Toast.makeText(requireContext(), R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
dismiss()
}
}
private fun saveBackup(file: File, output: Uri) {
@@ -91,7 +95,7 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
it.write(file.readBytes())
}
}
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_LONG).show()
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
dismiss()
} catch (e: InterruptedException) {
throw e

View File

@@ -43,7 +43,6 @@ class BackupViewModel @Inject constructor(
backup.finish()
progress.value = 1f
backup.close()
backup.file
}
onBackupDone.call(file)

View File

@@ -0,0 +1,98 @@
package org.koitharu.kotatsu.settings.backup
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.documentfile.provider.DocumentFile
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.backup.DIR_BACKUPS
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.resolveFile
import org.koitharu.kotatsu.core.util.ext.tryLaunch
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import java.io.File
import java.text.SimpleDateFormat
import javax.inject.Inject
@AndroidEntryPoint
class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodic_backups),
ActivityResultCallback<Uri?> {
@Inject
lateinit var scheduler: PeriodicalBackupWorker.Scheduler
private val outputSelectCall = registerForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
this,
)
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_backup_periodic)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bindOutputSummary()
bindLastBackupInfo()
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT -> outputSelectCall.tryLaunch(null)
else -> super.onPreferenceTreeClick(preference)
}
}
override fun onActivityResult(result: Uri?) {
if (result != null) {
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context?.contentResolver?.takePersistableUriPermission(result, takeFlags)
settings.periodicalBackupOutput = result
bindOutputSummary()
}
}
private fun bindOutputSummary() {
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT) ?: return
viewLifecycleScope.launch {
preference.summary = withContext(Dispatchers.Default) {
val value = settings.periodicalBackupOutput
value?.toUserFriendlyString(preference.context) ?: preference.context.run {
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
}.path
}
}
}
private fun bindLastBackupInfo() {
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_LAST) ?: return
viewLifecycleScope.launch {
val lastDate = withContext(Dispatchers.Default) {
scheduler.getLastSuccessfulBackup()
}
preference.summary = lastDate?.let {
val format = SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.SHORT)
preference.context.getString(R.string.last_successful_backup, format.format(it))
}
preference.isVisible = lastDate != null
}
}
private fun Uri.toUserFriendlyString(context: Context): String {
val df = DocumentFile.fromTreeUri(context, this)
if (df?.canWrite() != true) {
return context.getString(R.string.invalid_value_message)
}
return resolveFile(context)?.path ?: toString()
}
}

View File

@@ -0,0 +1,111 @@
package org.koitharu.kotatsu.settings.backup
import android.content.Context
import android.os.Build
import androidx.documentfile.provider.DocumentFile
import androidx.hilt.work.HiltWorker
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.await
import androidx.work.workDataOf
import dagger.Reusable
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import org.koitharu.kotatsu.core.backup.BackupRepository
import org.koitharu.kotatsu.core.backup.BackupZipOutput
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import java.util.Date
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@HiltWorker
class PeriodicalBackupWorker @AssistedInject constructor(
@Assisted appContext: Context,
@Assisted params: WorkerParameters,
private val repository: BackupRepository,
private val settings: AppSettings,
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val resultData = workDataOf(DATA_TIMESTAMP to Date().time)
val file = BackupZipOutput(applicationContext).use { backup ->
backup.put(repository.createIndex())
backup.put(repository.dumpHistory())
backup.put(repository.dumpCategories())
backup.put(repository.dumpFavourites())
backup.put(repository.dumpBookmarks())
backup.put(repository.dumpSettings())
backup.finish()
backup.file
}
val dirUri = settings.periodicalBackupOutput ?: return Result.success(resultData)
val target = DocumentFile.fromTreeUri(applicationContext, dirUri)
?.createFile("application/zip", file.nameWithoutExtension)
?.uri ?: return Result.failure()
applicationContext.contentResolver.openOutputStream(target, "wt")?.use { output ->
file.inputStream().copyTo(output)
} ?: return Result.failure()
file.deleteAwait()
return Result.success(resultData)
}
@Reusable
class Scheduler @Inject constructor(
private val workManager: WorkManager,
private val settings: AppSettings,
) : PeriodicWorkScheduler {
override suspend fun schedule() {
val constraints = Constraints.Builder()
.setRequiresStorageNotLow(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
constraints.setRequiresDeviceIdle(true)
}
val request = PeriodicWorkRequestBuilder<PeriodicalBackupWorker>(
settings.periodicalBackupFrequency,
TimeUnit.DAYS,
).setConstraints(constraints.build())
.keepResultsForAtLeast(20, TimeUnit.DAYS)
.addTag(TAG)
.build()
workManager
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
.await()
}
override suspend fun unschedule() {
workManager
.cancelUniqueWork(TAG)
.await()
}
override suspend fun isScheduled(): Boolean {
return workManager
.awaitUniqueWorkInfoByName(TAG)
.any { !it.state.isFinished }
}
suspend fun getLastSuccessfulBackup(): Date? {
return workManager
.awaitUniqueWorkInfoByName(TAG)
.lastOrNull { x -> x.state == WorkInfo.State.SUCCEEDED }
?.outputData
?.getLong(DATA_TIMESTAMP, 0)
?.let { if (it != 0L) Date(it) else null }
}
}
private companion object {
const val TAG = "backups"
const val DATA_TIMESTAMP = "ts"
}
}

View File

@@ -65,6 +65,7 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
findPreference<Preference>(AppSettings.KEY_PAGES_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.PAGES]))
findPreference<Preference>(AppSettings.KEY_THUMBS_CACHE_CLEAR)?.bindBytesSizeSummary(checkNotNull(viewModel.cacheSizes[CacheDir.THUMBS]))
findPreference<Preference>(AppSettings.KEY_HTTP_CACHE_CLEAR)?.bindBytesSizeSummary(viewModel.httpCacheSize)
bindPeriodicalBackupSummary()
findPreference<Preference>(AppSettings.KEY_SEARCH_HISTORY_CLEAR)?.let { pref ->
viewModel.searchHistoryCount.observe(viewLifecycleOwner) {
pref.summary = if (it < 0) {
@@ -200,6 +201,20 @@ class UserDataSettingsFragment : BasePreferenceFragment(R.string.data_and_privac
}
}
private fun bindPeriodicalBackupSummary() {
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_ENABLED) ?: return
val entries = resources.getStringArray(R.array.backup_frequency)
val entryValues = resources.getStringArray(R.array.values_backup_frequency)
viewModel.periodicalBackupFrequency.observe(viewLifecycleOwner) { freq ->
preference.summary = if (freq == 0L) {
getString(R.string.disabled)
} else {
val index = entryValues.indexOf(freq.toString())
entries.getOrNull(index)
}
}
}
private fun clearSearchHistory() {
MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.clear_search_history)

View File

@@ -5,12 +5,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runInterruptible
import okhttp3.Cache
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
@@ -29,6 +32,7 @@ class UserDataSettingsViewModel @Inject constructor(
private val searchRepository: MangaSearchRepository,
private val trackingRepository: TrackingRepository,
private val cookieJar: MutableCookieJar,
private val settings: AppSettings,
) : BaseViewModel() {
val onActionDone = MutableEventFlow<ReversibleAction>()
@@ -40,6 +44,20 @@ class UserDataSettingsViewModel @Inject constructor(
val cacheSizes = EnumMap<CacheDir, MutableStateFlow<Long>>(CacheDir::class.java)
val storageUsage = MutableStateFlow<StorageUsage?>(null)
val periodicalBackupFrequency = settings.observeAsFlow(
key = AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,
valueProducer = { isPeriodicalBackupEnabled },
).flatMapLatest { isEnabled ->
if (isEnabled) {
settings.observeAsFlow(
key = AppSettings.KEY_BACKUP_PERIODICAL_FREQUENCY,
valueProducer = { periodicalBackupFrequency },
)
} else {
flowOf(0)
}
}
private var storageUsageJob: Job? = null
init {

View File

@@ -5,6 +5,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
import org.koitharu.kotatsu.settings.backup.PeriodicalBackupWorker
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.work.TrackWorker
import javax.inject.Inject
@@ -13,6 +14,7 @@ class WorkScheduleManager @Inject constructor(
private val settings: AppSettings,
private val suggestionScheduler: SuggestionsWorker.Scheduler,
private val trackerScheduler: TrackWorker.Scheduler,
private val periodicalBackupScheduler: PeriodicalBackupWorker.Scheduler,
) : SharedPreferences.OnSharedPreferenceChangeListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
@@ -30,6 +32,13 @@ class WorkScheduleManager @Inject constructor(
isEnabled = settings.isSuggestionsEnabled,
force = key != AppSettings.KEY_SUGGESTIONS,
)
AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,
AppSettings.KEY_BACKUP_PERIODICAL_FREQUENCY -> updateWorker(
scheduler = periodicalBackupScheduler,
isEnabled = settings.isPeriodicalBackupEnabled,
force = key != AppSettings.KEY_BACKUP_PERIODICAL_ENABLED,
)
}
}
@@ -38,6 +47,7 @@ class WorkScheduleManager @Inject constructor(
processLifecycleScope.launch(Dispatchers.Default) {
updateWorkerImpl(trackerScheduler, settings.isTrackerEnabled, false)
updateWorkerImpl(suggestionScheduler, settings.isSuggestionsEnabled, false)
updateWorkerImpl(periodicalBackupScheduler, settings.isPeriodicalBackupEnabled, false)
}
}

View File

@@ -130,9 +130,6 @@ class SyncHelper @AssistedInject constructor(
private fun upsertHistory(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(authorityHistory, TABLE_HISTORY)
val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri)
.withSelection("updated_at < ?", arrayOf(timestamp.toString()))
.build()
json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityHistory))
ContentProviderOperation.newInsert(uri)
@@ -145,9 +142,6 @@ class SyncHelper @AssistedInject constructor(
private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES)
val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri)
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
.build()
json.mapJSONTo(operations) { jo ->
ContentProviderOperation.newInsert(uri)
.withValues(jo.toContentValues())
@@ -159,9 +153,6 @@ class SyncHelper @AssistedInject constructor(
private fun upsertFavourites(json: JSONArray, timestamp: Long): Array<ContentProviderResult> {
val uri = uri(authorityFavourites, TABLE_FAVOURITES)
val operations = ArrayList<ContentProviderOperation>()
operations += ContentProviderOperation.newDelete(uri)
.withSelection("created_at < ?", arrayOf(timestamp.toString()))
.build()
json.mapJSONTo(operations) { jo ->
operations.addAll(upsertManga(jo.removeJSONObject("manga"), authorityFavourites))
ContentProviderOperation.newInsert(uri)

View File

@@ -104,31 +104,6 @@
app:layout_constraintTop_toBottomOf="@id/textView_status"
tools:text="@tools:sample/lorem[3]" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_details"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginHorizontal="12dp"
android:layout_marginTop="12dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_default="wrap"
app:layout_constraintHeight_max="280dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/progressBar"
app:shapeAppearance="?shapeAppearanceCornerMedium">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_chapters"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="200"
tools:listitem="@layout/item_chapter_download" />
</com.google.android.material.card.MaterialCardView>
<Button
android:id="@+id/button_pause"
style="?materialButtonOutlinedStyle"
@@ -139,7 +114,7 @@
android:text="@string/pause"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/button_resume"
app:layout_constraintTop_toBottomOf="@id/card_details"
app:layout_constraintTop_toBottomOf="@id/progressBar"
tools:visibility="visible" />
<Button
@@ -152,7 +127,7 @@
android:text="@string/resume"
android:visibility="gone"
app:layout_constraintEnd_toStartOf="@id/button_cancel"
app:layout_constraintTop_toBottomOf="@id/card_details" />
app:layout_constraintTop_toBottomOf="@id/progressBar" />
<Button
android:id="@+id/button_cancel"
@@ -164,7 +139,7 @@
android:text="@android:string/cancel"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/card_details"
app:layout_constraintTop_toBottomOf="@id/progressBar"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -119,18 +119,35 @@
android:textColor="?colorOnSurfaceVariant"
app:drawableStartCompat="@drawable/ic_timer" />
<TextView
android:id="@+id/label_timer"
<LinearLayout
android:id="@+id/layout_timer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:text="@string/speed"
android:textAppearance="?attr/textAppearanceBodySmall"
android:layout_marginTop="@dimen/margin_normal"
android:textAppearance="?textAppearanceTitleSmall"
android:visibility="gone"
tools:visibility="visible" />
tools:visibility="visible">
<com.google.android.material.slider.Slider
<TextView
android:id="@+id/label_timer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/speed"
android:textAppearance="?attr/textAppearanceTitleSmall" />
<TextView
android:id="@+id/label_timer_value"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_small"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="x0.5" />
</LinearLayout>
<org.koitharu.kotatsu.core.ui.widgets.CubicSlider
android:id="@+id/slider_timer"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -2,18 +2,58 @@
<resources>
<plurals name="new_chapters">
<item quantity="zero">%1$d فصل جديد</item>
<item quantity="one"/>
<item quantity="two"/>
<item quantity="few"/>
<item quantity="one">%1$d فصل جديد</item>
<item quantity="two">%1$d فصول جديدة</item>
<item quantity="few">%1$d فصول جديدة</item>
<item quantity="many">%1$d فصول جديدة</item>
<item quantity="other"/>
<item quantity="other">%1$d فصول جديدة</item>
</plurals>
<plurals name="chapters">
<item quantity="zero">لا يوجد</item>
<item quantity="zero">%1$d فصل</item>
<item quantity="one">%1$d فصل</item>
<item quantity="two">%1$d فصلين</item>
<item quantity="few">%1$d بعض فصول</item>
<item quantity="many">%1$d عدة فصول</item>
<item quantity="other">أخرى</item>
<item quantity="few">%1$d فصول</item>
<item quantity="many">%1$d فصول</item>
<item quantity="other">%1$d فصول</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="zero">%1$d دقيقة مضت</item>
<item quantity="one">%1$d دقيقة مضت</item>
<item quantity="two">%1$d دقائق مضت</item>
<item quantity="few">%1$d دقائق مضت</item>
<item quantity="many">%1$d دقائق مضت</item>
<item quantity="other">%1$d دقائق مضت</item>
</plurals>
<plurals name="items">
<item quantity="zero">%1$d عنصر</item>
<item quantity="one">%1$d عنصر</item>
<item quantity="two">%1$d عناصر</item>
<item quantity="few">%1$d عناصر</item>
<item quantity="many">%1$d عناصر</item>
<item quantity="other">%1$d عناصر</item>
</plurals>
<plurals name="months_ago">
<item quantity="zero">%1$d شهر مضا</item>
<item quantity="one">%1$d شهر مضا</item>
<item quantity="two">%1$d شهرين مضت</item>
<item quantity="few">%1$d أشهر مضت</item>
<item quantity="many">%1$d أشهر مضت</item>
<item quantity="other">%1$d أشهر مضت</item>
</plurals>
<plurals name="days_ago">
<item quantity="zero">%1$d يوم مضا</item>
<item quantity="one">%1$d يوم مضا</item>
<item quantity="two">%1$d يومين مضت</item>
<item quantity="few">%1$d أيام مضت</item>
<item quantity="many">%1$d أيام مضت</item>
<item quantity="other">%1$d أيام مضت</item>
</plurals>
<plurals name="hours_ago">
<item quantity="zero">%1$d ساعة مضت</item>
<item quantity="one">%1$d ساعة مضت</item>
<item quantity="two">%1$d ساعات مضت</item>
<item quantity="few">%1$d ساعات مضت</item>
<item quantity="many">%1$d ساعات مضت</item>
<item quantity="other">%1$d ساعات مضت</item>
</plurals>
</resources>

View File

@@ -156,7 +156,7 @@
<string name="long_ago">منذ فترة</string>
<string name="notifications_settings">إعدادات الإشعارات</string>
<string name="save_manga">حفظ</string>
<string name="large_manga_save_confirm">تحتوي هذه المانجا على s%. حفظ الكل؟</string>
<string name="large_manga_save_confirm">هذه المانجا فيها %s . حفظ الكل</string>
<string name="read_more">اقرأ المزيد</string>
<string name="search_results">نتائج البحث</string>
<string name="file_not_found">الملف غير موجود</string>
@@ -172,9 +172,10 @@
<string name="data_restored_success">استعيدت جميع البيانات</string>
<string name="backup_information">يمكنك إنشاء نسخة احتياطية من السجل الخاص بك والمفضلة واستعادتها</string>
<string name="available_sources">المصادر المتاحة</string>
<string name="external_storage">التخزين الخارجي</string>
<string name="external_storage">تخزين خارجي</string>
<string name="silent">صامت</string>
<string name="today">اليوم</string>
<string name="system_default">الافتراضي</string>
<string name="sign_in">تسجبل الدخول</string>
<string name="domain">المجال</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="enhanced_colors">32-бітны каляровы рэжым</string>
<string name="suggest_new_sources_summary">Прапаноўваць крыніцы мангі, дададзеныя ў апошнім абнаўленні праграмы</string>
<string name="online_variant">Анлайн варыянт</string>
<string name="frequency_every_day">Кожны дзень</string>
<string name="backup_frequency">Частата стварэння рэзервовых копій</string>
<string name="periodic_backups_enable">Уключыць перыядычнае рэзервовае капіраванне</string>
<string name="frequency_every_2_days">Кожныя 2 дні</string>
<string name="frequency_once_per_week">Раз на тыдзень</string>
<string name="periodic_backups">Перыядычнае рэзервовае капіраванне</string>
<string name="frequency_twice_per_month">Два разы на месяц</string>
<string name="frequency_once_per_month">Адзін раз у месяц</string>
<string name="last_successful_backup">Апошняе паспяховае рэзервовае капіраванне: %s</string>
<string name="backups_output_directory">Вывадны каталог рэзервовых копій</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="enhanced_colors_summary">Reduce el banding, pero puede afectar al rendimiento</string>
<string name="by_relevance">Relevancia</string>
<string name="online_variant">Variante en línea</string>
<string name="frequency_every_day">Cada día</string>
<string name="backup_frequency">Frecuencia de creación de las copias de seguridad</string>
<string name="periodic_backups_enable">Activar las copias de seguridad periódicas</string>
<string name="frequency_every_2_days">Cada 2 días</string>
<string name="frequency_once_per_week">Una vez a la semana</string>
<string name="periodic_backups">Copias de seguridad periódicas</string>
<string name="frequency_twice_per_month">Dos veces al mes</string>
<string name="frequency_once_per_month">Una vez al mes</string>
<string name="backups_output_directory">Directorio para guardar la copia de seguridad</string>
<string name="last_successful_backup">La última copia de seguridad correcta: %s</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="list_options">Opsyon sa Listahan</string>
<string name="by_relevance">Kaugnayan</string>
<string name="online_variant">Online na baryante</string>
<string name="frequency_every_day">Araw araw</string>
<string name="backup_frequency">Dalas ng paglikha ng backup</string>
<string name="periodic_backups_enable">Paganahin ang periodic na pag-backup</string>
<string name="frequency_every_2_days">Kada 2 araw</string>
<string name="frequency_once_per_week">Isang beses kada linggo</string>
<string name="periodic_backups">Mga periodic na pag-backup</string>
<string name="frequency_twice_per_month">Dalawang beses bawat buwan</string>
<string name="frequency_once_per_month">Isang beses bawat buwan</string>
<string name="backups_output_directory">Output directory ng mga backup</string>
<string name="last_successful_backup">Huling matagumpay na pag-backup: %s</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -9,13 +9,13 @@
<string name="grid">Kisi</string>
<string name="list_mode">Mode daftar</string>
<string name="settings">Pengaturan</string>
<string name="remote_sources">Sumber jarak jauh</string>
<string name="remote_sources">Sumber manga</string>
<string name="loading_">Memuat…</string>
<string name="computing_">Menghitung…</string>
<string name="chapter_d_of_d">Bab %1$d dari %2$d</string>
<string name="close">Tutup</string>
<string name="try_again">Coba lagi</string>
<string name="nothing_found">Nihil</string>
<string name="nothing_found">Hasil tidak ditemukan</string>
<string name="history_is_empty">Belum ada riwayat</string>
<string name="read">Baca</string>
<string name="you_have_not_favourites_yet">Belum ada favorit</string>
@@ -37,8 +37,8 @@
<string name="updated">Diperbarui</string>
<string name="newest">Terbaru</string>
<string name="by_rating">Peringkat</string>
<string name="sort_order">Urutan penyortiran</string>
<string name="filter">Saring</string>
<string name="sort_order">Urutkan berdasarkan</string>
<string name="filter">Filter</string>
<string name="theme">Tema</string>
<string name="light">Terang</string>
<string name="dark">Gelap</string>
@@ -478,4 +478,31 @@
<string name="directories">Direktori</string>
<string name="main_screen_sections">Bagian layar utama</string>
<string name="to_top">Ke atas</string>
<string name="zoom_in">Perbesar</string>
<string name="frequency_every_day">Setiap Hari</string>
<string name="categories">Kategori</string>
<string name="frequency_every_2_days">Setiap 2 Hari</string>
<string name="online_variant">Variasi Online</string>
<string name="keep_screen_on">Biarkan Layar Menyala</string>
<string name="zoom_out">Perkecil</string>
<string name="keep_screen_on_summary">Jangan Matikan Layar Saat Membaca Komik</string>
<string name="list_options">Opsi daftar</string>
<string name="reader_zoom_buttons_summary">Apakah menampilkan tombol kontrol zoom di sudut kanan bawah</string>
<string name="backup_frequency">Frekuensi pembuatan cadangan</string>
<string name="suggest_new_sources">Sumber baru yang disarankan setelah pembaruan aplikasi</string>
<string name="periodic_backups_enable">Aktifkan pencadangan berkala</string>
<string name="moved_to_top">Bergerak ke atas</string>
<string name="enhanced_colors_summary">Mengurangi banding, tetapi dapat mempengaruhi kinerja</string>
<string name="frequency_once_per_week">Sekali dalam seminggu</string>
<string name="periodic_backups">Pencadangan berkala</string>
<string name="reader_zoom_buttons">Tampilkan tombol zoom</string>
<string name="frequency_twice_per_month">Dua kali sebulan</string>
<string name="by_relevance">Relevansi</string>
<string name="state_abandoned">Istirahat</string>
<string name="frequency_once_per_month">Sebulan sekali</string>
<string name="enhanced_colors">mode warna 32-bit</string>
<string name="speed_value">x%.1f</string>
<string name="last_successful_backup">Cadangan sukses terakhir: %s</string>
<string name="backups_output_directory">Direktori keluaran cadangan</string>
<string name="suggest_new_sources_summary">Prompt untuk mengaktifkan sumber baru yang ditambahkan setelah memperbarui aplikasi</string>
</resources>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="minutes_ago">
<item quantity="one">%1$d минут бұрын</item>
<item quantity="other">%1$d минут бұрын</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d елемент</item>
<item quantity="other">%1$d елемент</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d тарау</item>
<item quantity="other">%1$d тарау</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d жаңа тарау</item>
<item quantity="other">%1$d жаңа тарау</item>
</plurals>
<plurals name="months_ago">
<item quantity="one">%1$d ай бұрын</item>
<item quantity="other">%1$d ай бұрын</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d күн бұрын</item>
<item quantity="other">%1$d күн бұрын</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">%1$d сағат бұрын</item>
<item quantity="other">%1$d сағат бұрын</item>
</plurals>
</resources>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="text_search_holder_secondary">Сұрауыңызды қайталап көріңіз.</string>
<string name="text_history_holder_secondary">Шеткі мәзірден оқуға болатынды табыңыз.</string>
<string name="text_history_holder_secondary">«Шолу» бөлімінен не оқуға болатынын табыңыз.</string>
<string name="text_local_holder_secondary">Файл импорттаңыз не онлайн каталогтан бірдеңе сақтаңыз.</string>
<string name="other_storage">Басқа бума</string>
<string name="new_version_s">Жаңа нұсқа: %s</string>
@@ -65,14 +65,14 @@
<string name="search_on_s">%s-те іздеу</string>
<string name="delete_manga">Маңганы жою</string>
<string name="reader_settings">Оқыманы баптау</string>
<string name="switch_pages">Бет ауыстыру</string>
<string name="switch_pages">Парақтау</string>
<string name="taps_on_edges">Шет жақты түру</string>
<string name="clear_thumbs_cache">Нобай кәшін тазалау</string>
<string name="search_history_cleared">Тазаланды</string>
<string name="gestures_only">Ым ғана</string>
<string name="internal_storage">Ішкі жады</string>
<string name="external_storage">Сыртқы жады</string>
<string name="domain">Домен</string>
<string name="domain">Дәмейін</string>
<string name="app_update_available">Жаңа нұсқа қолжетімді</string>
<string name="open_in_browser">Уеб браузер арқылы ашу</string>
<string name="save_manga">Сақтау</string>
@@ -92,7 +92,7 @@
<string name="text_local_holder_primary">Алдымен бірдеңе сақтаңыз</string>
<string name="manga_shelf">Сөре</string>
<string name="recent_manga">Соңғы</string>
<string name="pages_animation">Бет анимациясы</string>
<string name="pages_animation">Бет анимасасы</string>
<string name="manga_save_location">Жүктеу бумасы</string>
<string name="not_available">Қолжетімсіз</string>
<string name="cannot_find_available_storage">Қолжетімді бума жоқ</string>
@@ -208,4 +208,301 @@
<string name="protect_application_subtitle">Қолданбаға кіру үшін құпиясөз енгізіңіз</string>
<string name="confirm">Растау</string>
<string name="password_length_hint">Құпиясөзде 4, не одан көп таңба болу керек</string>
<string name="status_re_reading">Қайталап оқып жатырмын</string>
<string name="detect_reader_mode">Оқу режімін өздігінен анықтау</string>
<string name="tracking">Бақылау</string>
<string name="email_enter_hint">Жалғастыру үшін email поштаңызды жазыңыз</string>
<string name="disable_all">Бәрін өшіру</string>
<string name="clear_feed">Лекті тазалау</string>
<string name="chapters_empty">Бұл маңгада тарау жоқ</string>
<string name="clear_all_history">Түгел тарихты тазалау</string>
<string name="preload_pages">Беттерді алдынала жүктей беру</string>
<string name="data_deletion">Деректі жою</string>
<string name="show_reading_indicators">Оқу прогрессін көрсету</string>
<string name="local_manga_processing">Сақталған маңгаңыз үдерісте</string>
<string name="show_notification_new_chapters_off">Мәлімдеме алмайсыз, бірақ жаңа тараулар тізімде көрсетіліп тұрады</string>
<string name="show_notification_new_chapters_on">Оқып жүрген маңгаңыздың жаңаруы туралы мәлімдеме алып тұрасыз</string>
<string name="status_reading">Оқып жүрмін</string>
<string name="various_languages">Түрлі тіл</string>
<string name="removal_completed">Жойылды</string>
<string name="edit">Өңдеу</string>
<string name="filter_load_error">Жанр тізімі жүктеліне алмады</string>
<string name="removed_from_history">Тарихтан жойылды</string>
<string name="crash_text">Ақау пайда болды. Жөндеу үшін әзірлеушіге шағым жіберіңізші.</string>
<string name="detect_reader_mode_summary">Маңга уебтүн бе екенін өздігінен анықтап береді</string>
<string name="appwidget_recent_description">Соңғы оқыған маңгаңыз</string>
<string name="appearance">Кейіп</string>
<string name="bookmark_remove">Бетбелгіні алып тастау</string>
<string name="disable_battery_optimization_summary">Аяда жаңарту іздеуді көмектеседі</string>
<string name="status_on_hold">Кейінге қалған</string>
<string name="last_2_hours">Соңғы 2 сағат</string>
<string name="name">Атау</string>
<string name="edit_category">Санатты өңдеу</string>
<string name="bookmark_removed">Бетбелгі жойылды</string>
<string name="select_range">Ауқымын таңдау</string>
<string name="suggestions_excluded_genres_summary">Көргіңіз келмейтін жанрды таңдаңыз</string>
<string name="only_using_wifi">Тек Wi-Fi арқылы</string>
<string name="back">Кері</string>
<string name="dns_over_https">HTTPS үстінен DNS</string>
<string name="sync_title">Дерегіңізді үйлестіріңіз</string>
<string name="appwidget_shelf_description">Таңдаулы маңгаңыз</string>
<string name="send">Жіберу</string>
<string name="bookmark_add">Бетбелгілеу</string>
<string name="new_sources_text">Жаңа маңга дереккөзі қолжетімді</string>
<string name="check_new_chapters_title">Жаңа тарау барын тексеріп, сол туралы мәлімдеу</string>
<string name="logged_in_as">%s деп тіркелгенсіз</string>
<string name="suggestions_info">Деректің бәрі тек осы құрылғы аясында қаралып, ешқайда жіберілмейді.</string>
<string name="history_cleared">Тарих тазарды</string>
<string name="undo">Қайтару</string>
<string name="download_slowdown_summary">IP мекенжайыңыздың бұғатқа түспеуіне көмектеседі</string>
<string name="text_delete_local_manga_batch">Таңдалғанды құрылғыдан жоямысыз\?</string>
<string name="report">Шағым</string>
<string name="download_slowdown">Жүктеп алуды баяулату</string>
<string name="bookmark_added">Бетбелгі қойылды</string>
<string name="sync">Үйлестіру</string>
<string name="search_chapters">Тарау табу</string>
<string name="always">Әрқашан</string>
<string name="suggestions_excluded_genres">Жанрды шектеу</string>
<string name="canceled">Доғарылды</string>
<string name="account_already_exists">Бұндай тіркелгі бос емес</string>
<string name="hide">Жасыру</string>
<string name="exclude_nsfw_from_history_summary">ҰЯТСЫЗ деген маңганы оқығаныңыз тарихыңызда сақталмайды</string>
<string name="show_reading_indicators_summary">Таңдаулы мен тарихта оқылған туынды пайызын көрсету</string>
<string name="use_fingerprint">Қолжетімді болса саусақ ізін қолдану</string>
<string name="onboard_text">Маңганы қай тілде оқығыңыз келетінін таңдаңыз. Кейінірек баптауда өзгертіп ала аласыз.</string>
<string name="suggestions_updating">Ұсынысты жаңарту</string>
<string name="percent_string_pattern">%%%1$s</string>
<string name="chapters_will_removed_background">Тараулар аяда жойылады</string>
<string name="default_mode">Әдепкі режім</string>
<string name="logout">Шығу</string>
<string name="status_completed">Аяқталған</string>
<string name="reset_filter">Сүзгіні тазарту</string>
<string name="status_dropped">Тастап кеткен</string>
<string name="nsfw">18+</string>
<string name="notifications_enable">Мәлімдеме қосу</string>
<string name="never">Ешқашан</string>
<string name="disable_battery_optimization">Қуат оңтайлығын өшіру</string>
<string name="status_planned">Жоспарланған</string>
<string name="bookmarks">Бетбелгілер</string>
<string name="show_all">Бәрін көрсету</string>
<string name="empty_favourite_categories">Таңдаулы санатыңыз жоқ</string>
<string name="invalid_domain_message">Қате дәмейін</string>
<string name="languages">Тілдер</string>
<string name="zoom_in">Ұлғайту</string>
<string name="captcha_required_summary">%s дұрыс істеуі үшін captcha өтіңіз</string>
<string name="download_option_all_unread">Түгел оқылмаған тарау</string>
<string name="restore_backup_description">Алдында жасалған жеке деректің сақтық көшірмесін импорттау</string>
<string name="frequency_every_day">Күнде</string>
<string name="download_started">Жүктеп алу басталды</string>
<string name="categories">Санат</string>
<string name="progress">Прогресс</string>
<string name="cancel_all">Бәрін доғару</string>
<string name="sync_host_description">Өзіңіздің үйлестіру сербірін я әдепкісін таңдай аласыз. Не істеп жатқаныңызды түсінбеңіз баспаңыз.</string>
<string name="error_corrupted_file">Қайта жарамсыз дерек қосылды яки файыл сынған</string>
<string name="pick_custom_directory">Жеке каталогты таңдау</string>
<string name="no_chapters">Тарау жоқ</string>
<string name="list_options">Тізімді реттеу</string>
<string name="related_manga_summary">Байланыс маңга тізімін көрсету. Кейде тізім қате я мүлде болмауы мүмкін</string>
<string name="remove_completed_downloads_confirm">Жүктеу тарихыңыз тазарады</string>
<string name="theme_name_dynamic">Динамикалық</string>
<string name="reader_zoom_buttons_summary">Ұлғайту батырмасын астыңғы оң жақта көрсету я көрсетпеу</string>
<string name="pages_cache">Бет кәші</string>
<string name="reset">Арылту</string>
<string name="tracker_wifi_only_summary">Шектеулі желі қосылымы болса жаңа тарау қолжетімдігін тексермеу</string>
<string name="order_added">Қосылды</string>
<string name="enable_logging">Логтауды қосу</string>
<string name="on_device">Құрылғыда</string>
<string name="password">Құпиясөз</string>
<string name="download_option_whole_manga">Маңганы толықтай</string>
<string name="settings_apply_restart_required">Өзгерту іске қосылуы үшін қолданбаны өшіріп қосыңыз</string>
<string name="source_disabled">Дереккөз сөніп жатыр</string>
<string name="backup_frequency">Сақтық көшірмесінің жиілігі</string>
<string name="data_and_privacy">Дерек пен құпиялық</string>
<string name="clear_cookies_summary">Қате болса көмектесе алады. Түгел тіркелгінің күші жойылады</string>
<string name="enable_logging_summary">Кей әрекетті түзеуге сақтау қою. Не екенін білмесеңіз қоспаңыз</string>
<string name="clear_source_cookies_summary">Осы дәмейіннің кукиін тазалау. Көп жағдайда тіркелгіден шығылып кетеді</string>
<string name="history_shortcuts">Соңғы маңганың таңбашасын көрсету</string>
<string name="downloads_wifi_only_summary">Ұялы желіге көшкенде жүктеп алуды тоқтату</string>
<string name="suggest_new_sources">Қолданбаны жаңартқан соң жаңа дереккөз ұсыну</string>
<string name="import_completed">Импорт аяқталды</string>
<string name="different_languages">Әртүрлі тіл</string>
<string name="user_agent">UserAgent басы</string>
<string name="ignore_ssl_errors">SSL қатеге мән бермеу</string>
<string name="reader_info_bar">Оқымада ақпар тақтасын көрсету</string>
<string name="periodic_backups_enable">Сақтық көшірмесін кезең-кезеңімен жасауды қосу</string>
<string name="server_address">Сербір адресі</string>
<string name="moved_to_top">Үстіге жылжыды</string>
<string name="explore">Қарау</string>
<string name="find_similar">Ұқсасын табу</string>
<string name="storage_usage">Жадты қолдану</string>
<string name="data_not_restored_text">Дұрыс сақтық көшірме файылын таңдағаныңызды тексеріңіз</string>
<string name="theme_name_sakura">Сакура</string>
<string name="view_list">Тізімді көру</string>
<string name="unknown">Белгісіз</string>
<string name="in_progress">Оқылып жатыр</string>
<string name="download_option_manual_selection">Тарауды таңдап шығу</string>
<string name="enhanced_colors_summary">Түс ыдырауын азайтады, бірақ өнімділікке әсер ете алады</string>
<string name="importing_manga">Маңга импорттау</string>
<string name="pause">Үзіліс</string>
<string name="clear_new_chapters_counters">Жаңа тарау ақпарын да жою</string>
<string name="nothing_here">Мұнда түк жоқ</string>
<string name="remove_completed">Дайынын жою</string>
<string name="items_limit_exceeded">Елемент сыймайды</string>
<string name="frequency_every_2_days">Екі күн сайын</string>
<string name="suggestions_notifications_summary">Анда-санда мәлімдеме арқылы маңга ұсынып тұру</string>
<string name="invalid_value_message">Жарамсыз өлшем</string>
<string name="downloads_cancelled">Жүктеп алу доғарылды</string>
<string name="webtoon_zoom">Уебтүнді ұлғайту</string>
<string name="theme_name_miku">Мику</string>
<string name="data_not_restored">Дерек қалыпқа келмеді</string>
<string name="directories">Каталог</string>
<string name="local_manga_directories">Жергілікті маңга тізімі</string>
<string name="manage_categories">Санатты реттеу</string>
<string name="scrobbling_empty_hint">Оқу прогрессін бақылау үшін маңга ақпар экранындағы «Мәзірге» өтіп, «Бақылау» батырмасын басыңыз.</string>
<string name="color_light">Жарық</string>
<string name="web_view_unavailable">WebView қолжетімсіз: WebView провайдері орнатылғанын тексеріп көріңіз</string>
<string name="port">Порт</string>
<string name="color_correction_hint">Таңдалған түс баптауы осы маңга үшін сақталып тұрады</string>
<string name="not_found_404">Контент табылмады, жойылған-мыс</string>
<string name="got_it">Ұқтым</string>
<string name="type">Түрі</string>
<string name="search_hint">Маңга атын, жанрын я дереккөз атауын жазыңыз</string>
<string name="frequency_once_per_week">Апта сайын</string>
<string name="description">Сипаттама</string>
<string name="periodic_backups">Сақтық көшірмесін кезең-кезеңімен жасау</string>
<string name="reader_zoom_buttons">Ұлғайту батырмасын көрсету</string>
<string name="sources_reorder_tip">Ретін өзгерту үшін елементті басып тұрыңыз</string>
<string name="resume">Жалғастыру</string>
<string name="server_error">Сербір қуып тұр (%1$d). Сәлден соң қайталап көріңіз</string>
<string name="images_proxy_title">Суретті оңтайлау проксиі</string>
<string name="network_unavailable_hint">Маңганы онлайн оқу үшін Wi-Fi-ды я ұялы желіні қосыңыз</string>
<string name="no_manga_sources">Маңға дереккөзі жоқ</string>
<string name="username">Қолданушы аты</string>
<string name="frequency_twice_per_month">Екі апта сайын</string>
<string name="prefetch_content">Алдын-ала жүктей беру</string>
<string name="main_screen_sections">Басты экран бөлімдері</string>
<string name="confirm_exit">Шығу үшін тағы бір рет басыңыз</string>
<string name="advanced">Қосымша баптау</string>
<string name="sync_settings">Үйлестіру баптауы</string>
<string name="online_variant">Онлайн нұсқа</string>
<string name="download_option_all_unread_b">Түгел оқылмаған тарау (%s)</string>
<string name="authorization_optional">Кіру (міндетті емес)</string>
<string name="color_dark">Күңгірт</string>
<string name="other_cache">Басқа кәш</string>
<string name="show_suspicious_content">Күмәнді контентті көрсету</string>
<string name="reader_info_bar_summary">Экранның үстінгі жағында уақыт пен оқу прогрессін көрсету</string>
<string name="allow_unstable_updates_summary">Тұрақсыз нұсқа туралы мәлімдеме алу</string>
<string name="translations">Аударма</string>
<string name="comics_archive_import_description">Бір не одан көп .cbz я .zip файлын таңдай аласыз, әр файыл бөлек маңга болып анықталады.</string>
<string name="downloads_paused">Жүктеп алу тоқтап қалды</string>
<string name="too_many_requests_message">Тым көп сұрату. Біраздан соң қайталап көріңіз</string>
<string name="downloads_wifi_only">Wi-Fi арқылы ғана жүктеу</string>
<string name="cancel_all_downloads_confirm">Белсенді жүктеудің бәрі жойылып, жартылай жүктелгендер жоғалып кетеді</string>
<string name="by_relevance">Өзектілігі</string>
<string name="related_manga">Ұқсас маңга</string>
<string name="discard">Сақтамау</string>
<string name="saved_manga">Сақталған маңга</string>
<string name="manga_error_description_pattern">Қате мәліметі:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Дереккөзде бар ма екенін тексеру үшін &lt;a href=%2$s&gt;маңганы уеб браузер арқылы&lt;/a&gt; ашып көріңіз&lt;br&gt;2. &lt;a href=kotatsu://about&gt;Kotatsu-ның ең соңғы нұсқасын&lt;/a&gt;&lt;br&gt;3 қолданып отырсыз ба, тексеріп көріңіз. Мүмкін болса: әзірлеушіге қате туралы шағып жіберіңіз.</string>
<string name="state_abandoned">Тасталған</string>
<string name="history_shortcuts_summary">Қолданба таңбасын ұзақ басып тұрғанда соңғы маңганы қолжетімді ету</string>
<string name="automatic_scroll">Өздігінен парақтау</string>
<string name="download_option_first_n_chapters">Бірінші %s</string>
<string name="keep_screen_on">Экранды сөндірмеу</string>
<string name="paused">Тоқтап тұр</string>
<string name="text_downloads_list_holder">Жүктеп алғаныңыз жоқ</string>
<string name="color_correction">Түс реттеу</string>
<string name="invalid_port_number">Порттың нөмірі қате</string>
<string name="suggestions_wifi_only_summary">Шектеулі желі қосылымы болса ұсынысты жаңартпау</string>
<string name="webtoon_zoom_summary">Уебтүн режімінде ұлғайту ымына рұқсат ету</string>
<string name="categories_delete_confirm">Таңдаулы санатты шынымен жойғыңыз келе ме\?
\nІшіндегі бар маңга жойылады, сосын оны қайтарып ала алмайсыз.</string>
<string name="frequency_once_per_month">Ай сайын</string>
<string name="reader_info_pattern">%1$d/%2$d-тарау %3$d/%4$d-бет</string>
<string name="contrast">Көреғарлық</string>
<string name="network">Желі</string>
<string name="reader_slider">Парақтау жүгірткісін көрсету</string>
<string name="no_manga_sources_text">Онлайн оқу үшін маңга дереккөзін қосыңыз</string>
<string name="downloaded">Жүктеп алынған</string>
<string name="options">Басқа</string>
<string name="services">Қызмет</string>
<string name="suggestions_enable_prompt">Сізге арналған маңга ұсынысын алғыңыз келе ме\?</string>
<string name="exit_confirmation">Шығуды растау</string>
<string name="comics_archive">Комикс мұрағаты</string>
<string name="custom_directory">Жеке каталог</string>
<string name="more">Тағы</string>
<string name="theme_name_asuka">Асука</string>
<string name="address">Адресі</string>
<string name="import_will_start_soon">Импорт қазір басталады</string>
<string name="compact">Жинақы</string>
<string name="enhanced_colors">32-биттік түс режімі</string>
<string name="folder_with_images_import_description">Мұрағат я сурет каталогын таңдай аласыз. Әр мұрағат (яки ішкі каталог) бір тарау деп анықталады.</string>
<string name="reorder">Ретін өзгерту</string>
<string name="default_section">Әдепкі бөлім</string>
<string name="background">Ая</string>
<string name="feed">Лек</string>
<string name="speed_value">x%.1f</string>
<string name="downloads_removed">Жүктеп алғаныңыз жойылды</string>
<string name="pages_animation_summary">Парақтау анимасасы</string>
<string name="no_access_to_file">Бұл файлға я каталогқа рұқсатыңыз жоқ</string>
<string name="mark_as_current">Қазіргі деп белгілеу</string>
<string name="random">Кездейсоқ</string>
<string name="mirror_switching">Айнаны өздігінен таңдау</string>
<string name="restore_summary">Бұған дейін жасалған сақтық көшірмесін қалпына келтіру</string>
<string name="show_pages_numbers_summary">Бет нөмірін төменгі жақта көрсету</string>
<string name="show_in_grid_view">Кесте қып көрсету</string>
<string name="zoom_out">Кішірейту</string>
<string name="keep_screen_on_summary">Маңга оқып отырғанда экранды сөндірмеу</string>
<string name="download_option_next_unread_n_chapters">Келесі оқылмаған %s</string>
<string name="clear_network_cache">Желі кәшін тазалау</string>
<string name="voice_search">Дауыс іздеуі</string>
<string name="enable">Қосу</string>
<string name="import_completed_hint">Орын үнемдеу үшін түпнұсқа файлды құрылғыдан жойып тастай аласыз</string>
<string name="theme_name_rikka">Рикка</string>
<string name="reader_control_ltr_summary">Оң жақты я оң жақ батырманы басқан сайын бет ауысады</string>
<string name="language">Тіл</string>
<string name="incognito_mode">Инкогнито режімі</string>
<string name="no_bookmarks_summary">Оқып жатқан маңгаға бетбелгі жасай аласыз</string>
<string name="images_procy_description">Трафик қолдануды азайтып, сурет жүктеп алуды тездету үшін wsrv.nl қызметін қолданыңыз</string>
<string name="theme_name_mamimi">Мамими</string>
<string name="manga_branch_title_template">%1$s (%2$s)</string>
<string name="manage">Реттеу</string>
<string name="manga_list">Маңга тізімі</string>
<string name="reader_control_ltr">Оқыманы эргономді басқару</string>
<string name="disable_nsfw">ҰЯТСЫЗ-ды өшіру</string>
<string name="last_successful_backup">Соңғы сақтық көшірме: %s</string>
<string name="available">Қолжетімді</string>
<string name="color_white">Ақ</string>
<string name="network_unavailable">Желі қолжетімсіз</string>
<string name="empty">Бос</string>
<string name="downloads_resumed">Жүктеп алу жалғасты</string>
<string name="details_button_tip">Қосымша реттеу көру үшін «Оқу» батырмасын басып тұрыңыз</string>
<string name="text_unsaved_changes_prompt">Өзгерісті сақтайсыз ба\?</string>
<string name="folder_with_images">Суреті бар бума</string>
<string name="to_top">Үстіге</string>
<string name="show">Көрсету</string>
<string name="show_on_shelf">Сөреде көрсету</string>
<string name="allow_unstable_updates">Тұрақсыз жаңартуды орнатуға рұқсат беру</string>
<string name="sync_auth_hint">Жаңа тіркелгі аша аласыз я жаңасын жасай аласыз</string>
<string name="theme_name_kanade">Канаде</string>
<string name="backups_output_directory">Сақтық көшірмесінің каталогы</string>
<string name="invert_colors">Түстерді терістету</string>
<string name="color_theme">Түс схемасы</string>
<string name="brightness">Ашықтық</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="exit_confirmation_summary">Шығып кету үшін «Кері» батырмасын екі рет басыңыз</string>
<string name="bookmarks_removed">Бетбелгілер жойылды</string>
<string name="theme_name_mion">Мион</string>
<string name="mirror_switching_summary">Дереккөз дәмейіннің қатесі пайда болып, айнасы қолжетімді болса, өздігінен соған ауыстыру</string>
<string name="no_bookmarks_yet">Бетбелгі жоқ</string>
<string name="no_thanks">Жоқ, рақмет</string>
<string name="suggest_new_sources_summary">Қолданбаның жаңа нұсқасында пайда болған дереккөзді ұсыну</string>
<string name="share_logs">Логты бөлісу</string>
<string name="speed">Жылдамдық</string>
<string name="download_option_all_chapters">%s аударған түгел тарау</string>
<string name="suggestion_manga">Ұсыныс: %s</string>
<string name="color_black">Қара</string>
<string name="removed_from_favourites">Таңдаулыдан жойылды</string>
<string name="this_month">Осы ай</string>
<string name="proxy">Прокси</string>
<string name="error_no_space_left">Жадта бос орын қалмады</string>
</resources>

View File

@@ -483,4 +483,25 @@
<string name="show">Mostrar</string>
<string name="color_black">Preto</string>
<string name="this_month">Esse mês</string>
<string name="frequency_every_day">Diariamente</string>
<string name="categories">Categorias</string>
<string name="list_options">Opções da lista</string>
<string name="backup_frequency">Frequência de criação de backup</string>
<string name="suggest_new_sources">Sugerir novas fontes após a atualização do aplicativo</string>
<string name="periodic_backups_enable">Ativar backups periódicos</string>
<string name="enhanced_colors_summary">Reduz a formação de faixas, mas pode afetar o desempenho</string>
<string name="frequency_every_2_days">A cada 2 dias</string>
<string name="frequency_once_per_week">Semanalmente</string>
<string name="periodic_backups">Backups periódicos</string>
<string name="frequency_twice_per_month">Duas vezes ao mês</string>
<string name="online_variant">Variante on-line</string>
<string name="by_relevance">Relevância</string>
<string name="state_abandoned">Abandonado</string>
<string name="keep_screen_on">Manter a tela ligada</string>
<string name="frequency_once_per_month">Uma vez por mês</string>
<string name="enhanced_colors">Modo de cor de 32 bits</string>
<string name="keep_screen_on_summary">Não desligue a tela enquanto estiver lendo mangá</string>
<string name="last_successful_backup">Último backup bem-sucedido: %s</string>
<string name="backups_output_directory">Local de saída de backups</string>
<string name="suggest_new_sources_summary">Solicitação para ativar fontes recém-adicionadas após a atualização do aplicativo</string>
</resources>

View File

@@ -41,11 +41,11 @@
<string name="remove">Remover</string>
<string name="_s_deleted_from_local_storage">«%s» deletado do armazenamento local</string>
<string name="save_page">Salvar página</string>
<string name="page_saved">Salvou</string>
<string name="page_saved">Salvo</string>
<string name="share_image">Compartilhar imagem</string>
<string name="_import">Importar</string>
<string name="updated">Atualizado</string>
<string name="delete">Deletar</string>
<string name="delete">Delete</string>
<string name="operation_not_supported">Essa operação não é suportada</string>
<string name="clear_pages_cache">Limpar cache de página</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
@@ -66,10 +66,10 @@
<string name="internal_storage">Armazenamento interno</string>
<string name="external_storage">Armazenamento externo</string>
<string name="domain">Domínio</string>
<string name="app_update_available">Uma nova versão da app está disponível</string>
<string name="app_update_available">Uma nova versão do app está disponível</string>
<string name="open_in_browser">Abrir no navegador da web</string>
<string name="large_manga_save_confirm">Este mangá tem %s. Salvar tudo isso\?</string>
<string name="save_manga">Salve</string>
<string name="save_manga">Salvar</string>
<string name="notifications">Notificações</string>
<string name="new_chapters">Novos capítulos</string>
<string name="download">Download</string>
@@ -483,4 +483,15 @@
<string name="show">Mostrar</string>
<string name="color_black">Preto</string>
<string name="this_month">Este mês</string>
<string name="categories">Categorias</string>
<string name="list_options">Listar opções</string>
<string name="suggest_new_sources">Sugira novas fontes após atualização do app</string>
<string name="enhanced_colors_summary">Reduz faixas, mas pode afetar o desempenho</string>
<string name="online_variant">Variante online</string>
<string name="by_relevance">Relevância</string>
<string name="enhanced_colors">Modo de cor 32-bit</string>
<string name="suggest_new_sources_summary">Solicitar a ativação de fontes recém-adicionadas após atualizar o aplicativo</string>
<string name="state_abandoned">Caiu</string>
<string name="keep_screen_on">Manter a tela ligada</string>
<string name="keep_screen_on_summary">Não desligue a tela enquanto estiver lendo mangá</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="enhanced_colors">32-битный цветовой режим</string>
<string name="suggest_new_sources_summary">Предлагать источники манги, добавленные в последнем обновлении приложения</string>
<string name="online_variant">Онлайн вариант</string>
<string name="frequency_every_day">Каждый день</string>
<string name="backup_frequency">Частота создания резервных копий</string>
<string name="periodic_backups_enable">Включить резервное копирование по расписанию</string>
<string name="frequency_every_2_days">Каждые 2 дня</string>
<string name="frequency_once_per_week">Раз в неделю</string>
<string name="periodic_backups">Резервное копирование по расписанию</string>
<string name="frequency_twice_per_month">Дважды в месяц</string>
<string name="frequency_once_per_month">Один раз в месяц</string>
<string name="backups_output_directory">Каталог для сохранения резервных копий</string>
<string name="last_successful_backup">Последняя резервная копия: %s</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="light">สว่าง</string>
<string name="automatic">ตั้งค่าตามเครื่อง</string>
<string name="text_clear_history_prompt">จะเคลียร์ประวัติการอ่านทั้งหมดแบบถาวรใช่ไหม\?</string>
@@ -160,7 +160,7 @@
<string name="zoom_mode_fit_center">พอดีตรงกลาง</string>
<string name="zoom_mode_fit_height">พอดีกับความสูง</string>
<string name="black_dark_theme">ดำ</string>
<string name="just_now">เมื่อี้</string>
<string name="just_now">เมื่อเร็วนี้</string>
<string name="clear_feed">เคลียร์ฟีด</string>
<string name="backup_restore">สำรองและคืนค่า</string>
<string name="create_backup">สร้างข้อมูลสำรอง</string>
@@ -344,4 +344,29 @@
<string name="exit_confirmation_summary">กดย้อนกลับสองครั้งเพื่อออกจากแอป</string>
<string name="bookmarks_removed">ลบบุ๊คมาร์กแล้ว</string>
<string name="error_no_space_left">ไม่มีพื้นที่เหลือบนอุปกรณ์แล้ว</string>
<string name="zoom_in">ซูมเข้า</string>
<string name="detect_reader_mode">โหมดตรวจจับเครื่องอ่านอัตโนมัติ</string>
<string name="frequency_every_day">ทุกวัน</string>
<string name="categories">หมวดหมู่</string>
<string name="backup_frequency">ความถี่การสำรองข้อมูล</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d of %2$d on</string>
<string name="unknown">ไม่ทราบ</string>
<string name="frequency_every_2_days">ทุก 2 วัน</string>
<string name="frequency_once_per_week">สัปดาห์ละครั้ง</string>
<string name="reader_zoom_buttons">โชว์ปุ่มซูม</string>
<string name="select_range">เลือกช่วง</string>
<string name="no_manga_sources">ไม่พบแหล่งมังงะ</string>
<string name="frequency_twice_per_month">สองครั้งต่อเดือน</string>
<string name="keep_screen_on">เปิดหน้าจอไว้ตลอด</string>
<string name="categories_delete_confirm">คุณแน่ใจหรือไม่ว่าต้องการลบหมวดหมู่รายการโปรดที่เลือก
\nมังงะทั้งหมดในนั้นจะหายไปและไม่สามารถยกเลิกได้</string>
<string name="frequency_once_per_month">เดือนละครั้ง</string>
<string name="no_manga_sources_text">เปิดใช้งานแหล่งมังงะเพื่ออ่านมังงะออนไลน์</string>
<string name="enhanced_colors">โหมดสี 32 บิต</string>
<string name="background">พื้นหลัง</string>
<string name="speed_value">x%.1f</string>
<string name="use_fingerprint">ใช้สแกนลายนิ้วมือหากมี</string>
<string name="zoom_out">ซูมออก</string>
<string name="text_search_holder_secondary">โปรดลองเรียบเรียงคำค้นหา</string>
<string name="last_successful_backup">สำรองข้อมูลสำเร็จล่าสุด: %s</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="list_options">Seçenekleri listele</string>
<string name="online_variant">Çevrimiçi varyant</string>
<string name="by_relevance">Alaka düzeyi</string>
<string name="frequency_every_day">Her gün</string>
<string name="backup_frequency">Yedek oluşturma sıklığı</string>
<string name="periodic_backups_enable">Zamanlı yedeklemeleri etkinleştirin</string>
<string name="frequency_every_2_days">2 günde 1</string>
<string name="frequency_once_per_week">Haftada 1 kez</string>
<string name="periodic_backups">Zamanlı yedekleme</string>
<string name="frequency_twice_per_month">Ayda 2 kere</string>
<string name="frequency_once_per_month">Ayda 1 kere</string>
<string name="backups_output_directory">Yedekleme dizini</string>
<string name="speed_value">x%.1f</string>
<string name="last_successful_backup">Son başarılı yedekleme: %s</string>
</resources>

View File

@@ -494,4 +494,15 @@
<string name="enhanced_colors">32-бітний колірний режим</string>
<string name="suggest_new_sources_summary">Пропонує включити нові джерела манґи після оновлення застосунку</string>
<string name="online_variant">Онлайн варіант</string>
<string name="frequency_every_day">Кожен день</string>
<string name="backup_frequency">Частота резервного копіювання</string>
<string name="periodic_backups_enable">Увімкніть періодичне резервне копіювання</string>
<string name="frequency_every_2_days">Кожні 2 дні</string>
<string name="frequency_once_per_week">Раз на тиждень</string>
<string name="periodic_backups">Періодичне резервне копіювання</string>
<string name="frequency_twice_per_month">Двічі на місяць</string>
<string name="frequency_once_per_month">Раз на місяць</string>
<string name="last_successful_backup">Останнє успішне резервне копіювання: %s</string>
<string name="backups_output_directory">Вихідний каталог резервних копій</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -1,18 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="new_chapters">
<item quantity="other">%1$d chương mới</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d chương</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d phút trước</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d giờ trước</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
</resources>
<plurals name="new_chapters">
<item quantity="other">%1$d chương mới</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d chương</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d phút trước</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d giờ trước</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
<plurals name="items">
<item quantity="other">%1$d mục</item>
</plurals>
<plurals name="months_ago">
<item quantity="other">%1$d tháng trước</item>
</plurals>
</resources>

View File

@@ -407,7 +407,7 @@
<string name="local_manga_processing">Xử lý các truyện được lưu</string>
<string name="text_delete_local_manga_batch">Xoá vĩnh viễn những thứ được chọn khỏi thiết bị\?</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="disable_battery_optimization_summary">Giúp kiểm tra chương mới dưới nền dễ dàng hơn</string>
<string name="disable_battery_optimization_summary">Giúp kiểm tra chương mới trong khi chạy dưới nền dễ dàng hơn</string>
<string name="show_on_shelf">Hiển thị trên kệ sách</string>
<string name="mirror_switching">Tự động chọn máy chủ dự phòng</string>
<string name="mirror_switching_summary">Tự động chuyển tên miền sang máy chủ dự phòng (nếu có) nếu gặp lỗi trên tên miền chính</string>

View File

@@ -17,16 +17,16 @@
<string name="silent">无声</string>
<string name="preparing_">准备…</string>
<string name="file_not_found">未找到文件</string>
<string name="yesterday"></string>
<string name="yesterday"></string>
<string name="backup_information">你可以创建你的历史和收藏的备份并恢复它</string>
<string name="just_now">刚刚</string>
<string name="long_ago">很久以前</string>
<string name="group">分组</string>
<string name="tap_to_try_again">击重试</string>
<string name="tap_to_try_again">击重试</string>
<string name="reader_mode_hint">所选配置将被这部漫画记住</string>
<string name="captcha_required">需要验证码</string>
<string name="captcha_solve">解决</string>
<string name="today"></string>
<string name="today"></string>
<string name="clear_cookies">清除cookies</string>
<string name="new_sources_text">有新的漫画源可用</string>
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
@@ -79,7 +79,7 @@
<string name="clear">清除</string>
<string name="text_clear_history_prompt">永久清除所有阅读历史\?</string>
<string name="remove">删除</string>
<string name="_s_deleted_from_local_storage">\"%s\"从本地存储中删除</string>
<string name="_s_deleted_from_local_storage">从本地存储中删除“%s”</string>
<string name="save_page">保存页面</string>
<string name="page_saved">保存</string>
<string name="share_image">分享图片</string>
@@ -110,8 +110,8 @@
<string name="internal_storage">内部存储</string>
<string name="external_storage">外部存储</string>
<string name="domain">范围</string>
<string name="app_update_available">新版本应用程序已经推出</string>
<string name="open_in_browser">网络浏览器中打开</string>
<string name="app_update_available">发现新版本</string>
<string name="open_in_browser">在浏览器中打开</string>
<string name="large_manga_save_confirm">这部漫画有 %s 。全部保存?</string>
<string name="save_manga">保存</string>
<string name="notifications">通知</string>
@@ -122,7 +122,7 @@
<string name="light_indicator">LED指示器</string>
<string name="vibration">振动</string>
<string name="favourites_categories">收藏分类</string>
<string name="remove_category"></string>
<string name="remove_category"></string>
<string name="text_empty_holder_primary">这里有点空…</string>
<string name="text_search_holder_secondary">尝试重新表述查询。</string>
<string name="text_history_holder_primary">你看过的内容将在这里显示</string>
@@ -146,9 +146,9 @@
<string name="new_version_s">新版本: %s</string>
<string name="clear_updates_feed">清除订阅更新记录</string>
<string name="updates_feed_cleared">已清除</string>
<string name="rotate_screen">旋转屏幕</string>
<string name="rotate_screen">屏幕旋转</string>
<string name="update">更新</string>
<string name="feed_will_update_soon">订阅更新即将开始</string>
<string name="feed_will_update_soon">即将开始更新订阅</string>
<string name="track_sources">查找更新</string>
<string name="dont_check">不要检查</string>
<string name="enter_password">输入密码</string>
@@ -160,7 +160,7 @@
<string name="about">关于</string>
<string name="app_version">版本%s</string>
<string name="check_for_updates">检查更新</string>
<string name="no_update_available">没有更新</string>
<string name="no_update_available">无可用更新</string>
<string name="right_to_left">从右到左</string>
<string name="create_category">新分类</string>
<string name="scale_mode">缩放模式</string>
@@ -245,7 +245,7 @@
<string name="bookmark_added">书签已添加</string>
<string name="undo">撤销</string>
<string name="removed_from_history">从历史中删除</string>
<string name="dns_over_https">DNS over HTTPS</string>
<string name="dns_over_https">基于 HTTPS 的 DNS</string>
<string name="default_mode">默认模式</string>
<string name="detect_reader_mode">自动检测阅读器模式</string>
<string name="detect_reader_mode_summary">自动检测漫画是否为条漫</string>
@@ -369,7 +369,7 @@
<string name="allow_unstable_updates">允许不稳定更新</string>
<string name="allow_unstable_updates_summary">接收不稳定版本更新的通知</string>
<string name="download_started">下载已开始</string>
<string name="user_agent">UserAgent 标</string>
<string name="user_agent">UserAgent 标</string>
<string name="settings_apply_restart_required">要应用这些更改请重启程序</string>
<string name="sources_reorder_tip">点击并长按项目排序</string>
<string name="got_it">知道了</string>
@@ -451,7 +451,7 @@
<string name="progress">阅读进度</string>
<string name="error_corrupted_file">无效数据回传或文件已损坏</string>
<string name="related_manga_summary">显示相关漫画。可能并不准确或缺失</string>
<string name="tracker_wifi_only_summary">使用计量网络时停止检查新章节</string>
<string name="tracker_wifi_only_summary">使用按流量计费的网络时停止检查新章节</string>
<string name="order_added">添加日期</string>
<string name="on_device">本地</string>
<string name="moved_to_top">移动到顶部</string>
@@ -468,7 +468,7 @@
<string name="advanced">高级</string>
<string name="color_dark">深色</string>
<string name="too_many_requests_message">请求次数过多,稍候再尝试</string>
<string name="suggestions_wifi_only_summary">使用计量网络时停止推荐漫画</string>
<string name="suggestions_wifi_only_summary">使用按流量计费的网络时停止推荐漫画</string>
<string name="default_section">默认栏目</string>
<string name="background">阅读背景色</string>
<string name="manga_list">漫画列表</string>
@@ -488,4 +488,17 @@
<string name="enhanced_colors_summary">减少色带,但可能会影响性能</string>
<string name="enhanced_colors">32位色彩模式</string>
<string name="suggest_new_sources_summary">应用更新后快速启用新增的漫画源</string>
<string name="frequency_every_day">每天一次</string>
<string name="categories">分类</string>
<string name="backup_frequency">备份频率</string>
<string name="periodic_backups_enable">定期备份</string>
<string name="view_list">查看列表</string>
<string name="frequency_every_2_days">每两天一次</string>
<string name="frequency_once_per_week">每周一次</string>
<string name="periodic_backups">自动备份</string>
<string name="frequency_twice_per_month">每月两次</string>
<string name="frequency_once_per_month">每月一次</string>
<string name="last_successful_backup">上次备份成功:%s</string>
<string name="backups_output_directory">备份保存路径</string>
<string name="download_option_all_chapters">所有已翻译的章节 %s</string>
</resources>

View File

@@ -1,51 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="themes">
<string-array name="themes" translatable="false">
<item>@string/automatic</item>
<item>@string/light</item>
<item>@string/dark</item>
</string-array>
<string-array name="reader_switchers">
<string-array name="reader_switchers" translatable="false">
<item>@string/taps_on_edges</item>
<item>@string/volume_buttons</item>
</string-array>
<string-array name="zoom_modes">
<string-array name="zoom_modes" translatable="false">
<item>@string/zoom_mode_fit_center</item>
<item>@string/zoom_mode_fit_height</item>
<item>@string/zoom_mode_fit_width</item>
<item>@string/zoom_mode_keep_start</item>
</string-array>
<string-array name="track_sources">
<string-array name="track_sources" translatable="false">
<item>@string/favourites</item>
<item>@string/history</item>
</string-array>
<string-array name="list_modes">
<string-array name="list_modes" translatable="false">
<item>@string/list</item>
<item>@string/detailed_list</item>
<item>@string/grid</item>
</string-array>
<string-array name="screenshots_policy">
<string-array name="screenshots_policy" translatable="false">
<item>@string/screenshots_allow</item>
<item>@string/screenshots_block_nsfw</item>
<item>@string/screenshots_block_all</item>
</string-array>
<string-array name="network_policy">
<string-array name="network_policy" translatable="false">
<item>@string/always</item>
<item>@string/only_using_wifi</item>
<item>@string/never</item>
</string-array>
<string-array name="doh_providers">
<string-array name="doh_providers" translatable="false">
<item>@string/disabled</item>
<item>Google</item>
<item>CloudFlare</item>
<item>AdGuard</item>
</string-array>
<string-array name="reader_modes">
<string-array name="reader_modes" translatable="false">
<item>@string/standard</item>
<item>@string/right_to_left</item>
<item>@string/webtoon</item>
</string-array>
<string-array name="scrobbling_statuses">
<string-array name="scrobbling_statuses" translatable="false">
<item>@string/status_planned</item>
<item>@string/status_reading</item>
<item>@string/status_re_reading</item>
@@ -53,25 +53,32 @@
<item>@string/status_on_hold</item>
<item>@string/status_dropped</item>
</string-array>
<string-array name="proxy_types">
<string-array name="proxy_types" translatable="false">
<item>@string/disabled</item>
<item>HTTP</item>
<item>SOCKS (v4/v5)</item>
</string-array>
<string-array name="reader_backgrounds">
<string-array name="reader_backgrounds" translatable="false">
<item>@string/system_default</item>
<item>@string/color_light</item>
<item>@string/color_dark</item>
<item>@string/color_white</item>
<item>@string/color_black</item>
</string-array>
<string-array name="reader_animation">
<string-array name="reader_animation" translatable="false">
<item>@string/disabled</item>
<item>@string/system_default</item>
<item>@string/advanced</item>
</string-array>
<string-array name="first_nav_item">
<string-array name="first_nav_item" translatable="false">
<item>@string/history</item>
<item>@string/favourites</item>
</string-array>
<string-array name="backup_frequency" translatable="false">
<item>@string/frequency_every_day</item>
<item>@string/frequency_every_2_days</item>
<item>@string/frequency_once_per_week</item>
<item>@string/frequency_twice_per_month</item>
<item>@string/frequency_once_per_month</item>
</string-array>
</resources>

View File

@@ -64,4 +64,11 @@
<item>0</item>
<item>1</item>
</string-array>
<string-array name="values_backup_frequency" translatable="false">
<item>1</item>
<item>2</item>
<item>7</item>
<item>14</item>
<item>30</item>
</string-array>
</resources>

View File

@@ -500,4 +500,15 @@
<string name="by_relevance">Relevance</string>
<string name="categories">Categories</string>
<string name="online_variant">Online variant</string>
<string name="periodic_backups">Periodic backups</string>
<string name="backup_frequency">Backup creation frequency</string>
<string name="frequency_every_day">Every day</string>
<string name="frequency_every_2_days">Every 2 days</string>
<string name="frequency_once_per_week">Once per week</string>
<string name="frequency_twice_per_month">Twice per month</string>
<string name="frequency_once_per_month">Once per month</string>
<string name="periodic_backups_enable">Enable periodic backups</string>
<string name="backups_output_directory">Backups output directory</string>
<string name="last_successful_backup">Last successful backup: %s</string>
<string name="speed_value">x%.1f</string>
</resources>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="backup_periodic"
android:layout="@layout/preference_toggle_header"
android:title="@string/periodic_backups_enable" />
<ListPreference
android:defaultValue="7"
android:dependency="backup_periodic"
android:entries="@array/backup_frequency"
android:entryValues="@array/values_backup_frequency"
android:key="backup_periodic_freq"
android:title="@string/backup_frequency"
app:useSimpleSummaryProvider="true" />
<Preference
android:dependency="backup_periodic"
android:key="backup_periodic_output"
android:title="@string/backups_output_directory" />
<Preference
android:dependency="backup_periodic"
android:icon="@drawable/ic_info_outline"
android:key="backup_periodic_last"
android:persistent="false"
android:selectable="false"
app:allowDividerAbove="true"
app:isPreferenceVisible="false" />
</androidx.preference.PreferenceScreen>

View File

@@ -34,6 +34,12 @@
android:summary="@string/restore_summary"
android:title="@string/restore_backup" />
<Preference
android:fragment="org.koitharu.kotatsu.settings.backup.PeriodicalBackupSettingsFragment"
android:key="backup_periodic"
android:persistent="false"
android:title="@string/periodic_backups" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/storage_usage">

View File

@@ -5,9 +5,9 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:8.1.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.20'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48.1'
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.10-1.0.13'
classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.20-RC2-1.0.13'
}
}