Compare commits

...

17 Commits

Author SHA1 Message Date
9b0dc8b413 Fix hiding FastScroller 2022-09-08 15:53:16 +03:00
f43fe5830e Fix crashes 2022-09-08 15:39:12 +03:00
ce616b328c Update dependencies 2022-09-08 14:46:27 +03:00
824a8ff97a Merge branch 'master' into devel 2022-09-08 14:42:01 +03:00
no
ef9018b92f Translated using Weblate (Turkish)
Currently translated at 90.2% (350 of 388 strings)

Co-authored-by: no <cbasroyalasroyal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-09-08 14:38:29 +03:00
J. Lavoie
a088d10935 Translated using Weblate (French)
Currently translated at 100.0% (388 of 388 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2022-09-08 14:38:29 +03:00
2e561697ac Update parsers 2022-09-07 14:07:48 +03:00
d242acd502 Sort local list by manga name 2022-09-03 15:57:48 +03:00
d37b44d3f6 Update parsers 2022-09-03 15:56:16 +03:00
6243fc88c9 Sorting local manga #219 #217 2022-09-03 15:45:06 +03:00
Neko Nekowazarashi
f74e865b06 Translated using Weblate (Indonesian)
Currently translated at 91.7% (356 of 388 strings)

Co-authored-by: Neko Nekowazarashi <kodra@nekoweb.my.id>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
no
1a2bc02188 Translated using Weblate (Turkish)
Currently translated at 88.9% (345 of 388 strings)

Co-authored-by: no <cbasroyalasroyal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
b0153e9f61 Translated using Weblate (Russian)
Currently translated at 100.0% (388 of 388 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (388 of 388 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
J. Lavoie
7f3fc1b88a Translated using Weblate (French)
Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
Zero O
40bf0c75bf Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (377 of 377 strings)

Added translation using Weblate (Chinese (Simplified))

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: Zero O <godarms2010@live.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-09-03 13:45:59 +03:00
Dpper
9407bd205c Translated using Weblate (Ukrainian)
Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
kuragehime
3317c8dddc Translated using Weblate (Japanese)
Currently translated at 100.0% (377 of 377 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-09-03 13:45:59 +03:00
24 changed files with 466 additions and 59 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 33
versionCode 494 versionCode 496
versionName '4.0-a5' versionName '4.0-a7'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -82,7 +82,7 @@ afterEvaluate {
} }
} }
dependencies { dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:f112a06ab6') { implementation('com.github.KotatsuApp:kotatsu-parsers:b3a9c5fcda') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
@@ -102,7 +102,7 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04' implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.7.0-beta01' implementation 'com.google.android.material:material:1.7.0-rc01'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1' kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
@@ -122,10 +122,9 @@ dependencies {
implementation 'androidx.hilt:hilt-work:1.0.0' implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'io.coil-kt:coil-base:2.2.0' implementation 'io.coil-kt:coil-base:2.2.1'
implementation 'io.coil-kt:coil-svg:2.2.0' implementation 'io.coil-kt:coil-svg:2.2.1'
// implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.github.KotatsuApp:subsampling-scale-image-view:fc92ecb766'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:2942b797a2'
implementation 'com.github.solkin:disk-lru-cache:1.4' implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'ch.acra:acra-http:5.9.6' implementation 'ch.acra:acra-http:5.9.6'
@@ -145,7 +144,7 @@ dependencies {
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'androidx.room:room-testing:2.4.3' androidTestImplementation 'androidx.room:room-testing:2.4.3'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.13.0' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2' androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.43.2' kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.43.2'

View File

@@ -12,7 +12,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.ActionBarContextView import androidx.appcompat.widget.ActionBarContextView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@@ -92,7 +91,8 @@ abstract class BaseActivity<B : ViewBinding> :
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_VOLUME_UP) { // TODO remove
ActivityCompat.recreate(this) // ActivityCompat.recreate(this)
TODO("Test error")
return true return true
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)

View File

@@ -33,6 +33,7 @@ class ScrollbarAnimator(
.animate() .animate()
.translationX(0f) .translationX(0f)
.alpha(1f) .alpha(1f)
.setListener(null)
.setDuration(animationDuration) .setDuration(animationDuration)
} }

View File

@@ -6,6 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.browser.BrowserActivity
@@ -17,9 +20,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.utils.TaggedActivityResult import org.koitharu.kotatsu.utils.TaggedActivityResult
import org.koitharu.kotatsu.utils.isSuccess import org.koitharu.kotatsu.utils.isSuccess
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ExceptionResolver private constructor( class ExceptionResolver private constructor(
private val activity: FragmentActivity?, private val activity: FragmentActivity?,
@@ -65,7 +65,7 @@ class ExceptionResolver private constructor(
cont.invokeOnCancellation { cont.invokeOnCancellation {
continuations.remove(CloudFlareDialog.TAG, cont) continuations.remove(CloudFlareDialog.TAG, cont)
fm.clearFragmentResultListener(CloudFlareDialog.TAG) fm.clearFragmentResultListener(CloudFlareDialog.TAG)
dialog.dismiss() dialog.dismissAllowingStateLoss()
} }
} }
} }
@@ -94,4 +94,4 @@ class ExceptionResolver private constructor(
fun canResolve(e: Throwable) = getResolveStringId(e) != 0 fun canResolve(e: Throwable) = getResolveStringId(e) != 0
} }
} }

