mirror of
https://github.com/mihonapp/mihon.git
synced 2025-06-27 11:37:51 +02:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
f590378761 | |||
f5f592be91 | |||
7a373fb43a | |||
aded11e599 | |||
41d7cee020 | |||
f2ef6a20e6 | |||
a398c3fb81 | |||
2a454b44cc | |||
7b66ece895 | |||
b5017eebbf | |||
aa67229daf | |||
5af68186d6 | |||
545bc0e605 | |||
291168f4de | |||
9facb51f22 | |||
5b7d8c5e37 | |||
5945937e4b | |||
9f9f9872eb | |||
3566072f4a | |||
b85cd86b24 | |||
79c3767fff | |||
cf1609a429 | |||
3aeac7e7b5 | |||
1557f713f4 | |||
b63d24ac1a | |||
348c1ff29d | |||
717e55497f | |||
d84b5e8b46 | |||
5f9ddf9ff5 | |||
bbee093c63 | |||
e8c35ae4e1 | |||
1607658c30 | |||
2e9ef373f3 | |||
ec6eef6d37 |
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@ -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
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -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
|
||||
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -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
|
||||
|
||||
|
@ -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()}\"")
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
)
|
||||
}
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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)
|
@ -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
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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/"
|
||||
}
|
||||
}
|
||||
|
@ -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/*"
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.LNavigation
|
||||
|
||||
class WebtoonDefaultNavigation : LNavigation()
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -178,6 +178,7 @@ class SettingsGeneralController : SettingsController() {
|
||||
"cv",
|
||||
"de",
|
||||
"el",
|
||||
"eo",
|
||||
"es",
|
||||
"es-419",
|
||||
"en-US",
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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 d’entrées</string>
|
||||
<string name="right_and_left_nav">Droite et gauche</string>
|
||||
</resources>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
@ -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
Reference in New Issue
Block a user