Compare commits

..

34 Commits

Author SHA1 Message Date
f590378761 Release 0.10.9 2021-02-12 16:01:23 -05:00
f5f592be91 Require minimum WebView v88, try to catch fatal errors too 2021-02-12 12:42:33 -05:00
7a373fb43a Minor download icon optimizations 2021-02-12 12:27:40 -05:00
aded11e599 Make backup restoring logic more sequential 2021-02-12 12:27:40 -05:00
41d7cee020 Remove ExperimentalSerializationApi opt-in annotations 2021-02-12 12:27:40 -05:00
f2ef6a20e6 Weblate translations (#4378)
Co-authored-by: Adaś <adam.prosniak@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Cream π <f.t.nayeem014@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Eugene <eugcheung94@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Habibur Rahman <habiburr016@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Iuri Jikidze <ijiki16@freeuni.edu.ge>
Co-authored-by: Jimly Asshiddiqy <j_mly@ymail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Murilo Simionato Arnemann <murilo2110@hotmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 赤城 悠 <hapipon815@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/bn/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ka/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ms/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/nl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sc/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/sv/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/tr/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uk/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hant/
Translation: Tachiyomi/Tachiyomi 0.x

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Adaś <adam.prosniak@gmail.com>
Co-authored-by: Ajeje Brazorf <lmelonimamo@yahoo.it>
Co-authored-by: Alex <linuxrf@gmail.com>
Co-authored-by: Ava <Sasu.ruotsalainen@live.fi>
Co-authored-by: Crazyom <naxom@laposte.net>
Co-authored-by: Cream π <f.t.nayeem014@gmail.com>
Co-authored-by: DarKCroX <darkcrox.2020@outlook.com>
Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat>
Co-authored-by: Eric <spice2wolf@gmail.com>
Co-authored-by: Eugene <eugcheung94@gmail.com>
Co-authored-by: Flamm <robindevaux25@gmail.com>
Co-authored-by: Habibur Rahman <habiburr016@gmail.com>
Co-authored-by: Huang Zhiyi <hzy980512@126.com>
Co-authored-by: Iuri Jikidze <ijiki16@freeuni.edu.ge>
Co-authored-by: Jimly Asshiddiqy <j_mly@ymail.com>
Co-authored-by: Kurocon <weblate@kurocon.nl>
Co-authored-by: Lyfja <yassinelaoud@gmail.com>
Co-authored-by: Lzmxya <lzmxya@gmail.com>
Co-authored-by: Matteo Gaeta <matteo.gaeta.1998@gmail.com>
Co-authored-by: Michalis <michalisntovas@yahoo.gr>
Co-authored-by: Murilo Simionato Arnemann <murilo2110@hotmail.com>
Co-authored-by: Oğuz Ersen <oguzersen@protonmail.com>
Co-authored-by: Paulo Pinho <kebrus@gmail.com>
Co-authored-by: Pitpe11 <giorgos2550@gmail.com>
Co-authored-by: Rocco Casadei <roccobot@gmail.com>
Co-authored-by: Rostyslav <info@ubilling.net.ua>
Co-authored-by: Samuel Carvalho de Araújo <samuelnegro12345@gmail.com>
Co-authored-by: Soitora <simon.mattila@protonmail.com>
Co-authored-by: darkbeast13 <nikhil15mps@gmail.com>
Co-authored-by: Роман <Rozhenkov69@gmail.com>
Co-authored-by: 赤城 悠 <hapipon815@gmail.com>
2021-02-12 12:27:32 -05:00
a398c3fb81 Handle link for multisource extension commits (closes #4432) 2021-02-11 17:35:15 -05:00
2a454b44cc Adjust some scopes 2021-02-09 19:14:38 -05:00
7b66ece895 Fix invisible overflow icon in chapter filter sheet in light blue theme 2021-02-09 19:12:44 -05:00
b5017eebbf Added dual page split setting (#4252)
* Add DualPageSplit option

* remove extra line

* Split double-page into two pages

* Remove !isAnimated check and add (ALPHA) to the label

* Fix missing insert pages

* Pager cleanup

* Add dual split to Webtoon and fix Vertical

* Fix L2R/R2L

* Add comments and refactor code in ImageUtil

* Use a simpler split solution in webtoon mode

Co-authored-by: weng <>
Co-authored-by: Andreas E <andreas.everos@gmail.com>
2021-02-09 17:54:44 -05:00
aa67229daf Add weekly to library update frequency options (closes #4422) 2021-02-09 17:49:02 -05:00
5af68186d6 Clean up LibraryUpdateService a bit 2021-02-09 17:44:22 -05:00
545bc0e605 Open manga when clicking thumbnail in migration list (closes #4152) 2021-02-08 17:47:44 -05:00
291168f4de Remove unnecessary LayoutContainer implementations 2021-02-08 17:45:42 -05:00
9facb51f22 Add action to directly share crash log file from notification 2021-02-07 23:05:13 -05:00
5b7d8c5e37 Show locales in list of sources to migrate 2021-02-07 22:54:13 -05:00
5945937e4b Update AboutLibraries 2021-02-07 22:51:23 -05:00
9f9f9872eb Fix legacy backups
(cherry picked from commit ded58541f5903c109b70799683829e26018d2af6)
2021-02-07 22:33:07 -05:00
3566072f4a Revert attempt to programmatically determine user agent string; fallback to Edge 2021-02-07 17:54:28 -05:00
b85cd86b24 Add Esperanto locale 2021-02-07 16:55:44 -05:00
79c3767fff Chapter backup optimization
From fc6d9aaf51 (diff-9872ccc3c9af14d2872ec99199409e60a11cb754ab23e733b1d45843778f7c95R24)
2021-02-07 16:20:07 -05:00
cf1609a429 Massage user agent string from WebView a bit more 2021-02-07 16:19:13 -05:00
3aeac7e7b5 Fix selected tab in sheets not being the accent color 2021-02-07 10:54:35 -05:00
1557f713f4 Don't restrict filter sheet height anymore 2021-02-07 10:49:08 -05:00
b63d24ac1a Add Right and Left navigation (#4392)
and remove default navigation classes in favor of the navigation classes
2021-02-06 23:26:56 -05:00
348c1ff29d Avoid some unnecessary re-renderings of download icons 2021-02-06 23:25:39 -05:00
717e55497f Fix downloads getting deleted when marked as unread 2021-02-06 22:48:06 -05:00
d84b5e8b46 Show help action when source fails to load 2021-02-06 13:09:56 -05:00
5f9ddf9ff5 Use AndroidX version of ContextThemeWrapper 2021-02-06 12:51:40 -05:00
bbee093c63 Remove some logic around old legacy backup versions + minor optimizations 2021-02-06 12:15:34 -05:00
e8c35ae4e1 Do a regular return to cancel update jobs instead of throwing an exception 2021-02-06 12:14:55 -05:00
1607658c30 Set clip data when sharing content URIs (closes #4198) 2021-02-06 09:43:33 -05:00
2e9ef373f3 Minor optimizations for restoring full backups
Based on fc6d9aaf51
2021-02-06 09:32:00 -05:00
ec6eef6d37 Switch back to new image decoder for preview builds 2021-02-06 09:31:18 -05:00
119 changed files with 851 additions and 560 deletions

View File

@ -2,7 +2,7 @@
I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.8)
- I have updated to the latest version of the app (stable is v0.10.9)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions

View File

@ -9,7 +9,7 @@ labels: "bug"
I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.8)
- I have updated to the latest version of the app (stable is v0.10.9)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions

View File

@ -9,7 +9,7 @@ labels: "feature"
I acknowledge that:
- I have updated to the latest version of the app (stable is v0.10.8)
- I have updated to the latest version of the app (stable is v0.10.9)
- I have updated all extensions
- If this is an issue with an extension, that I should be opening an issue in https://github.com/tachiyomiorg/tachiyomi-extensions

View File

@ -9,7 +9,6 @@ plugins {
id("com.mikepenz.aboutlibraries.plugin")
kotlin("android")
kotlin("kapt")
kotlin("plugin.parcelize")
kotlin("plugin.serialization")
id("com.github.zellius.shortcut-helper")
}
@ -30,8 +29,8 @@ android {
minSdkVersion(AndroidConfig.minSdk)
targetSdkVersion(AndroidConfig.targetSdk)
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
versionCode = 55
versionName = "0.10.8"
versionCode = 56
versionName = "0.10.9"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")

View File

@ -80,6 +80,13 @@ abstract class AbstractBackupManager(protected val context: Context) {
databaseHelper.updateChaptersBackup(chapters).executeAsBlocking()
}
/**
* Updates a list of chapters with known database ids
*/
protected fun updateKnownChapters(chapters: List<Chapter>) {
databaseHelper.updateKnownChaptersBackup(chapters).executeAsBlocking()
}
/**
* Return number of backups.
*

View File

@ -37,9 +37,9 @@ abstract class AbstractBackupRestore<T : AbstractBackupManager>(protected val co
protected val errors = mutableListOf<Pair<Date, String>>()
abstract fun performRestore(uri: Uri): Boolean
abstract suspend fun performRestore(uri: Uri): Boolean
fun restoreBackup(uri: Uri): Boolean {
suspend fun restoreBackup(uri: Uri): Boolean {
val startTime = System.currentTimeMillis()
restoreProgress = 0
errors.clear()

View File

@ -111,7 +111,7 @@ class BackupCreateService : Service() {
val backupFileUri = backupManager.createBackup(uri, backupFlags, false)?.toUri()
val unifile = UniFile.fromUri(this, backupFileUri)
notifier.showBackupComplete(unifile)
notifier.showBackupComplete(unifile, backupType == BackupConst.BACKUP_TYPE_LEGACY)
} catch (e: Exception) {
notifier.showBackupError(e.message)
}

View File

@ -60,7 +60,7 @@ class BackupNotifier(private val context: Context) {
}
}
fun showBackupComplete(unifile: UniFile) {
fun showBackupComplete(unifile: UniFile, isLegacyFormat: Boolean) {
context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) {
@ -73,7 +73,7 @@ class BackupNotifier(private val context: Context) {
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE)
NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, isLegacyFormat, Notifications.ID_BACKUP_COMPLETE)
)
show(Notifications.ID_BACKUP_COMPLETE)

View File

@ -14,7 +14,10 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isServiceRunning
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import timber.log.Timber
@ -68,12 +71,14 @@ class BackupRestoreService : Service() {
*/
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var ioScope: CoroutineScope
private var backupRestore: AbstractBackupRestore<*>? = null
private lateinit var notifier: BackupNotifier
override fun onCreate() {
super.onCreate()
ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
notifier = BackupNotifier(this)
wakeLock = acquireWakeLock(javaClass.name)
@ -92,6 +97,7 @@ class BackupRestoreService : Service() {
private fun destroyJob() {
backupRestore?.job?.cancel()
ioScope?.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
@ -122,6 +128,7 @@ class BackupRestoreService : Service() {
BackupConst.BACKUP_TYPE_FULL -> FullBackupRestore(this, notifier, online)
else -> LegacyBackupRestore(this, notifier)
}
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
backupRestore?.writeErrorLog()
@ -129,14 +136,15 @@ class BackupRestoreService : Service() {
notifier.showRestoreError(exception.message)
stopSelf(startId)
}
backupRestore?.job = GlobalScope.launch(handler) {
val job = ioScope.launch(handler) {
if (backupRestore?.restoreBackup(uri) == false) {
notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
}
}
backupRestore?.job?.invokeOnCompletion {
job.invokeOnCompletion {
stopSelf(startId)
}
backupRestore?.job = job
return START_NOT_STICKY
}

View File

@ -29,7 +29,6 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.toSManga
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.protobuf.ProtoBuf
import okio.buffer
import okio.gzip
@ -37,7 +36,6 @@ import okio.sink
import timber.log.Timber
import kotlin.math.max
@OptIn(ExperimentalSerializationApi::class)
class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val parser = ProtoBuf
@ -247,7 +245,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
*/
internal fun restoreCategoriesForManga(manga: Manga, categories: List<Int>, backupCategories: List<BackupCategory>) {
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = mutableListOf<MangaCategory>()
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
categories.forEach { backupCategoryOrder ->
backupCategories.firstOrNull {
it.order == backupCategoryOrder
@ -274,7 +272,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
*/
internal fun restoreHistoryForManga(history: List<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = mutableListOf<History>()
val historyToBeUpdated = ArrayList<History>(history.size)
for ((url, lastRead) in history) {
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
// Check if history already in database and update
@ -358,9 +356,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
}
chapters.forEach { chapter ->
val pos = dbChapters.indexOfFirst { it.url == chapter.url }
if (pos != -1) {
val dbChapter = dbChapters[pos]
val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter != null) {
chapter.id = dbChapter.id
chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) {
@ -373,12 +370,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
chapter.bookmark = dbChapter.bookmark
}
}
}
// Filter the chapters that couldn't be found.
chapters.filter { it.id != null }
chapters.map { it.manga_id = manga.id }
updateChapters(chapters)
chapter.manga_id = manga.id
}
// Filter the chapters that couldn't be found.
updateChapters(chapters.filter { it.id != null })
return true
}
@ -386,9 +384,8 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
val dbChapters = databaseHelper.getChapters(manga).executeAsBlocking()
chapters.forEach { chapter ->
val pos = dbChapters.indexOfFirst { it.url == chapter.url }
if (pos != -1) {
val dbChapter = dbChapters[pos]
val dbChapter = dbChapters.find { it.url == chapter.url }
if (dbChapter != null) {
chapter.id = dbChapter.id
chapter.copyFrom(dbChapter)
if (dbChapter.read && !chapter.read) {
@ -401,10 +398,12 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
chapter.bookmark = dbChapter.bookmark
}
}
}
chapters.map { it.manga_id = manga.id }
updateChapters(chapters.filter { it.id != null })
insertChapters(chapters.filter { it.id == null })
chapter.manga_id = manga.id
}
val newChapters = chapters.groupBy { it.id != null }
newChapters[true]?.let { updateKnownChapters(it) }
newChapters[false]?.let { insertChapters(it) }
}
}

View File

@ -13,17 +13,14 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.serialization.ExperimentalSerializationApi
import okio.buffer
import okio.gzip
import okio.source
import java.util.Date
@OptIn(ExperimentalSerializationApi::class)
class FullBackupRestore(context: Context, notifier: BackupNotifier, private val online: Boolean) : AbstractBackupRestore<FullBackupManager>(context, notifier) {
override fun performRestore(uri: Uri): Boolean {
override suspend fun performRestore(uri: Uri): Boolean {
backupManager = FullBackupManager(context)
val backupString = context.contentResolver.openInputStream(uri)!!.source().gzip().buffer().use { it.readByteArray() }
@ -60,7 +57,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
}
private fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) {
private suspend fun restoreManga(backupManga: BackupManga, backupCategories: List<BackupCategory>, online: Boolean) {
val manga = backupManga.getMangaImpl()
val chapters = backupManga.getChaptersImpl()
val categories = backupManga.categories
@ -94,7 +91,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
* @param history history data from json
* @param tracks tracking data from json
*/
private fun restoreMangaData(
private suspend fun restoreMangaData(
manga: Manga,
source: Source?,
chapters: List<Chapter>,
@ -126,7 +123,7 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreMangaFetch(
private suspend fun restoreMangaFetch(
source: Source?,
manga: Manga,
chapters: List<Chapter>,
@ -136,10 +133,9 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
backupCategories: List<BackupCategory>,
online: Boolean
) {
launchIO {
try {
val fetchedManga = backupManager.restoreMangaFetch(source, manga, online)
fetchedManga.id ?: (return@launchIO)
fetchedManga.id ?: return
if (online && source != null) {
updateChapters(source, fetchedManga, chapters)
@ -154,9 +150,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
errors.add(Date() to "${manga.title} - ${e.message}")
}
}
}
private fun restoreMangaNoFetch(
private suspend fun restoreMangaNoFetch(
source: Source?,
backupManga: Manga,
chapters: List<Chapter>,
@ -166,7 +161,6 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
backupCategories: List<BackupCategory>,
online: Boolean
) {
launchIO {
if (online && source != null) {
if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
updateChapters(source, backupManga, chapters)
@ -179,7 +173,6 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier, private val
updateTracking(backupManga, tracks)
}
}
private fun restoreExtraForManga(manga: Manga, categories: List<Int>, history: List<BackupHistory>, tracks: List<Track>, backupCategories: List<BackupCategory>) {
// Restore categories

View File

@ -5,12 +5,10 @@ import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.AbstractBackupRestoreValidator
import eu.kanade.tachiyomi.data.backup.full.models.BackupSerializer
import kotlinx.serialization.ExperimentalSerializationApi
import okio.buffer
import okio.gzip
import okio.source
@OptIn(ExperimentalSerializationApi::class)
class FullBackupRestoreValidator : AbstractBackupRestoreValidator() {
/**
* Checks for critical backup file data.

View File

@ -53,24 +53,8 @@ import kotlin.math.max
class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : AbstractBackupManager(context) {
var parserVersion: Int = version
private set
var parser: Gson = initParser()
/**
* Set version of parser
*
* @param version version of parser
*/
internal fun setVersion(version: Int) {
this.parserVersion = version
parser = initParser()
}
private fun initParser(): Gson = when (parserVersion) {
2 ->
GsonBuilder()
val parser: Gson = when (version) {
2 -> GsonBuilder()
.registerTypeAdapter<MangaImpl>(MangaTypeAdapter.build())
.registerTypeHierarchyAdapter<ChapterImpl>(ChapterTypeAdapter.build())
.registerTypeAdapter<CategoryImpl>(CategoryTypeAdapter.build())
@ -308,7 +292,7 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
*/
internal fun restoreCategoriesForManga(manga: Manga, categories: List<String>) {
val dbCategories = databaseHelper.getCategories().executeAsBlocking()
val mangaCategoriesToUpdate = mutableListOf<MangaCategory>()
val mangaCategoriesToUpdate = ArrayList<MangaCategory>(categories.size)
for (backupCategoryStr in categories) {
for (dbCategory in dbCategories) {
if (backupCategoryStr == dbCategory.name) {
@ -332,7 +316,7 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
*/
internal fun restoreHistoryForManga(history: List<DHistory>) {
// List containing history to be updated
val historyToBeUpdated = mutableListOf<History>()
val historyToBeUpdated = ArrayList<History>(history.size)
for ((url, lastRead) in history) {
val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking()
// Check if history already in database and update
@ -361,14 +345,14 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
* @param tracks the track list to restore.
*/
internal fun restoreTrackForManga(manga: Manga, tracks: List<Track>) {
// Fix foreign keys with the current manga id
tracks.map { it.manga_id = manga.id!! }
// Get tracks from database
val dbTracks = databaseHelper.getTracks(manga).executeAsBlocking()
val trackToUpdate = mutableListOf<Track>()
val trackToUpdate = ArrayList<Track>(tracks.size)
tracks.forEach { track ->
// Fix foreign keys with the current manga id
track.manga_id = manga.id!!
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged) {
var isInDatabase = false
@ -423,12 +407,13 @@ class LegacyBackupManager(context: Context, version: Int = CURRENT_VERSION) : Ab
chapter.copyFrom(dbChapter)
break
}
}
// Filter the chapters that couldn't be found.
chapters.filter { it.id != null }
chapters.map { it.manga_id = manga.id }
updateChapters(chapters)
chapter.manga_id = manga.id
}
// Filter the chapters that couldn't be found.
updateChapters(chapters.filter { it.id != null })
return true
}
}

View File

@ -21,12 +21,11 @@ import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.TrackImpl
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.lang.launchIO
import java.util.Date
class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBackupRestore<LegacyBackupManager>(context, notifier) {
override fun performRestore(uri: Uri): Boolean {
override suspend fun performRestore(uri: Uri): Boolean {
val reader = JsonReader(context.contentResolver.openInputStream(uri)!!.bufferedReader())
val json = JsonParser.parseReader(reader).asJsonObject
@ -63,7 +62,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
showRestoreProgress(restoreProgress, restoreAmount, context.getString(R.string.categories))
}
private fun restoreManga(mangaJson: JsonObject) {
private suspend fun restoreManga(mangaJson: JsonObject) {
val manga = backupManager.parser.fromJson<MangaImpl>(
mangaJson.get(
Backup.MANGA
@ -113,7 +112,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
* @param history history data from json
* @param tracks tracking data from json
*/
private fun restoreMangaData(
private suspend fun restoreMangaData(
manga: Manga,
source: Source,
chapters: List<Chapter>,
@ -143,7 +142,7 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
* @param chapters chapters of manga that needs updating
* @param categories categories that need updating
*/
private fun restoreMangaFetch(
private suspend fun restoreMangaFetch(
source: Source,
manga: Manga,
chapters: List<Chapter>,
@ -151,10 +150,9 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
history: List<DHistory>,
tracks: List<Track>
) {
launchIO {
try {
val fetchedManga = backupManager.fetchManga(source, manga)
fetchedManga.id ?: (return@launchIO)
fetchedManga.id ?: return
updateChapters(source, fetchedManga, chapters)
@ -165,9 +163,8 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
errors.add(Date() to "${manga.title} - ${e.message}")
}
}
}
private fun restoreMangaNoFetch(
private suspend fun restoreMangaNoFetch(
source: Source,
backupManga: Manga,
chapters: List<Chapter>,
@ -175,7 +172,6 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
history: List<DHistory>,
tracks: List<Track>
) {
launchIO {
if (!backupManager.restoreChaptersForManga(backupManga, chapters)) {
updateChapters(source, backupManga, chapters)
}
@ -184,7 +180,6 @@ class LegacyBackupRestore(context: Context, notifier: BackupNotifier) : Abstract
updateTracking(backupManga, tracks)
}
}
private fun restoreExtraForManga(manga: Manga, categories: List<String>, history: List<DHistory>, tracks: List<Track>) {
// Restore categories

View File

@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaChapter
import eu.kanade.tachiyomi.data.database.resolvers.ChapterBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterKnownBackupPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterProgressPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.ChapterSourceOrderPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterGetResolver
@ -84,6 +85,11 @@ interface ChapterQueries : DbProvider {
.withPutResolver(ChapterBackupPutResolver())
.prepare()
fun updateKnownChaptersBackup(chapters: List<Chapter>) = db.put()
.objects(chapters)
.withPutResolver(ChapterKnownBackupPutResolver())
.prepare()
fun updateChapterProgress(chapter: Chapter) = db.put()
.`object`(chapter)
.withPutResolver(ChapterProgressPutResolver())

View File

@ -0,0 +1,34 @@
package eu.kanade.tachiyomi.data.database.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
class ChapterKnownBackupPutResolver : PutResolver<Chapter>() {
override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(chapter)
val contentValues = mapToContentValues(chapter)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id)
.build()
fun mapToContentValues(chapter: Chapter) =
contentValuesOf(
ChapterTable.COL_READ to chapter.read,
ChapterTable.COL_BOOKMARK to chapter.bookmark,
ChapterTable.COL_LAST_PAGE_READ to chapter.last_page_read
)
}

View File

@ -16,8 +16,8 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
import eu.kanade.tachiyomi.util.lang.RetryWithDelay
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNow
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.saveTo
@ -228,8 +228,8 @@ class Downloader(
* @param chapters the list of chapters to download.
* @param autoStart whether to start the downloader after enqueing the chapters.
*/
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchUI {
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchUI
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO {
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO
val wasEmpty = queue.isEmpty()
// Called in background thread, the operation can be slow with SAF.
val chaptersWithoutDir = async {

View File

@ -33,10 +33,10 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.isServiceRunning
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
@ -158,8 +158,8 @@ class LibraryUpdateService(
* lock.
*/
override fun onDestroy() {
ioScope?.cancel()
updateJob?.cancel()
ioScope?.cancel()
if (wakeLock.isHeld) {
wakeLock.release()
}
@ -254,48 +254,35 @@ class LibraryUpdateService(
val failedUpdates = mutableListOf<Pair<Manga, String?>>()
var hasDownloads = false
mangaToUpdate
.map { manga ->
mangaToUpdate.forEach { manga ->
if (updateJob?.isActive != true) {
throw CancellationException()
return
}
// Notify manga that will update.
notifier.showProgressNotification(manga, progressCount.andIncrement, mangaToUpdate.size)
// Update the chapters of the manga
try {
val newChapters = updateManga(manga).first
Pair(manga, newChapters)
} catch (e: Throwable) {
// If there's any error, return empty update and continue.
val errorMessage = if (e is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
e.message
}
failedUpdates.add(Pair(manga, errorMessage))
Pair(manga, emptyList())
}
}
// Filter out mangas without new chapters (or failed).
.filter { (_, newChapters) -> newChapters.isNotEmpty() }
.forEach { (manga, newChapters) ->
val (newChapters, _) = updateManga(manga)
if (newChapters.isNotEmpty()) {
if (manga.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(manga, newChapters)
hasDownloads = true
}
// Convert to the manga that contains new chapters.
newUpdates.add(
Pair(
manga,
newChapters.sortedByDescending { ch -> ch.source_order }.toTypedArray()
)
)
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.sortedByDescending { ch -> ch.source_order }.toTypedArray())
}
} catch (e: Throwable) {
val errorMessage = if (e is NoChaptersException) {
getString(R.string.no_chapters_error)
} else {
e.message
}
failedUpdates.add(manga to errorMessage)
}
}
// Notify result of the overall update.
notifier.cancelProgressNotification()
if (newUpdates.isNotEmpty()) {
@ -334,7 +321,7 @@ class LibraryUpdateService(
val handler = CoroutineExceptionHandler { _, exception ->
Timber.e(exception)
}
ioScope.launch(handler) {
GlobalScope.launch(Dispatchers.IO + handler) {
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
val sManga = updatedManga.toSManga()
// Avoid "losing" existing cover
@ -360,7 +347,7 @@ class LibraryUpdateService(
mangaToUpdate.forEach { manga ->
if (updateJob?.isActive != true) {
throw CancellationException()
return
}
notifier.showProgressNotification(manga, progressCount++, mangaToUpdate.size)
@ -394,7 +381,7 @@ class LibraryUpdateService(
mangaToUpdate.forEach { manga ->
if (updateJob?.isActive != true) {
throw CancellationException()
return
}
// Notify manga that will update.

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.data.notification
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.net.Uri
@ -69,9 +70,10 @@ class NotificationReceiver : BroadcastReceiver() {
)
// Share backup file
ACTION_SHARE_BACKUP ->
shareBackup(
shareFile(
context,
intent.getParcelableExtra(EXTRA_URI),
if (intent.getBooleanExtra(EXTRA_IS_LEGACY_BACKUP, false)) "application/json" else "application/octet-stream+gzip",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
ACTION_CANCEL_RESTORE -> cancelRestore(
@ -100,6 +102,14 @@ class NotificationReceiver : BroadcastReceiver() {
markAsRead(urls, mangaId)
}
}
// Share crash dump file
ACTION_SHARE_CRASH_LOG ->
shareFile(
context,
intent.getParcelableExtra(EXTRA_URI),
"text/plain",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
)
}
}
@ -120,14 +130,13 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification
*/
private fun shareImage(context: Context, path: String, notificationId: Int) {
// Create intent
val intent = Intent(Intent.ACTION_SEND).apply {
val uri = File(path).getUriCompat(context)
putExtra(Intent.EXTRA_STREAM, uri)
clipData = ClipData.newRawUri(null, uri)
type = "image/*"
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
// Dismiss notification
dismissNotification(context, notificationId)
// Launch share activity
context.startActivity(intent)
@ -140,10 +149,11 @@ class NotificationReceiver : BroadcastReceiver() {
* @param path path of file
* @param notificationId id of notification
*/
private fun shareBackup(context: Context, uri: Uri, notificationId: Int) {
private fun shareFile(context: Context, uri: Uri, fileMimeType: String, notificationId: Int) {
val sendIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_STREAM, uri)
type = "application/json"
clipData = ClipData.newRawUri(null, uri)
type = fileMimeType
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
// Dismiss notification
@ -244,59 +254,34 @@ class NotificationReceiver : BroadcastReceiver() {
companion object {
private const val NAME = "NotificationReceiver"
// Called to launch share intent.
private const val ACTION_SHARE_IMAGE = "$ID.$NAME.SHARE_IMAGE"
// Called to delete image.
private const val ACTION_DELETE_IMAGE = "$ID.$NAME.DELETE_IMAGE"
// Called to launch send intent.
private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP"
// Called to cancel backup restore job.
private const val ACTION_SHARE_CRASH_LOG = "$ID.$NAME.SEND_CRASH_LOG"
private const val ACTION_CANCEL_RESTORE = "$ID.$NAME.CANCEL_RESTORE"
// Called to cancel library update.
private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
// Called to mark manga chapters as read.
private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
// Called to open chapter.
private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
// Value containing file location.
private const val EXTRA_FILE_LOCATION = "$ID.$NAME.FILE_LOCATION"
// Called to resume downloads.
private const val ACTION_RESUME_DOWNLOADS = "$ID.$NAME.ACTION_RESUME_DOWNLOADS"
// Called to pause downloads.
private const val ACTION_PAUSE_DOWNLOADS = "$ID.$NAME.ACTION_PAUSE_DOWNLOADS"
// Called to clear downloads.
private const val ACTION_CLEAR_DOWNLOADS = "$ID.$NAME.ACTION_CLEAR_DOWNLOADS"
// Called to dismiss notification.
private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION"
// Value containing uri.
private const val EXTRA_FILE_LOCATION = "$ID.$NAME.FILE_LOCATION"
private const val EXTRA_URI = "$ID.$NAME.URI"
// Value containing notification id.
private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID"
// Value containing group id.
private const val EXTRA_GROUP_ID = "$ID.$NAME.EXTRA_GROUP_ID"
// Value containing manga id.
private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID"
// Value containing chapter id.
private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID"
// Value containing chapter url.
private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
private const val EXTRA_IS_LEGACY_BACKUP = "$ID.$NAME.EXTRA_IS_LEGACY_BACKUP"
/**
* Returns a [PendingIntent] that resumes the download of a chapter
@ -509,10 +494,11 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification
* @return [PendingIntent]
*/
internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent {
internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, isLegacyFormat: Boolean, notificationId: Int): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_SHARE_BACKUP
putExtra(EXTRA_URI, uri)
putExtra(EXTRA_IS_LEGACY_BACKUP, isLegacyFormat)
putExtra(EXTRA_NOTIFICATION_ID, notificationId)
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
@ -534,6 +520,23 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getActivity(context, 0, intent, 0)
}
/**
* Returns [PendingIntent] that starts a share activity for a crash log dump file.
*
* @param context context of application
* @param uri uri of file
* @param notificationId id of notification
* @return [PendingIntent]
*/
internal fun shareCrashLogPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_SHARE_CRASH_LOG
putExtra(EXTRA_URI, uri)
putExtra(EXTRA_NOTIFICATION_ID, notificationId)
}
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
/**
* Returns [PendingIntent] that cancels a backup restore job.
*

View File

@ -23,6 +23,8 @@ object PreferenceKeys {
const val showPageNumber = "pref_show_page_number_key"
const val dualPageSplit = "pref_dual_page_split"
const val showReadingMode = "pref_show_reading_mode"
const val trueColor = "pref_true_color_key"

View File

@ -89,6 +89,8 @@ class PreferencesHelper(val context: Context) {
fun showPageNumber() = flowPrefs.getBoolean(Keys.showPageNumber, true)
fun dualPageSplit() = flowPrefs.getBoolean(Keys.dualPageSplit, false)
fun showReadingMode() = prefs.getBoolean(Keys.showReadingMode, true)
fun trueColor() = flowPrefs.getBoolean(Keys.trueColor, false)

View File

@ -18,6 +18,7 @@ sealed class Extension {
override val versionCode: Int,
override val lang: String,
override val isNsfw: Boolean,
val pkgFactory: String?,
val sources: List<Source>,
val hasUpdate: Boolean = false,
val isObsolete: Boolean = false,

View File

@ -31,6 +31,7 @@ internal object ExtensionLoader {
private const val EXTENSION_FEATURE = "tachiyomi.extension"
private const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
private const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
private const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val LIB_VERSION_MIN = 1.2
const val LIB_VERSION_MAX = 1.2
@ -184,7 +185,8 @@ internal object ExtensionLoader {
versionCode,
lang,
isNsfw,
sources,
sources = sources,
pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY),
isUnofficial = signatureHash != officialSignature
)
return LoadResult.Success(extension)

View File

@ -9,6 +9,7 @@ import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.Toast
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
import eu.kanade.tachiyomi.util.system.WebViewUtil
@ -98,7 +99,7 @@ class CloudflareInterceptor(private val context: Context) : Interceptor {
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
webview.settings.userAgentString = request.header("User-Agent")
?: WebViewUtil.DEFAULT_USER_AGENT
?: HttpSource.DEFAULT_USER_AGENT
webview.webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView, url: String) {

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.network
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.Interceptor
import okhttp3.Response
@ -12,7 +12,7 @@ class UserAgentInterceptor : Interceptor {
val newRequest = originalRequest
.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", WebViewUtil.DEFAULT_USER_AGENT)
.addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT)
.build()
chain.proceed(newRequest)
} else {

View File

@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.system.WebViewUtil
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
@ -75,7 +74,7 @@ abstract class HttpSource : CatalogueSource {
* Headers builder for requests. Implementations can override this method for custom headers.
*/
protected open fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", WebViewUtil.DEFAULT_USER_AGENT)
add("User-Agent", DEFAULT_USER_AGENT)
}
/**
@ -370,4 +369,8 @@ abstract class HttpSource : CatalogueSource {
* Returns the list of filters for the source.
*/
override fun getFilterList() = FilterList()
companion object {
const val DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36 Edg/88.0.705.63"
}
}

View File

@ -11,15 +11,13 @@ import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RestoreViewOnCreateController
import kotlinx.android.extensions.LayoutContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import timber.log.Timber
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
RestoreViewOnCreateController(bundle),
LayoutContainer {
RestoreViewOnCreateController(bundle) {
lateinit var binding: VB
@ -53,9 +51,6 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
)
}
override val containerView: View?
get() = view
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedViewState: Bundle?): View {
return inflateView(inflater, container)
}

View File

@ -1,11 +0,0 @@
package eu.kanade.tachiyomi.ui.base.holder
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view), LayoutContainer {
override val containerView: View?
get() = itemView
}

View File

@ -208,7 +208,11 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private fun openCommitHistory() {
val pkgName = presenter.extension!!.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
val url = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master/src/${pkgName.replace(".", "/")}"
val pkgFactory = presenter.extension!!.pkgFactory
val url = when {
!pkgFactory.isNullOrEmpty() -> "$URL_EXTENSION_COMMITS/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
else -> "$URL_EXTENSION_COMMITS/src/${pkgName.replace(".", "/")}"
}
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
startActivity(intent)
}
@ -232,5 +236,7 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
private companion object {
const val PKGNAME_KEY = "pkg_name"
private const val URL_EXTENSION_COMMITS = "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
}
}

View File

@ -4,10 +4,10 @@ import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.os.bundleOf
import androidx.preference.DialogPreference
import androidx.preference.EditTextPreference

View File

@ -0,0 +1,14 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
class MigrationMangaAdapter(controller: MigrationMangaController) :
FlexibleAdapter<IFlexible<*>>(null, controller, true) {
val coverClickListener: OnCoverClickListener = controller
interface OnCoverClickListener {
fun onCoverClick(position: Int)
}
}

View File

@ -7,17 +7,18 @@ import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.LinearLayoutManager
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.MigrationMangaControllerBinding
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.browse.migration.search.SearchController
import eu.kanade.tachiyomi.ui.manga.MangaController
class MigrationMangaController :
NucleusController<MigrationMangaControllerBinding, MigrationMangaPresenter>,
FlexibleAdapter.OnItemClickListener {
FlexibleAdapter.OnItemClickListener,
MigrationMangaAdapter.OnCoverClickListener {
private var adapter: FlexibleAdapter<IFlexible<*>>? = null
private var adapter: MigrationMangaAdapter? = null
constructor(sourceId: Long, sourceName: String?) : super(
bundleOf(
@ -51,7 +52,7 @@ class MigrationMangaController :
override fun onViewCreated(view: View) {
super.onViewCreated(view)
adapter = FlexibleAdapter<IFlexible<*>>(null, this)
adapter = MigrationMangaAdapter(this)
binding.recycler.layoutManager = LinearLayoutManager(view.context)
binding.recycler.adapter = adapter
adapter?.fastScroller = binding.fastScroller
@ -62,17 +63,22 @@ class MigrationMangaController :
super.onDestroyView(view)
}
fun setManga(manga: List<MangaItem>) {
fun setManga(manga: List<MigrationMangaItem>) {
adapter?.updateDataSet(manga)
}
override fun onItemClick(view: View, position: Int): Boolean {
val item = adapter?.getItem(position) as? MangaItem ?: return false
val item = adapter?.getItem(position) as? MigrationMangaItem ?: return false
val controller = SearchController(item.manga)
router.pushController(controller.withFadeTransaction())
return false
}
override fun onCoverClick(position: Int) {
val mangaItem = adapter?.getItem(position) as? MigrationMangaItem ?: return
router.pushController(MangaController(mangaItem.manga).withFadeTransaction())
}
companion object {
const val SOURCE_ID_EXTRA = "source_id_extra"
const val SOURCE_NAME_EXTRA = "source_name_extra"

View File

@ -5,29 +5,27 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
class MangaHolder(
class MigrationMangaHolder(
view: View,
adapter: FlexibleAdapter<*>
private val adapter: MigrationMangaAdapter
) : FlexibleViewHolder(view, adapter) {
private val binding = SourceListItemBinding.bind(view)
fun bind(item: MangaItem) {
// Update the title of the manga.
binding.title.text = item.manga.title
// Create thumbnail onclick to simulate long click
init {
binding.thumbnail.setOnClickListener {
// Simulate long click on this view to enter selection mode
onLongClick(itemView)
adapter.coverClickListener.onCoverClick(bindingAdapterPosition)
}
}
fun bind(item: MigrationMangaItem) {
binding.title.text = item.manga.title
// Update the cover.
GlideApp.with(itemView.context).clear(binding.thumbnail)

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.os.Parcelable
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
@ -8,25 +7,20 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import kotlinx.parcelize.Parcelize
@Parcelize
class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>(), Parcelable {
class MigrationMangaItem(val manga: Manga) : AbstractFlexibleItem<MigrationMangaHolder>() {
override fun getLayoutRes(): Int {
return R.layout.source_list_item
}
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MangaHolder {
return MangaHolder(
view,
adapter
)
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): MigrationMangaHolder {
return MigrationMangaHolder(view, adapter as MigrationMangaAdapter)
}
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: MangaHolder,
holder: MigrationMangaHolder,
position: Int,
payloads: List<Any?>?
) {
@ -34,7 +28,7 @@ class MangaItem(val manga: Manga) : AbstractFlexibleItem<MangaHolder>(), Parcela
}
override fun equals(other: Any?): Boolean {
if (other is MangaItem) {
if (other is MigrationMangaItem) {
return manga.id == other.manga.id
}
return false

View File

@ -23,9 +23,9 @@ class MigrationMangaPresenter(
.subscribeLatestCache(MigrationMangaController::setManga)
}
private fun libraryToMigrationItem(library: List<Manga>): List<MangaItem> {
private fun libraryToMigrationItem(library: List<Manga>): List<MigrationMangaItem> {
return library.filter { it.source == sourceId }
.sortedBy { it.title }
.map { MangaItem(it) }
.map { MigrationMangaItem(it) }
}
}

View File

@ -1,9 +1,11 @@
package eu.kanade.tachiyomi.ui.browse.migration.sources
import android.view.View
import androidx.core.view.isVisible
import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SourceMainControllerCardItemBinding
import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.util.system.LocaleHelper
class SourceHolder(view: View, val adapter: SourceAdapter) :
FlexibleViewHolder(view, adapter) {
@ -13,10 +15,10 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
fun bind(item: SourceItem) {
val source = item.source
// Set source name
binding.title.text = source.name
binding.subtitle.isVisible = true
binding.subtitle.text = LocaleHelper.getDisplayName(source.lang)
// Set source icon
itemView.post {
binding.image.setImageDrawable(source.icon())
}

View File

@ -39,6 +39,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.more.MoreController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.system.connectivityManager
import eu.kanade.tachiyomi.util.system.openInBrowser
@ -391,16 +392,16 @@ open class BrowseSourceController(bundle: Bundle) :
}
if (adapter.isEmpty) {
val actions = emptyList<EmptyView.Action>().toMutableList()
if (presenter.source is LocalSource) {
actions += EmptyView.Action(R.string.local_source_help_guide) { openLocalSourceHelpGuide() }
val actions = if (presenter.source is LocalSource) {
listOf(
EmptyView.Action(R.string.local_source_help_guide, R.drawable.ic_help_24dp) { openLocalSourceHelpGuide() }
)
} else {
actions += EmptyView.Action(R.string.action_retry, retryAction)
}
if (presenter.source is HttpSource) {
actions += EmptyView.Action(R.string.action_open_in_web_view) { openInWebView() }
listOf(
EmptyView.Action(R.string.action_retry, R.drawable.ic_refresh_24dp, retryAction),
EmptyView.Action(R.string.action_open_in_web_view, R.drawable.ic_public_24dp) { openInWebView() },
EmptyView.Action(R.string.label_help, R.drawable.ic_help_24dp) { activity?.openInBrowser(MoreController.URL_HELP) }
)
}
binding.emptyView.show(message, actions)

View File

@ -316,17 +316,26 @@ class MangaPresenter(
private fun observeDownloads() {
observeDownloadsStatusSubscription?.let { remove(it) }
observeDownloadsStatusSubscription = downloadManager.queue.getStatusObservable()
.observeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.onBackpressureLatest()
.filter { download -> download.manga.id == manga.id }
.doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(MangaController::onChapterDownloadUpdate) { _, error ->
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(
{ view, it ->
onDownloadStatusChange(it)
view.onChapterDownloadUpdate(it)
},
{ _, error ->
Timber.e(error)
}
)
observeDownloadsPageSubscription?.let { remove(it) }
observeDownloadsPageSubscription = downloadManager.queue.getProgressObservable()
.observeOn(AndroidSchedulers.mainThread())
.observeOn(Schedulers.io())
.onBackpressureLatest()
.filter { download -> download.manga.id == manga.id }
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(MangaController::onChapterDownloadUpdate) { _, error ->
Timber.e(error)
}
@ -484,7 +493,7 @@ class MangaPresenter(
db.updateChaptersProgress(chapters).executeAsBlocking()
if (preferences.removeAfterMarkedAsRead()) {
deleteChapters(chapters)
deleteChapters(chapters.filter { it.read })
}
}
}

View File

@ -14,6 +14,9 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
private val binding: ChapterDownloadViewBinding
private var state = Download.State.NOT_DOWNLOADED
private var progress = 0
private var downloadIconAnimator: ObjectAnimator? = null
private var isAnimating = false
@ -23,6 +26,17 @@ class ChapterDownloadView @JvmOverloads constructor(context: Context, attrs: Att
}
fun setState(state: Download.State, progress: Int = 0) {
val isDirty = this.state.value != state.value || this.progress != progress
this.state = state
this.progress = progress
if (isDirty) {
updateLayout()
}
}
private fun updateLayout() {
binding.downloadIconBorder.isVisible = state == Download.State.NOT_DOWNLOADED
binding.downloadIcon.isVisible = state == Download.State.NOT_DOWNLOADED || state == Download.State.DOWNLOADING

View File

@ -2,13 +2,13 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.annotation.SuppressLint
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.databinding.TrackItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat
class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : BaseViewHolder(binding.root) {
class TrackHolder(private val binding: TrackItemBinding, adapter: TrackAdapter) : RecyclerView.ViewHolder(binding.root) {
private val preferences: PreferencesHelper by injectLazy()

View File

@ -151,6 +151,6 @@ class MoreController :
}
companion object {
private const val URL_HELP = "https://tachiyomi.org/help/"
const val URL_HELP = "https://tachiyomi.org/help/"
}
}

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.app.ProgressDialog
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
@ -595,10 +596,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
val manga = presenter.manga ?: return
val chapter = page.chapter.chapter
val stream = file.getUriCompat(this)
val uri = file.getUriCompat(this)
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_TEXT, getString(R.string.share_page_info, manga.title, chapter.name, page.number))
putExtra(Intent.EXTRA_STREAM, stream)
putExtra(Intent.EXTRA_STREAM, uri)
clipData = ClipData.newRawUri(null, uri)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
type = "image/*"
}

View File

@ -65,6 +65,7 @@ class ReaderSettingsSheet(private val activity: ReaderActivity) : BaseBottomShee
binding.backgroundColor.bindToIntPreference(preferences.readerTheme(), R.array.reader_themes_values)
binding.showPageNumber.bindToPreference(preferences.showPageNumber())
binding.fullscreen.bindToPreference(preferences.fullscreen())
binding.dualPageSplit.bindToPreference(preferences.dualPageSplit())
binding.keepscreen.bindToPreference(preferences.keepScreenOn())
binding.longTap.bindToPreference(preferences.readWithLongTap())
binding.alwaysShowChapterTransition.bindToPreference(preferences.alwaysShowChapterTransition())

View File

@ -0,0 +1,10 @@
package eu.kanade.tachiyomi.ui.reader.model
class InsertPage(val parent: ReaderPage) : ReaderPage(parent.index, parent.url, parent.imageUrl) {
override var chapter: ReaderChapter = parent.chapter
init {
stream = parent.stream
}
}

View File

@ -3,12 +3,12 @@ package eu.kanade.tachiyomi.ui.reader.model
import eu.kanade.tachiyomi.source.model.Page
import java.io.InputStream
class ReaderPage(
open class ReaderPage(
index: Int,
url: String = "",
imageUrl: String? = null,
var stream: (() -> InputStream)? = null
) : Page(index, url, imageUrl, null) {
lateinit var chapter: ReaderChapter
open lateinit var chapter: ReaderChapter
}

View File

@ -24,6 +24,7 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C
var volumeKeysInverted = false
var trueColor = false
var alwaysShowChapterTransition = true
var dualPageSplit = false
var navigationMode = 0
protected set
@ -54,6 +55,9 @@ abstract class ViewerConfig(preferences: PreferencesHelper, private val scope: C
preferences.alwaysShowChapterTransition()
.register({ alwaysShowChapterTransition = it })
preferences.dualPageSplit()
.register({ dualPageSplit = it }, { imagePropertyChangedListener?.invoke() })
}
protected abstract fun defaultNavigation(): ViewerNavigation

View File

@ -1,8 +1,7 @@
package eu.kanade.tachiyomi.ui.reader.viewer.pager
package eu.kanade.tachiyomi.ui.reader.viewer.navigation
import android.graphics.RectF
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
/**
* Visualization of default state without any inversion
@ -14,7 +13,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
* | N | M | P | N: Move Left
* +---+---+---+
*/
class PagerDefaultNavigation : ViewerNavigation() {
class RightAndLeftNavigation : ViewerNavigation() {
override var regions: List<Region> = listOf(
Region(
@ -27,5 +26,3 @@ class PagerDefaultNavigation : ViewerNavigation() {
),
)
}
class VerticalPagerDefaultNavigation : LNavigation()

View File

@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.RightAndLeftNavigation
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -69,8 +70,8 @@ class PagerConfig(
override fun defaultNavigation(): ViewerNavigation {
return when (viewer) {
is VerticalPagerViewer -> VerticalPagerDefaultNavigation()
else -> PagerDefaultNavigation()
is VerticalPagerViewer -> LNavigation()
else -> RightAndLeftNavigation()
}
}
@ -80,6 +81,7 @@ class PagerConfig(
1 -> LNavigation()
2 -> KindlishNavigation()
3 -> EdgeNavigation()
4 -> RightAndLeftNavigation()
else -> defaultNavigation()
}
}

View File

@ -28,6 +28,7 @@ import com.github.chrisbanes.photoview.PhotoView
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.glide.GlideApp
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
@ -241,6 +242,9 @@ class PagerPageHolder(
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
openStream = processDualPageSplit(openStream!!)
}
if (!isAnimated) {
initSubsamplingImageView().setImage(ImageSource.inputStream(openStream!!))
} else {
@ -253,6 +257,38 @@ class PagerPageHolder(
.subscribe({}, {})
}
private fun processDualPageSplit(openStream: InputStream): InputStream {
var inputStream = openStream
val (isDoublePage, stream) = when (page) {
is InsertPage -> Pair(true, inputStream)
else -> ImageUtil.isDoublePage(inputStream)
}
inputStream = stream
if (isDoublePage) {
val side = when {
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
viewer is R2LPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
viewer is L2RPagerViewer && page !is InsertPage -> ImageUtil.Side.LEFT
viewer is R2LPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
viewer is VerticalPagerViewer && page !is InsertPage -> ImageUtil.Side.RIGHT
viewer is VerticalPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT
else -> error("We should choose a side!")
}
if (page !is InsertPage) {
onPageSplit()
}
inputStream = ImageUtil.splitInHalf(inputStream, side)
}
return inputStream
}
private fun onPageSplit() {
val newPage = InsertPage(page)
viewer.onPageSplit(page, newPage)
}
/**
* Called when the page has an error.
*/

View File

@ -12,6 +12,7 @@ import androidx.viewpager.widget.ViewPager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
@ -371,4 +372,8 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
}
return false
}
fun onPageSplit(currentPage: ReaderPage, newPage: InsertPage) {
adapter.onPageSplit(currentPage, newPage, this::class.java)
}
}

View File

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
import android.view.View
import android.view.ViewGroup
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
@ -18,7 +19,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
/**
* List of currently set items.
*/
var items: List<Any> = emptyList()
var items: MutableList<Any> = mutableListOf()
private set
var nextTransition: ChapterTransition.Next? = null
@ -80,6 +81,9 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
}
}
// Resets double-page splits, else insert pages get misplaced
items.filterIsInstance<InsertPage>().also { items.removeAll(it) }
if (viewer is R2LPagerViewer) {
newItems.reverse()
}
@ -120,4 +124,31 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
}
return POSITION_NONE
}
fun onPageSplit(current: Any?, newPage: InsertPage, clazz: Class<out PagerViewer>) {
if (current !is ReaderPage) return
val currentIndex = items.indexOf(current)
val placeAtIndex = when {
clazz.isAssignableFrom(L2RPagerViewer::class.java) -> currentIndex + 1
clazz.isAssignableFrom(VerticalPagerViewer::class.java) -> currentIndex + 1
clazz.isAssignableFrom(R2LPagerViewer::class.java) -> currentIndex
else -> currentIndex
}
// It will enter a endless cycle of insert pages
if (clazz.isAssignableFrom(R2LPagerViewer::class.java) && items[placeAtIndex - 1] is InsertPage) {
return
}
// Same here it will enter a endless cycle of insert pages
if (items[placeAtIndex] is InsertPage) {
return
}
items.add(placeAtIndex, newPage)
notifyDataSetChanged()
}
}

View File

@ -3,13 +3,13 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
import android.content.Context
import android.view.View
import android.view.ViewGroup.LayoutParams
import eu.kanade.tachiyomi.ui.base.holder.BaseViewHolder
import androidx.recyclerview.widget.RecyclerView
import rx.Subscription
abstract class WebtoonBaseHolder(
view: View,
protected val viewer: WebtoonViewer
) : BaseViewHolder(view) {
) : RecyclerView.ViewHolder(view) {
/**
* Context getter because it's used often.

View File

@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.EdgeNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.KindlishNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.RightAndLeftNavigation
import kotlinx.coroutines.CoroutineScope
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@ -44,7 +45,7 @@ class WebtoonConfig(
}
override fun defaultNavigation(): ViewerNavigation {
return WebtoonDefaultNavigation()
return LNavigation()
}
override fun updateNavigation(navigationMode: Int) {
@ -53,6 +54,7 @@ class WebtoonConfig(
1 -> LNavigation()
2 -> KindlishNavigation()
3 -> EdgeNavigation()
4 -> RightAndLeftNavigation()
else -> defaultNavigation()
}
}

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
class WebtoonDefaultNavigation : LNavigation()

View File

@ -287,6 +287,14 @@ class WebtoonPageHolder(
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { isAnimated ->
if (viewer.config.dualPageSplit) {
val (isDoublePage, stream) = ImageUtil.isDoublePage(openStream!!)
openStream = if (!isDoublePage) {
stream
} else {
ImageUtil.splitAndMerge(stream)
}
}
if (!isAnimated) {
val subsamplingView = initSubsamplingImageView()
subsamplingView.isVisible = true

View File

@ -40,13 +40,22 @@ class UpdatesPresenter(
.subscribeLatestCache(UpdatesController::onNextRecentChapters)
downloadManager.queue.getStatusObservable()
.observeOn(Schedulers.io())
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { onDownloadStatusChange(it) }
.subscribeLatestCache(UpdatesController::onChapterDownloadUpdate) { _, error ->
.subscribeLatestCache(
{ view, it ->
onDownloadStatusChange(it)
view.onChapterDownloadUpdate(it)
},
{ _, error ->
Timber.e(error)
}
)
downloadManager.queue.getProgressObservable()
.observeOn(Schedulers.io())
.onBackpressureLatest()
.observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(UpdatesController::onChapterDownloadUpdate) { _, error ->
Timber.e(error)

View File

@ -6,11 +6,11 @@ import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.util.TypedValue
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.updatePadding
import androidx.preference.PreferenceController
import androidx.preference.PreferenceGroup

View File

@ -178,6 +178,7 @@ class SettingsGeneralController : SettingsController() {
"cv",
"de",
"el",
"eo",
"es",
"es-419",
"en-US",

View File

@ -129,9 +129,10 @@ class SettingsLibraryController : SettingsController() {
R.string.update_6hour,
R.string.update_12hour,
R.string.update_24hour,
R.string.update_48hour
R.string.update_48hour,
R.string.update_weekly
)
entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48")
entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48", "168")
defaultValue = "24"
summary = "%s"

View File

@ -50,6 +50,11 @@ class SettingsReaderController : SettingsController() {
summaryRes = R.string.pref_show_reading_mode_summary
defaultValue = true
}
switchPreference {
key = Keys.dualPageSplit
titleRes = R.string.pref_dual_page_split
defaultValue = false
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
switchPreference {
key = Keys.trueColor

View File

@ -44,6 +44,12 @@ class CrashLogUtil(private val context: Context) {
NotificationReceiver.openErrorLogPendingActivity(context, uri)
)
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
NotificationReceiver.shareCrashLogPendingBroadcast(context, uri, Notifications.ID_CRASH_LOGS)
)
context.notificationManager.notify(Notifications.ID_CRASH_LOGS, build())
}
}

View File

@ -1,5 +1,11 @@
package eu.kanade.tachiyomi.util.system
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Rect
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URLConnection
@ -68,4 +74,71 @@ object ImageUtil {
GIF("image/gif", "gif"),
WEBP("image/webp", "webp")
}
/**
* Check whether the image is a double image (width > height), return the result and original stream
*/
fun isDoublePage(imageStream: InputStream): Pair<Boolean, InputStream> {
val imageBytes = imageStream.readBytes()
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
return Pair(options.outWidth > options.outHeight, ByteArrayInputStream(imageBytes))
}
/**
* Extract the 'side' part from imageStream and return it as InputStream.
*/
fun splitInHalf(imageStream: InputStream, side: Side): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height
val width = imageBitmap.width
val singlePage = Rect(0, 0, width / 2, height)
val half = Bitmap.createBitmap(width / 2, height, Bitmap.Config.ARGB_8888)
val part = when (side) {
Side.RIGHT -> Rect(width - width / 2, 0, width, height)
Side.LEFT -> Rect(0, 0, width / 2, height)
}
val canvas = Canvas(half)
canvas.drawBitmap(imageBitmap, part, singlePage, null)
val output = ByteArrayOutputStream()
half.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
}
/**
* Split the image into left and right parts, then merge them into a new image.
*/
fun splitAndMerge(imageStream: InputStream): InputStream {
val imageBytes = imageStream.readBytes()
val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
val height = imageBitmap.height
val width = imageBitmap.width
val result = Bitmap.createBitmap(width / 2, height * 2, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
// right -> upper
val rightPart = Rect(width - width / 2, 0, width, height)
val upperPart = Rect(0, 0, width / 2, height)
canvas.drawBitmap(imageBitmap, rightPart, upperPart, null)
// left -> bottom
val leftPart = Rect(0, 0, width / 2, height)
val bottomPart = Rect(0, height, width / 2, height * 2)
canvas.drawBitmap(imageBitmap, leftPart, bottomPart, null)
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
return ByteArrayInputStream(output.toByteArray())
}
enum class Side {
RIGHT, LEFT
}
}

View File

@ -6,33 +6,19 @@ import android.content.pm.PackageManager
import android.webkit.CookieManager
import android.webkit.WebSettings
import android.webkit.WebView
import eu.kanade.tachiyomi.util.lang.launchUI
import timber.log.Timber
object WebViewUtil {
val WEBVIEW_UA_VERSION_REGEX by lazy {
Regex(""".*Chrome/(\d+)\..*""")
}
var DEFAULT_USER_AGENT: String = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
private set
const val REQUESTED_WITH = "com.android.browser"
const val MINIMUM_WEBVIEW_VERSION = 87
const val MINIMUM_WEBVIEW_VERSION = 88
fun supportsWebView(context: Context): Boolean {
try {
// May throw android.webkit.WebViewFactory$MissingWebViewPackageException if WebView
// is not installed
CookieManager.getInstance()
launchUI {
DEFAULT_USER_AGENT = WebView(context)
.getDefaultUserAgentString()
.replace("; wv", "")
}
} catch (e: Exception) {
} catch (e: Throwable) {
Timber.e(e)
return false
}
@ -59,7 +45,7 @@ fun WebView.setDefaultSettings() {
}
private fun WebView.getWebViewMajorVersion(): Int {
val uaRegexMatch = WebViewUtil.WEBVIEW_UA_VERSION_REGEX.matchEntire(getDefaultUserAgentString())
val uaRegexMatch = """.*Chrome/(\d+)\..*""".toRegex().matchEntire(getDefaultUserAgentString())
return if (uaRegexMatch != null && uaRegexMatch.groupValues.size > 1) {
uaRegexMatch.groupValues[1].toInt()
} else {

View File

@ -1,24 +1,26 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.view.isVisible
import com.google.android.material.button.MaterialButton
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.CommonViewEmptyBinding
import kotlin.random.Random
class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
RelativeLayout(context, attrs) {
private val binding: CommonViewEmptyBinding
init {
binding = CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true)
}
private val binding: CommonViewEmptyBinding =
CommonViewEmptyBinding.inflate(LayoutInflater.from(context), this, true)
/**
* Hide the information view
@ -40,21 +42,26 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
binding.textLabel.text = message
binding.actionsContainer.removeAllViews()
if (!actions.isNullOrEmpty()) {
actions.forEach {
val button = AppCompatButton(context).apply {
actions?.forEach {
val button = MaterialButton(ContextThemeWrapper(context, R.style.Theme_Widget_Button_Action)).apply {
layoutParams = LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
1f / actions.size
)
setText(it.resId)
backgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
stateListAnimator = null
elevation = 0f
setIconResource(it.iconResId)
setText(it.stringResId)
setOnClickListener(it.listener)
}
binding.actionsContainer.addView(button)
}
}
this.isVisible = true
}
@ -75,7 +82,8 @@ class EmptyView @JvmOverloads constructor(context: Context, attrs: AttributeSet?
}
data class Action(
@StringRes val resId: Int,
@StringRes val stringResId: Int,
@DrawableRes val iconResId: Int,
val listener: OnClickListener
)
}

View File

@ -1,29 +0,0 @@
package eu.kanade.tachiyomi.widget
import android.content.Context
import android.util.AttributeSet
import androidx.core.view.forEach
import androidx.viewpager.widget.ViewPager
/**
* A [ViewPager] that sets its height to the maximum height of its children.
* This is a way to mimic WRAP_CONTENT for its height.
*/
class MaxHeightViewPager(context: Context, attrs: AttributeSet?) : ViewPager(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
var measuredHeight = heightMeasureSpec
var height = 0
forEach {
it.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED))
val h = it.measuredHeight
if (h > height) height = h
}
if (height != 0) {
measuredHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
}
super.onMeasure(widthMeasureSpec, measuredHeight)
}
}

View File

@ -5,6 +5,6 @@
Ensures visibility on top of the background color.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorOnBackground" android:state_selected="true"/>
<item android:color="?attr/colorAccent" android:state_selected="true"/>
<item android:alpha="0.60" android:color="?attr/colorOnBackground"/>
</selector>

View File

@ -22,6 +22,7 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabTextColor="@color/tabs_selector_background"
app:tabIndicatorColor="?attr/colorAccent"
app:tabGravity="fill"
app:tabMode="fixed" />
@ -38,12 +39,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_more_vert_24dp"
app:tint="?attr/colorOnPrimary"
app:tint="?attr/colorOnBackground"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
<eu.kanade.tachiyomi.widget.MaxHeightViewPager
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

View File

@ -27,9 +27,8 @@
<LinearLayout
android:id="@+id/actions_container"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical" />
android:orientation="horizontal" />
</LinearLayout>

View File

@ -151,6 +151,14 @@
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toBottomOf="@id/show_page_number" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/dual_page_split"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/pref_dual_page_split"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintTop_toBottomOf="@id/fullscreen" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/cutout_short"
android:layout_width="match_parent"
@ -158,7 +166,7 @@
android:text="@string/pref_cutout_short"
android:textColor="?android:attr/textColorSecondary"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/fullscreen"
app:layout_constraintTop_toBottomOf="@id/dual_page_split"
tools:visibility="visible" />
<com.google.android.material.switchmaterial.SwitchMaterial

View File

@ -39,7 +39,6 @@
<string name="pref_library_update_restriction_summary">ሁኔታዎቹ ሲሟሉ ብቻ ያዘምኑ</string>
<string name="pref_library_update_restriction">የቤተ-መጽሐፍት ዝመና ገደቦች</string>
<string name="pref_library_update_prioritization">የቤተ-መጽሐፍት ዝመና ትዕዛዝ</string>
<string name="update_monthly">ወርሃዊ</string>
<string name="update_weekly">ሳምንታዊ</string>
<string name="update_48hour">በየ 2 ቀኑ</string>
<string name="update_24hour">በየቀኑ</string>

View File

@ -91,7 +91,6 @@
<string name="update_24hour">يومياً</string>
<string name="update_48hour">كل يومان</string>
<string name="update_weekly">أسبوعياً</string>
<string name="update_monthly">شهرياً</string>
<string name="pref_library_update_categories">الفئات المتضمنة فى التحديث الشامل</string>
<string name="all">الكل</string>
<string name="pref_library_update_restriction">قيود تحديثات المكتبة</string>

View File

@ -231,7 +231,6 @@
<string name="pref_library_update_restriction_summary">Actualizar solo cuando se cumplan las condiciones</string>
<string name="pref_library_update_restriction">Restricciones de actualización de biblioteca</string>
<string name="pref_library_update_prioritization">Orden de actualización de la biblioteca</string>
<string name="update_monthly">Mensual</string>
<string name="update_weekly">Semanal</string>
<string name="update_48hour">Cada 2 días</string>
<string name="update_24hour">Diario</string>

View File

@ -308,7 +308,6 @@
<string name="app_not_available">Приложението не е достъпно</string>
<string name="short_recent_updates">Обновления</string>
<string name="update_weekly">Ежеседмично</string>
<string name="update_monthly">Ежемесечно</string>
<string name="default_category">Категория по подразбиране</string>
<string name="default_category_summary">Питай всеки път</string>
<string name="pref_crop_borders">Изрязвай границите</string>

View File

@ -95,7 +95,6 @@
<string name="update_24hour">প্রতিদিন</string>
<string name="update_48hour">প্রতি ২ দিন</string>
<string name="update_weekly">সাপ্তাহিক</string>
<string name="update_monthly">মাসিক</string>
<string name="pref_library_update_categories">সার্বজনীন হালনাগাদের জন্য যে বিভাগসমূহ অন্তর্ভুক্ত করবেন</string>
<string name="all">সব</string>
<string name="pref_library_update_restriction">সংগ্রহশালা হালনাগেদের সীমাবদ্ধতা</string>
@ -458,7 +457,7 @@
<string name="set_chapter_settings_as_default">প্রকৃত হিসেবে সংরক্ষণ করুন</string>
<string name="confirm_set_chapter_settings">আপনি কি নিশ্চিত সেটিংসগুলো প্রকৃত হিসেবে সংরক্ষণ করবেন\?</string>
<string name="label_data">তথ্য</string>
<string name="requires_app_restart">কার্যকর করতে অ্যাপ পুনরারম্ভ করতে হব</string>
<string name="requires_app_restart">কার্যকর করতে অ্যাপ পুনরারম্ভ করা লাগ</string>
<string name="label_network">নেটওয়ার্ক</string>
<string name="restoring_backup_canceled">পুনরুদ্ধার বাতিল করা হয়েছে</string>
<string name="restoring_backup_error">ব্যাকআপ পুনরুদ্ধার ব্যর্থ হয়েছে</string>
@ -622,13 +621,38 @@
<string name="pref_disable_battery_optimization_summary">পিছেনে লাইব্রেরী হালনাগাদ ও ব্যাকআপে সাহায্য করে</string>
<string name="pref_disable_battery_optimization">ব্যাটারি অপ্টিমাইজেশন অক্ষম করুন</string>
<string name="tracking_info">ট্র্যাকিং সার্ভিসে অধ্যায়ের অগ্রগতি হালনাগাদের একমুখী পথ। স্বতন্ত্র মাঙ্গার ট্র্যকিং সেটাপের জন্য তাদের নিজস্ব ট্র্যাকিং বাটন ব্যবহার করুন।</string>
<string name="full_restore_online">অনলাইন পুনরুদ্ধার , অনেক ধীর ধীরে হবে কিন্তু আপনাকে আরও আপডেট করা তথ্য এবং অধ্যায় দিবে</string>
<string name="full_restore_online">অনলাইন পুনরুদ্ধার, অনেক ধীর কিন্তু আপনাকে আরও আপডেট করা তথ্য এবং অধ্যায় দিবে</string>
<string name="full_restore_mode">নেটওয়ার্ক মোডে</string>
<string name="pref_backup_auto_create_legacy">লিগ্যাসি ব্যাকআপও তৈরি করুন</string>
<string name="pref_create_legacy_backup_summary">"আগের তাছিয়ুমি ভার্এর ব্যবহার করা জন্য"</string>
<string name="pref_create_legacy_backup_summary">পুরাতন তাচ্চিওমি ভার্গুলোতে ব্যবহার করা যাবে</string>
<string name="pref_create_legacy_backup">উত্তরাধিকার এর ব্যাকআপ তৈরি করা</string>
<string name="pref_label_nsfw_extension">লেবেল এর এক্সটেনশন গুলো তালিকা</string>
<string name="pref_show_nsfw_extension">১৮+ এক্সটেনশন গুলো তালিকা</string>
<string name="pref_show_nsfw_source">১৮+ মাধ্যম গুলোর তালিকা</string>
<string name="pref_category_nsfw_content">খারাপ জিনিস (১৮+) মাধ্যম</string>
<string name="pref_label_nsfw_extension">এক্সটেনশন গুলো তালিকায় লেবেল করুন</string>
<string name="pref_show_nsfw_extension">এক্সটেনশন গুলো তালিকায় প্রদর্শন করুন</string>
<string name="pref_show_nsfw_source">উৎস তালিকায় প্রদর্শন করুন</string>
<string name="pref_category_nsfw_content">খারাপ জিনিস (১৮+) উত্স</string>
<string name="action_filter_tracked">ট্র্যাকড</string>
<string name="pref_viewer_nav">ন্যাভিগেশন নকশা</string>
<string name="action_display_show_number_of_items">আইটেমের সংখ্যা প্রদর্শন করুন</string>
<string name="spen_next_page">পরের পৃষ্ঠা</string>
<string name="spen_previous_page">আগের পৃষ্ঠা</string>
<string name="channel_crash_logs">ক্র্যাশ লগগুলো</string>
<string name="file_picker_error">কোনো ফাইল বাছাইকারী অ্যাপ পাওয়া যায় নি</string>
<string name="migration_help_guide">উৎস স্থানান্তর গাইড</string>
<string name="myanimelist_relogin">দয়া করে MAL এ আবার লগইন করুন</string>
<string name="myanimelist_creds_missing">MAL লগইন পরিচয়পত্র পাওয়া যায় নি</string>
<string name="track_finished_reading_date">পড়া শেষ করার তারিখ</string>
<string name="track_started_reading_date">পড়া শুরু করার তারিখ</string>
<string name="crash_log_saved">ক্র্যাশ লগগুলো সংরক্ষিত হয়েছে</string>
<string name="pref_dump_crash_logs_summary">বিকাশকারীদের সাথে সেয়ার করার জন্য এরর লগগুলো একটি ফাইলে সংরক্ষণ করে</string>
<string name="invalid_backup_file_type">অকার্যকর ব্যাকআপ ফাইল ধরণ: %1$s
\nএটা .proto.gz বা .json দিয়ে শেষ হওয়া উচিত।</string>
<string name="full_restore_offline">অফলাইন পুনরুদ্ধার, দ্রুত শেষ হয় তবে আপনার ব্যাকআপে যা ছিল শুধু তাই থাকে</string>
<string name="right_and_left_nav">ডান ও বাম</string>
<string name="edge_nav">কিনার</string>
<string name="kindlish_nav">কিন্ডেলের মতো</string>
<string name="l_nav">L আকারের</string>
<string name="action_desc">অধোগামী</string>
<string name="action_asc">ঊর্ধ্বগামী</string>
<string name="action_order_by_chapter_number">অধ্যায়ের নম্বর অনুসারে</string>
<string name="action_order_by_upload_date">আপলোডের তারিখ অনুসারে</string>
</resources>

View File

@ -98,7 +98,6 @@
<string name="update_24hour">Cada dia</string>
<string name="update_48hour">Cada 2 dies</string>
<string name="update_weekly">Cada setmana</string>
<string name="update_monthly">Cada mes</string>
<string name="pref_library_update_categories">Categories a incloure a l\'actualització global</string>
<string name="all">Tot</string>
<string name="pref_library_update_restriction">Restriccions d\'actualització de la biblioteca</string>
@ -658,4 +657,6 @@
<string name="action_order_by_upload_date">Per data de pujada</string>
<string name="action_filter_tracked">En seguiment</string>
<string name="action_display_show_number_of_items">Mostra el nombre d\'elements</string>
<string name="right_and_left_nav">A la dreta i a l\'esquerra</string>
<string name="pref_dual_page_split">Dividit a doble pàgina (ALFA)</string>
</resources>

View File

@ -59,7 +59,6 @@
<string name="update_24hour">Denně</string>
<string name="update_48hour">Každé 2 dny</string>
<string name="update_weekly">Týdně</string>
<string name="update_monthly">Měsíčně</string>
<string name="pref_update_only_non_completed">Aktualizovat pouze vycházející mangy</string>
<string name="pref_auto_update_manga_sync">Synchronizovat kapitoly po přečtení</string>
<string name="pref_start_screen">Úvodní obrazovka</string>

View File

@ -51,7 +51,6 @@
<string name="charging">Тулать</string>
<string name="pref_library_update_restriction">Вулавӑша ҫӗнетни чарӑвӗсем</string>
<string name="pref_library_update_prioritization">Вулавӑша ҫӗнетни йӗрки</string>
<string name="update_monthly">Кашни уйӑх</string>
<string name="update_weekly">Кашни эрне</string>
<string name="update_48hour">Кашни 2 кун</string>
<string name="update_24hour">Кашни кун</string>

View File

@ -89,7 +89,6 @@
<string name="update_24hour">Täglich</string>
<string name="update_48hour">Alle 2 Tage</string>
<string name="update_weekly">Wöchentlich</string>
<string name="update_monthly">Monatlich</string>
<string name="pref_library_update_categories">Kategorien für die globale Aktualisierung</string>
<string name="all">Alle</string>
<string name="pref_library_update_restriction">Bibliotheksaktualisierung einschränken</string>
@ -480,7 +479,7 @@
<string name="webtoon_side_padding_20">20 %</string>
<string name="webtoon_side_padding_15">15 %</string>
<string name="webtoon_side_padding_10">10 %</string>
<string name="webtoon_side_padding_0">Keinen</string>
<string name="webtoon_side_padding_0">Keine</string>
<string name="pref_webtoon_side_padding">Seitenränder</string>
<string name="pinned_sources">Angeheftet</string>
<string name="action_unpin">Loslösen</string>
@ -492,7 +491,7 @@
<string name="add_to_library">Zur Bibliothek hinzufügen</string>
<string name="confirm_exit">Zum Beenden nochmal die Zurück-Taste drücken</string>
<string name="information_webview_required">WebView ist für Tachiyomi erforderlich</string>
<string name="licenses">Quelloffene Lizenzen</string>
<string name="licenses">Open Source-Lizenzen</string>
<string name="website">Website</string>
<string name="pref_confirm_exit">Beenden bestätigen</string>
<string name="label_downloaded_only">Nur Heruntergeladenes</string>
@ -658,4 +657,6 @@
<string name="action_order_by_upload_date">Nach Upload-Datum</string>
<string name="action_filter_tracked">Getrackt</string>
<string name="action_display_show_number_of_items">Anzahl der Elemente anzeigen</string>
<string name="right_and_left_nav">Rechts und Links</string>
<string name="pref_dual_page_split">Doppelseitige Aufteilung (Alpha)</string>
</resources>

View File

@ -20,7 +20,7 @@
<string name="action_filter">Φίλτρο</string>
<string name="action_filter_downloaded">Λήψεις</string>
<string name="action_filter_bookmarked">Στους Σελιδοδείκτες</string>
<string name="action_filter_unread">Μη αναγνωσμένα</string>
<string name="action_filter_unread">Αδιάβαστα</string>
<string name="action_filter_empty">Αφαίρεση φίλτρου</string>
<string name="action_sort_alpha">Αλφαβητικά</string>
<string name="action_sort_total">Σύνολο κεφαλαίων</string>
@ -54,19 +54,19 @@
<string name="action_previous_chapter">Προηγούμενο κεφάλαιο</string>
<string name="action_next_chapter">Επόμενο κεφάλαιο</string>
<string name="action_retry">Επανάληψη</string>
<string name="action_remove">Διαγραφή</string>
<string name="action_remove">Αφαίρεση</string>
<string name="action_resume">Συνέχεια</string>
<string name="action_move">Μεταφορά</string>
<string name="action_move">Μετακίνηση</string>
<string name="action_open_in_browser">Άνοιγμα σε πρόγραμμα περιήγησης</string>
<string name="action_display_mode">Λειτουργία προβολής</string>
<string name="action_display">Προβολή</string>
<string name="action_display_grid">Συμπαγές πλέγμα</string>
<string name="action_display_list">Λίστα</string>
<string name="action_display_download_badge">Σήματα ληφθέντων</string>
<string name="action_cancel">Ακύρωση</string>
<string name="action_cancel">Άκυρο</string>
<string name="action_sort">Ταξινόμηση</string>
<string name="action_install">Εγκατάσταση</string>
<string name="action_share">Κοινή χρήση</string>
<string name="action_share">Κοινοποίηση</string>
<string name="action_save">Αποθήκευση</string>
<string name="action_reset">Επαναφορά</string>
<string name="action_undo">Αναίρεση</string>
@ -98,7 +98,6 @@
<string name="update_24hour">Καθημερινά</string>
<string name="update_48hour">Κάθε 2 ημέρες</string>
<string name="update_weekly">Εβδομαδιαία</string>
<string name="update_monthly">Μηνιαία</string>
<string name="pref_library_update_categories">Κατηγορίες που περιλαμβάνονται στην ολική ενημέρωση</string>
<string name="all">Όλα</string>
<string name="pref_library_update_restriction">Περιορισμοί ενημέρωσης βιβλιοθήκης</string>
@ -282,9 +281,9 @@
<string name="download_1">Επόμενου κεφαλαίου</string>
<string name="download_5">Επόμενων 5 κεφαλαίων</string>
<string name="download_10">Επόμενων 10 κεφαλαίων</string>
<string name="download_custom">Προσαρμοσμένη</string>
<string name="download_all">Όλων</string>
<string name="download_unread">Μη αναγνωσμένων</string>
<string name="download_custom">Προσαρμοσμένο</string>
<string name="download_all">Όλα</string>
<string name="download_unread">Αδιάβαστα</string>
<string name="confirm_delete_chapters">Είστε βέβαιοι ότι θέλετε να διαγράψετε τα επιλεγμένα κεφάλαια;</string>
<string name="manga_tracking_tab">Παρακολούθηση</string>
<string name="reading">Ανάγνωση</string>
@ -437,7 +436,7 @@
<string name="email">Διεύθυνση ηλεκτ. ταχυδρομείου</string>
<string name="check_for_updates">Έλεγχος για ενημερώσεις</string>
<string name="licenses">Άδειες ανοιχτού κώδικα</string>
<string name="website">Ιστοσελίδα</string>
<string name="website">Ιστότοπος</string>
<string name="battery_optimization_disabled">Η βελτιστοποίηση μπαταρίας είναι ήδη απενεργοποιημένη</string>
<string name="pref_disable_battery_optimization_summary">Βοηθά στις ενημερώσεις βιβλιοθήκης και τα αντίγραφα ασφαλείας στο παρασκήνιο</string>
<string name="pref_disable_battery_optimization">Απενεργοποίηση βελτιστοποίησης μπαταρίας</string>
@ -658,4 +657,6 @@
<string name="action_order_by_upload_date">Κατά ημερομηνία μεταφόρτωσης</string>
<string name="action_filter_tracked">Παρακολουθούνται</string>
<string name="action_display_show_number_of_items">Εμφάνιση αριθμού στοιχείων</string>
<string name="right_and_left_nav">Δεξιά και Aριστερά</string>
<string name="pref_dual_page_split">Διπλή διαίρεση σελίδας (ALPHA)</string>
</resources>

View File

@ -273,7 +273,6 @@
<string name="app_not_available">Aplicación no disponible</string>
<string name="short_recent_updates">Actualizaciones</string>
<string name="update_weekly">Semanalmente</string>
<string name="update_monthly">Mensualmente</string>
<string name="pref_library_update_categories">Categorías que incluir en actualización global</string>
<string name="all">Todos</string>
<string name="pref_start_screen">Pantalla inicial</string>

View File

@ -348,7 +348,6 @@
<string name="pref_library_update_restriction_summary">در چه شرایطی کتابخانه آپدیت شود</string>
<string name="pref_library_update_restriction">محدودیت‌های آپدیت کتابخانه</string>
<string name="pref_library_update_prioritization">ترتیب آپدیت کتابخانه</string>
<string name="update_monthly">ماهانه</string>
<string name="update_weekly">هفتگی</string>
<string name="update_48hour">هر ۴۸ ساعت یکبار</string>
<string name="update_3hour">هر ۳ ساعت یکبار</string>

View File

@ -161,7 +161,6 @@
<string name="update_24hour">Päivittäin</string>
<string name="update_48hour">Kahden päivän välein</string>
<string name="update_weekly">Viikoittain</string>
<string name="update_monthly">Kuukausittain</string>
<string name="pref_library_update_categories">Globaalin päivityksen sisältämät kategoriat</string>
<string name="all">Kaikki</string>
<string name="pref_library_update_restriction">Kirjaston päivityksen rajoitukset</string>
@ -657,4 +656,6 @@
<string name="action_order_by_chapter_number">Luvunumeron mukaan</string>
<string name="action_order_by_upload_date">Lisäyspäivämäärän mukaan</string>
<string name="action_filter_tracked">Seuratut</string>
<string name="action_display_show_number_of_items">Näytä kohteiden määrä</string>
<string name="right_and_left_nav">Oikea ja vasen</string>
</resources>

View File

@ -270,7 +270,6 @@
<string name="pref_library_update_restriction_summary">Isapanahon kung nakamit ang (mga) kondisyon</string>
<string name="pref_library_update_restriction">Kondisyon sa pag-update</string>
<string name="pref_library_update_prioritization">Ayos ng pagsapanahon</string>
<string name="update_monthly">Buwan-buwan</string>
<string name="update_weekly">Linggo-linggo</string>
<string name="update_48hour">Kada 2 araw</string>
<string name="update_24hour">Araw-araw</string>

View File

@ -106,8 +106,8 @@
<string name="black_background">Noir</string>
<string name="pref_viewer_type">Mode de lecture par défaut</string>
<string name="default_viewer">Par défaut</string>
<string name="left_to_right_viewer">Gauche à droite</string>
<string name="right_to_left_viewer">Droite à gauche</string>
<string name="left_to_right_viewer">De gauche à droite</string>
<string name="right_to_left_viewer">De droite à gauche</string>
<string name="vertical_viewer">Vertical</string>
<string name="webtoon_viewer">Webtoon</string>
<string name="pref_image_decoder">Décodeur d\'image</string>
@ -303,7 +303,6 @@
<string name="action_create">Créer</string>
<string name="app_not_available">Application indisponible</string>
<string name="update_weekly">Chaque semaine</string>
<string name="update_monthly">Chaque mois</string>
<string name="default_category">Catégorie par défaut</string>
<string name="download_notifier_download_paused">Téléchargement en pause</string>
<string name="local_source">Source locale</string>
@ -403,7 +402,7 @@
<string name="migrate">Déplacer</string>
<string name="copy">Copier</string>
<string name="pref_read_with_long_tap">Menu contextuel (appui prolongé)</string>
<string name="action_open_in_web_view">Ouvrir dans le WebView</string>
<string name="action_open_in_web_view">Ouvrir dans WebView</string>
<string name="pref_true_color">Couleurs à 32 bits</string>
<string name="pref_skip_read_chapters">Passer les chapitres marqués comme lus</string>
<string name="filter_mode_default">Par défaut</string>
@ -691,4 +690,6 @@
<string name="action_order_by_chapter_number">Par numéro de chapitre</string>
<string name="action_order_by_upload_date">Par date de téléversement</string>
<string name="action_filter_tracked">Suivi</string>
<string name="action_display_show_number_of_items">Afficher le nombre dentrées</string>
<string name="right_and_left_nav">Droite et gauche</string>
</resources>

View File

@ -134,7 +134,6 @@
<string name="pref_library_update_restriction_summary">Actualizar soamente cando as condicións se cumpren</string>
<string name="pref_library_update_restriction">Restriccións de actualización da biblioteca</string>
<string name="pref_library_update_prioritization">Orden de actualización da biblioteca</string>
<string name="update_monthly">Mensualmente</string>
<string name="update_weekly">Semanalmente</string>
<string name="update_48hour">Cada 2 días</string>
<string name="update_24hour">Diariamente</string>

View File

@ -200,7 +200,6 @@
<string name="pref_library_update_restriction_summary">עדכן רק כאשר מתקיימים התנאים</string>
<string name="pref_library_update_restriction">מגבלות עדכון הספריה</string>
<string name="pref_library_update_prioritization">סדר עדכון הספריה</string>
<string name="update_monthly">פעם בחודש</string>
<string name="update_weekly">פעם בשבוע</string>
<string name="update_48hour">פעם ביומיים</string>
<string name="update_1hour">פעם בשעה</string>

View File

@ -95,7 +95,6 @@
<string name="update_24hour">हर रोज़</string>
<string name="update_48hour">हर २ दिन</string>
<string name="update_weekly">साप्ताहिक</string>
<string name="update_monthly">मासिक</string>
<string name="pref_library_update_categories">वैश्विक श्रेणियाँ जो अद्यतन में शामिल करनी है</string>
<string name="all">समस्त</string>
<string name="pref_library_update_restriction">पुस्तकालय अद्यतन प्रतिबंध</string>
@ -598,9 +597,9 @@
<string name="backup_restore_missing_trackers">ट्रैकर्स में लॉग इन नहीं किया गया:</string>
<string name="pref_remove_bookmarked_chapters">बुकमार्क अध्यायों को हटाएं</string>
<string name="pref_category_delete_chapters">अध्यायों को हटाएं</string>
<string name="ext_nsfw_warning">18+ सामग्री हो सकती है</string>
<string name="ext_nsfw_warning">इसमें NSFW (18+) सामग्री हो सकती है</string>
<string name="ext_nsfw_short">18+</string>
<string name="parental_controls_info">यह अनौपचारिक या संभावित रूप से फ़्लैग किए गए एक्सटेंशन को ऐप के भीतर 18+ सामग्री के सामने आने से नहीं रोकता है।</string>
<string name="parental_controls_info">यह अनौपचारिक या संभावित रूप से फ़्लैग किए गए एक्सटेंशन को ऐप के भीतर NSFW (18+) सामग्री के सामने आने से नहीं रोकता है।</string>
<plurals name="missing_chapters_warning">
<item quantity="one">1 लापता अध्याय है</item>
<item quantity="other">%d लापता अध्याय हैं</item>
@ -622,7 +621,8 @@
<string name="pref_clear_history">इतिहास मिटा दें</string>
<string name="clear_history_confirmation">क्या आपको यकीन है\? सारा इतिहास खो जाएगा।</string>
<string name="clear_history_completed">इतिहास हटाया गया</string>
<string name="invalid_backup_file_type">अमान्य बैकअप फ़ाइल: %1$s</string>
<string name="invalid_backup_file_type">अमान्य बैकअप फ़ाइल प्रकार: %1$s
\nयह .proto.gz या .json के साथ समाप्त होना चाहिए।</string>
<string name="spen_next_page">अगला पृष्ठ</string>
<string name="spen_previous_page">पिछला पृष्ठ</string>
<string name="migration_help_guide">स्रोत माइग्रेशन गाइड</string>
@ -635,5 +635,27 @@
<string name="pref_label_nsfw_extension">एक्सटेंशन सूची में लेबल</string>
<string name="pref_show_nsfw_extension">एक्सटेंशन सूची में दिखाएं</string>
<string name="pref_show_nsfw_source">स्रोत सूची में दिखाएं</string>
<string name="pref_category_nsfw_content">18+ सामग्री</string>
<string name="pref_category_nsfw_content">NSFW (18+)्रोत</string>
<string name="channel_crash_logs">क्रैश लॉग</string>
<string name="file_picker_error">कोई फ़ाइल पिकर ऐप नहीं मिला</string>
<string name="myanimelist_relogin">कृपया फिर से MAL पर लॉगिन करें</string>
<string name="myanimelist_creds_missing">MAL लॉगिन क्रेडेंशियल नहीं मिला</string>
<string name="track_finished_reading_date">पढ़ना समाप्त की तिथि</string>
<string name="track_started_reading_date">पढ़ना शुरू करने की तारीख</string>
<string name="crash_log_saved">क्रैश लॉग सहेजे गए</string>
<string name="pref_dump_crash_logs_summary">डेवलपर्स के साथ साझा करने के लिए फ़ाइल में त्रुटि लॉग सहेजता है</string>
<string name="pref_dump_crash_logs">डंप क्रैश लॉग</string>
<string name="pref_viewer_nav">नेविगेशन लेआउट</string>
<string name="right_and_left_nav">दाएं और बाएं</string>
<string name="edge_nav">धार</string>
<string name="kindlish_nav">किंडल-ईश</string>
<string name="l_nav">एल आकार</string>
<string name="default_nav">डिफॉल्ट</string>
<string name="network_unmetered">अनमीटर्ड नेटवर्क</string>
<string name="action_desc">अवरोही</string>
<string name="action_asc">आरोही</string>
<string name="action_order_by_chapter_number">अध्याय संख्या से</string>
<string name="action_order_by_upload_date">अपलोड तिथि से</string>
<string name="action_display_show_number_of_items">वस्तुओं की संख्या दिखाएं</string>
<string name="action_filter_tracked">ट्रैक किए गए</string>
</resources>

View File

@ -45,7 +45,6 @@
<string name="pref_library_update_restriction_summary">Ažuriraj samo kad su ispunjeni uvjeti</string>
<string name="pref_library_update_restriction">Ograničenja za ažuriranje biblioteke</string>
<string name="pref_library_update_prioritization">Redoslijed ažuriranja biblioteke</string>
<string name="update_monthly">Mjesečno</string>
<string name="update_weekly">Tjedno</string>
<string name="update_48hour">Svaki drugi dan</string>
<string name="update_24hour">Dnevno</string>

View File

@ -46,7 +46,6 @@
<string name="update_24hour">Naponta</string>
<string name="update_48hour">2 naponta</string>
<string name="update_weekly">Hetente</string>
<string name="update_monthly">Havonta</string>
<string name="pref_library_update_categories">Globális frissítésben tartalmazott kategóriák</string>
<string name="all">Összes</string>
<string name="pref_library_update_restriction">Könyvtárfrissítési korlátozások</string>

View File

@ -91,7 +91,6 @@
<string name="update_24hour">Tiap hari</string>
<string name="update_48hour">Tiap 2 hari</string>
<string name="update_weekly">Tiap minggu</string>
<string name="update_monthly">Tiap bulan</string>
<string name="pref_library_update_categories">Kategori untuk disertakan dalam pembaruan global</string>
<string name="all">Semua</string>
<string name="pref_library_update_restriction">Pembatasan pembaruan perpustakaan</string>
@ -501,7 +500,7 @@
<item quantity="other">%1$s lagi</item>
</plurals>
<string name="downloaded_only_summary">Filter semua manga di Perpustakaan</string>
<string name="check_for_updates">Cek pembaruan</string>
<string name="check_for_updates">Periksa pembaruan</string>
<string name="restoring_backup_canceled">Pemulihan dibatalkan</string>
<string name="restore_in_progress">Pemulihan masih dalam proses</string>
<string name="backup_in_progress">Pencadangan masih dalam proses</string>
@ -611,7 +610,8 @@
<string name="clear_history_completed">Riwayat telah dihapus</string>
<string name="pref_hide_bottom_bar_on_scroll">Sembunyikan bilah bawah saat menggulirkan halaman</string>
<string name="pref_category_nsfw_content">Sumber NSFW (18+)</string>
<string name="invalid_backup_file_type">Cadangan tidak valid: %1$s</string>
<string name="invalid_backup_file_type">Tipe file berkas cadangan tidak valid: %1$s
\nHarusnya nama file berakhir dengan .proto.gz atau .json.</string>
<string name="pref_backup_auto_create_legacy">Buat cadangan versi lama juga</string>
<string name="pref_create_legacy_backup">Buat cadangan versi</string>
<string name="spen_next_page">Halaman selanjutnya</string>
@ -635,4 +635,14 @@
<string name="action_order_by_chapter_number">Berdasarkan nomor bab</string>
<string name="action_order_by_upload_date">Berdasarkan tanggal unggahan</string>
<string name="action_filter_tracked">Dilacak</string>
<string name="pref_dump_crash_logs">Buang log kerusakan</string>
<string name="channel_crash_logs">Log kerusakan</string>
<string name="track_finished_reading_date">Tanggal selesai membaca</string>
<string name="track_started_reading_date">Tanggal mulai membaca</string>
<string name="crash_log_saved">Log kerusakan disimpan</string>
<string name="pref_dump_crash_logs_summary">Simpan log kesalahan ke sebuah file untuk dibagikan dengan pengembang aplikasi</string>
<string name="pref_viewer_nav">Tata letak navigasi</string>
<string name="right_and_left_nav">Kanan dan Kiri</string>
<string name="network_unmetered">Jaringan yang tidak terukur</string>
<string name="action_display_show_number_of_items">Tampilkan jumlah item</string>
</resources>

View File

@ -299,7 +299,6 @@
<string name="app_not_available">App non disponibile</string>
<string name="short_recent_updates">Aggiornamenti</string>
<string name="update_weekly">Settimanalmente</string>
<string name="update_monthly">Mensilmente</string>
<string name="default_category">Categoria predefinita</string>
<string name="track">Monitoraggio</string>
<string name="pref_category_tracking">Monitoraggio</string>
@ -630,7 +629,7 @@
<string name="no_pinned_sources">Non hai fonti fissate</string>
<string name="pref_remove_bookmarked_chapters">Elimina capitoli contrassegnati</string>
<string name="pref_category_delete_chapters">Elimina capitoli</string>
<string name="ext_nsfw_warning">Potrebbe contenere materiale 18+</string>
<string name="ext_nsfw_warning">Potrebbe contenere materiale per adulti</string>
<string name="ext_nsfw_short">18+</string>
<plurals name="missing_chapters_warning">
<item quantity="one">C\'è un capitolo mancante</item>
@ -638,7 +637,7 @@
</plurals>
<string name="backup_restore_missing_trackers">Trackers non collegati:</string>
<string name="no_chapters_error">Nessun capitolo trovato</string>
<string name="parental_controls_info">Questo non impedisce a estensioni non ufficiali o potenzialmente segnalate in modo errato di far mostrare contenuti 18+ all\'interno dell\'app.</string>
<string name="parental_controls_info">Non impedisce ad estensioni non ufficiali o classificate in modo errato/fuorviante di mostrare contenuti per adulti all\'interno dell\'app.</string>
<string name="chapter_settings_updated">Impostazioni predefinite del capitolo aggiornate</string>
<string name="share_page_info">%1$s: %2$s, pagina %3$d</string>
<string name="set_chapter_settings_as_default">Imposta come predefinito</string>
@ -658,7 +657,8 @@
<string name="spen_next_page">Pagina successiva</string>
<string name="spen_previous_page">Pagina precedente</string>
<string name="migration_help_guide">Guida alla migrazione di origine</string>
<string name="invalid_backup_file_type">File di backup invalido: %1$s</string>
<string name="invalid_backup_file_type">File di backup non valido: %1$s
\nDovrebbe terminare con .proto.gz oppure .json.</string>
<string name="full_restore_offline">Ripristina fuori linea, termina rapidamente ma contiene solo ciò che ha il backup</string>
<string name="full_restore_online">Ripristina in linea, molto più lentamente ma fornisce informazioni e capitoli più aggiornati</string>
<string name="full_restore_mode">Modalità rete</string>
@ -677,4 +677,18 @@
<string name="edge_nav">Bordo</string>
<string name="kindlish_nav">Tipo un Kindle</string>
<string name="l_nav">A forma di L</string>
<string name="channel_crash_logs">Resoconto dei crash</string>
<string name="track_finished_reading_date">Data di fine lettura</string>
<string name="track_started_reading_date">Data di inizio lettura</string>
<string name="crash_log_saved">Resoconto salvato</string>
<string name="pref_dump_crash_logs_summary">Salva un resoconto degli errori su un file per condividerlo con gli sviluppatori</string>
<string name="pref_dump_crash_logs">Salva un resoconto dei crash</string>
<string name="network_unmetered">Connessione illimitata</string>
<string name="action_desc">Decrescente</string>
<string name="action_asc">Crescente</string>
<string name="action_order_by_chapter_number">Per numero di capitolo</string>
<string name="action_order_by_upload_date">Per data di caricamento</string>
<string name="action_filter_tracked">Tracciati</string>
<string name="action_display_show_number_of_items">Elementi visualizzati</string>
<string name="right_and_left_nav">Navigazione destra/sinistra</string>
</resources>

View File

@ -81,7 +81,6 @@
<string name="update_24hour">毎日</string>
<string name="update_48hour">2日ごと</string>
<string name="update_weekly">毎週</string>
<string name="update_monthly">毎月</string>
<string name="pref_library_update_categories">グローバル更新に含まれるカテゴリ</string>
<string name="all">すべて</string>
<string name="pref_library_update_restriction">ライブラリ更新制限</string>
@ -619,7 +618,8 @@
<string name="pref_create_legacy_backup_summary">古いバージョンのTachiyomiで利用可能</string>
<string name="pref_create_legacy_backup">レガシー バックアップを作成</string>
<string name="migration_help_guide">ソース移行ガイド</string>
<string name="invalid_backup_file_type">無効なバックアップ ファイル:%1$s</string>
<string name="invalid_backup_file_type">無効なバックアップ ファイル:%1$s
\nファイル拡張子は.proto.gzもしくは.jsonであるよう確認してください。</string>
<string name="pref_category_nsfw_content">成人向けのソース</string>
<string name="pref_label_nsfw_extension">拡張機能リストでマーク</string>
<string name="pref_show_nsfw_extension">拡張機能リストに表示</string>
@ -627,4 +627,23 @@
<string name="file_picker_error">ファイルを選択できるアプリが見つかりません</string>
<string name="myanimelist_relogin">もう一度MALにログインしてください</string>
<string name="myanimelist_creds_missing">MALログイン資格情報が見つかりません</string>
<string name="action_display_show_number_of_items">アイテム数を表示する</string>
<string name="channel_crash_logs">クラッシュログ</string>
<string name="track_finished_reading_date">読み終わった日付</string>
<string name="track_started_reading_date">読み始めた日付</string>
<string name="crash_log_saved">クラッシュログが保存されました</string>
<string name="edge_nav"></string>
<string name="default_nav">デフォルト</string>
<string name="action_order_by_chapter_number">章の番号順</string>
<string name="action_order_by_upload_date">アップロードされた日付順</string>
<string name="action_filter_tracked">登録済み</string>
<string name="pref_dump_crash_logs_summary">開発者に渡すよう、エラー ログを保存します</string>
<string name="pref_dump_crash_logs">クラッシュ ログをダンプ</string>
<string name="pref_viewer_nav">ナビゲーションレイアウト</string>
<string name="right_and_left_nav">右と左</string>
<string name="kindlish_nav">Kindleスタイル</string>
<string name="l_nav">L形</string>
<string name="network_unmetered">従量制データではないネットワーク</string>
<string name="action_desc">降順</string>
<string name="action_asc">昇順</string>
</resources>

View File

@ -130,7 +130,6 @@
<string name="update_24hour">ყოველდღე</string>
<string name="update_48hour">ყოველ 2 დღეში ერთხელ</string>
<string name="update_weekly">ყოველ კვირა</string>
<string name="update_monthly">ყოველთვე</string>
<string name="pref_library_update_prioritization">ბიბლიოთეკის განახლების რიგითობა</string>
<string name="pref_library_update_restriction">ბიბლიოთეკის განახლების შეზღუდვები</string>
<string name="pref_library_update_restriction_summary">განაახლე მხოლოდ როცა პირობები დაკმაყოფილდება</string>
@ -253,12 +252,12 @@
<string name="backup_created">რეზერვი შექმნილია</string>
<string name="invalid_backup_file">არასწორი სარეზერვო ფაილი</string>
<string name="invalid_backup_file_missing_data">ფაილს აკლია მონაცემები.</string>
<string name="invalid_backup_file_missing_manga">რეზერვი არ შეიცავს არცერთ მანგას</string>
<string name="invalid_backup_file_missing_manga">რეზერვი არ შეიცავს არცერთ მანგას.</string>
<string name="backup_restore_missing_sources">დაკარგული წყაროები:</string>
<string name="restore_completed">აღდგენა შესრულებულია</string>
<string name="restore_duration">%02d წუთი, %02d წამი</string>
<string name="backup_in_progress">რეზერვის შემქნა პროგრესშია</string>
<string name="backup_choice">რას გსურს რომ შეუქმნა რეზერვი</string>
<string name="backup_choice">რას გსურს რომ შეუქმნა რეზერვი\?</string>
<string name="creating_backup">რეზერვის შექმნა</string>
<string name="creating_backup_error">რეზერვის შექმნა ვერ მოხერხდა</string>
<string name="restore_in_progress">აღდგენა უკვე მიმდინარეობს</string>
@ -333,7 +332,7 @@
<string name="manga_removed_library">წაშლილია ბიბლიოთეკიდან</string>
<string name="manga_info_expand">მეტი ინფორმაციის ჩვენება</string>
<string name="manga_info_collapse">ნაკლები ინფორმაციის ჩვენება</string>
<string name="delete_downloads_for_manga">გადმოწერილი თავების წაშლა</string>
<string name="delete_downloads_for_manga">წაიშალოს გადმოწერილი თავები\?</string>
<string name="copied_to_clipboard">დაკოპირებულია ბუფერში:
\n%1$s</string>
<string name="source_not_installed">წყარო არ არის დაინსტალირებული: %1$s</string>
@ -392,7 +391,7 @@
<string name="downloading">გადმოწერა…</string>
<string name="download_progress">გადმოწერილია %1$d%%</string>
<string name="chapter_progress">გვერდი: %1$d</string>
<string name="chapter_subtitle">თავი: %1$d</string>
<string name="chapter_subtitle">თავი: %1$s</string>
<string name="no_next_chapter">შემდეგი თავი ვერ მოიძებნა</string>
<string name="no_previous_chapter">წინა თავი ვერ მოიძებნა</string>
<string name="decode_image_error">სურათის გადმოტვირთვა ვერ მოხერხდა</string>
@ -408,7 +407,7 @@
<string name="transition_pages_error">გვერდების ჩატვირთვა ვერ მოხერხდა: %1$s</string>
<string name="page_list_empty_error">ვერცერთი გვერდი ვერ მოიძევნა</string>
<string name="updating_library">ბიბლიოთეკის განახლება</string>
<string name="recent_manga_time">თავ. %1$s - %2$</string>
<string name="recent_manga_time">თავ. %1$s - %2$s</string>
<string name="migration_info">დააჭირე რომ მონიშნო წყარო საიდანაც გინდა მიგრაცია</string>
<string name="migration_dialog_what_to_include">ამოირჩიე მონაცემები შესაყვანად</string>
<string name="migration_selection_prompt">ამოირჩიე წყარო საიდანაც გინდა მიგრაცია</string>
@ -441,7 +440,7 @@
<string name="description_cover">მანგას ყდა</string>
<string name="information_no_downloads">გადმოწერები არ არის</string>
<string name="information_no_recent">განახლებები არ არის</string>
<string name="information_empty_library">შენი ბიბლიოთეკა ცარიელია, დაამატე სერიები შენს ბიბლიოთეკაში \"დაათვალიერე\"-დან</string>
<string name="information_empty_library">შენი ბიბლიოთეკა ცარიელია, დაამატე სერიები შენს ბიბლიოთეკაში \"დაათვალიერე\"-დან.</string>
<string name="download_notifier_text_only_wifi">Wi-Fi კავშირი არ არის ხელმისაწვდომი</string>
<string name="download_notifier_no_network">ინტერნეტთან კავშირი არ არის ხელმისაწვდომი</string>
<string name="download_notifier_download_paused">გადმოწერა დაპაუზებულია</string>
@ -472,8 +471,8 @@
<string name="ext_trust">ნდობა</string>
<string name="ext_untrusted">არასანდო</string>
<string name="untrusted_extension">არასანდო დამატება</string>
<string name="obsolete_extension_message">დამატება აღარ არის ხელმისაწვდომი</string>
<string name="unofficial_extension_message">დამატება არ არის Tachiyomi-ს ოფიციალური დამატებების სიიდან</string>
<string name="obsolete_extension_message">დამატება აღარ არის ხელმისაწვდომი.</string>
<string name="unofficial_extension_message">დამატება არ არის Tachiyomi-ს ოფიციალური დამატებების სიიდან.</string>
<string name="pref_double_tap_anim_speed">ორჯერ დაჭერისას ანიმაციის სისწრაფე</string>
<string name="pref_true_color_summary">აუმჯობესებს ხარისხს, თუმცა ამცირებს წარმადობას</string>
<string name="pref_custom_brightness">გამოიყენე პერსონალიზებული სიკაშკაშე</string>
@ -497,7 +496,7 @@
<string name="page_downloaded">გვერდი დაკოპირდა %1$s -ში</string>
<string name="confirm_set_image_as_cover">გამოვიყენოთ ეს სურათი ყდის ნახატად?</string>
<string name="information_no_recent_manga">ბოლო ხანებში არაფერი არ არის წაკითხული</string>
<string name="information_empty_category">შენ არ გაქვს კატეგორიები, აირჩიე \"+\" ღილაკი რათა შექმნა ერთი, შენი ბიბლიოთეკის დასაორგანიზებლად</string>
<string name="information_empty_category">შენ არ გაქვს კატეგორიები, აირჩიე \"+\" ღილაკი რათა შექმნა ერთი, შენი ბიბლიოთეკის დასაორგანიზებლად.</string>
<string name="information_webview_required">WebView არის აუცილებელი Tachiyomi-ს სამუშაოდ</string>
<string name="information_webview_outdated">გთხოვთ განაახლოთ WebView აპლიკაცია უკეთესი თავსებდობისთვის</string>
<string name="download_notifier_downloader_title">გადმომწერი</string>
@ -507,15 +506,15 @@
<string name="action_enable_all">ჩართე ყველა</string>
<string name="action_disable_all">გამორთე ყველა</string>
<string name="secure_screen_summary">აპლიკაციებს შორის გადართვისას შემადგენლობის დამალვა და სკრინშოტების დაბლოკვა</string>
<string name="untrusted_extension_message">ეს დამატება ხელმოწერილია უცნობი სერთიფიკატის მიერ და არ არის აქტივირებული\\n\\nმავნე დამატებას შეუძლია
<string name="untrusted_extension_message">ეს დამატება ხელმოწერილია უცნობი სერთიფიკატის მიერ და არ არის აქტივირებული.
\n
\nწაიკითხოს შესვლის მონაცემები შენახული ამ აპლიკაციაში ან გაუშვას თავისი კოდი \\n\\nამ სერტიფიკატის ნდობით თქვენ
\nმავნე დამატებას შეუძლია წაიკითხოს შესვლის მონაცემები შენახული ამ აპლიკაციაში ან გაუშვას თავისი კოდი.
\n
\nთქვენს თავზე იღებთ რისკებს და პასუხისმგებლობას.</string>
\nამ სერტიფიკატის ნდობით თქვენ თქვენს თავზე იღებთ რისკებს და პასუხისმგებლობას.</string>
<string name="tracking_info">ცალმხრივი სინქრონიზაცია თვალყურის სადევნებელ სერვისებში თავების პროგრესის განსაახლებლად. მიადევნე თვალყური ინდივიდუალურ მანგებს მათი ჩანართებიდან</string>
<string name="backup_restore_content">აღდგენა იყენებს წყაროებს მონაცემების გადმოსაწერად.\\n\\nდარწმუნდი რომ გაქვს დაინსტალირებული ყველა
<string name="backup_restore_content">აღდგენა იყენებს წყაროებს მონაცემების გადმოსაწერად.
\n
\nსაჭირო დამატება და ხარ დალოგინებული წყაროებში და თვალყურის სადევნებელ სერვისებში ააღდგენამდე.</string>
\nდარწმუნდი რომ გაქვს დაინსტალირებული ყველა საჭირო დამატება და ხარ დალოგინებული წყაროებში და თვალყურის სადევნებელ სერვისებში ააღდგენამდე.</string>
<string name="pref_refresh_library_tracking_summary">ანახლებს სტატუსს, შეფასებას და ბოლო თავს წაკითხულს თვალყურის სადევნებელ სერვისებიდან</string>
<string name="download_notifier_page_ready_error">გვერდი ვერ ჩაიტვირთა</string>
<string name="filter_mode_overlay">გადაფარება</string>
@ -584,4 +583,6 @@
<string name="action_start">დაწყება</string>
<string name="action_sort_date_added">დამატების თარიღი</string>
<string name="action_download_unread">გადმოიწეროს წაუკითხავი თავები</string>
<string name="ext_nsfw_warning">შეიძლება შეიჩაცდეს უცენზურო(18+) კონტენტს</string>
<string name="ext_nsfw_short">18+</string>
</resources>

View File

@ -287,7 +287,6 @@
<string name="pref_library_update_restriction_summary">ಷರತ್ತುಗಳನ್ನು ಪೂರೈಸಿದಾಗ ಮಾತ್ರ ನವೀಕರಿಸಿ</string>
<string name="pref_library_update_restriction">ಗ್ರಂಥಾಲಯ ನವೀಕರಣ ನಿರ್ಬಂಧಗಳು</string>
<string name="pref_library_update_prioritization">ಗ್ರಂಥಾಲಯ ನವೀಕರಣ ಪಾಳಿ</string>
<string name="update_monthly">ತಿಂಗಳಿಗೊಮ್ಮೆ</string>
<string name="update_weekly">ವಾರಕ್ಕೊಮ್ಮೆ</string>
<string name="update_48hour">ಪ್ರತಿ 2 ದಿನಗಳಿಗೊಮ್ಮೆ</string>
<string name="update_24hour">ಪ್ರತಿದಿನ</string>

View File

@ -95,7 +95,6 @@
<string name="update_24hour">1일</string>
<string name="update_48hour">2일</string>
<string name="update_weekly">1주</string>
<string name="update_monthly">1달</string>
<string name="pref_library_update_categories">전역 업데이트에 포함할 카테고리</string>
<string name="all">전부</string>
<string name="pref_library_update_restriction">서재 업데이트 제한</string>

View File

@ -185,7 +185,6 @@
<string name="pref_library_update_restriction_summary">अटी पूर्ण झाल्यावरच अध्यातन करावे</string>
<string name="pref_library_update_restriction">लायब्ररी अद्ययावत प्रतिबंधी</string>
<string name="pref_library_update_prioritization">लायब्ररी अद्ययावत क्रम</string>
<string name="update_monthly">मासिक</string>
<string name="double_tap_anim_speed_0">अनिमेशन नाही</string>
<string name="zoom_start_center">सेंटर</string>
<string name="zoom_start_right">उजवी बाजू</string>

View File

@ -94,7 +94,6 @@
<string name="update_24hour">Setiap hari</string>
<string name="update_48hour">Setiap 2 hari</string>
<string name="update_weekly">Setiap minggu</string>
<string name="update_monthly">Setiap bulan</string>
<string name="pref_library_update_categories">Kategori bagi kemas kini keseluruhan</string>
<string name="all">Semua</string>
<string name="pref_library_update_restriction">Batasan kemas kini pustaka</string>
@ -645,4 +644,6 @@
<string name="track_started_reading_date">Tarikh mula membaca</string>
<string name="track_finished_reading_date">Tarikh selesai membaca</string>
<string name="action_filter_tracked">Dijejaki</string>
<string name="action_display_show_number_of_items">Tunjuk jumlah bilangan</string>
<string name="right_and_left_nav">Kanan dan kiri</string>
</resources>

View File

@ -90,7 +90,6 @@
<string name="update_24hour">Daglig</string>
<string name="update_48hour">Annenhver dag</string>
<string name="update_weekly">Ukentlig</string>
<string name="update_monthly">Månedlig</string>
<string name="all">Alle</string>
<string name="charging">Lading</string>
<string name="pref_start_screen">Startskjerm</string>

View File

@ -81,7 +81,6 @@
<string name="update_24hour">Dagelijks</string>
<string name="update_48hour">Tweedagelijks</string>
<string name="update_weekly">Wekelijks</string>
<string name="update_monthly">Maandelijks</string>
<string name="all">Alles</string>
<string name="pref_start_screen">Beginscherm</string>
<string name="pref_language">Taal</string>
@ -645,4 +644,17 @@
<string name="kindlish_nav">Kindle-achtig</string>
<string name="l_nav">L-vormig</string>
<string name="default_nav">Standaard</string>
<string name="channel_crash_logs">Crashlogboeken</string>
<string name="track_finished_reading_date">Datum klaar met lezen</string>
<string name="track_started_reading_date">Datum begonnen met lezen</string>
<string name="crash_log_saved">Crashlogboeken opgeslagen</string>
<string name="pref_dump_crash_logs_summary">Slaat foutmeldingslogboeken op in een bestand om te delen met de ontwikkelaars</string>
<string name="pref_dump_crash_logs">Crashlogboeken opslaan</string>
<string name="network_unmetered">Onbeperkt netwerk</string>
<string name="action_desc">Aflopend</string>
<string name="action_asc">Oplopend</string>
<string name="action_order_by_chapter_number">Op hoofdstuknummer</string>
<string name="action_order_by_upload_date">Op uploaddatum</string>
<string name="action_display_show_number_of_items">Aantal items tonen</string>
<string name="action_filter_tracked">Getracked</string>
</resources>

View File

@ -161,7 +161,6 @@
<string name="update_24hour">Co 1 dzień</string>
<string name="update_48hour">Co 2 dni</string>
<string name="update_weekly">Co tydzień</string>
<string name="update_monthly">Co miesiąc</string>
<string name="pref_library_update_categories">Kategorie zawarte w globalnej aktualizacji</string>
<string name="all">Wszystko</string>
<string name="pref_library_update_restriction">Warunki aktualizacji biblioteki</string>
@ -680,4 +679,5 @@
<string name="action_order_by_chapter_number">Według numeru rozdziału</string>
<string name="action_order_by_upload_date">Po dacie dodania</string>
<string name="action_filter_tracked">Śledzone</string>
<string name="action_display_show_number_of_items">Pokaż liczbę elementów</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More