Merge branch 'devel' into feature/nextgen

This commit is contained in:
Koitharu
2022-07-06 16:15:37 +03:00
25 changed files with 243 additions and 114 deletions

View File

@@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 32
versionCode 411
versionName '3.3.2'
versionCode 412
versionName '3.4'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -77,19 +77,19 @@ afterEvaluate {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:c92f89f307') {
implementation('com.github.nv95:kotatsu-parsers:da3b0ae0cf') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3'
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.activity:activity-ktx:1.5.0-rc01'
implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-service:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-process:2.5.0-rc02'
implementation 'androidx.activity:activity-ktx:1.5.0'
implementation 'androidx.fragment:fragment-ktx:1.5.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0'
implementation 'androidx.lifecycle:lifecycle-service:2.5.0'
implementation 'androidx.lifecycle:lifecycle-process:2.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
@@ -99,7 +99,7 @@ dependencies {
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.7.0-alpha02'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0-rc02'
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0'
implementation 'androidx.room:room-runtime:2.4.2'
implementation 'androidx.room:room-ktx:2.4.2'

View File

@@ -3,14 +3,13 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import android.os.Parcelable
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.utils.ext.createList
class ParcelableMangaChapters(
val chapters: List<MangaChapter>,
) : Parcelable {
constructor(parcel: Parcel) : this(
createList(parcel.readInt()) { parcel.readMangaChapter() }
List(parcel.readInt()) { parcel.readMangaChapter() }
)
override fun writeToParcel(parcel: Parcel, flags: Int) {

View File

@@ -3,14 +3,13 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import android.os.Parcelable
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.utils.ext.createList
class ParcelableMangaPages(
val pages: List<MangaPage>,
) : Parcelable {
constructor(parcel: Parcel) : this(
createList(parcel.readInt()) { parcel.readMangaPage() }
List(parcel.readInt()) { parcel.readMangaPage() }
)
override fun writeToParcel(parcel: Parcel, flags: Int) {

View File

@@ -3,14 +3,14 @@ package org.koitharu.kotatsu.core.model.parcelable
import android.os.Parcel
import android.os.Parcelable
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.utils.ext.createSet
import org.koitharu.kotatsu.utils.ext.Set
class ParcelableMangaTags(
val tags: Set<MangaTag>,
) : Parcelable {
constructor(parcel: Parcel) : this(
createSet(parcel.readInt()) { parcel.readMangaTag() }
Set(parcel.readInt()) { parcel.readMangaTag() }
)
override fun writeToParcel(parcel: Parcel, flags: Int) {

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.ui
import android.text.Html
import coil.ComponentRegistry
import coil.ImageLoader
import coil.disk.DiskCache
@@ -10,6 +11,7 @@ import org.koin.dsl.module
import org.koitharu.kotatsu.core.parser.FaviconMapper
import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.CbzFetcher
import org.koitharu.kotatsu.utils.image.CoilImageGetter
val uiModule
get() = module {
@@ -40,4 +42,5 @@ val uiModule
.build()
).build()
}
factory<Html.ImageGetter> { CoilImageGetter(androidContext(), get()) }
}

View File

@@ -8,6 +8,6 @@ val detailsModule
get() = module {
viewModel { intent ->
DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get(), get())
DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get(), get(), get())
}
}

View File

@@ -21,6 +21,7 @@ import coil.size.Scale
import coil.util.CoilUtils
import com.google.android.material.chip.Chip
import kotlinx.coroutines.launch
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.R
@@ -80,6 +81,7 @@ class DetailsFragment :
viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged)
viewModel.bookmarks.observe(viewLifecycleOwner, ::onBookmarksChanged)
viewModel.scrobblingInfo.observe(viewLifecycleOwner, ::onScrobblingInfoChanged)
viewModel.description.observe(viewLifecycleOwner, ::onDescriptionChanged)
addMenuProvider(DetailsMenuProvider())
}
@@ -108,8 +110,6 @@ class DetailsFragment :
textViewTitle.text = manga.title
textViewSubtitle.textAndVisible = manga.altTitle
textViewAuthor.textAndVisible = manga.author
textViewDescription.text = manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank)
?: getString(R.string.no_description)
when (manga.state) {
MangaState.FINISHED -> {
textViewState.apply {
@@ -172,6 +172,14 @@ class DetailsFragment :
}
}
private fun onDescriptionChanged(description: CharSequence?) {
if (description.isNullOrBlank()) {
binding.textViewDescription.setText(R.string.no_description)
} else {
binding.textViewDescription.text = description
}
}
private fun onHistoryChanged(history: MangaHistory?) {
with(binding.buttonRead) {
if (history == null) {

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.details.ui
import android.text.Html
import androidx.core.text.parseAsHtml
import androidx.lifecycle.LiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.asLiveData
@@ -43,6 +45,7 @@ class DetailsViewModel(
private val bookmarksRepository: BookmarksRepository,
private val settings: AppSettings,
private val scrobbler: Scrobbler,
private val imageGetter: Html.ImageGetter,
) : BaseViewModel() {
private val delegate = MangaDetailsDelegate(
@@ -79,7 +82,19 @@ class DetailsViewModel(
val bookmarks = delegate.manga.flatMapLatest {
if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
val description = delegate.manga
.distinctUntilChangedBy { it?.description.orEmpty() }
.transformLatest {
val description = it?.description
if (description.isNullOrEmpty()) {
emit(null)
} else {
emit(description.parseAsHtml())
emit(description.parseAsHtml(imageGetter = imageGetter))
}
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, null)
val onMangaRemoved = SingleLiveEvent<Manga>()
val isScrobblingAvailable: Boolean

View File

@@ -13,9 +13,9 @@ import java.io.InputStream
class PagesCache(context: Context) {
private val cacheDir = context.externalCacheDir ?: context.cacheDir
private val lruCache = DiskLruCache.create(
cacheDir.subdir(CacheDir.PAGES.dir),
FileSize.MEGABYTES.convert(200, FileSize.BYTES),
private val lruCache = createDiskLruCacheSafe(
dir = cacheDir.subdir(CacheDir.PAGES.dir),
size = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
)
operator fun get(url: String): File? {
@@ -60,4 +60,14 @@ class PagesCache(context: Context) {
progress.value = (bytesCopied.toDouble() / contentLength.toDouble()).toFloat()
}
}
}
private fun createDiskLruCacheSafe(dir: File, size: Long): DiskLruCache {
return try {
DiskLruCache.create(dir, size)
} catch (e: Exception) {
dir.deleteRecursively()
dir.mkdir()
DiskLruCache.create(dir, size)
}
}

View File

@@ -10,7 +10,7 @@ import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.domain.model.*
import org.koitharu.kotatsu.utils.ext.findKey
import org.koitharu.kotatsu.utils.ext.findKeyByValue
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
abstract class Scrobbler(
@@ -59,7 +59,7 @@ abstract class Scrobbler(
scrobbler = scrobblerService,
mangaId = mangaId,
targetId = targetId,
status = statuses.findKey(status),
status = statuses.findKeyByValue(status),
chapter = chapter,
comment = comment,
rating = rating,

View File

@@ -4,9 +4,9 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.annotation.MainThread
import androidx.core.net.toUri
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -23,9 +23,6 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.concurrent.TimeUnit
@@ -74,7 +71,8 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
.setTitle(R.string.app_update_available)
.setMessage(message)
.setPositiveButton(R.string.download) { _, _ ->
activity.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(version.apkUrl)))
val intent = Intent(Intent.ACTION_VIEW, version.apkUrl.toUri())
activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.open_in_browser)))
}
.setNegativeButton(R.string.close, null)
.setCancelable(false)
@@ -88,42 +86,23 @@ class AppUpdateChecker(private val activity: ComponentActivity) {
private val PERIOD = TimeUnit.HOURS.toMillis(6)
fun isUpdateSupported(context: Context): Boolean {
return getCertificateSHA1Fingerprint(context) == CERT_SHA1
return BuildConfig.DEBUG || getCertificateSHA1Fingerprint(context) == CERT_SHA1
}
@Suppress("DEPRECATION")
@SuppressLint("PackageManagerGetSignatures")
private fun getCertificateSHA1Fingerprint(context: Context): String? {
val packageInfo = try {
context.packageManager.getPackageInfo(
context.packageName,
PackageManager.GET_SIGNATURES
)
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTraceDebug()
return null
}
val signatures = packageInfo?.signatures
val cert: ByteArray = signatures?.firstOrNull()?.toByteArray() ?: return null
private fun getCertificateSHA1Fingerprint(context: Context): String? = runCatching {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)
val signatures = requireNotNull(packageInfo?.signatures)
val cert: ByteArray = signatures.first().toByteArray()
val input: InputStream = ByteArrayInputStream(cert)
val c = try {
val cf = CertificateFactory.getInstance("X509")
cf.generateCertificate(input) as X509Certificate
} catch (e: CertificateException) {
e.printStackTraceDebug()
return null
}
return try {
val md: MessageDigest = MessageDigest.getInstance("SHA1")
val publicKey: ByteArray = md.digest(c.encoded)
publicKey.byte2HexFormatted()
} catch (e: NoSuchAlgorithmException) {
e.printStackTraceDebug()
null
} catch (e: CertificateEncodingException) {
e.printStackTraceDebug()
null
}
}
val cf = CertificateFactory.getInstance("X509")
val c = cf.generateCertificate(input) as X509Certificate
val md: MessageDigest = MessageDigest.getInstance("SHA1")
val publicKey: ByteArray = md.digest(c.encoded)
return publicKey.byte2HexFormatted()
}.onFailure { error ->
error.printStackTraceDebug()
}.getOrNull()
}
}

View File

@@ -3,25 +3,25 @@ package org.koitharu.kotatsu.settings
import android.os.Bundle
import android.view.View
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.serializableArgument
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs
import org.koitharu.kotatsu.utils.ext.*
class SourceSettingsFragment : BasePreferenceFragment(0) {
private val source by serializableArgument<MangaSource>(EXTRA_SOURCE)
private var repository: RemoteMangaRepository? = null
private val exceptionResolver = ExceptionResolver(this)
override fun onResume() {
super.onResume()
@@ -63,6 +63,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
private fun loadUsername(preference: Preference) = viewLifecycleScope.launch {
runCatching {
preference.summary = null
withContext(Dispatchers.Default) {
requireNotNull(repository?.getAuthProvider()?.getUsername())
}
@@ -70,10 +71,28 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
preference.title = getString(R.string.logged_in_as, username)
}.onFailure { error ->
preference.isEnabled = error is AuthRequiredException
when {
error is AuthRequiredException -> Unit
ExceptionResolver.canResolve(error) -> {
Snackbar.make(listView, error.getDisplayMessage(resources), Snackbar.LENGTH_INDEFINITE)
.setAction(ExceptionResolver.getResolveStringId(error)) { resolveError(error) }
.show()
}
else -> preference.summary = error.getDisplayMessage(resources)
}
error.printStackTraceDebug()
}
}
private fun resolveError(error: Throwable): Unit {
viewLifecycleScope.launch {
if (exceptionResolver.resolve(error)) {
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch
loadUsername(pref)
}
}
}
companion object {
private const val KEY_AUTH = "auth"

View File

@@ -1,10 +1,12 @@
package org.koitharu.kotatsu.settings.newsources
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
import org.koitharu.kotatsu.utils.ext.mapToSet
class NewSourcesViewModel(
private val settings: AppSettings,
@@ -30,12 +32,14 @@ class NewSourcesViewModel(
}
private fun buildList() {
val locales = LocaleListCompat.getDefault().mapToSet { it.language }
val hidden = settings.hiddenSources
sources.value = initialList.map {
val locale = it.locale
SourceConfigItem.SourceItem(
source = it,
summary = it.getLocaleTitle(),
isEnabled = it.name !in hidden,
isEnabled = it.name !in hidden && (locale == null || locale in locales),
isDraggable = false,
)
}

View File

@@ -18,25 +18,20 @@ inline fun <T> MutableSet(size: Int, init: (index: Int) -> T): MutableSet<T> {
return set
}
inline fun <T> createSet(size: Int, init: (index: Int) -> T): Set<T> = when (size) {
@Suppress("FunctionName")
inline fun <T> Set(size: Int, init: (index: Int) -> T): Set<T> = when (size) {
0 -> emptySet()
1 -> Collections.singleton(init(0))
else -> MutableSet(size, init)
}
inline fun <T> createList(size: Int, init: (index: Int) -> T): List<T> = when (size) {
0 -> emptyList()
1 -> Collections.singletonList(init(0))
else -> MutableList(size, init)
}
fun <T> List<T>.asArrayList(): ArrayList<T> = if (this is ArrayList<*>) {
this as ArrayList<T>
} else {
ArrayList(this)
}
fun <K, V> Map<K, V>.findKey(value: V): K? {
fun <K, V> Map<K, V>.findKeyByValue(value: V): K? {
for ((k, v) in entries) {
if (v == value) {
return k

View File

@@ -1,34 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import androidx.core.os.LocaleListCompat
import java.util.*
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw kotlin.NoSuchElementException()
fun LocaleListCompat.toList(): List<Locale> = createList(size()) { i -> getOrThrow(i) }
operator fun LocaleListCompat.iterator() = object : Iterator<Locale> {
private var index = 0
override fun hasNext(): Boolean = index < size()
override fun next(): Locale = getOrThrow(index++)
}
inline fun <R, C : MutableCollection<in R>> LocaleListCompat.mapTo(
destination: C,
block: (Locale) -> R,
): C {
val len = size()
for (i in 0 until len) {
val item = get(i) ?: continue
destination.add(block(item))
}
return destination
}
inline fun <T> LocaleListCompat.map(block: (Locale) -> T): List<T> {
return mapTo(ArrayList(size()), block)
}
inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
return mapTo(LinkedHashSet(size()), block)
}

View File

@@ -0,0 +1,35 @@
package org.koitharu.kotatsu.utils.ext
import androidx.core.os.LocaleListCompat
import java.util.*
operator fun LocaleListCompat.iterator(): ListIterator<Locale> = LocaleListCompatIterator(this)
fun LocaleListCompat.toList(): List<Locale> = List(size()) { i -> getOrThrow(i) }
inline fun <T> LocaleListCompat.map(block: (Locale) -> T): List<T> {
return List(size()) { i -> block(getOrThrow(i)) }
}
inline fun <T> LocaleListCompat.mapToSet(block: (Locale) -> T): Set<T> {
return Set(size()) { i -> block(getOrThrow(i)) }
}
fun LocaleListCompat.getOrThrow(index: Int) = get(index) ?: throw NoSuchElementException()
private class LocaleListCompatIterator(private val list: LocaleListCompat) : ListIterator<Locale> {
private var index = 0
override fun hasNext() = index < list.size()
override fun hasPrevious() = index > 0
override fun next() = list.get(index++) ?: throw NoSuchElementException()
override fun nextIndex() = index
override fun previous() = list.get(--index) ?: throw NoSuchElementException()
override fun previousIndex() = index - 1
}

View File

@@ -0,0 +1,25 @@
package org.koitharu.kotatsu.utils.image
import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Html
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
class CoilImageGetter(
private val context: Context,
private val coil: ImageLoader,
) : Html.ImageGetter {
override fun getDrawable(source: String?): Drawable? {
return coil.executeBlocking(
ImageRequest.Builder(context)
.data(source)
.allowHardware(false)
.build()
).drawable?.apply {
setBounds(0, 0, intrinsicHeight, intrinsicHeight)
}
}
}

View File

@@ -302,4 +302,16 @@
<string name="use_fingerprint">Fingerabdruck verwenden, falls vorhanden</string>
<string name="appwidget_shelf_description">Manga aus Ihren Favoriten</string>
<string name="appwidget_recent_description">Ihr kürzlich gelesener Manga</string>
<string name="report">Melden</string>
<string name="tracking">Nachverfolgung</string>
<string name="logout">Abmelden</string>
<string name="status_planned">Geplant</string>
<string name="status_on_hold">In der Warteschleife</string>
<string name="show_reading_indicators">Indikatoren für den Lesefortschritt anzeigen</string>
<string name="show_all">Alle anzeigen</string>
<string name="show_reading_indicators_summary">Gelesenen Prozentsatz in Verlauf und Favoriten anzeigen</string>
<string name="clear_cookies_summary">Kann im Falle einiger Probleme helfen. Alle Berechtigungen werden für ungültig erklärt</string>
<string name="status_completed">Abgeschlossen</string>
<string name="exclude_nsfw_from_history_summary">Manga, die als NSFW markiert sind, werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert.</string>
<string name="data_deletion">Datenlöschung</string>
</resources>

View File

@@ -301,4 +301,5 @@
<string name="use_fingerprint">Utilizar la huella dactilar si está disponible</string>
<string name="appwidget_shelf_description">Mangas de tus favoritos</string>
<string name="appwidget_recent_description">Sus mangas recientemente leídos</string>
<string name="logout">Cerrar sesión</string>
</resources>

View File

@@ -302,4 +302,10 @@
<string name="use_fingerprint">Käytä sormenjälkeä, jos käytettävissä</string>
<string name="appwidget_shelf_description">Manga suosikeistasi</string>
<string name="appwidget_recent_description">Äskettäin lukemasi manga</string>
<string name="tracking">Seuranta</string>
<string name="logout">Kirjaudu ulos</string>
<string name="status_reading">Lukemassa</string>
<string name="status_re_reading">Lukemassa uudelleen</string>
<string name="data_deletion">Tietojen poistaminen</string>
<string name="show_all">Näytä kaikki</string>
</resources>

View File

@@ -302,4 +302,19 @@
<string name="use_fingerprint">Utiliser l\'empreinte digitale si elle est disponible</string>
<string name="appwidget_recent_description">Vos mangas récemment lus</string>
<string name="appwidget_shelf_description">Les mangas de vos favoris</string>
<string name="report">Signaler</string>
<string name="tracking">Suivi</string>
<string name="status_planned">Planifié</string>
<string name="status_reading">Lecture</string>
<string name="show_reading_indicators">Afficher les indicateurs de progression de lecture</string>
<string name="show_reading_indicators_summary">Afficher le pourcentage de lecture dans l\'historique et les favoris</string>
<string name="exclude_nsfw_from_history_summary">Les mangas marqués comme étant pour adultes ne seront jamais ajoutés à l\'historique et votre progression ne sera pas sauvegardée</string>
<string name="clear_cookies_summary">Peut aider en cas de problème. Toutes les autorisations seront invalidées</string>
<string name="show_all">Tout afficher</string>
<string name="status_on_hold">En attente</string>
<string name="status_dropped">Abandonné</string>
<string name="data_deletion">Suppression des données</string>
<string name="logout">Se déconnecter</string>
<string name="status_completed">Terminé</string>
<string name="status_re_reading">Relecture</string>
</resources>

View File

@@ -302,4 +302,19 @@
<string name="use_fingerprint">Usa le impronte digitali se disponibili</string>
<string name="appwidget_shelf_description">Manga dai preferiti</string>
<string name="appwidget_recent_description">I manga letti di recente</string>
<string name="report">Segnala</string>
<string name="tracking">Tracciamento</string>
<string name="status_reading">Lettura</string>
<string name="status_re_reading">Rilettura</string>
<string name="status_on_hold">In attesa</string>
<string name="show_reading_indicators">Mostrare gli indicatori di progresso della lettura</string>
<string name="data_deletion">Eliminazione dei dati</string>
<string name="show_reading_indicators_summary">Mostra la percentuale di lettura nella cronologia e nei preferiti</string>
<string name="exclude_nsfw_from_history_summary">I manga contrassegnati come per adulti non verranno mai aggiunti alla cronologia e i vostri progressi non verranno salvati</string>
<string name="clear_cookies_summary">Può aiutare in caso di problemi. Tutte le autorizzazioni saranno invalidate</string>
<string name="show_all">Mostra tutto</string>
<string name="logout">Esci</string>
<string name="status_planned">Pianificato</string>
<string name="status_completed">Finito</string>
<string name="status_dropped">Abbandonato</string>
</resources>

View File

@@ -303,4 +303,18 @@
<string name="use_fingerprint">指紋がある場合は、指紋を使用する</string>
<string name="appwidget_shelf_description">お気に入りの漫画</string>
<string name="report">報告</string>
<string name="status_reading">読書</string>
<string name="status_re_reading">再読込</string>
<string name="status_completed">完了</string>
<string name="status_on_hold">保留中</string>
<string name="tracking">追跡</string>
<string name="logout">ログアウト</string>
<string name="status_planned">予定</string>
<string name="status_dropped">ドロップ</string>
<string name="data_deletion">データの削除</string>
<string name="show_reading_indicators_summary">履歴とお気に入りに既読率を表示する</string>
<string name="clear_cookies_summary">いくつかの問題の場合に助けることができる。すべての認証が無効になります</string>
<string name="show_reading_indicators">読書の進行状況インジケーターを表示</string>
<string name="exclude_nsfw_from_history_summary">NSFWとマークされたマンガは履歴に追加されず、進行状況も保存されない</string>
<string name="show_all">すべて表示</string>
</resources>

View File

@@ -303,4 +303,18 @@
<string name="appwidget_shelf_description">Favorilerinizden mangalar</string>
<string name="appwidget_recent_description">Son okuduğunuz mangalar</string>
<string name="report">Bildir</string>
<string name="tracking">İzleme</string>
<string name="logout">Oturumu kapat</string>
<string name="status_reading">Okunuyor</string>
<string name="status_completed">Tamamlandı</string>
<string name="show_reading_indicators">Okuma ilerleme göstergelerini göster</string>
<string name="data_deletion">Verileri sil</string>
<string name="show_reading_indicators_summary">Geçmişte ve favorilerde okunma yüzdesini göster</string>
<string name="exclude_nsfw_from_history_summary">Uygunsuz olarak işaretlenen mangalar asla geçmişe eklenmeyecek ve ilerlemeniz kaydedilmeyecektir</string>
<string name="clear_cookies_summary">Bazı sorunlarda yardımcı olabilir. Tüm yetkilendirmeler geçersiz kılınacaktır</string>
<string name="status_on_hold">Beklemede</string>
<string name="status_dropped">Bırakıldı</string>
<string name="status_planned">Planlandı</string>
<string name="status_re_reading">Yeniden okunuyor</string>
<string name="show_all">Tümünü göster</string>
</resources>

View File

@@ -56,9 +56,4 @@
</PreferenceCategory>
<Preference
android:key="cookies_clear"
android:persistent="false"
android:title="@string/clear_cookies" />
</PreferenceScreen>