View File

@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
// Limits to avoid TransactionTooLargeException // Limits to avoid TransactionTooLargeException
private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size
private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe private const val MAX_SAFE_CHAPTERS_COUNT = 32 // this is 100% safe
class ParcelableManga( class ParcelableManga(
val manga: Manga, val manga: Manga,

View File

@@ -4,7 +4,6 @@ import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.media.ThumbnailUtils
import android.os.Build import android.os.Build
import android.util.Size import android.util.Size
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
@@ -15,6 +14,8 @@ import androidx.core.graphics.drawable.IconCompat
import androidx.room.InvalidationTracker import androidx.room.InvalidationTracker
import coil.ImageLoader import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Precision
import coil.size.Scale
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@@ -112,13 +113,14 @@ class ShortcutsUpdater @Inject constructor(
private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder { private suspend fun buildShortcutInfo(manga: Manga): ShortcutInfoCompat.Builder {
val icon = runCatching { val icon = runCatching {
val bmp = coil.execute( coil.execute(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(manga.coverUrl) .data(manga.coverUrl)
.size(iconSize.width, iconSize.height) .size(iconSize.width, iconSize.height)
.precision(Precision.EXACT)
.scale(Scale.FILL)
.build(), .build(),
).requireBitmap() ).requireBitmap()
ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0)
}.fold( }.fold(
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) }, onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) }, onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },

View File

@@ -21,6 +21,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.network.DoHProvider import org.koitharu.kotatsu.core.network.DoHProvider
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getEnumValue import org.koitharu.kotatsu.utils.ext.getEnumValue
import org.koitharu.kotatsu.utils.ext.observe import org.koitharu.kotatsu.utils.ext.observe
import org.koitharu.kotatsu.utils.ext.putEnumValue import org.koitharu.kotatsu.utils.ext.putEnumValue
@@ -208,6 +209,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val dnsOverHttps: DoHProvider val dnsOverHttps: DoHProvider
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE) get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
var localListOrder: SortOrder
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean { fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) { return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
NETWORK_ALWAYS -> true NETWORK_ALWAYS -> true
@@ -325,6 +330,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_READER_BAR = "reader_bar" const val KEY_READER_BAR = "reader_bar"
const val KEY_SHORTCUTS = "dynamic_shortcuts" const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr" const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
const val KEY_LOCAL_LIST_ORDER = "local_order"
// About // About
const val KEY_APP_UPDATE = "app_update" const val KEY_APP_UPDATE = "app_update"

View File

@@ -0,0 +1,48 @@
package org.koitharu.kotatsu.local.domain
import java.io.File
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
class LocalManga(
val manga: Manga,
val file: File,
) {
var createdAt: Long = -1L
get() {
if (field == -1L) {
field = file.lastModified()
}
return field
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as LocalManga
if (manga != other.manga) return false
if (file != other.file) return false
return true
}
override fun hashCode(): Int {
var result = manga.hashCode()
result = 31 * result + file.hashCode()
return result
}
}
fun Collection<LocalManga>.unwrap(): List<Manga> = map { it.manga }
fun LocalManga.isMatchesQuery(query: String): Boolean {
return manga.title.contains(query, ignoreCase = true) ||
manga.altTitle?.contains(query, ignoreCase = true) == true
}
fun LocalManga.containsTags(tags: Set<MangaTag>): Boolean {
return manga.tags.containsAll(tags)
}

View File

@@ -45,12 +45,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
} }
val list = getRawList() val list = getRawList()
if (query.isNotEmpty()) { if (query.isNotEmpty()) {
list.retainAll { x -> list.retainAll { x -> x.isMatchesQuery(query) }
x.title.contains(query, ignoreCase = true) ||
x.altTitle?.contains(query, ignoreCase = true) == true
}
} }
return list return list.unwrap()
} }
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> { override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
@@ -59,15 +56,17 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
} }
val list = getRawList() val list = getRawList()
if (!tags.isNullOrEmpty()) { if (!tags.isNullOrEmpty()) {
list.retainAll { x -> list.retainAll { x -> x.containsTags(tags) }
x.tags.containsAll(tags)
}
} }
@Suppress("NON_EXHAUSTIVE_WHEN_STATEMENT")
when (sortOrder) { when (sortOrder) {
SortOrder.ALPHABETICAL -> list.sortBy { it.title } SortOrder.ALPHABETICAL -> list.sortWith(compareBy(AlphanumComparator()) { x -> x.manga.title })
SortOrder.RATING -> list.sortBy { it.rating } SortOrder.RATING -> list.sortByDescending { it.manga.rating }
SortOrder.NEWEST,
SortOrder.UPDATED,
-> list.sortByDescending { it.createdAt }
} }
return list return list.unwrap()
} }
override suspend fun getDetails(manga: Manga) = when { override suspend fun getDetails(manga: Manga) = when {
@@ -235,9 +234,9 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
private fun CoroutineScope.getFromFileAsync( private fun CoroutineScope.getFromFileAsync(
file: File, file: File,
context: CoroutineContext, context: CoroutineContext,
): Deferred<Manga?> = async(context) { ): Deferred<LocalManga?> = async(context) {
runInterruptible { runInterruptible {
runCatching { getFromFile(file) }.getOrNull() runCatching { LocalManga(getFromFile(file), file) }.getOrNull()
} }
} }
@@ -283,7 +282,7 @@ class LocalMangaRepository @Inject constructor(private val storageManager: Local
locks.unlock(id) locks.unlock(id)
} }
private suspend fun getRawList(): ArrayList<Manga> { private suspend fun getRawList(): ArrayList<LocalManga> {
val files = getAllFiles() val files = getAllFiles()
return coroutineScope { return coroutineScope {
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM) val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)

View File

@@ -5,6 +5,7 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.appcompat.widget.PopupMenu
import androidx.core.net.toFile import androidx.core.net.toFile
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@@ -13,10 +14,11 @@ import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.ListSelectionController import org.koitharu.kotatsu.base.ui.list.ListSelectionController
import org.koitharu.kotatsu.list.ui.MangaListFragment import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.addMenuProvider
class LocalListFragment : MangaListFragment() { class LocalListFragment : MangaListFragment(), PopupMenu.OnMenuItemClickListener {
override val viewModel by viewModels<LocalListViewModel>() override val viewModel by viewModels<LocalListViewModel>()
@@ -30,6 +32,14 @@ class LocalListFragment : MangaListFragment() {
ImportDialogFragment.show(childFragmentManager) ImportDialogFragment.show(childFragmentManager)
} }
override fun onFilterClick(view: View?) {
super.onFilterClick(view)
val menu = PopupMenu(requireContext(), view ?: binding.recyclerView)
menu.inflate(R.menu.popup_order)
menu.setOnMenuItemClickListener(this)
menu.show()
}
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
@@ -53,6 +63,17 @@ class LocalListFragment : MangaListFragment() {
} }
} }
override fun onMenuItemClick(item: MenuItem): Boolean {
val order = when (item.itemId) {
R.id.action_order_new -> SortOrder.NEWEST
R.id.action_order_abs -> SortOrder.ALPHABETICAL
R.id.action_order_rating -> SortOrder.RATING
else -> return false
}
viewModel.setSortOrder(order)
return true
}
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) { private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
MaterialAlertDialogBuilder(context ?: return) MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.delete_manga) .setTitle(R.string.delete_manga)

View File

@@ -23,4 +23,4 @@ class LocalListMenuProvider(
else -> false else -> false
} }
} }
} }

View File

@@ -1,27 +1,31 @@
package org.koitharu.kotatsu.local.ui package org.koitharu.kotatsu.local.ui
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.IOException import java.io.IOException
import java.util.*
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
@@ -30,18 +34,24 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
class LocalListViewModel @Inject constructor( class LocalListViewModel @Inject constructor(
private val repository: LocalMangaRepository, private val repository: LocalMangaRepository,
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
settings: AppSettings, private val trackingRepository: TrackingRepository,
) : MangaListViewModel(settings) { private val settings: AppSettings,
) : MangaListViewModel(settings), ListExtraProvider {
val onMangaRemoved = SingleLiveEvent<Unit>() val onMangaRemoved = SingleLiveEvent<Unit>()
val sortOrder = MutableLiveData(settings.localListOrder)
private val listError = MutableStateFlow<Throwable?>(null) private val listError = MutableStateFlow<Throwable?>(null)
private val mangaList = MutableStateFlow<List<Manga>?>(null) private val mangaList = MutableStateFlow<List<Manga>?>(null)
private val selectedTags = MutableStateFlow<Set<MangaTag>>(emptySet())
private var refreshJob: Job? = null
override val content = combine( override val content = combine(
mangaList, mangaList,
createListModeFlow(), createListModeFlow(),
sortOrder.asFlow(),
selectedTags,
listError, listError,
) { list, mode, error -> ) { list, mode, order, tags, error ->
when { when {
error != null -> listOf(error.toErrorState(canRetry = true)) error != null -> listOf(error.toErrorState(canRetry = true))
list == null -> listOf(LoadingState) list == null -> listOf(LoadingState)
@@ -53,7 +63,10 @@ class LocalListViewModel @Inject constructor(
actionStringRes = R.string._import, actionStringRes = R.string._import,
), ),
) )
else -> list.toUi(mode) else -> buildList(list.size + 1) {
add(createHeader(list, tags, order))
list.toUi(this, mode, this@LocalListViewModel)
}
} }
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
@@ -63,14 +76,27 @@ class LocalListViewModel @Inject constructor(
watchDirectories() watchDirectories()
} }
override fun onUpdateFilter(tags: Set<MangaTag>) {
selectedTags.value = tags
onRefresh()
}
override fun onRefresh() { override fun onRefresh() {
launchLoadingJob(Dispatchers.Default) { val prevJob = refreshJob
refreshJob = launchLoadingJob(Dispatchers.Default) {
prevJob?.cancelAndJoin()
doRefresh() doRefresh()
} }
} }
override fun onRetry() = onRefresh() override fun onRetry() = onRefresh()
fun setSortOrder(value: SortOrder) {
sortOrder.value = value
settings.localListOrder = value
onRefresh()
}
fun delete(ids: Set<Long>) { fun delete(ids: Set<Long>) {
launchLoadingJob { launchLoadingJob {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@@ -93,7 +119,7 @@ class LocalListViewModel @Inject constructor(
private suspend fun doRefresh() { private suspend fun doRefresh() {
try { try {
listError.value = null listError.value = null
mangaList.value = repository.getList(0, null, null) mangaList.value = repository.getList(0, selectedTags.value, sortOrder.value)
} catch (e: Throwable) { } catch (e: Throwable) {
listError.value = e listError.value = e
} }
@@ -119,4 +145,46 @@ class LocalListViewModel @Inject constructor(
} }
} }
} }
private fun createHeader(mangaList: List<Manga>, selectedTags: Set<MangaTag>, order: SortOrder): ListHeader2 {
val tags = HashMap<MangaTag, Int>()
for (item in mangaList) {
for (tag in item.tags) {
tags[tag] = tags[tag]?.plus(1) ?: 1
}
}
val topTags = tags.entries.sortedByDescending { it.value }.take(6)
val chips = LinkedList<ChipsView.ChipModel>()
for ((tag, _) in topTags) {
val model = ChipsView.ChipModel(
icon = 0,
title = tag.title,
isCheckable = true,
isChecked = tag in selectedTags,
data = tag,
)
if (model.isChecked) {
chips.addFirst(model)
} else {
chips.addLast(model)
}
}
return ListHeader2(
chips = chips,
sortOrder = order,
hasSelectedTags = selectedTags.isNotEmpty(),
)
}
override suspend fun getCounter(mangaId: Long): Int {
return trackingRepository.getNewChaptersCount(mangaId)
}
override suspend fun getProgress(mangaId: Long): Float {
return if (settings.isReadingIndicatorsEnabled) {
historyRepository.getProgress(mangaId)
} else {
PROGRESS_NONE
}
}
} }

View File

@@ -8,6 +8,9 @@ import java.io.Serializable
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? { inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? {
if (!containsKey(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, T::class.java) getParcelable(key, T::class.java)
} else { } else {
@@ -17,6 +20,9 @@ inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T?
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? { inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? {
if (!hasExtra(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelableExtra(key, T::class.java) getParcelableExtra(key, T::class.java)
} else { } else {
@@ -26,6 +32,9 @@ inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String)
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? { inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? {
if (!containsKey(key)) {
return null
}
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getSerializable(key, T::class.java) getSerializable(key, T::class.java)
} else { } else {

View File

@@ -13,6 +13,7 @@ import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
@@ -22,6 +23,7 @@ import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
@AndroidEntryPoint
class ShelfConfigActivity : class ShelfConfigActivity :
BaseActivity<ActivityCategoriesBinding>(), BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<CategoryItem>, OnListItemClickListener<CategoryItem>,

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_order_new"
android:title="@string/newest" />
<item
android:id="@+id/action_order_abs"
android:title="@string/by_name" />
<item
android:id="@+id/action_order_rating"
android:title="@string/by_rating" />
</menu>

View File

@@ -51,7 +51,7 @@
<string name="scale_mode">Mode mise à l\'échelle</string> <string name="scale_mode">Mode mise à l\'échelle</string>
<string name="report_github">Signaler un problème sur GitHub</string> <string name="report_github">Signaler un problème sur GitHub</string>
<string name="create_category">Nouvelle catégorie</string> <string name="create_category">Nouvelle catégorie</string>
<string name="right_to_left">De droite à gauche (←)</string> <string name="right_to_left">De droite à gauche</string>
<string name="no_update_available">Aucune mise à jour disponible</string> <string name="no_update_available">Aucune mise à jour disponible</string>
<string name="update_check_failed">Échec de la recherche de mise à jour</string> <string name="update_check_failed">Échec de la recherche de mise à jour</string>
<string name="checking_for_updates">Recherche de mises à jour…</string> <string name="checking_for_updates">Recherche de mises à jour…</string>
@@ -320,4 +320,69 @@
<string name="invalid_domain_message">Domaine invalide</string> <string name="invalid_domain_message">Domaine invalide</string>
<string name="select_range">Sélectionner une plage</string> <string name="select_range">Sélectionner une plage</string>
<string name="not_found_404">Contenu non trouvé ou supprimé</string> <string name="not_found_404">Contenu non trouvé ou supprimé</string>
<string name="manga_error_description_pattern">Détails de l\'erreur :&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Essayez de &lt;a href=%2$s&gt;ouvrir le manga dans un navigateur web&lt;/a&gt; pour vous assurer qu\'il est disponible sur sa source&lt;br&gt;2. S\'il est disponible, envoyez un rapport d\'erreur aux développeurs.</string>
<string name="confirm_exit">Rappuyez sur Retour pour quitter</string>
<string name="categories_delete_confirm">Êtes-vous sûr·e de vouloir supprimer les catégories de favoris sélectionnées \?
\nTous les mangas qui s\'y trouvent seront perdus et ceci ne peut pas être annulé.</string>
<string name="exit_confirmation_summary">Appuyez deux fois sur la touche Retour pour quitter l\'appli</string>
<string name="available">Disponible</string>
<string name="exit_confirmation">Confirmation de sortie</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="feed">Flux</string>
<string name="enter_email_text">Entrez votre courriel pour continuer</string>
<string name="app_update_available_s">Mise à jour de l\'application disponible : %s</string>
<string name="importing_manga">Importation de mangas</string>
<string name="removed_from_favourites">Retiré des favoris</string>
<string name="clear_all_history">Effacer tout l\'historique</string>
<string name="last_2_hours">Les 2 dernières heures</string>
<string name="history_cleared">Historique effacé</string>
<string name="manage">Gérer</string>
<string name="no_bookmarks_yet">Aucun marque-page</string>
<string name="no_bookmarks_summary">Vous pouvez créer un marque-page pendant la lecture d\'un manga</string>
<string name="bookmarks_removed">Marque-pages supprimés</string>
<string name="no_manga_sources">Aucune source de mangas</string>
<string name="no_manga_sources_text">Autoriser les sources de mangas de lire des mangas en ligne</string>
<string name="random">Aléatoire</string>
<string name="reorder">Réordonner</string>
<string name="empty">Vide</string>
<string name="changelog">Journal des modifications</string>
<string name="explore">Explorer</string>
<string name="text_shelf_holder_primary">Vos mangas seront affichés ici</string>
<string name="text_shelf_holder_secondary">Trouvez ce que vous voulez lire dans la section « Explorer »</string>
<string name="canceled">Annulé</string>
<string name="account_already_exists">Le compte existe déjà</string>
<string name="back">Retour</string>
<string name="sync">Synchronisation</string>
<string name="sync_title">Synchronisez vos données</string>
<string name="email_enter_hint">Entrez votre courriel pour continuer</string>
<string name="saved_manga">Mangas sauvegardés</string>
<string name="pages_cache">Cache des pages</string>
<string name="other_cache">Autre cache</string>
<string name="storage_usage">Utilisation du stockage</string>
<string name="removed_from_s">Retiré de « %s »</string>
<string name="options">Options</string>
<string name="downloading_manga">Télécharger des mangas</string>
<string name="incognito_mode">Mode incognito</string>
<string name="no_chapters">Aucun chapitre</string>
<string name="automatic_scroll">Défilement automatique</string>
<string name="off_short">Désactivé</string>
<string name="seconds_pattern">%s s</string>
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
<string name="reader_info_bar">Afficher la barre d\'infos dans le lecteur</string>
<string name="comics_archive">Archives des BD</string>
<string name="folder_with_images">Dossier avec des images</string>
<string name="import_completed">Importation terminée</string>
<string name="import_completed_hint">Vous pouvez supprimer le fichier original du stockage pour gagner de l\'espace</string>
<string name="import_will_start_soon">L\'importation va bientôt commencer</string>
<string name="text_unsaved_changes_prompt">Vous avez des modifications non sauvegardées, voulez-vous les sauvegarder ou les abandonner \?</string>
<string name="discard">Abandonner</string>
<string name="history_shortcuts_summary">Rendre les mangas récents disponibles en appuyant longuement sur l\'icône de l\'application</string>
<string name="reader_control_ltr_summary">Taper sur le bord droit ou appuyer sur la touche droite permet toujours de passer à la page suivante</string>
<string name="reader_control_ltr">Contrôle ergonomique du lecteur</string>
<string name="history_shortcuts">Afficher les raccourcis des mangas récents</string>
<string name="color_correction">Correction des couleurs</string>
<string name="brightness">Luminosité</string>
<string name="contrast">Contraste</string>
<string name="reset">Réinitialiser</string>
<string name="color_correction_hint">Les paramètres de couleurs choisis seront sauvegardés pour ce manga</string>
</resources> </resources>

View File

@@ -326,4 +326,46 @@
<string name="removed_from_favourites">Dihapus dari favorit</string> <string name="removed_from_favourites">Dihapus dari favorit</string>
<string name="import_will_start_soon">Impor akan segera dimulai</string> <string name="import_will_start_soon">Impor akan segera dimulai</string>
<string name="not_found_404">Konten tidak ditemukan atau dihapus</string> <string name="not_found_404">Konten tidak ditemukan atau dihapus</string>
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string>
<string name="exit_confirmation">Konfirmasi keluar</string>
<string name="pages_cache">Tembolok halaman</string>
<string name="other_cache">Tembolok lainnya</string>
<string name="storage_usage">Penggunaan penyimpanan</string>
<string name="available">Tersedia</string>
<string name="incognito_mode">Mode Incognito</string>
<string name="app_update_available_s">Pembaruan aplikasi tersedia: %s</string>
<string name="automatic_scroll">Gulir otomatis</string>
<string name="comics_archive">Arsip komik</string>
<string name="folder_with_images">Folder dengan gambar</string>
<string name="import_completed_hint">Anda bisa menghapus berkas asli dari penyimpanan untuk menghemat ruang</string>
<string name="feed">Umpan</string>
<string name="manga_error_description_pattern">Detail galat:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Coba untuk &lt;a href=%2$s&gt;membuka manga di peramban web&lt;/a&gt; untuk memastikan bahwa itu tersedia di sumbernya&lt;br&gt;2. Jika tersedia, kirim laporan galat ke pengembang.</string>
<string name="brightness">Kecerahan</string>
<string name="contrast">Kontras</string>
<string name="reset">Atur Ulang</string>
<string name="color_correction_hint">Pengaturan warna yang dipilih akan diingat untuk manga ini</string>
<string name="text_unsaved_changes_prompt">Anda memiliki perubahan belum tersimpan, apakah Anda ingin menyimpannya atau membuangnya\?</string>
<string name="discard">Buang</string>
<string name="use_fingerprint">Gunakan sidik jari jika tersedia</string>
<string name="appwidget_shelf_description">Manga dari favorit Anda</string>
<string name="appwidget_recent_description">Manga yang baru-baru ini Anda baca</string>
<string name="data_deletion">Penghapusan data</string>
<string name="account_already_exists">Akun sudah ada</string>
<string name="back">Kembali</string>
<string name="disable_battery_optimization_summary">Membantu pemeriksaan pembaruan di latar belakang</string>
<string name="crash_text">Ada sesuatu yang salah. Mohon untuk mengirim laporan kutu (bug) ke pengembang untuk membantu kami memperbaikinya.</string>
<string name="report">Lapor</string>
<string name="exclude_nsfw_from_history_summary">Manga yang ditandai sebagai NSFW tidak akan ditambahkan ke riwayat dan progres Anda tidak akan disimpan</string>
<string name="clear_cookies_summary">Bisa membantu dalam beberapa masalah. Seluruh otorisasi akan menjadi tidak valid.</string>
<string name="manage">Kelola</string>
<string name="no_manga_sources_text">Aktifkan sumber manga untuk membaca manga daring</string>
<string name="categories_delete_confirm">Apakah Anda yakin ingin menghapus kategori favorit yang dipilih\?
\n Semua manga di sana akan hilang dan ini tidak bisa diurungkan.</string>
<string name="reorder">Atur Ulang</string>
<string name="saved_manga">Manga tersimpan</string>
<string name="confirm_exit">Tekan Kembali lagi untuk keluar</string>
<string name="no_chapters">Tidak ada bab</string>
<string name="history_shortcuts">Tampilkan pintasan manga baru-baru ini</string>
<string name="history_shortcuts_summary">Buat manga baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
<string name="select_range">Pilih jangkauan</string>
</resources> </resources>

View File

@@ -374,4 +374,5 @@
<string name="comics_archive">コミックアーカイブ</string> <string name="comics_archive">コミックアーカイブ</string>
<string name="downloading_manga">漫画をダウンロードする</string> <string name="downloading_manga">漫画をダウンロードする</string>
<string name="feed">フィード</string> <string name="feed">フィード</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;Webブラウザで漫画を開いてみて&lt;/a&gt;、そのソースで利用できるか確認する&lt;br&gt;2. 利用できる場合は、開発者にエラーレポートを送信する&lt;/a&gt;。</string>
</resources> </resources>

View File

@@ -374,4 +374,16 @@
<string name="import_will_start_soon">Импорт скоро начнётся</string> <string name="import_will_start_soon">Импорт скоро начнётся</string>
<string name="feed">Лента</string> <string name="feed">Лента</string>
<string name="downloading_manga">Загрузка манги</string> <string name="downloading_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. Если она доступна, отправьте отчёт об ошибке разработчикам.</string>
<string name="history_shortcuts">Показывать ярлыки последней прочитанной манги</string>
<string name="history_shortcuts_summary">Сделать недавно прочитанную доступной по долгому нажатию на иконку приложения</string>
<string name="reader_control_ltr_summary">Нажатие на правый край или нажатие правой клавиши всегда переключает на следующую страницу</string>
<string name="reader_control_ltr">Эргономичное управление режимом чтения</string>
<string name="reset">Сбросить</string>
<string name="discard">Отменить</string>
<string name="text_unsaved_changes_prompt">У вас есть несохраненные изменения, вы хотите сохранить или отменить их\?</string>
<string name="contrast">Контрастность</string>
<string name="brightness">Яркость</string>
<string name="color_correction">Цветокоррекция</string>
<string name="color_correction_hint">Выбранные настройки цвета запомнятся для этой манги</string>
</resources> </resources>

View File

@@ -16,7 +16,7 @@
<string name="try_again">Tekrar dene</string> <string name="try_again">Tekrar dene</string>
<string name="clear_history">Geçmişi temizle</string> <string name="clear_history">Geçmişi temizle</string>
<string name="nothing_found">Hiçbir şey bulunamadı</string> <string name="nothing_found">Hiçbir şey bulunamadı</string>
<string name="history_is_empty">Sen geçmişiniz yok</string> <string name="history_is_empty">Geçmiş yok</string>
<string name="read">Oku</string> <string name="read">Oku</string>
<string name="you_have_not_favourites_yet">Henüz favorileriniz yok</string> <string name="you_have_not_favourites_yet">Henüz favorileriniz yok</string>
<string name="add_to_favourites">Favoriniz</string> <string name="add_to_favourites">Favoriniz</string>
@@ -209,7 +209,7 @@
<string name="new_version_s">Yeni sürüm: %s</string> <string name="new_version_s">Yeni sürüm: %s</string>
<string name="protect_application">Uygulamayı koru</string> <string name="protect_application">Uygulamayı koru</string>
<string name="passwords_mismatch">Parolalar eşleşmiyor</string> <string name="passwords_mismatch">Parolalar eşleşmiyor</string>
<string name="right_to_left">Sağdan sola (←)</string> <string name="right_to_left">Sağdan-sola</string>
<string name="create_category">Yeni kategori</string> <string name="create_category">Yeni kategori</string>
<string name="file_not_found">Dosya bulunamadı</string> <string name="file_not_found">Dosya bulunamadı</string>
<string name="data_restored_success">Tüm veriler geri yüklendi</string> <string name="data_restored_success">Tüm veriler geri yüklendi</string>
@@ -320,4 +320,30 @@
<string name="invalid_domain_message">Geçersiz etki alanı</string> <string name="invalid_domain_message">Geçersiz etki alanı</string>
<string name="select_range">Aralık seç</string> <string name="select_range">Aralık seç</string>
<string name="not_found_404">İçerik bulunamadı veya kaldırıldı</string> <string name="not_found_404">İçerik bulunamadı veya kaldırıldı</string>
<string name="text_shelf_holder_primary">Manganız burada görüntülenecek</string>
<string name="canceled">İptal edilmiş</string>
<string name="account_already_exists">Hesap zaten var</string>
<string name="back">Geri</string>
<string name="sync">Senkronizasyon</string>
<string name="sync_title">Verini yedekle</string>
<string name="email_enter_hint">Devam etmek için E-Postanızı girin</string>
<string name="clear_all_history">Tüm gecmişi temizle</string>
<string name="last_2_hours">Son 2 saat</string>
<string name="history_cleared">Geçmiş temizlendi</string>
<string name="manage">Yönet</string>
<string name="no_bookmarks_yet">Yer işareti yok</string>
<string name="no_bookmarks_summary">Manga okurken yer işareti oluşturabilirsiniz</string>
<string name="bookmarks_removed">Yer işaretleri kaldırıldı</string>
<string name="no_manga_sources">Manga kaynağı yok</string>
<string name="no_manga_sources_text">Çevrimiçi manga okumak için manga kaynaklarını aktif edin</string>
<string name="random">Rastgele</string>
<string name="empty">Boş</string>
<string name="explore">Keşfet</string>
<string name="confirm_exit">Çıkmak için tekrar Geri tıkla</string>
<string name="exit_confirmation_summary">Çıkmak için iki defa Geri tıkla</string>
<string name="removed_from_favourites">Favorilerden kaldırıldı</string>
<string name="downloading_manga">Manga indiriliyor</string>
<string name="changelog">Değişiklik günlüğü</string>
<string name="exit_confirmation">Çıkış doğrulaması</string>
<string name="enter_email_text">Devam etmek için e-postanızı girin</string>
</resources> </resources>

View File

@@ -373,4 +373,5 @@
<string name="removed_from_favourites">Видалено з уподобань</string> <string name="removed_from_favourites">Видалено з уподобань</string>
<string name="other_cache">Інший кеш</string> <string name="other_cache">Інший кеш</string>
<string name="memory_usage_pattern">%s - %s</string> <string name="memory_usage_pattern">%s - %s</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. Якщо вона доступна, то надішліть звіт про помилку розробникам.</string>
</resources> </resources>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="items">
<item quantity="other">%1$d 个项目</item>
</plurals>
<plurals name="new_chapters">
<item quantity="other">%1$d 个新章节</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d 章节</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="other">来自 %2$d 中的 %1$d 个章节</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d 分钟前</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d 小时前</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d 天前</item>
</plurals>
<plurals name="pages">
<item quantity="other">共 %1$d 页</item>
</plurals>
</resources>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<string name="settings">设置</string> <string name="settings">设置</string>
<string name="local_storage">本地存储</string> <string name="local_storage">本地存储</string>
<string name="favourites">喜欢</string> <string name="favourites">喜欢</string>
@@ -78,7 +78,7 @@
<string name="sort_order">排序顺序</string> <string name="sort_order">排序顺序</string>
<string name="filter">过滤器</string> <string name="filter">过滤器</string>
<string name="theme">主题</string> <string name="theme">主题</string>
<string name="dark">黑暗</string> <string name="dark">深色</string>
<string name="light">浅色</string> <string name="light">浅色</string>
<string name="automatic">跟随系统</string> <string name="automatic">跟随系统</string>
<string name="pages">页数</string> <string name="pages">页数</string>
@@ -101,7 +101,7 @@
<string name="cache">缓存</string> <string name="cache">缓存</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string> <string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">标准</string> <string name="standard">标准</string>
<string name="webtoon">Webtoon</string> <string name="webtoon">条漫</string>
<string name="read_mode">阅读模式</string> <string name="read_mode">阅读模式</string>
<string name="grid_size">网格大小</string> <string name="grid_size">网格大小</string>
<string name="search_on_s">在%s上搜索</string> <string name="search_on_s">在%s上搜索</string>
@@ -188,7 +188,7 @@
<string name="checking_for_updates">检查更新…</string> <string name="checking_for_updates">检查更新…</string>
<string name="update_check_failed">无法查找更新</string> <string name="update_check_failed">无法查找更新</string>
<string name="no_update_available">没有更新</string> <string name="no_update_available">没有更新</string>
<string name="right_to_left">从右到左 (←)</string> <string name="right_to_left">从右到左</string>
<string name="create_category">新类别</string> <string name="create_category">新类别</string>
<string name="scale_mode">缩放模式</string> <string name="scale_mode">缩放模式</string>
<string name="zoom_mode_fit_center">适应中心</string> <string name="zoom_mode_fit_center">适应中心</string>
@@ -249,7 +249,7 @@
<string name="screenshots_block_all">始终阻止</string> <string name="screenshots_block_all">始终阻止</string>
<string name="suggestions">建议</string> <string name="suggestions">建议</string>
<string name="suggestions_enable">启用建议</string> <string name="suggestions_enable">启用建议</string>
<string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string> <string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string>
<string name="exclude_nsfw_from_suggestions">请勿推荐18+漫画</string> <string name="exclude_nsfw_from_suggestions">请勿推荐18+漫画</string>
<string name="enabled">启用</string> <string name="enabled">启用</string>
<string name="disabled">禁用</string> <string name="disabled">禁用</string>
@@ -290,7 +290,7 @@
<string name="dns_over_https">DNS over HTTPS</string> <string name="dns_over_https">DNS over HTTPS</string>
<string name="default_mode">默认模式</string> <string name="default_mode">默认模式</string>
<string name="detect_reader_mode">自动检测阅读器模式</string> <string name="detect_reader_mode">自动检测阅读器模式</string>
<string name="detect_reader_mode_summary">自动检测漫画是否为网络漫画</string> <string name="detect_reader_mode_summary">自动检测漫画是否为条漫</string>
<string name="disable_battery_optimization">禁用电池优化</string> <string name="disable_battery_optimization">禁用电池优化</string>
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string> <string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
<string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string> <string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string>
@@ -313,4 +313,65 @@
<string name="exclude_nsfw_from_history_summary">标记为NSFW的漫画将永远不会被添加到历史中你的进度也不会被保存</string> <string name="exclude_nsfw_from_history_summary">标记为NSFW的漫画将永远不会被添加到历史中你的进度也不会被保存</string>
<string name="clear_cookies_summary">可以在出现一些问题时提供帮助. 所有授权将被视为无效</string> <string name="clear_cookies_summary">可以在出现一些问题时提供帮助. 所有授权将被视为无效</string>
<string name="show_all">显示全部</string> <string name="show_all">显示全部</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.如果可用,请向开发人员发送错误报告。</string>
<string name="invalid_domain_message">无效域名</string>
<string name="text_shelf_holder_primary">此处将显示你的漫画</string>
<string name="text_shelf_holder_secondary">在【浏览】页面搜索想要阅读的漫画</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="canceled">已取消</string>
<string name="account_already_exists">账号已存在</string>
<string name="back">返回</string>
<string name="sync">同步</string>
<string name="sync_title">同步您的数据</string>
<string name="email_enter_hint">输入您的邮箱以继续</string>
<string name="status_dropped">已放弃</string>
<string name="select_range">选择范围</string>
<string name="clear_all_history">清除所有历史</string>
<string name="last_2_hours">过去2小时</string>
<string name="bookmarks_removed">书签已移除</string>
<string name="history_cleared">历史已清除</string>
<string name="manage">管理</string>
<string name="no_bookmarks_yet">还没有书签</string>
<string name="no_bookmarks_summary">您可以在阅读漫画时创建书签</string>
<string name="no_manga_sources">无漫画源</string>
<string name="no_manga_sources_text">启用漫画源以在线阅读漫画</string>
<string name="random">随机</string>
<string name="categories_delete_confirm">您确定要删除选定的收藏夹吗?
\n所有收藏夹中的漫画将丢失且无法恢复。</string>
<string name="reorder">重新排序</string>
<string name="empty">留空</string>
<string name="changelog">变更日志</string>
<string name="explore">浏览</string>
<string name="automatic_scroll">自动滚动</string>
<string name="off_short">关闭</string>
<string name="reader_info_bar">在阅读器中显示信息栏</string>
<string name="comics_archive">漫画压缩包</string>
<string name="folder_with_images">图片文件夹</string>
<string name="importing_manga">漫画导入中</string>
<string name="seconds_pattern">%ss</string>
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">%1$d 的 %2$d 启用</string>
<string name="size_s">大小:%s</string>
<string name="confirm_exit">再按一次返回键退出</string>
<string name="exit_confirmation_summary">按两次返回键退出应用</string>
<string name="exit_confirmation">退出确认</string>
<string name="saved_manga">已保存漫画</string>
<string name="pages_cache">页面缓存</string>
<string name="other_cache">其他缓存</string>
<string name="storage_usage">存储占用</string>
<string name="available">可用</string>
<string name="enter_email_text">输入您的邮箱以继续</string>
<string name="removed_from_favourites">从收藏中移除</string>
<string name="removed_from_s">从 \"%s\" 中移除</string>
<string name="options">选项</string>
<string name="downloading_manga">下载中的漫画</string>
<string name="incognito_mode">隐身模式</string>
<string name="app_update_available_s">应用更新可用: %s</string>
<string name="no_chapters">无章节</string>
<string name="import_completed">导入完毕</string>
<string name="import_completed_hint">您可以从存储中删除原文件以节省空间</string>
<string name="import_will_start_soon">即将开始导入</string>
<string name="feed">订阅源</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="not_found_404">内容未找到或已移除</string>
</resources> </resources>

View File

@@ -6,7 +6,7 @@
<string name="url_twitter" translatable="false">https://twitter.com/kotatsuapp</string> <string name="url_twitter" translatable="false">https://twitter.com/kotatsuapp</string>
<string name="url_reddit" translatable="false">https://reddit.com/user/kotatsuapp</string> <string name="url_reddit" translatable="false">https://reddit.com/user/kotatsuapp</string>
<string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string> <string name="url_weblate" translatable="false">https://hosted.weblate.org/engage/kotatsu</string>
<string name="url_error_report" translatable="false">http://86.57.183.214:8082/report</string> <string name="url_error_report" translatable="false">https://acra.rumblur.space/report</string>
<string name="account_type_sync" translatable="false">org.kotatsu.sync</string> <string name="account_type_sync" translatable="false">org.kotatsu.sync</string>
<string name="url_sync_server" translatable="false">http://86.57.183.214:8081</string> <string name="url_sync_server" translatable="false">http://86.57.183.214:8081</string>
<string-array name="values_theme" translatable="false"> <string-array name="values_theme" translatable="false">