Compare commits

..

43 Commits
v7.2 ... v7.3

Author SHA1 Message Date
Koitharu
d8fa0e33f1 Update parsers 2024-07-07 12:29:08 +03:00
Koitharu
97bc638f5f Translated using Weblate (Ukrainian)
Currently translated at 100.0% (658 of 658 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (658 of 658 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Scrambled777
064c0ae425 Translated using Weblate (Hindi)
Currently translated at 100.0% (651 of 651 strings)

Co-authored-by: Scrambled777 <weblate.scrambled777@simplelogin.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hi/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Jesús Hernández santillan
ae7aa52177 Added translation using Weblate (Abkhazian)
Co-authored-by: Jesús Hernández santillan <jesusguibel122@gmail.com>
2024-07-07 12:05:34 +03:00
gallegonovato
6edda72d61 Translated using Weblate (Spanish)
Currently translated at 100.0% (651 of 651 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (650 of 650 strings)

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Akhil Raj
2f58f32bdd Translated using Weblate (Malayalam)
Currently translated at 2.6% (17 of 648 strings)

Co-authored-by: Akhil Raj <akhilakae07@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ml/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Eno
0b821db046 Translated using Weblate (Arabic)
Currently translated at 60.4% (392 of 648 strings)

Translated using Weblate (Arabic)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Eno <msa39716@zbock.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-07-07 12:05:34 +03:00
Tawsif
36472998ee Translated using Weblate (Bengali)
Currently translated at 24.5% (159 of 648 strings)

Co-authored-by: Tawsif <tawsif7492@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Abay Emes
c2e7325876 Translated using Weblate (Kazakh)
Currently translated at 84.4% (547 of 648 strings)

Co-authored-by: Abay Emes <abayemes@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/kk/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Zhafran Aziz
28a4a3849c Translated using Weblate (Indonesian)
Currently translated at 99.2% (643 of 648 strings)

Co-authored-by: Zhafran Aziz <aanmts70@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Biagio Ricci
6e9c934912 Translated using Weblate (Italian)
Currently translated at 94.5% (613 of 648 strings)

Co-authored-by: Biagio Ricci <biagior00@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Макар Разин
675ef0e629 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Belarusian)

Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: Макар Разин <makarrazin14@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2024-07-07 12:05:34 +03:00
Koitharu
484914b2dc Fix detection webtoon mode for local manga 2024-07-07 11:54:24 +03:00
Koitharu
ee85ef50f4 Use image proxy for downloading #897 2024-07-07 11:50:08 +03:00
Koitharu
dcee5542c5 Add recent sources to search suggestions 2024-07-07 11:38:35 +03:00
Koitharu
9b3ce4d849 Ability to pin manga sources (close #830, close #531) 2024-07-07 11:15:44 +03:00
Koitharu
5ab7e586f3 Option to sort manga sources by last used #947 2024-07-07 10:18:18 +03:00
Koitharu
9f5d4ed52c Refactor details title expansion 2024-07-07 09:38:34 +03:00
Koitharu
c3ca734005 Update reader state on chapter switch 2024-07-07 09:05:01 +03:00
Koitharu
a158a488f2 Fix error if manga has no chapters 2024-07-07 09:05:01 +03:00
Mac135135
6048cb917e Add functionality to expand manga title on click 2024-07-07 09:04:40 +03:00
Koitharu
81aac0d431 Pages crop feature #326 #919 2024-07-06 19:25:08 +03:00
Koitharu
dfb50fbddc Add image server option to reader config sheet 2024-07-06 14:21:46 +03:00
Koitharu
1f03e0a84b Update parsers and add image server option support 2024-07-06 12:47:01 +03:00
Koitharu
77e393ae48 Pages crop proof-of-concept 2024-06-29 15:56:32 +03:00
Koitharu
77bb5c2fcd Support UNKNOWN manga source 2024-06-22 13:19:21 +03:00
Milo Ivir
475a4904a9 Translated using Weblate (Croatian)
Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (9 of 9 strings)

Co-authored-by: Milo Ivir <mail@milotype.de>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-06-22 12:56:50 +03:00
lukapiplica
cf43b8ebda Translated using Weblate (Croatian)
Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: lukapiplica <github163007297@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
2024-06-22 12:56:50 +03:00
Duh051
f34096af98 Translated using Weblate (Arabic)
Currently translated at 60.1% (390 of 648 strings)

Co-authored-by: Duh051 <duhduh272@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
2024-06-22 12:56:50 +03:00
Bela K
d60ff2a052 Translated using Weblate (German)
Currently translated at 95.2% (617 of 648 strings)

Co-authored-by: Bela K <na0341-dev@posteo.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translation: Kotatsu/Strings
2024-06-22 12:56:50 +03:00
Anon
59d4953554 Translated using Weblate (Serbian)
Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: Anon <anonymousprivate76@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/sr/
Translation: Kotatsu/Strings
2024-06-22 12:56:50 +03:00
acetknan
f76052b1d6 Translated using Weblate (Arabic)
Currently translated at 55.4% (359 of 648 strings)

Translated using Weblate (Arabic)

Currently translated at 88.8% (8 of 9 strings)

Co-authored-by: acetknan <acetknan18@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-06-22 12:56:50 +03:00
Koitharu
26e59b0953 Fix opening reader with specific branch 2024-06-22 12:55:53 +03:00
Koitharu
9ee1164f08 Update parsers 2024-06-22 12:13:52 +03:00
Koitharu
cfc3823593 Improve double-tap zoom in reader 2024-06-22 10:20:48 +03:00
Koitharu
8407a414c5 Fix crashes 2024-06-15 14:03:38 +03:00
Koitharu
a379604974 Transfer scrobbling information within migration #930 2024-06-15 13:49:57 +03:00
Koitharu
c01d80f7da Update dependencies 2024-06-15 10:51:42 +03:00
lukapiplica
7533dce0d2 Translated using Weblate (Croatian)
Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Croatian)

Currently translated at 100.0% (9 of 9 strings)

Added translation using Weblate (Croatian)

Added translation using Weblate (Croatian)

Co-authored-by: lukapiplica <github163007297@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/hr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/hr/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2024-06-15 10:44:09 +03:00
Fikri Akbar
9f1e97fd54 Translated using Weblate (Indonesian)
Currently translated at 99.0% (642 of 648 strings)

Co-authored-by: Fikri Akbar <akbarfikri1221@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2024-06-15 10:44:09 +03:00
Johan
382a73310c Translated using Weblate (Czech)
Currently translated at 100.0% (648 of 648 strings)

Translated using Weblate (Czech)

Currently translated at 98.7% (640 of 648 strings)

Co-authored-by: Johan <jakub.bilku@outlook.cz>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/cs/
Translation: Kotatsu/Strings
2024-06-15 10:44:09 +03:00
Infy's Tagalog Translations
5eeab7fd08 Translated using Weblate (Filipino)
Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: Infy's Tagalog Translations <ced.paltep10@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fil/
Translation: Kotatsu/Strings
2024-06-15 10:44:09 +03:00
gekka
bc54e7cfba Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (648 of 648 strings)

Co-authored-by: gekka <1778962971@qq.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2024-06-15 10:44:09 +03:00
105 changed files with 2169 additions and 354 deletions

View File

@@ -16,8 +16,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdk = 21
targetSdk = 34
versionCode = 648
versionName = '7.2'
versionCode = 651
versionName = '7.3'
generatedDensities = []
testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner'
ksp {
@@ -82,7 +82,7 @@ afterEvaluate {
}
dependencies {
//noinspection GradleDependency
implementation('com.github.KotatsuApp:kotatsu-parsers:56fd22b43f') {
implementation('com.github.KotatsuApp:kotatsu-parsers:74b8aaa94e') {
exclude group: 'org.json', module: 'json'
}
@@ -93,12 +93,12 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.activity:activity-ktx:1.9.0'
implementation 'androidx.fragment:fragment-ktx:1.7.1'
implementation 'androidx.fragment:fragment-ktx:1.8.1'
implementation 'androidx.transition:transition-ktx:1.5.0'
implementation 'androidx.collection:collection-ktx:1.4.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.1'
implementation 'androidx.lifecycle:lifecycle-service:2.8.1'
implementation 'androidx.lifecycle:lifecycle-process:2.8.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3'
implementation 'androidx.lifecycle:lifecycle-service:2.8.3'
implementation 'androidx.lifecycle:lifecycle-process:2.8.3'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
@@ -106,7 +106,7 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.1'
implementation 'androidx.lifecycle:lifecycle-common-java8:2.8.3'
implementation 'androidx.webkit:webkit:1.11.0'
implementation 'androidx.work:work-runtime:2.9.0'
@@ -136,7 +136,7 @@ dependencies {
implementation 'io.coil-kt:coil-base:2.6.0'
implementation 'io.coil-kt:coil-svg:2.6.0'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:02e6d6cfe9'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:882bc0620c'
implementation 'com.github.solkin:disk-lru-cache:1.4'
implementation 'io.noties.markwon:core:4.6.2'
@@ -154,10 +154,10 @@ dependencies {
testImplementation 'org.json:json:20240303'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation 'androidx.test:core-ktx:1.5.0'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5'
androidTestImplementation 'androidx.test:runner:1.6.1'
androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test:core-ktx:1.6.1'
androidTestImplementation 'androidx.test.ext:junit-ktx:1.2.1'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1'

View File

@@ -12,136 +12,184 @@ import org.koitharu.kotatsu.history.data.toMangaHistory
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.tracker.data.TrackEntity
import javax.inject.Inject
class MigrateUseCase @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val mangaDataRepository: MangaDataRepository,
private val database: MangaDatabase,
private val progressUpdateUseCase: ProgressUpdateUseCase,
) {
suspend operator fun invoke(oldManga: Manga, newManga: Manga) {
val oldDetails = if (oldManga.chapters.isNullOrEmpty()) {
runCatchingCancellable {
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
}.getOrDefault(oldManga)
} else {
oldManga
}
val newDetails = if (newManga.chapters.isNullOrEmpty()) {
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
} else {
newManga
}
mangaDataRepository.storeManga(newDetails)
database.withTransaction {
// replace favorites
val favoritesDao = database.getFavouritesDao()
val oldFavourites = favoritesDao.findAllRaw(oldDetails.id)
if (oldFavourites.isNotEmpty()) {
favoritesDao.delete(oldManga.id)
for (f in oldFavourites) {
val e = f.copy(
mangaId = newManga.id,
class MigrateUseCase
@Inject
constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val mangaDataRepository: MangaDataRepository,
private val database: MangaDatabase,
private val progressUpdateUseCase: ProgressUpdateUseCase,
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
) {
suspend operator fun invoke(
oldManga: Manga,
newManga: Manga,
) {
val oldDetails =
if (oldManga.chapters.isNullOrEmpty()) {
runCatchingCancellable {
mangaRepositoryFactory.create(oldManga.source).getDetails(oldManga)
}.getOrDefault(oldManga)
} else {
oldManga
}
val newDetails =
if (newManga.chapters.isNullOrEmpty()) {
mangaRepositoryFactory.create(newManga.source).getDetails(newManga)
} else {
newManga
}
mangaDataRepository.storeManga(newDetails)
database.withTransaction {
// replace favorites
val favoritesDao = database.getFavouritesDao()
val oldFavourites = favoritesDao.findAllRaw(oldDetails.id)
if (oldFavourites.isNotEmpty()) {
favoritesDao.delete(oldManga.id)
for (f in oldFavourites) {
val e =
f.copy(
mangaId = newManga.id,
)
favoritesDao.upsert(e)
}
}
// replace history
val historyDao = database.getHistoryDao()
val oldHistory = historyDao.find(oldDetails.id)
val newHistory =
if (oldHistory != null) {
val newHistory = makeNewHistory(oldDetails, newDetails, oldHistory)
historyDao.delete(oldDetails.id)
historyDao.upsert(newHistory)
newHistory
} else {
null
}
// track
val tracksDao = database.getTracksDao()
val oldTrack = tracksDao.find(oldDetails.id)
if (oldTrack != null) {
val lastChapter = newDetails.chapters?.lastOrNull()
val newTrack =
TrackEntity(
mangaId = newDetails.id,
lastChapterId = lastChapter?.id ?: 0L,
newChapters = 0,
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = lastChapter?.uploadDate ?: 0L,
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
lastError = null,
)
tracksDao.delete(oldDetails.id)
tracksDao.upsert(newTrack)
}
// scrobbling
for (scrobbler in scrobblers) {
if (!scrobbler.isEnabled) {
continue
}
val prevInfo = scrobbler.getScrobblingInfoOrNull(oldDetails.id) ?: continue
scrobbler.unregisterScrobbling(oldDetails.id)
scrobbler.linkManga(newDetails.id, prevInfo.targetId)
scrobbler.updateScrobblingInfo(
mangaId = newDetails.id,
rating = prevInfo.rating,
status =
prevInfo.status ?: when {
newHistory == null -> ScrobblingStatus.PLANNED
newHistory.percent == 1f -> ScrobblingStatus.COMPLETED
else -> ScrobblingStatus.READING
},
comment = prevInfo.comment,
)
favoritesDao.upsert(e)
if (newHistory != null) {
scrobbler.scrobble(
manga = newDetails,
chapterId = newHistory.chapterId,
)
}
}
}
// replace history
val historyDao = database.getHistoryDao()
val oldHistory = historyDao.find(oldDetails.id)
if (oldHistory != null) {
val newHistory = makeNewHistory(oldDetails, newDetails, oldHistory)
historyDao.delete(oldDetails.id)
historyDao.upsert(newHistory)
}
// track
val tracksDao = database.getTracksDao()
val oldTrack = tracksDao.find(oldDetails.id)
if (oldTrack != null) {
val lastChapter = newDetails.chapters?.lastOrNull()
val newTrack = TrackEntity(
mangaId = newDetails.id,
lastChapterId = lastChapter?.id ?: 0L,
newChapters = 0,
lastCheckTime = System.currentTimeMillis(),
lastChapterDate = lastChapter?.uploadDate ?: 0L,
lastResult = TrackEntity.RESULT_EXTERNAL_MODIFICATION,
lastError = null,
)
tracksDao.delete(oldDetails.id)
tracksDao.upsert(newTrack)
}
progressUpdateUseCase(newManga)
}
progressUpdateUseCase(newManga)
}
private fun makeNewHistory(
oldManga: Manga,
newManga: Manga,
history: HistoryEntity,
): HistoryEntity {
if (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source
val branch = newManga.getPreferredBranch(null)
val chapters = checkNotNull(newManga.getChapters(branch))
val currentChapter = if (history.percent in 0f..1f) {
chapters[(chapters.lastIndex * history.percent).toInt()]
} else {
chapters.first()
private fun makeNewHistory(
oldManga: Manga,
newManga: Manga,
history: HistoryEntity,
): HistoryEntity {
if (oldManga.chapters.isNullOrEmpty()) { // probably broken manga/source
val branch = newManga.getPreferredBranch(null)
val chapters = checkNotNull(newManga.getChapters(branch))
val currentChapter =
if (history.percent in 0f..1f) {
chapters[(chapters.lastIndex * history.percent).toInt()]
} else {
chapters.first()
}
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
chapterId = currentChapter.id,
page = history.page,
scroll = history.scroll,
percent = history.percent,
deletedAt = 0,
chaptersCount = chapters.size,
)
}
val branch = oldManga.getPreferredBranch(history.toMangaHistory())
val oldChapters = checkNotNull(oldManga.getChapters(branch))
var index = oldChapters.indexOfFirst { it.id == history.chapterId }
if (index < 0) {
index =
if (history.percent in 0f..1f) {
(oldChapters.lastIndex * history.percent).toInt()
} else {
0
}
}
val newChapters = checkNotNull(newManga.chapters).groupBy { it.branch }
val newBranch =
if (newChapters.containsKey(branch)) {
branch
} else {
newManga.getPreferredBranch(null)
}
val newChapterId =
checkNotNull(newChapters[newBranch])
.let {
val oldChapter = oldChapters[index]
it.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last()
}.id
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
chapterId = currentChapter.id,
chapterId = newChapterId,
page = history.page,
scroll = history.scroll,
percent = history.percent,
percent = PROGRESS_NONE,
deletedAt = 0,
chaptersCount = chapters.size,
chaptersCount = checkNotNull(newChapters[newBranch]).size,
)
}
val branch = oldManga.getPreferredBranch(history.toMangaHistory())
val oldChapters = checkNotNull(oldManga.getChapters(branch))
var index = oldChapters.indexOfFirst { it.id == history.chapterId }
if (index < 0) {
index = if (history.percent in 0f..1f) {
(oldChapters.lastIndex * history.percent).toInt()
private fun List<MangaChapter>.findByNumber(
volume: Int,
number: Float,
): MangaChapter? =
if (number <= 0f) {
null
} else {
0
firstOrNull { it.volume == volume && it.number == number }
}
}
val newChapters = checkNotNull(newManga.chapters).groupBy { it.branch }
val newBranch = if (newChapters.containsKey(branch)) {
branch
} else {
newManga.getPreferredBranch(null)
}
val newChapterId = checkNotNull(newChapters[newBranch]).let {
val oldChapter = oldChapters[index]
it.findByNumber(oldChapter.volume, oldChapter.number) ?: it.getOrNull(index) ?: it.last()
}.id
return HistoryEntity(
mangaId = newManga.id,
createdAt = history.createdAt,
updatedAt = System.currentTimeMillis(),
chapterId = newChapterId,
page = history.page,
scroll = history.scroll,
percent = PROGRESS_NONE,
deletedAt = 0,
chaptersCount = checkNotNull(newChapters[newBranch]).size,
)
}
private fun List<MangaChapter>.findByNumber(volume: Int, number: Float): MangaChapter? {
return if (number <= 0f) {
null
} else {
firstOrNull { it.volume == volume && it.number == number }
}
}
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault
import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
class JsonDeserializer(private val json: JSONObject) {
@@ -85,6 +86,8 @@ class JsonDeserializer(private val json: JSONObject) {
isEnabled = json.getBoolean("enabled"),
sortKey = json.getInt("sort_key"),
addedIn = json.getIntOrDefault("added_in", 0),
lastUsedAt = json.getLongOrDefault("used_at", 0L),
isPinned = json.getBooleanOrDefault("pinned", false),
)
fun toMap(): Map<String, Any?> {

View File

@@ -89,6 +89,9 @@ class JsonSerializer private constructor(private val json: JSONObject) {
put("source", e.source)
put("enabled", e.isEnabled)
put("sort_key", e.sortKey)
put("added_in", e.addedIn)
put("used_at", e.lastUsedAt)
put("pinned", e.isPinned)
},
)

View File

@@ -34,6 +34,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration18To19
import org.koitharu.kotatsu.core.db.migrations.Migration19To20
import org.koitharu.kotatsu.core.db.migrations.Migration1To2
import org.koitharu.kotatsu.core.db.migrations.Migration20To21
import org.koitharu.kotatsu.core.db.migrations.Migration21To22
import org.koitharu.kotatsu.core.db.migrations.Migration2To3
import org.koitharu.kotatsu.core.db.migrations.Migration3To4
import org.koitharu.kotatsu.core.db.migrations.Migration4To5
@@ -59,7 +60,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity
import org.koitharu.kotatsu.tracker.data.TrackLogEntity
import org.koitharu.kotatsu.tracker.data.TracksDao
const val DATABASE_VERSION = 21
const val DATABASE_VERSION = 22
@Database(
entities = [
@@ -120,6 +121,7 @@ fun getDatabaseMigrations(context: Context): Array<Migration> = arrayOf(
Migration18To19(),
Migration19To20(),
Migration20To21(),
Migration21To22(),
)
fun MangaDatabase(context: Context): MangaDatabase = Room

View File

@@ -18,7 +18,7 @@ import org.koitharu.kotatsu.explore.data.SourcesSortOrder
@Dao
abstract class MangaSourcesDao {
@Query("SELECT * FROM sources ORDER BY sort_key")
@Query("SELECT * FROM sources ORDER BY pinned DESC, sort_key")
abstract suspend fun findAll(): List<MangaSourceEntity>
@Query("SELECT source FROM sources WHERE enabled = 1")
@@ -27,7 +27,10 @@ abstract class MangaSourcesDao {
@Query("SELECT * FROM sources WHERE added_in >= :version")
abstract suspend fun findAllFromVersion(version: Int): List<MangaSourceEntity>
@Query("SELECT * FROM sources ORDER BY sort_key")
@Query("SELECT * FROM sources ORDER BY used_at DESC LIMIT :limit")
abstract suspend fun findLastUsed(limit: Int): List<MangaSourceEntity>
@Query("SELECT * FROM sources ORDER BY pinned DESC, sort_key")
abstract fun observeAll(): Flow<List<MangaSourceEntity>>
@Query("SELECT enabled FROM sources WHERE source = :source")
@@ -42,6 +45,12 @@ abstract class MangaSourcesDao {
@Query("UPDATE sources SET sort_key = :sortKey WHERE source = :source")
abstract suspend fun setSortKey(source: String, sortKey: Int)
@Query("UPDATE sources SET used_at = :value WHERE source = :source")
abstract suspend fun setLastUsed(source: String, value: Long)
@Query("UPDATE sources SET pinned = :isPinned WHERE source = :source")
abstract suspend fun setPinned(source: String, isPinned: Boolean)
@Insert(onConflict = OnConflictStrategy.IGNORE)
@Transaction
abstract suspend fun insertIfAbsent(entries: Collection<MangaSourceEntity>)
@@ -49,11 +58,14 @@ abstract class MangaSourcesDao {
@Upsert
abstract suspend fun upsert(entry: MangaSourceEntity)
@Query("SELECT * FROM sources WHERE pinned = 1")
abstract suspend fun findAllPinned(): List<MangaSourceEntity>
fun observeEnabled(order: SourcesSortOrder): Flow<List<MangaSourceEntity>> {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY pinned DESC, $orderBy")
return observeImpl(query)
}
@@ -61,7 +73,7 @@ abstract class MangaSourcesDao {
val orderBy = getOrderBy(order)
@Language("RoomSql")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy")
val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY pinned DESC, $orderBy")
return findAllImpl(query)
}
@@ -73,6 +85,8 @@ abstract class MangaSourcesDao {
isEnabled = isEnabled,
sortKey = getMaxSortKey() + 1,
addedIn = BuildConfig.VERSION_CODE,
lastUsedAt = 0,
isPinned = false,
)
upsert(entity)
}
@@ -91,5 +105,6 @@ abstract class MangaSourcesDao {
SourcesSortOrder.ALPHABETIC -> "source ASC"
SourcesSortOrder.POPULARITY -> "(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC"
SourcesSortOrder.MANUAL -> "sort_key ASC"
SourcesSortOrder.LAST_USED -> "used_at DESC"
}
}

View File

@@ -15,4 +15,6 @@ data class MangaSourceEntity(
@ColumnInfo(name = "enabled") val isEnabled: Boolean,
@ColumnInfo(name = "sort_key", index = true) val sortKey: Int,
@ColumnInfo(name = "added_in") val addedIn: Int,
@ColumnInfo(name = "used_at") val lastUsedAt: Long,
@ColumnInfo(name = "pinned") val isPinned: Boolean,
)

View File

@@ -0,0 +1,12 @@
package org.koitharu.kotatsu.core.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
class Migration21To22 : Migration(21, 22) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE sources ADD COLUMN `used_at` INTEGER NOT NULL DEFAULT 0")
db.execSQL("ALTER TABLE sources ADD COLUMN `pinned` INTEGER NOT NULL DEFAULT 0")
}
}

View File

@@ -21,7 +21,7 @@ fun MangaSource(name: String): MangaSource {
MangaSource.entries.forEach {
if (it.name == name) return it
}
return MangaSource.DUMMY
return MangaSource.UNKNOWN
}
fun MangaSource.isNsfw() = contentType == ContentType.HENTAI

View File

@@ -25,7 +25,7 @@ data class ParcelableChapter(
scanlator = parcel.readString(),
uploadDate = parcel.readLong(),
branch = parcel.readString(),
source = parcel.readSerializableCompat() ?: MangaSource.DUMMY,
source = parcel.readSerializableCompat() ?: MangaSource.UNKNOWN,
)
)

View File

@@ -0,0 +1,40 @@
package org.koitharu.kotatsu.core.parser
import org.koitharu.kotatsu.core.exceptions.UnsupportedSourceException
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import java.util.EnumSet
/**
* This parser is just for parser development, it should not be used in releases
*/
class EmptyParser(context: MangaLoaderContext) : MangaParser(context, MangaSource.DUMMY) {
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain("localhost")
override val availableSortOrders: Set<SortOrder>
get() = EnumSet.allOf(SortOrder::class.java)
override suspend fun getDetails(manga: Manga): Manga = stub(manga)
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> = stub(null)
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> = stub(null)
override suspend fun getAvailableTags(): Set<MangaTag> = stub(null)
override suspend fun getRelatedManga(seed: Manga): List<Manga> = stub(seed)
private fun stub(manga: Manga?): Nothing {
throw UnsupportedSourceException("This manga source is not supported", manga)
}
}

View File

@@ -36,7 +36,7 @@ class MangaLinkResolver @Inject constructor(
require(uri.pathSegments.singleOrNull() == "manga") { "Invalid url" }
val sourceName = requireNotNull(uri.getQueryParameter("source")) { "Source is not specified" }
val source = MangaSource(sourceName)
require(source != MangaSource.DUMMY) { "Manga source $sourceName is not supported" }
require(source != MangaSource.UNKNOWN) { "Manga source $sourceName is not supported" }
val repo = repositoryFactory.create(source)
return repo.findExact(
url = uri.getQueryParameter("url"),

View File

@@ -5,9 +5,9 @@ import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.MangaSource
fun MangaParser(source: MangaSource, loaderContext: MangaLoaderContext): MangaParser {
return if (source == MangaSource.DUMMY) {
DummyParser(loaderContext)
} else {
loaderContext.newParserInstance(source)
return when (source) {
MangaSource.UNKNOWN -> EmptyParser(loaderContext)
MangaSource.DUMMY -> DummyParser(loaderContext)
else -> loaderContext.newParserInstance(source)
}
}

View File

@@ -33,7 +33,6 @@ import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import java.io.File
import java.net.Proxy
import java.util.EnumSet
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@@ -485,6 +484,15 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isAutoLocalChaptersCleanupEnabled: Boolean
get() = prefs.getBoolean(KEY_CHAPTERS_CLEAR_AUTO, false)
fun isPagesCropEnabled(mode: ReaderMode): Boolean {
val rawValue = prefs.getStringSet(KEY_READER_CROP, emptySet())
if (rawValue.isNullOrEmpty()) {
return false
}
val needle = if (mode == ReaderMode.WEBTOON) READER_CROP_WEBTOON else READER_CROP_PAGED
return needle.toString() in rawValue
}
fun isTipEnabled(tip: String): Boolean {
return prefs.getStringSet(KEY_TIPS_CLOSED, emptySet())?.contains(tip) != true
}
@@ -597,6 +605,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_READER_ANIMATION = "reader_animation2"
const val KEY_READER_MODE = "reader_mode"
const val KEY_READER_MODE_DETECT = "reader_mode_detect"
const val KEY_READER_CROP = "reader_crop"
const val KEY_APP_PASSWORD = "app_password"
const val KEY_APP_PASSWORD_NUMERIC = "app_password_num"
const val KEY_PROTECT_APP = "protect_app"
@@ -698,5 +707,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
// old keys are for migration only
private const val KEY_IMAGES_PROXY_OLD = "images_proxy"
// values
private const val READER_CROP_PAGED = 1
private const val READER_CROP_WEBTOON = 2
}
}

View File

@@ -12,5 +12,6 @@ enum class SearchSuggestionType(
QUERIES_SUGGEST(R.string.suggested_queries),
MANGA(R.string.content_type_manga),
SOURCES(R.string.remote_sources),
RECENT_SOURCES(R.string.recent_sources),
AUTHORS(R.string.authors),
}

View File

@@ -38,6 +38,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue)
is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue)
is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.takeUnless(String::isEmpty)
} as T
}
@@ -47,6 +48,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig
is ConfigKey.ShowSuspiciousContent -> putBoolean(key.key, value as Boolean)
is ConfigKey.UserAgent -> putString(key.key, (value as String?)?.sanitizeHeaderValue())
is ConfigKey.SplitByTranslations -> putBoolean(key.key, value as Boolean)
is ConfigKey.PreferredImageServer -> putString(key.key, value as String? ?: "")
}
}

View File

@@ -68,7 +68,7 @@ abstract class BaseViewModel : ViewModel() {
errorEvent.call(error)
}
protected inline suspend fun <T> withLoading(block: () -> T): T = try {
protected inline fun <T> withLoading(block: () -> T): T = try {
loadingCounter.increment()
block()
} finally {

View File

@@ -1,15 +1,10 @@
package org.koitharu.kotatsu.core.ui.image
import android.graphics.Bitmap
import androidx.annotation.ColorInt
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import coil.size.Size
import coil.transform.Transformation
import kotlin.math.abs
import org.koitharu.kotatsu.reader.domain.EdgeDetector.Companion.isColorTheSame
class TrimTransformation(
private val tolerance: Int = 20,
@@ -28,7 +23,7 @@ class TrimTransformation(
var isColBlank = true
val prevColor = input[x, 0]
for (y in 1 until input.height) {
if (!isColorTheSame(input[x, y], prevColor)) {
if (!isColorTheSame(input[x, y], prevColor, tolerance)) {
isColBlank = false
break
}
@@ -47,7 +42,7 @@ class TrimTransformation(
var isColBlank = true
val prevColor = input[x, 0]
for (y in 1 until input.height) {
if (!isColorTheSame(input[x, y], prevColor)) {
if (!isColorTheSame(input[x, y], prevColor, tolerance)) {
isColBlank = false
break
}
@@ -63,7 +58,7 @@ class TrimTransformation(
var isRowBlank = true
val prevColor = input[0, y]
for (x in 1 until input.width) {
if (!isColorTheSame(input[x, y], prevColor)) {
if (!isColorTheSame(input[x, y], prevColor, tolerance)) {
isRowBlank = false
break
}
@@ -79,7 +74,7 @@ class TrimTransformation(
var isRowBlank = true
val prevColor = input[0, y]
for (x in 1 until input.width) {
if (!isColorTheSame(input[x, y], prevColor)) {
if (!isColorTheSame(input[x, y], prevColor, tolerance)) {
isRowBlank = false
break
}
@@ -98,13 +93,6 @@ class TrimTransformation(
}
}
private fun isColorTheSame(@ColorInt a: Int, @ColorInt b: Int): Boolean {
return abs(a.red - b.red) <= tolerance &&
abs(a.green - b.green) <= tolerance &&
abs(a.blue - b.blue) <= tolerance &&
abs(a.alpha - b.alpha) <= tolerance
}
override fun equals(other: Any?): Boolean {
return this === other || (other is TrimTransformation && other.tolerance == tolerance)
}

View File

@@ -23,11 +23,16 @@ class SelectableTextView @JvmOverloads constructor(
private fun fixSelectionRange() {
if (selectionStart < 0 || selectionEnd < 0) {
val spannableText = text as? Spannable ?: return
Selection.setSelection(spannableText, text.length)
Selection.setSelection(spannableText, spannableText.length)
}
}
override fun scrollTo(x: Int, y: Int) {
super.scrollTo(0, 0)
}
fun selectAll() {
val spannableText = text as? Spannable ?: return
Selection.selectAll(spannableText)
}
}

View File

@@ -69,4 +69,11 @@ fun <T> Iterable<T>.sortedWithSafe(comparator: Comparator<in T>): List<T> = try
}
}
fun Collection<*>?.sizeOrZero() = if (this == null) 0 else size
fun Collection<*>?.sizeOrZero() = this?.size ?: 0
@Suppress("UNCHECKED_CAST")
inline fun <T, reified R> Collection<T>.mapToArray(transform: (T) -> R): Array<R> {
val result = arrayOfNulls<R>(size)
forEachIndexed { index, t -> result[index] = transform(t) }
return result as Array<R>
}

View File

@@ -12,12 +12,16 @@ import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.plus
import kotlinx.coroutines.suspendCancellableCoroutine
import org.koitharu.kotatsu.core.util.AcraCoroutineErrorHandler
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
import org.koitharu.kotatsu.parsers.util.cancelAll
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.cancellation.CancellationException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@@ -90,3 +94,10 @@ fun <T> Deferred<T>.peek(): T? = if (isCompleted) {
} else {
null
}
@Suppress("SuspendFunctionOnCoroutineScope")
suspend fun CoroutineScope.cancelChildrenAndJoin(cause: CancellationException? = null) {
val jobs = coroutineContext[Job]?.children?.toList() ?: return
jobs.cancelAll(cause)
jobs.joinAll()
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.util.ext
import android.graphics.Bitmap
import android.graphics.Rect
import kotlin.math.roundToInt
@@ -11,3 +12,9 @@ fun Rect.scale(factor: Double) {
(height() - newHeight) / 2,
)
}
inline fun <R> Bitmap.use(block: (Bitmap) -> R) = try {
block(this)
} finally {
recycle()
}

View File

@@ -123,7 +123,6 @@ class DetailsActivity :
lateinit var tagHighlighter: ListExtraProvider
private val viewModel: DetailsViewModel by viewModels()
private lateinit var menuProvider: DetailsMenuProvider
override fun onCreate(savedInstanceState: Bundle?) {
@@ -157,6 +156,7 @@ class DetailsActivity :
viewBinding.containerBottomSheet?.let { sheet ->
onBackPressedDispatcher.addCallback(BottomSheetCollapseCallback(sheet))
}
TitleExpandListener(viewBinding.textViewTitle).attach()
viewModel.details.filterNotNull().observe(this, ::onMangaUpdated)
viewModel.onMangaRemoved.observeEvent(this, ::onMangaRemoved)
@@ -463,7 +463,7 @@ class DetailsActivity :
imageViewState.isVisible = false
}
if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.DUMMY) {
if (manga.source == MangaSource.LOCAL || manga.source == MangaSource.UNKNOWN) {
infoLayout.chipSource.isVisible = false
} else {
infoLayout.chipSource.text = manga.source.title
@@ -538,7 +538,7 @@ class DetailsActivity :
}
val isFirstCall = buttonRead.tag == null
buttonRead.tag = Unit
buttonRead.setProgress(info.history?.percent?.coerceIn(0f, 1f) ?: 0f, !isFirstCall)
buttonRead.setProgress(info.percent.coerceIn(0f, 1f), !isFirstCall)
buttonDownload?.isEnabled = info.isValid && info.canDownload
buttonRead.isEnabled = info.isValid
}

View File

@@ -93,15 +93,19 @@ class DetailsViewModel @Inject constructor(
val details = MutableStateFlow(intent.manga?.let { MangaDetails(it, null, null, false) })
val manga = details.map { x -> x?.toManga() }
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
val history = historyRepository.observeOne(mangaId)
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
val favouriteCategories = interactor.observeFavourite(mangaId)
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptySet())
val isStatsAvailable = statsRepository.observeHasStats(mangaId)
.withErrorHandling()
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
val remoteManga = MutableStateFlow<Manga?>(null)
@@ -162,7 +166,7 @@ class DetailsViewModel @Inject constructor(
val onMangaRemoved = MutableEventFlow<Manga>()
val isScrobblingAvailable: Boolean
get() = scrobblers.any { it.isAvailable }
get() = scrobblers.any { it.isEnabled }
val scrobblingInfo: StateFlow<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList())
@@ -393,7 +397,7 @@ class DetailsViewModel @Inject constructor(
private fun getScrobbler(index: Int): Scrobbler? {
val info = scrobblingInfo.value.getOrNull(index)
val scrobbler = if (info != null) {
scrobblers.find { it.scrobblerService == info.scrobbler && it.isAvailable }
scrobblers.find { it.scrobblerService == info.scrobbler && it.isEnabled }
} else {
null
}

View File

@@ -0,0 +1,45 @@
package org.koitharu.kotatsu.details.ui
import android.annotation.SuppressLint
import android.transition.TransitionManager
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import android.view.ViewGroup
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.widgets.SelectableTextView
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
@SuppressLint("ClickableViewAccessibility")
class TitleExpandListener(
private val textView: SelectableTextView,
) : GestureDetector.SimpleOnGestureListener(), OnTouchListener {
private val gestureDetector = GestureDetector(textView.context, this)
private val linesExpanded = textView.resources.getInteger(R.integer.details_description_lines)
private val linesCollapsed = textView.resources.getInteger(R.integer.details_title_lines)
override fun onTouch(v: View?, event: MotionEvent) = gestureDetector.onTouchEvent(event)
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
if (textView.context.isAnimationsEnabled) {
TransitionManager.beginDelayedTransition(textView.parent as ViewGroup)
}
if (textView.maxLines in 1 until Integer.MAX_VALUE) {
textView.maxLines = Integer.MAX_VALUE
} else {
textView.maxLines = linesCollapsed
}
return true
}
override fun onLongPress(e: MotionEvent) {
textView.maxLines = Integer.MAX_VALUE
textView.selectAll()
}
fun attach() {
textView.setOnTouchListener(this)
}
}

View File

@@ -16,6 +16,13 @@ data class HistoryInfo(
val canContinue
get() = currentChapter >= 0
val percent: Float
get() = if (history != null && (canContinue || isChapterMissing)) {
history.percent
} else {
0f
}
}
fun HistoryInfo(
@@ -24,7 +31,11 @@ fun HistoryInfo(
history: MangaHistory?,
isIncognitoMode: Boolean
): HistoryInfo {
val chapters = manga?.chapters?.get(branch)
val chapters = if (manga?.chapters?.isEmpty() == true) {
emptyList()
} else {
manga?.chapters?.get(branch)
}
val currentChapter = if (history != null && !chapters.isNullOrEmpty()) {
chapters.indexOfFirst { it.id == history.chapterId }
} else {

View File

@@ -92,7 +92,7 @@ class MangaPageFetcher(
}
else -> {
val request = PageLoader.createPageRequest(page, pageUrl)
val request = PageLoader.createPageRequest(pageUrl, page.source)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
if (!response.isSuccessful) {
throw HttpException(response)

View File

@@ -35,16 +35,16 @@ import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.internal.closeQuietly
import okio.IOException
import okio.buffer
import okio.sink
import okio.use
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
import org.koitharu.kotatsu.core.model.ids
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -55,6 +55,7 @@ import org.koitharu.kotatsu.core.util.ext.awaitWorkInfosByTag
import org.koitharu.kotatsu.core.util.ext.deleteAwait
import org.koitharu.kotatsu.core.util.ext.deleteWork
import org.koitharu.kotatsu.core.util.ext.deleteWorks
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
import org.koitharu.kotatsu.core.util.ext.getWorkInputData
import org.koitharu.kotatsu.core.util.ext.getWorkSpec
@@ -73,9 +74,9 @@ import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.domain.PageLoader
import java.io.File
import java.util.UUID
import java.util.concurrent.TimeUnit
@@ -93,6 +94,7 @@ class DownloadWorker @AssistedInject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val settings: AppSettings,
@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,
private val imageProxyInterceptor: ImageProxyInterceptor,
notificationFactoryFactory: DownloadNotificationFactory.Factory,
) : CoroutineWorker(appContext, params) {
@@ -327,28 +329,24 @@ class DownloadWorker @AssistedInject constructor(
destination: File,
source: MangaSource,
): File {
val request = Request.Builder()
.url(url)
.tag(MangaSource::class.java, source)
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
.cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)
.get()
.build()
val request = PageLoader.createPageRequest(url, source)
slowdownDispatcher.delay(source)
val call = okHttp.newCall(request)
val file = File(destination, UUID.randomUUID().toString() + ".tmp")
try {
val response = call.clone().await()
checkNotNull(response.body).use { body ->
file.sink(append = false).buffer().use {
it.writeAllCancellable(body.source())
return imageProxyInterceptor.interceptPageRequest(request, okHttp)
.ensureSuccess()
.use { response ->
val file = File(destination, UUID.randomUUID().toString() + ".tmp")
try {
checkNotNull(response.body).use { body ->
file.sink(append = false).buffer().use {
it.writeAllCancellable(body.source())
}
}
} catch (e: CancellationException) {
file.delete()
throw e
}
file
}
} catch (e: CancellationException) {
file.delete()
throw e
}
return file
}
private suspend fun publishState(state: DownloadState) {

View File

@@ -36,6 +36,7 @@ class MangaSourcesRepository @Inject constructor(
private val remoteSources = EnumSet.allOf(MangaSource::class.java).apply {
remove(MangaSource.LOCAL)
remove(MangaSource.UNKNOWN)
if (!BuildConfig.DEBUG) {
remove(MangaSource.DUMMY)
}
@@ -50,6 +51,19 @@ class MangaSourcesRepository @Inject constructor(
return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order)
}
suspend fun getPinnedSources(): Set<MangaSource> {
assimilateNewSources()
val skipNsfw = settings.isNsfwContentDisabled
return dao.findAllPinned().mapNotNullTo(EnumSet.noneOf(MangaSource::class.java)) {
it.source.toMangaSourceOrNull()?.takeUnless { x -> skipNsfw && x.isNsfw() }
}
}
suspend fun getTopSources(limit: Int): List<MangaSource> {
assimilateNewSources()
return dao.findLastUsed(limit).toSources(settings.isNsfwContentDisabled, null)
}
suspend fun getDisabledSources(): Set<MangaSource> {
assimilateNewSources()
val result = EnumSet.copyOf(remoteSources)
@@ -213,6 +227,8 @@ class MangaSourcesRepository @Inject constructor(
isEnabled = false,
sortKey = ++maxSortKey,
addedIn = BuildConfig.VERSION_CODE,
lastUsedAt = 0,
isPinned = false,
)
}
dao.insertIfAbsent(entities)
@@ -223,6 +239,19 @@ class MangaSourcesRepository @Inject constructor(
return settings.sourcesVersion == 0 && dao.findAllEnabledNames().isEmpty()
}
suspend fun setIsPinned(sources: Collection<MangaSource>, isPinned: Boolean): ReversibleHandle {
setSourcesPinnedImpl(sources, isPinned)
return ReversibleHandle {
setSourcesEnabledImpl(sources, !isPinned)
}
}
suspend fun trackUsage(source: MangaSource) {
if (!settings.isIncognitoModeEnabled && !(settings.isHistoryExcludeNsfw && source.isNsfw())) {
dao.setLastUsed(source.name, System.currentTimeMillis())
}
}
private suspend fun setSourcesEnabledImpl(sources: Collection<MangaSource>, isEnabled: Boolean) {
if (sources.size == 1) { // fast path
dao.setEnabled(sources.first().name, isEnabled)
@@ -235,6 +264,18 @@ class MangaSourcesRepository @Inject constructor(
}
}
private suspend fun setSourcesPinnedImpl(sources: Collection<MangaSource>, isPinned: Boolean) {
if (sources.size == 1) { // fast path
dao.setPinned(sources.first().name, isPinned)
return
}
db.withTransaction {
for (source in sources) {
dao.setPinned(source.name, isPinned)
}
}
}
private suspend fun getNewSources(): MutableSet<MangaSource> {
val entities = dao.findAll()
val result = EnumSet.copyOf(remoteSources)
@@ -249,6 +290,7 @@ class MangaSourcesRepository @Inject constructor(
sortOrder: SourcesSortOrder?,
): MutableList<MangaSource> {
val result = ArrayList<MangaSource>(size)
val pinned = EnumSet.noneOf(MangaSource::class.java)
for (entity in this) {
val source = entity.source.toMangaSourceOrNull() ?: continue
if (skipNsfwSources && source.isNsfw()) {
@@ -256,10 +298,13 @@ class MangaSourcesRepository @Inject constructor(
}
if (source in remoteSources) {
result.add(source)
if (entity.isPinned) {
pinned.add(source)
}
}
}
if (sortOrder == SourcesSortOrder.ALPHABETIC) {
result.sortBy { it.title }
result.sortWith(compareBy<MangaSource> { it in pinned }.thenBy { it.title })
}
return result
}

View File

@@ -9,4 +9,5 @@ enum class SourcesSortOrder(
ALPHABETIC(R.string.by_name),
POPULARITY(R.string.popular),
MANUAL(R.string.manual),
LAST_USED(R.string.last_used),
}

View File

@@ -196,6 +196,16 @@ class ExploreFragment :
mode.finish()
}
R.id.action_pin -> {
viewModel.setSourcesPinned(selectedSources, isPinned = true)
mode.finish()
}
R.id.action_unpin -> {
viewModel.setSourcesPinned(selectedSources, isPinned = false)
mode.finish()
}
else -> return false
}
return true

View File

@@ -108,6 +108,18 @@ class ExploreViewModel @Inject constructor(
}
}
fun setSourcesPinned(sources: Set<MangaSource>, isPinned: Boolean) {
launchJob(Dispatchers.Default) {
sourcesRepository.setIsPinned(sources, isPinned)
val message = if (sources.size == 1) {
if (isPinned) R.string.source_pinned else R.string.source_unpinned
} else {
if (isPinned) R.string.sources_pinned else R.string.sources_unpinned
}
onActionDone.call(ReversibleAction(message, null))
}
}
fun respondSuggestionTip(isAccepted: Boolean) {
settings.isSuggestionsEnabled = isAccepted
settings.closeTip(TIP_SUGGESTIONS)

View File

@@ -82,6 +82,13 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
}
}
suspend fun clear() {
val cache = lruCache.get()
runInterruptible(Dispatchers.IO) {
cache.clearCache()
}
}
private suspend fun getAvailableSize(): Long = runCatchingCancellable {
val statFs = StatFs(cacheDir.get().absolutePath)
statFs.availableBytes

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.list.domain.ListExtraProvider
@@ -39,6 +40,7 @@ class LocalListViewModel @Inject constructor(
exploreRepository: ExploreRepository,
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
private val localStorageManager: LocalStorageManager,
sourcesRepository: MangaSourcesRepository,
) : RemoteListViewModel(
savedStateHandle,
mangaRepositoryFactory,
@@ -47,6 +49,7 @@ class LocalListViewModel @Inject constructor(
listExtraProvider,
downloadScheduler,
exploreRepository,
sourcesRepository,
), SharedPreferences.OnSharedPreferenceChangeListener {
val onMangaRemoved = MutableEventFlow<Unit>()

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.domain
import android.graphics.BitmapFactory
import android.net.Uri
import android.util.Size
import androidx.core.net.toFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import okhttp3.OkHttpClient
@@ -14,6 +15,8 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.local.data.isFileUri
import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
@@ -61,19 +64,28 @@ class DetectReaderModeUseCase @Inject constructor(
val page = requireNotNull(pages.getOrNull(pageIndex)) { "No pages" }
val url = repository.getPageUrl(page)
val uri = Uri.parse(url)
val size = if (uri.scheme == "cbz") {
runInterruptible(Dispatchers.IO) {
val size = when {
uri.isZipUri() -> runInterruptible(Dispatchers.IO) {
val zip = ZipFile(uri.schemeSpecificPart)
val entry = zip.getEntry(uri.fragment)
zip.getInputStream(entry).use {
getBitmapSize(it)
}
}
} else {
val request = PageLoader.createPageRequest(page, url)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use {
runInterruptible(Dispatchers.IO) {
getBitmapSize(it.body?.byteStream())
uri.isFileUri() -> runInterruptible(Dispatchers.IO) {
uri.toFile().inputStream().use {
getBitmapSize(it)
}
}
else -> {
val request = PageLoader.createPageRequest(url, page.source)
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use {
runInterruptible(Dispatchers.IO) {
getBitmapSize(it.body?.byteStream())
}
}
}
}

View File

@@ -0,0 +1,150 @@
package org.koitharu.kotatsu.reader.domain
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import androidx.annotation.ColorInt
import androidx.core.graphics.alpha
import androidx.core.graphics.blue
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder
import com.davemorrissey.labs.subscaleview.decoder.SkiaPooledImageRegionDecoder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.util.ext.use
import kotlin.math.abs
class EdgeDetector(private val context: Context) {
private val mutex = Mutex()
suspend fun getBounds(imageSource: ImageSource): Rect? = mutex.withLock {
withContext(Dispatchers.IO) {
val decoder = SkiaPooledImageRegionDecoder(Bitmap.Config.RGB_565)
try {
val size = runInterruptible {
decoder.init(context, imageSource)
}
val edges = coroutineScope {
listOf(
async { detectLeftRightEdge(decoder, size, isLeft = true) },
async { detectTopBottomEdge(decoder, size, isTop = true) },
async { detectLeftRightEdge(decoder, size, isLeft = false) },
async { detectTopBottomEdge(decoder, size, isTop = false) },
).awaitAll()
}
var hasEdges = false
for (edge in edges) {
if (edge > 0) {
hasEdges = true
} else if (edge < 0) {
return@withContext null
}
}
if (hasEdges) {
Rect(edges[0], edges[1], size.x - edges[2], size.y - edges[3])
} else {
null
}
} finally {
decoder.recycle()
}
}
}
private fun detectLeftRightEdge(decoder: ImageRegionDecoder, size: Point, isLeft: Boolean): Int {
var width = size.x
val rectCount = size.x / BLOCK_SIZE
val maxRect = rectCount / 3
for (i in 0 until rectCount) {
if (i > maxRect) {
return -1
}
var dd = BLOCK_SIZE
for (j in 0 until size.y / BLOCK_SIZE) {
val regionX = if (isLeft) i * BLOCK_SIZE else size.x - (i + 1) * BLOCK_SIZE
decoder.decodeRegion(region(regionX, j * BLOCK_SIZE), 1).use { bitmap ->
for (ii in 0 until minOf(BLOCK_SIZE, dd)) {
for (jj in 0 until BLOCK_SIZE) {
val bi = if (isLeft) ii else BLOCK_SIZE - ii - 1
if (bitmap[bi, jj].isNotWhite()) {
width = minOf(width, BLOCK_SIZE * i + ii)
dd--
break
}
}
}
}
if (dd == 0) {
break
}
}
if (dd < BLOCK_SIZE) {
break // We have already found vertical field or it is not exist
}
}
return width
}
private fun detectTopBottomEdge(decoder: ImageRegionDecoder, size: Point, isTop: Boolean): Int {
var height = size.y
val rectCount = size.y / BLOCK_SIZE
val maxRect = rectCount / 3
for (j in 0 until rectCount) {
if (j > maxRect) {
return -1
}
var dd = BLOCK_SIZE
for (i in 0 until size.x / BLOCK_SIZE) {
val regionY = if (isTop) j * BLOCK_SIZE else size.y - (j + 1) * BLOCK_SIZE
decoder.decodeRegion(region(i * BLOCK_SIZE, regionY), 1).use { bitmap ->
for (jj in 0 until minOf(BLOCK_SIZE, dd)) {
for (ii in 0 until BLOCK_SIZE) {
val bj = if (isTop) jj else BLOCK_SIZE - jj - 1
if (bitmap[ii, bj].isNotWhite()) {
height = minOf(height, BLOCK_SIZE * j + jj)
dd--
break
}
}
}
}
if (dd == 0) {
break
}
}
if (dd < BLOCK_SIZE) {
break // We have already found vertical field or it is not exist
}
}
return height
}
companion object {
private const val BLOCK_SIZE = 100
private const val COLOR_TOLERANCE = 16
fun isColorTheSame(@ColorInt a: Int, @ColorInt b: Int, tolerance: Int): Boolean {
return abs(a.red - b.red) <= tolerance &&
abs(a.green - b.green) <= tolerance &&
abs(a.blue - b.blue) <= tolerance &&
abs(a.alpha - b.alpha) <= tolerance
}
private fun Int.isNotWhite() = !isColorTheSame(this, Color.WHITE, COLOR_TOLERANCE)
private fun region(x: Int, y: Int) = Rect(x, y, x + BLOCK_SIZE, y + BLOCK_SIZE)
}
}

View File

@@ -2,12 +2,14 @@ package org.koitharu.kotatsu.reader.domain
import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.Rect
import android.net.Uri
import androidx.annotation.AnyThread
import androidx.collection.LongSparseArray
import androidx.collection.set
import androidx.core.net.toFile
import androidx.core.net.toUri
import com.davemorrissey.labs.subscaleview.ImageSource
import dagger.hilt.android.ActivityRetainedLifecycle
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ActivityRetainedScoped
@@ -35,6 +37,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
import org.koitharu.kotatsu.core.util.ext.compressToPNG
import org.koitharu.kotatsu.core.util.ext.ensureRamAtLeast
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
@@ -44,6 +47,7 @@ import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
import org.koitharu.kotatsu.core.util.ext.isTargetNotEmpty
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.ramAvailable
import org.koitharu.kotatsu.core.util.ext.use
import org.koitharu.kotatsu.core.util.ext.withProgress
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
import org.koitharu.kotatsu.local.data.PagesCache
@@ -51,6 +55,7 @@ import org.koitharu.kotatsu.local.data.isFileUri
import org.koitharu.kotatsu.local.data.isZipUri
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import java.util.LinkedList
import java.util.concurrent.atomic.AtomicInteger
@@ -83,6 +88,7 @@ class PageLoader @Inject constructor(
private val prefetchQueue = LinkedList<MangaPage>()
private val counter = AtomicInteger(0)
private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive
private val edgeDetector = EdgeDetector(context)
fun isPrefetchApplicable(): Boolean {
return repository is RemoteMangaRepository
@@ -142,22 +148,33 @@ class PageLoader @Inject constructor(
} else {
val file = uri.toFile()
context.ensureRamAtLeast(file.length() * 2)
val image = runInterruptible(Dispatchers.IO) {
runInterruptible(Dispatchers.IO) {
BitmapFactory.decodeFile(file.absolutePath)
}
try {
}.use { image ->
image.compressToPNG(file)
} finally {
image.recycle()
}
uri
}
}
suspend fun getTrimmedBounds(uri: Uri): Rect? = runCatchingCancellable {
edgeDetector.getBounds(ImageSource.Uri(uri))
}.onFailure { error ->
error.printStackTraceDebug()
}.getOrNull()
suspend fun getPageUrl(page: MangaPage): String {
return getRepository(page.source).getPageUrl(page)
}
suspend fun invalidate(clearCache: Boolean) {
tasks.clear()
loaderScope.cancelChildrenAndJoin()
if (clearCache) {
cache.clear()
}
}
private fun onIdle() = loaderScope.launch {
prefetchLock.withLock {
while (prefetchQueue.isNotEmpty()) {
@@ -213,7 +230,7 @@ class PageLoader @Inject constructor(
uri.isFileUri() -> uri
else -> {
val request = createPageRequest(page, pageUrl)
val request = createPageRequest(pageUrl, page.source)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
val body = checkNotNull(response.body) { "Null response body" }
body.withProgress(progress).use {
@@ -248,12 +265,12 @@ class PageLoader @Inject constructor(
private const val PREFETCH_LIMIT_DEFAULT = 6
private const val PREFETCH_MIN_RAM_MB = 80L
fun createPageRequest(page: MangaPage, pageUrl: String) = Request.Builder()
fun createPageRequest(pageUrl: String, mangaSource: MangaSource) = Request.Builder()
.url(pageUrl)
.get()
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
.cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)
.tag(MangaSource::class.java, page.source)
.tag(MangaSource::class.java, mangaSource)
.build()
}
}

View File

@@ -17,7 +17,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
@@ -33,6 +32,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.model.findChapter
import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.parser.MangaDataRepository
@@ -70,7 +70,9 @@ private const val BOUNDS_PAGE_OFFSET = 2
private const val PREFETCH_LIMIT = 10
@HiltViewModel
class ReaderViewModel @Inject constructor(
class ReaderViewModel
@Inject
constructor(
savedStateHandle: SavedStateHandle,
private val dataRepository: MangaDataRepository,
private val historyRepository: HistoryRepository,
@@ -85,7 +87,6 @@ class ReaderViewModel @Inject constructor(
private val detectReaderModeUseCase: DetectReaderModeUseCase,
private val statsCollector: StatsCollector,
) : BaseViewModel() {
private val intent = MangaIntent(savedStateHandle)
private val preselectedBranch = savedStateHandle.get<String>(ReaderActivity.EXTRA_BRANCH)
@@ -105,9 +106,11 @@ class ReaderViewModel @Inject constructor(
val incognitoMode = if (savedStateHandle.get<Boolean>(ReaderActivity.EXTRA_INCOGNITO) == true) {
MutableStateFlow(true)
} else mangaFlow.map {
it != null && historyRepository.shouldSkip(it)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
} else {
mangaFlow.map {
it != null && historyRepository.shouldSkip(it)
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
}
val isPagesSheetEnabled = observeIsPagesSheetEnabled()
@@ -166,9 +169,7 @@ class ReaderViewModel @Inject constructor(
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null),
)
val isMangaNsfw = mangaFlow.map {
it?.isNsfw == true
}
val isMangaNsfw = mangaFlow.map { it?.isNsfw == true }
val isBookmarkAdded = currentState.flatMapLatest { state ->
val manga = mangaData.value?.toManga()
@@ -282,7 +283,9 @@ class ReaderViewModel @Inject constructor(
prevJob?.cancelAndJoin()
content.value = ReaderContent(emptyList(), null)
chaptersLoader.loadSingleChapter(id)
content.value = ReaderContent(chaptersLoader.snapshot(), ReaderState(id, page, 0))
val newState = ReaderState(id, page, 0)
content.value = ReaderContent(chaptersLoader.snapshot(), newState)
saveCurrentState(newState)
}
}
@@ -290,17 +293,27 @@ class ReaderViewModel @Inject constructor(
val prevJob = loadingJob
loadingJob = launchLoadingJob(Dispatchers.Default) {
prevJob?.cancelAndJoin()
val currentChapterId = currentState.requireValue().chapterId
val allChapters = checkNotNull(manga).allChapters
var index = allChapters.indexOfFirst { x -> x.id == currentChapterId }
if (index < 0) {
return@launchLoadingJob
val prevState = currentState.requireValue()
val newChapterId = if (delta != 0) {
val allChapters = checkNotNull(manga).allChapters
var index = allChapters.indexOfFirst { x -> x.id == prevState.chapterId }
if (index < 0) {
return@launchLoadingJob
}
index += delta
(allChapters.getOrNull(index) ?: return@launchLoadingJob).id
} else {
prevState.chapterId
}
index += delta
val newChapterId = (allChapters.getOrNull(index) ?: return@launchLoadingJob).id
content.value = ReaderContent(emptyList(), null)
chaptersLoader.loadSingleChapter(newChapterId)
content.value = ReaderContent(chaptersLoader.snapshot(), ReaderState(newChapterId, 0, 0))
val newState = ReaderState(
chapterId = newChapterId,
page = if (delta == 0) prevState.page else 0,
scroll = if (delta == 0) prevState.scroll else 0,
)
content.value = ReaderContent(chaptersLoader.snapshot(), newState)
saveCurrentState(newState)
}
}
@@ -381,9 +394,7 @@ class ReaderViewModel @Inject constructor(
val manga = details.toManga()
// obtain state
if (currentState.value == null) {
currentState.value = historyRepository.getOne(manga)?.let {
ReaderState(it)
} ?: ReaderState(manga, preselectedBranch ?: manga.getPreferredBranch(null))
currentState.value = getStateFromIntent(manga)
}
val mode = detectReaderModeUseCase.invoke(manga, currentState.value)
val branch = chaptersLoader.peekChapter(currentState.value?.chapterId ?: 0L)?.branch
@@ -480,4 +491,18 @@ class ReaderViewModel @Inject constructor(
.filter { it == AppSettings.KEY_PAGES_TAB || it == AppSettings.KEY_DETAILS_TAB || it == AppSettings.KEY_DETAILS_LAST_TAB }
.map { settings.defaultDetailsTab == TAB_PAGES }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.defaultDetailsTab == TAB_PAGES)
private suspend fun getStateFromIntent(manga: Manga): ReaderState {
val history = historyRepository.getOne(manga)
val result = if (history != null) {
if (preselectedBranch != null && preselectedBranch != manga.findChapter(history.chapterId)?.branch) {
null
} else {
ReaderState(history)
}
} else {
null
}
return result ?: ReaderState(manga, preselectedBranch ?: manga.getPreferredBranch(null))
}
}

View File

@@ -0,0 +1,85 @@
package org.koitharu.kotatsu.reader.ui.config
import android.content.Context
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.SuspendLazy
import kotlin.coroutines.resume
class ImageServerDelegate(
private val mangaRepositoryFactory: MangaRepository.Factory,
private val mangaSource: MangaSource?,
) {
private val repositoryLazy = SuspendLazy {
mangaRepositoryFactory.create(checkNotNull(mangaSource)) as RemoteMangaRepository
}
suspend fun isAvailable() = withContext(Dispatchers.Default) {
repositoryLazy.tryGet().map { repository ->
repository.getConfigKeys().any { it is ConfigKey.PreferredImageServer }
}.getOrDefault(false)
}
suspend fun getValue(): String? = withContext(Dispatchers.Default) {
repositoryLazy.tryGet().map { repository ->
val key = repository.getConfigKeys().firstNotNullOfOrNull { it as? ConfigKey.PreferredImageServer }
if (key != null) {
key.presetValues[repository.getConfig()[key]]
} else {
null
}
}.getOrNull()
}
suspend fun showDialog(context: Context): Boolean {
val repository = withContext(Dispatchers.Default) {
repositoryLazy.tryGet().getOrNull()
} ?: return false
val key = repository.getConfigKeys().firstNotNullOfOrNull {
it as? ConfigKey.PreferredImageServer
} ?: return false
val entries = key.presetValues.values.mapToArray {
it ?: context.getString(R.string.automatic)
}
val entryValues = key.presetValues.keys.toTypedArray()
val config = repository.getConfig()
val initialValue = config[key]
var currentValue = initialValue
val changed = suspendCancellableCoroutine { cont ->
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.image_server)
.setCancelable(true)
.setSingleChoiceItems(entries, entryValues.indexOf(initialValue)) { _, i ->
currentValue = entryValues[i]
}.setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.cancel()
}.setPositiveButton(android.R.string.ok) { _, _ ->
if (currentValue != initialValue) {
config[key] = currentValue
cont.resume(true)
} else {
cont.resume(false)
}
}.setOnCancelListener {
cont.resume(false)
}.create()
dialog.show()
cont.invokeOnCancellation {
dialog.cancel()
}
}
if (changed) {
repository.invalidateCache()
}
return changed
}
}

View File

@@ -16,8 +16,10 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
@@ -29,6 +31,7 @@ import org.koitharu.kotatsu.core.util.ext.showDistinct
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.core.util.ext.withArgs
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderViewModel
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
import org.koitharu.kotatsu.settings.SettingsActivity
@@ -47,7 +50,14 @@ class ReaderConfigSheet :
@Inject
lateinit var orientationHelper: ScreenOrientationHelper
@Inject
lateinit var mangaRepositoryFactory: MangaRepository.Factory
@Inject
lateinit var pageLoader: PageLoader
private lateinit var mode: ReaderMode
private lateinit var imageServerDelegate: ImageServerDelegate
@Inject
lateinit var settings: AppSettings
@@ -57,6 +67,10 @@ class ReaderConfigSheet :
mode = arguments?.getInt(ARG_MODE)
?.let { ReaderMode.valueOf(it) }
?: ReaderMode.STANDARD
imageServerDelegate = ImageServerDelegate(
mangaRepositoryFactory = mangaRepositoryFactory,
mangaSource = viewModel.manga?.toManga()?.source,
)
}
override fun onCreateViewBinding(
@@ -83,11 +97,20 @@ class ReaderConfigSheet :
binding.buttonSavePage.setOnClickListener(this)
binding.buttonScreenRotate.setOnClickListener(this)
binding.buttonSettings.setOnClickListener(this)
binding.buttonImageServer.setOnClickListener(this)
binding.buttonColorFilter.setOnClickListener(this)
binding.sliderTimer.addOnChangeListener(this)
binding.switchScrollTimer.setOnCheckedChangeListener(this)
binding.switchDoubleReader.setOnCheckedChangeListener(this)
viewLifecycleScope.launch {
val isAvailable = imageServerDelegate.isAvailable()
if (isAvailable) {
bindImageServerTitle()
}
binding.buttonImageServer.isVisible = isAvailable
}
settings.observeAsStateFlow(
scope = lifecycleScope + Dispatchers.Default,
key = AppSettings.KEY_READER_AUTOSCROLL_SPEED,
@@ -124,6 +147,14 @@ class ReaderConfigSheet :
val manga = viewModel.manga?.toManga() ?: return
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
}
R.id.button_image_server -> viewLifecycleScope.launch {
if (imageServerDelegate.showDialog(v.context)) {
bindImageServerTitle()
pageLoader.invalidate(clearCache = true)
viewModel.switchChapterBy(0)
}
}
}
}
@@ -194,6 +225,14 @@ class ReaderConfigSheet :
switch.setOnCheckedChangeListener(this)
}
private suspend fun bindImageServerTitle() {
viewBinding?.buttonImageServer?.text = getString(
R.string.inline_preference_pattern,
getString(R.string.image_server),
imageServerDelegate.getValue() ?: getString(R.string.automatic),
)
}
interface Callback {
var isAutoScrollEnabled: Boolean

View File

@@ -18,6 +18,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
@@ -54,6 +55,10 @@ class ReaderSettings(
view.background = bg.resolve(view.context)
}
fun isPagesCropEnabled(isWebtoon: Boolean) = settings.isPagesCropEnabled(
if (isWebtoon) ReaderMode.WEBTOON else ReaderMode.STANDARD,
)
@CheckResult
fun applyBitmapConfig(ssiv: SubsamplingScaleImageView): Boolean {
val config = bitmapConfig

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
import org.koitharu.kotatsu.reader.ui.pager.PageHolderDelegate.State
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonHolder
abstract class BasePageHolder<B : ViewBinding>(
protected val binding: B,
@@ -24,7 +25,14 @@ abstract class BasePageHolder<B : ViewBinding>(
) : LifecycleAwareViewHolder(binding.root, lifecycleOwner), PageHolderDelegate.Callback {
@Suppress("LeakingThis")
protected val delegate = PageHolderDelegate(loader, settings, this, networkState, exceptionResolver)
protected val delegate = PageHolderDelegate(
loader = loader,
readerSettings = settings,
callback = this,
networkState = networkState,
exceptionResolver = exceptionResolver,
isWebtoon = this is WebtoonHolder,
)
protected val bindingInfo = LayoutPageInfoBinding.bind(binding.root)
val context: Context
@@ -70,8 +78,8 @@ abstract class BasePageHolder<B : ViewBinding>(
delegate.onRecycle()
}
protected fun SubsamplingScaleImageView.applyDownsampling(isForeground: Boolean) {
downsampling = when {
protected fun SubsamplingScaleImageView.applyDownSampling(isForeground: Boolean) {
downSampling = when {
isForeground || !settings.isReaderOptimizationEnabled -> 1
context.isLowRamDevice() -> 8
else -> 4

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.reader.ui.pager
import android.graphics.Rect
import android.net.Uri
import androidx.lifecycle.Observer
import com.davemorrissey.labs.subscaleview.DefaultOnImageEventListener
@@ -32,6 +33,7 @@ class PageHolderDelegate(
private val callback: Callback,
private val networkState: NetworkState,
private val exceptionResolver: ExceptionResolver,
private val isWebtoon: Boolean,
) : DefaultOnImageEventListener, Observer<ReaderSettings> {
private val scope = loader.loaderScope + Dispatchers.Main.immediate
@@ -39,6 +41,7 @@ class PageHolderDelegate(
private set
private var job: Job? = null
private var uri: Uri? = null
private var cachedBounds: Rect? = null
private var error: Throwable? = null
init {
@@ -88,6 +91,7 @@ class PageHolderDelegate(
fun onRecycle() {
state = State.EMPTY
uri = null
cachedBounds = null
error = null
job?.cancel()
}
@@ -95,7 +99,7 @@ class PageHolderDelegate(
fun reload() {
if (state == State.SHOWN) {
uri?.let {
callback.onImageReady(it)
callback.onImageReady(it, cachedBounds)
}
}
}
@@ -138,8 +142,13 @@ class PageHolderDelegate(
state = State.CONVERTING
try {
val newUri = loader.convertBimap(uri)
cachedBounds = if (readerSettings.isPagesCropEnabled(isWebtoon)) {
loader.getTrimmedBounds(newUri)
} else {
null
}
state = State.CONVERTED
callback.onImageReady(newUri)
callback.onImageReady(newUri, cachedBounds)
} catch (ce: CancellationException) {
throw ce
} catch (e2: Throwable) {
@@ -166,7 +175,12 @@ class PageHolderDelegate(
file
}
state = State.LOADED
callback.onImageReady(checkNotNull(uri))
cachedBounds = if (readerSettings.isPagesCropEnabled(isWebtoon)) {
loader.getTrimmedBounds(checkNotNull(uri))
} else {
null
}
callback.onImageReady(checkNotNull(uri), cachedBounds)
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
@@ -196,7 +210,7 @@ class PageHolderDelegate(
fun onError(e: Throwable)
fun onImageReady(uri: Uri)
fun onImageReady(uri: Uri, bounds: Rect?)
fun onImageShowing(settings: ReaderSettings)

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui.pager.standard
import android.annotation.SuppressLint
import android.graphics.PointF
import android.graphics.Rect
import android.net.Uri
import android.view.View
import android.view.animation.DecelerateInterpolator
@@ -46,12 +47,12 @@ open class PageHolder(
override fun onResume() {
super.onResume()
binding.ssiv.applyDownsampling(isForeground = true)
binding.ssiv.applyDownSampling(isForeground = true)
}
override fun onPause() {
super.onPause()
binding.ssiv.applyDownsampling(isForeground = false)
binding.ssiv.applyDownSampling(isForeground = false)
}
override fun onConfigChanged() {
@@ -59,7 +60,7 @@ open class PageHolder(
if (settings.applyBitmapConfig(binding.ssiv)) {
delegate.reload()
}
binding.ssiv.applyDownsampling(isResumed())
binding.ssiv.applyDownSampling(isResumed())
binding.textViewNumber.isVisible = settings.isPagesNumbersEnabled
}
@@ -89,8 +90,12 @@ open class PageHolder(
}
}
override fun onImageReady(uri: Uri) {
binding.ssiv.setImage(ImageSource.Uri(uri))
override fun onImageReady(uri: Uri, bounds: Rect?) {
val source = ImageSource.Uri(uri)
if (bounds != null) {
source.region(bounds)
}
binding.ssiv.setImage(source)
}
override fun onImageShowing(settings: ReaderSettings) {

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.graphics.Rect
import android.net.Uri
import android.view.View
import androidx.core.view.isVisible
@@ -39,12 +40,12 @@ class WebtoonHolder(
override fun onResume() {
super.onResume()
binding.ssiv.applyDownsampling(isForeground = true)
binding.ssiv.applyDownSampling(isForeground = true)
}
override fun onPause() {
super.onPause()
binding.ssiv.applyDownsampling(isForeground = false)
binding.ssiv.applyDownSampling(isForeground = false)
}
override fun onConfigChanged() {
@@ -52,7 +53,7 @@ class WebtoonHolder(
if (settings.applyBitmapConfig(binding.ssiv)) {
delegate.reload()
}
binding.ssiv.applyDownsampling(isResumed())
binding.ssiv.applyDownSampling(isResumed())
}
override fun onBind(data: ReaderPage) {
@@ -89,8 +90,12 @@ class WebtoonHolder(
}
}
override fun onImageReady(uri: Uri) {
binding.ssiv.setImage(ImageSource.Uri(uri))
override fun onImageReady(uri: Uri, bounds: Rect?) {
val source = ImageSource.Uri(uri)
if (bounds != null) {
source.region(bounds)
}
binding.ssiv.setImage(source)
}
override fun onImageShowing(settings: ReaderSettings) {

View File

@@ -97,8 +97,8 @@ class WebtoonImageView @JvmOverloads constructor(
setMeasuredDimension(desiredWidth, desiredHeight)
}
override fun onDownsamplingChanged() {
super.onDownsamplingChanged()
override fun onDownSamplingChanged() {
super.onDownSamplingChanged()
post {
adjustScale()
}

View File

@@ -221,7 +221,14 @@ class WebtoonScalingFrame @JvmOverloads constructor(
syncMatrixValues()
}
private fun scaleChild(newScale: Float, focusX: Float, focusY: Float) {
private fun scaleChild(
newScale: Float,
focusX: Float,
focusY: Float,
): Boolean {
if (scale.isNaN() || scale == 0f) {
return false
}
val factor = newScale / scale
if (newScale > 1) {
translateBounds.set(
@@ -240,13 +247,12 @@ class WebtoonScalingFrame @JvmOverloads constructor(
}
transformMatrix.postScale(factor, factor, focusX, focusY)
invalidateTarget()
return true
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
val newScale = (scale * detector.scaleFactor).coerceIn(MIN_SCALE, MAX_SCALE)
scaleChild(newScale, detector.focusX, detector.focusY)
return true
return scaleChild(newScale, detector.focusX, detector.focusY)
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {

View File

@@ -28,6 +28,7 @@ import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require
import org.koitharu.kotatsu.core.util.ext.sizeOrZero
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.explore.domain.ExploreRepository
import org.koitharu.kotatsu.filter.ui.FilterCoordinator
import org.koitharu.kotatsu.filter.ui.MangaFilter
@@ -45,7 +46,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.util.concatUrl
import javax.inject.Inject
private const val FILTER_MIN_INTERVAL = 250L
@@ -59,6 +59,7 @@ open class RemoteListViewModel @Inject constructor(
listExtraProvider: ListExtraProvider,
downloadScheduler: DownloadWorker.Scheduler,
private val exploreRepository: ExploreRepository,
sourcesRepository: MangaSourcesRepository,
) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter {
val source = savedStateHandle.require<MangaSource>(RemoteListFragment.ARG_SOURCE)
@@ -117,6 +118,10 @@ open class RemoteListViewModel @Inject constructor(
}.catch { error ->
listError.value = error
}.launchIn(viewModelScope)
launchJob(Dispatchers.Default) {
sourcesRepository.trackUsage(source)
}
}
override fun onRefresh() {

View File

@@ -51,7 +51,7 @@ abstract class Scrobbler(
}
}
val isAvailable: Boolean
val isEnabled: Boolean
get() = repository.isAuthorized
suspend fun authorize(authCode: String): ScrobblerUser {

View File

@@ -42,7 +42,7 @@ class ScrobblingSelectorViewModel @Inject constructor(
val manga = savedStateHandle.require<ParcelableManga>(MangaIntent.KEY_MANGA).manga
val availableScrobblers = scrobblers.filter { it.isAvailable }
val availableScrobblers = scrobblers.filter { it.isEnabled }
val selectedScrobblerIndex = MutableStateFlow(0)

View File

@@ -125,6 +125,8 @@ class MangaSearchRepository @Inject constructor(
return db.getTagsDao().findRareTags(source.name, limit).toMangaTagsList()
}
suspend fun getSourcesSuggestion(limit: Int): List<MangaSource> = sourcesRepository.getTopSources(limit)
fun getSourcesSuggestion(query: String, limit: Int): List<MangaSource> {
if (query.length < 3) {
return emptyList()

View File

@@ -37,6 +37,7 @@ private const val MAX_HINTS_ITEMS = 3
private const val MAX_AUTHORS_ITEMS = 2
private const val MAX_TAGS_ITEMS = 8
private const val MAX_SOURCES_ITEMS = 6
private const val MAX_SOURCES_TIPS_ITEMS = 2
@HiltViewModel
class SearchSuggestionViewModel @Inject constructor(
@@ -149,12 +150,18 @@ class SearchSuggestionViewModel @Inject constructor(
} else {
null
}
val sourcesTipsDeferred = if (searchQuery.isEmpty() && SearchSuggestionType.RECENT_SOURCES in types) {
async { repository.getSourcesSuggestion(MAX_SOURCES_TIPS_ITEMS) }
} else {
null
}
val tags = tagsDeferred?.await()
val mangaList = mangaDeferred?.await()
val queries = queriesDeferred?.await()
val hints = hintsDeferred?.await()
val authors = authorsDeferred?.await()
val sourcesTips = sourcesTipsDeferred?.await()
buildList(queries.sizeOrZero() + sources.sizeOrZero() + authors.sizeOrZero() + hints.sizeOrZero() + 2) {
if (!tags.isNullOrEmpty()) {
@@ -167,6 +174,7 @@ class SearchSuggestionViewModel @Inject constructor(
queries?.mapTo(this) { SearchSuggestionItem.RecentQuery(it) }
authors?.mapTo(this) { SearchSuggestionItem.Author(it) }
hints?.mapTo(this) { SearchSuggestionItem.Hint(it) }
sourcesTips?.mapTo(this) { SearchSuggestionItem.SourceTip(it) }
}
}

View File

@@ -18,6 +18,7 @@ class SearchSuggestionAdapter(
delegatesManager
.addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener))
.addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionSourceTipAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionTagsAD(listener))
.addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener))
.addDelegate(searchSuggestionQueryHintAD(listener))

View File

@@ -0,0 +1,43 @@
package org.koitharu.kotatsu.search.ui.suggestion.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.getSummary
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceTipBinding
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
fun searchSuggestionSourceTipAD(
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
listener: SearchSuggestionListener,
) =
adapterDelegateViewBinding<SearchSuggestionItem.SourceTip, SearchSuggestionItem, ItemSearchSuggestionSourceTipBinding>(
{ inflater, parent -> ItemSearchSuggestionSourceTipBinding.inflate(inflater, parent, false) },
) {
binding.root.setOnClickListener {
listener.onSourceClick(item.source)
}
bind {
binding.textViewTitle.text = item.source.getTitle(context)
binding.textViewSubtitle.text = item.source.getSummary(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
fallback(fallbackIcon)
placeholder(fallbackIcon)
error(fallbackIcon)
source(item.source)
enqueueWith(coil)
}
}
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.search.ui.suggestion.model
import org.koitharu.kotatsu.core.model.isNsfw
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
import org.koitharu.kotatsu.list.ui.model.ListModel
@@ -69,6 +70,18 @@ sealed interface SearchSuggestionItem : ListModel {
}
}
data class SourceTip(
val source: MangaSource,
) : SearchSuggestionItem {
val isNsfw: Boolean
get() = source.isNsfw()
override fun areItemsTheSame(other: ListModel): Boolean {
return other is Source && other.source == source
}
}
data class Tags(
val tags: List<ChipsView.ChipModel>,
) : SearchSuggestionItem {

View File

@@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
@@ -17,6 +18,7 @@ import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.settings.utils.PercentSummaryProvider
import org.koitharu.kotatsu.settings.utils.SliderPreference
@@ -48,6 +50,9 @@ class ReaderSettingsFragment :
entryValues = ZoomMode.entries.names()
setDefaultValueCompat(ZoomMode.FIT_CENTER.name)
}
findPreference<MultiSelectListPreference>(AppSettings.KEY_READER_CROP)?.run {
summaryProvider = MultiSummaryProvider(R.string.disabled)
}
findPreference<SliderPreference>(AppSettings.KEY_WEBTOON_ZOOM_OUT)?.summaryProvider = PercentSummaryProvider()
updateReaderModeDependency()
}

View File

@@ -2,11 +2,13 @@ package org.koitharu.kotatsu.settings.sources
import android.view.inputmethod.EditorInfo
import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.settings.utils.AutoCompleteTextViewPreference
@@ -23,9 +25,9 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
is ConfigKey.Domain -> {
val presetValues = key.presetValues
if (presetValues.size <= 1) {
EditTextPreference(requireContext())
EditTextPreference(screen.context)
} else {
AutoCompleteTextViewPreference(requireContext()).apply {
AutoCompleteTextViewPreference(screen.context).apply {
entries = presetValues.toStringArray()
}
}.apply {
@@ -43,7 +45,7 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
}
is ConfigKey.UserAgent -> {
AutoCompleteTextViewPreference(requireContext()).apply {
AutoCompleteTextViewPreference(screen.context).apply {
entries = arrayOf(
UserAgents.FIREFOX_MOBILE,
UserAgents.CHROME_MOBILE,
@@ -64,19 +66,32 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
}
is ConfigKey.ShowSuspiciousContent -> {
SwitchPreferenceCompat(requireContext()).apply {
SwitchPreferenceCompat(screen.context).apply {
setDefaultValue(key.defaultValue)
setTitle(R.string.show_suspicious_content)
}
}
is ConfigKey.SplitByTranslations -> {
SwitchPreferenceCompat(requireContext()).apply {
SwitchPreferenceCompat(screen.context).apply {
setDefaultValue(key.defaultValue)
setTitle(R.string.split_by_translations)
setSummary(R.string.split_by_translations_summary)
}
}
is ConfigKey.PreferredImageServer -> {
ListPreference(screen.context).apply {
entries = key.presetValues.values.mapToArray {
it ?: context.getString(R.string.automatic)
}
entryValues = key.presetValues.keys.mapToArray { it.orEmpty() }
setDefaultValue(key.defaultValue.orEmpty())
setTitle(R.string.image_server)
setDialogTitle(R.string.image_server)
summaryProvider = ListPreference.SimpleSummaryProvider.getInstance()
}
}
}
preference.isIconSpaceReserved = false
preference.key = key.key

View File

@@ -36,7 +36,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0), Preference.OnPreferenc
addPreferencesFromRepository(viewModel.repository)
findPreference<SwitchPreferenceCompat>(KEY_ENABLE)?.run {
setOnPreferenceChangeListener(this@SourceSettingsFragment)
onPreferenceChangeListener = this@SourceSettingsFragment
}
findPreference<Preference>(KEY_AUTH)?.run {
val authProvider = viewModel.repository.getAuthProvider()

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.settings.sources.adapter
import android.view.View
import androidx.appcompat.widget.PopupMenu
import androidx.core.content.ContextCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
@@ -16,49 +17,14 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
import org.koitharu.kotatsu.core.util.ext.crossfade
import org.koitharu.kotatsu.core.util.ext.drawableStart
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding
import org.koitharu.kotatsu.databinding.ItemSourceConfigCheckableBinding
import org.koitharu.kotatsu.databinding.ItemTipBinding
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
fun sourceConfigItemCheckableDelegate(
listener: SourceConfigListener,
coil: ImageLoader,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigCheckableBinding>(
{ layoutInflater, parent ->
ItemSourceConfigCheckableBinding.inflate(
layoutInflater,
parent,
false,
)
},
) {
binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
listener.onItemEnabledChanged(item, isChecked)
}
bind {
binding.textViewTitle.text = item.source.getTitle(context)
binding.switchToggle.isChecked = item.isEnabled
binding.switchToggle.isEnabled = item.isAvailable
binding.textViewDescription.text = item.source.getSummary(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
crossfade(context)
error(fallbackIcon)
placeholder(fallbackIcon)
fallback(fallbackIcon)
source(item.source)
enqueueWith(coil)
}
}
}
fun sourceConfigItemDelegate2(
listener: SourceConfigListener,
coil: ImageLoader,
@@ -73,6 +39,7 @@ fun sourceConfigItemDelegate2(
},
) {
val iconPinned = ContextCompat.getDrawable(context, R.drawable.ic_pin_small)
val eventListener = View.OnClickListener { v ->
when (v.id) {
R.id.imageView_add -> listener.onItemEnabledChanged(item, true)
@@ -89,6 +56,7 @@ fun sourceConfigItemDelegate2(
binding.imageViewAdd.isGone = item.isEnabled || !item.isAvailable
binding.imageViewRemove.isVisible = item.isEnabled
binding.imageViewMenu.isVisible = item.isEnabled
binding.textViewTitle.drawableStart = if (item.isPinned) iconPinned else null
binding.textViewDescription.text = item.source.getSummary(context)
val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)
binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run {
@@ -132,12 +100,15 @@ private fun showSourceMenu(
menu.inflate(R.menu.popup_source_config)
menu.menu.findItem(R.id.action_shortcut)
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(anchor.context)
menu.menu.findItem(R.id.action_pin)?.isVisible = item.isEnabled
menu.menu.findItem(R.id.action_pin)?.isChecked = item.isPinned
menu.menu.findItem(R.id.action_lift)?.isVisible = item.isDraggable
menu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.action_settings -> listener.onItemSettingsClick(item)
R.id.action_lift -> listener.onItemLiftClick(item)
R.id.action_shortcut -> listener.onItemShortcutClick(item)
R.id.action_pin -> listener.onItemPinClick(item)
}
true
}

View File

@@ -11,5 +11,7 @@ interface SourceConfigListener : OnTipCloseListener<SourceConfigItem.Tip> {
fun onItemShortcutClick(item: SourceConfigItem.SourceItem)
fun onItemPinClick(item: SourceConfigItem.SourceItem)
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean)
}

View File

@@ -61,6 +61,7 @@ class SourcesListProducer @Inject constructor(
private suspend fun buildList(): List<SourceConfigItem> {
val enabledSources = repository.getEnabledSources()
val pinned = repository.getPinnedSources()
val isNsfwDisabled = settings.isNsfwContentDisabled
val isReorderAvailable = settings.sourcesSortOrder == SourcesSortOrder.MANUAL
val withTip = isReorderAvailable && settings.isTipEnabled(TIP_REORDER)
@@ -75,6 +76,7 @@ class SourcesListProducer @Inject constructor(
isEnabled = it in enabledSet,
isDraggable = false,
isAvailable = !isNsfwDisabled || !it.isNsfw(),
isPinned = it in pinned,
)
}.ifEmpty {
listOf(SourceConfigItem.EmptySearchResult)
@@ -95,6 +97,7 @@ class SourcesListProducer @Inject constructor(
isEnabled = true,
isDraggable = isReorderAvailable,
isAvailable = false,
isPinned = it in pinned,
)
}
}

View File

@@ -120,6 +120,10 @@ class SourcesManageFragment :
}
}
override fun onItemPinClick(item: SourceConfigItem.SourceItem) {
viewModel.setPinned(item.source, !item.isPinned)
}
override fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
viewModel.setEnabled(item.source, isEnabled)
}

View File

@@ -58,8 +58,9 @@ class SourcesManageViewModel @Inject constructor(
fun canReorder(oldPos: Int, newPos: Int): Boolean {
val snapshot = content.value
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
return (snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled == true
val oldPosItem = snapshot.getOrNull(oldPos) as? SourceConfigItem.SourceItem ?: return false
val newPosItem = snapshot.getOrNull(newPos) as? SourceConfigItem.SourceItem ?: return false
return oldPosItem.isEnabled && newPosItem.isEnabled && oldPosItem.isPinned == newPosItem.isPinned
}
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
@@ -71,6 +72,14 @@ class SourcesManageViewModel @Inject constructor(
}
}
fun setPinned(source: MangaSource, isPinned: Boolean) {
launchJob(Dispatchers.Default) {
val rollback = repository.setIsPinned(setOf(source), isPinned)
val message = if (isPinned) R.string.source_pinned else R.string.source_unpinned
onActionDone.call(ReversibleAction(message, rollback))
}
}
fun bringToTop(source: MangaSource) {
val snapshot = content.value
launchJob(Dispatchers.Default) {

View File

@@ -13,6 +13,7 @@ sealed interface SourceConfigItem : ListModel {
val isEnabled: Boolean,
val isDraggable: Boolean,
val isAvailable: Boolean,
val isPinned: Boolean,
) : SourceConfigItem {
val isNsfw: Boolean

View File

@@ -9,6 +9,8 @@ import kotlinx.coroutines.launch
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.stats.data.StatsEntity
import javax.inject.Inject
@@ -62,7 +64,11 @@ class StatsCollector @Inject constructor(
private fun commit(entity: StatsEntity) {
viewModelScope.launch(Dispatchers.Default) {
db.getStatsDao().upsert(entity)
runCatchingCancellable {
db.getStatsDao().upsert(entity)
}.onFailure { e ->
e.printStackTraceDebug()
}
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M21,17H7V3H21M21,1H7A2,2 0 0,0 5,3V17A2,2 0 0,0 7,19H21A2,2 0 0,0 23,17V3A2,2 0 0,0 21,1M3,5H1V21A2,2 0 0,0 3,23H19V21H3M15.96,10.29L13.21,13.83L11.25,11.47L8.5,15H19.5L15.96,10.29Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
</vector>

View File

@@ -6,6 +6,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:fillColor="#000000"
android:pathData="M19.43 12.98c0.04-0.32 0.07-0.64 0.07-0.98 0-0.34-0.03-0.66-0.07-0.98l2.11-1.65c0.19-0.15 0.24-0.42 0.12-0.64l-2-3.46c-0.09-0.16-0.26-0.25-0.44-0.25-0.06 0-0.12 0.01-0.17 0.03l-2.49 1c-0.52-0.4-1.08-0.73-1.69-0.98l-0.38-2.65C14.46 2.18 14.25 2 14 2h-4C9.75 2 9.54 2.18 9.51 2.42L9.13 5.07C8.52 5.32 7.96 5.66 7.44 6.05l-2.49-1C4.89 5.03 4.83 5.02 4.77 5.02c-0.17 0-0.34 0.09-0.43 0.25l-2 3.46C2.21 8.95 2.27 9.22 2.46 9.37l2.11 1.65C4.53 11.34 4.5 11.67 4.5 12c0 0.33 0.03 0.66 0.07 0.98l-2.11 1.65c-0.19 0.15-0.24 0.42-0.12 0.64l2 3.46c0.09 0.16 0.26 0.25 0.44 0.25 0.06 0 0.12-0.01 0.17-0.03l2.49-1c0.52 0.4 1.08 0.73 1.69 0.98l0.38 2.65C9.54 21.82 9.75 22 10 22h4c0.25 0 0.46-0.18 0.49-0.42l0.38-2.65c0.61-0.25 1.17-0.59 1.69-0.98l2.49 1c0.06 0.02 0.12 0.03 0.18 0.03 0.17 0 0.34-0.09 0.43-0.25l2-3.46c0.12-0.22 0.07-0.49-0.12-0.64l-2.11-1.65zm-1.98-1.71c0.04 0.31 0.05 0.52 0.05 0.73 0 0.21-0.02 0.43-0.05 0.73l-0.14 1.13 0.89 0.7 1.08 0.84-0.7 1.21-1.27-0.51-1.04-0.42-0.9 0.68c-0.43 0.32-0.84 0.56-1.25 0.73l-1.06 0.43-0.16 1.13L12.7 20h-1.4l-0.19-1.35-0.16-1.13-1.06-0.43c-0.43-0.18-0.83-0.41-1.23-0.71l-0.91-0.7-1.06 0.43-1.27 0.51-0.7-1.21 1.08-0.84 0.89-0.7-0.14-1.13C6.52 12.43 6.5 12.2 6.5 12s0.02-0.43 0.05-0.73l0.14-1.13-0.89-0.7L4.72 8.6l0.7-1.21L6.69 7.9l1.04 0.42 0.9-0.68c0.43-0.32 0.84-0.56 1.25-0.73l1.06-0.43 0.16-1.13L11.3 4h1.39l0.19 1.35 0.16 1.13 1.06 0.43c0.43 0.18 0.83 0.41 1.23 0.71l0.91 0.7 1.06-0.43 1.27-0.51 0.7 1.21-1.07 0.85-0.89 0.7 0.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-0.9-2-2s0.9-2 2-2 2 0.9 2 2-0.9 2-2 2z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M14,14.5V12H10V15H8V11A1,1 0 0,1 9,10H14V7.5L17.5,11M21.71,11.29L12.71,2.29H12.7C12.31,1.9 11.68,1.9 11.29,2.29L2.29,11.29C1.9,11.68 1.9,12.32 2.29,12.71L11.29,21.71C11.68,22.09 12.31,22.1 12.71,21.71L21.71,12.71C22.1,12.32 22.1,11.68 21.71,11.29Z" />
</vector>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M8,6.2V4H7V2H17V4H16V12L18,14V16H17.8L14,12.2V4H10V8.2L8,6.2M20,20.7L18.7,22L12.8,16.1V22H11.2V16H6V14L8,12V11.3L2,5.3L3.3,4L20,20.7M8.8,14H10.6L9.7,13.1L8.8,14Z" />
</vector>

View File

@@ -84,7 +84,7 @@
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:maxLines="5"
android:maxLines="@integer/details_title_lines"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -48,6 +48,7 @@
android:layout_marginEnd="6dp"
android:focusable="true"
android:focusableInTouchMode="true"
app:collapseIcon="@null"
app:contentInsetStartWithNavigation="0dp"
app:navigationContentDescription="@string/search"
app:navigationIcon="?attr/actionModeWebSearchDrawable">

View File

@@ -12,6 +12,7 @@
android:layout_height="match_parent"
android:defaultFocusHighlightEnabled="false"
android:focusable="true"
app:doubleTapZoomStyle="center"
app:restoreStrategy="deferred" />
<TextView

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeightSmall"
android:orientation="horizontal"
android:paddingVertical="@dimen/margin_small">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imageView_cover"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="?listPreferredItemPaddingStart"
android:scaleType="centerCrop"
app:shapeAppearance="?shapeAppearanceCornerSmall"
tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="?listPreferredItemPaddingEnd"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall"
tools:text="@tools:sample/lorem[2]" />
<TextView
android:id="@+id/textView_subtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="@tools:sample/lorem[2]" />
</LinearLayout>
</LinearLayout>

View File

@@ -35,9 +35,11 @@
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceTitleSmall"
tools:drawableStart="@drawable/ic_pin_small"
tools:text="@tools:sample/lorem[15]" />
<TextView

View File

@@ -210,6 +210,19 @@
android:textAppearance="?attr/textAppearanceButton"
app:drawableStartCompat="@drawable/ic_appearance" />
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
android:id="@+id/button_image_server"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/image_server"
android:textAppearance="?attr/textAppearanceButton"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_images"
tools:visibility="visible" />
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
android:id="@+id/button_settings"
android:layout_width="match_parent"

View File

@@ -11,10 +11,22 @@
<item
android:id="@+id/action_shortcut"
android:icon="@drawable/ic_pin"
android:icon="@drawable/ic_shortcut"
android:title="@string/create_shortcut"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_pin"
android:icon="@drawable/ic_pin"
android:title="@string/pin"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_unpin"
android:icon="@drawable/ic_unpin"
android:title="@string/unpin"
app:showAsAction="ifRoom|withText" />
<item
android:id="@+id/action_settings"
android:icon="@drawable/ic_settings"

View File

@@ -6,6 +6,11 @@
android:id="@+id/action_lift"
android:title="@string/to_top" />
<item
android:id="@+id/action_pin"
android:checkable="true"
android:title="@string/pin" />
<item
android:id="@+id/action_shortcut"
android:title="@string/create_shortcut" />

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>

View File

@@ -19,7 +19,7 @@
<plurals name="minutes_ago">
<item quantity="zero">%1$d دقيقة مضت</item>
<item quantity="one">%1$d دقيقة مضت</item>
<item quantity="two">%1$d دقائق مضت</item>
<item quantity="two">%1$d دقيقتين مضت</item>
<item quantity="few">%1$d دقائق مضت</item>
<item quantity="many">%1$d دقائق مضت</item>
<item quantity="other">%1$d دقائق مضت</item>
@@ -57,11 +57,19 @@
<item quantity="other">%1$d ساعات مضت</item>
</plurals>
<plurals name="minutes">
<item quantity="zero"></item>
<item quantity="zero"/>
<item quantity="one">دقيقة</item>
<item quantity="two">دقيقتان</item>
<item quantity="few">ثلاث دقائق</item>
<item quantity="many">إحدى عشر دقيقة</item>
<item quantity="other">مئة دقيقة</item>
</plurals>
<plurals name="hours">
<item quantity="zero">العربية</item>
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="many"></item>
<item quantity="other"></item>
</plurals>
</resources>

View File

@@ -307,7 +307,7 @@
<string name="options">خيارات</string>
<string name="incognito_mode">الوضع الخفي</string>
<string name="automatic_scroll">تمرير تلقائي</string>
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
<string name="reader_info_pattern">فصل %1$d/%2$d صفحة %3$d/%4$d</string>
<string name="reader_info_bar">عرض شريط المعلومات في قارئ الصفحات</string>
<string name="folder_with_images">مجلد مع صور</string>
<string name="exclude_nsfw_from_history_summary">المانغا +18 لن يتم إضافتها إلى السجل، ولن يتم حفظ تقدمك فيها.\"</string>
@@ -334,4 +334,55 @@
<string name="import_completed_hint">يمكنك حذف الملف الأصلي من التخزين لتوفير مساحة</string>
<string name="import_will_start_soon">الإستيراد سيبدأ عن قريب</string>
<string name="history_shortcuts">إظهار اختصارات المانجا الحديثة</string>
<string name="network_unavailable_hint">قم بتشغيل الواي فاي أو شبكة الهاتف المحمول لقراءة المانجا عبر الإنترنت</string>
<string name="contrast">تباين</string>
<string name="text_unsaved_changes_prompt">هل تريد حفظ أو تجاهل التغييرات الغير المحفوظة؟</string>
<string name="error_no_space_left">لا توجد مساحة متبقية على الجهاز</string>
<string name="reader_slider">إظهار شريط التمرير لتبديل الصفحات</string>
<string name="server_error">خطأ من جانب الخادم (%1$s). الرجاء المحاولة مرة أخرى لاحقًا</string>
<string name="chapters_grid_view">عرض الشبكة</string>
<string name="manga_error_description_pattern">تفاصيل الخطأ:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. حاول &lt;a href=\"%2$s\"&gt;فتح المانجا في متصفح ويب&lt;/a&gt; للتأكد من أنها متوفرة على مصدرها&lt;br&gt;2. تأكد من أنك تستخدم &lt;a href=kotatsu://about&gt;أحدث إصدار من Kotatsu&lt;/a&gt;&lt;br&gt;3. إذا كانت متوفرة، أرسل تقرير خطأ إلى المطورين.</string>
<string name="history_shortcuts_summary">إتاحة المانجا الحديثة بالضغط المطول على أيقونة التطبيق</string>
<string name="reader_control_ltr_summary">النقر على الحافة اليمنى أو الضغط على المفتاح الأيمن يؤدي دائمًا إلى الانتقال للصفحة التالية.</string>
<string name="reader_control_ltr">تحكم مريح في القراءة</string>
<string name="brightness">سطوع</string>
<string name="clear_new_chapters_counters">قم أيضًا بمسح المعلومات حول الفصول الجديدة</string>
<string name="color_correction">تصحيح الألوان</string>
<string name="reset">إعادة تعيين</string>
<string name="discard">تجاهل</string>
<string name="webtoon_zoom">تكبير الويبتون</string>
<string name="network_unavailable">الشبكة غير متاحة</string>
<string name="more">المزيد</string>
<string name="prefetch_content">اعادة تحميل المحتوى</string>
<string name="enable_logging">تفعيل التسجيل</string>
<string name="theme_name_asuka">أسوكا</string>
<string name="remove_completed_downloads_confirm">سيتم حذف سجل التحميلات خاصتك بشكل دائم</string>
<string name="theme_name_dynamic">الديناميكية</string>
<string name="mark_as_current">تسجيل على كونها الحالي</string>
<string name="theme_name_mamimi">ماميمي</string>
<string name="theme_name_kanade">كانادي</string>
<string name="got_it">وجدتها</string>
<string name="downloads_wifi_only_summary">إيقاف التحميل عند الانتقال إلى شبكة الهاتف المحمول</string>
<string name="resume">استئناف</string>
<string name="cancel_all">إلغاء الكل</string>
<string name="source_disabled">المصدر معطل</string>
<string name="theme_name_rikka">ريكا</string>
<string name="theme_name_sakura">ساكورا</string>
<string name="pause">إيقاف مؤقت</string>
<string name="show_on_shelf">العرض في الرف</string>
<string name="remove_completed">تمت الإزالة</string>
<string name="enable_logging_summary">سجل بعض الأفعال لغايات التصحيح. لا تقم بتشغيله إذا لم تكن متأكدًا مما تفعله</string>
<string name="language">اللغة</string>
<string name="show_suspicious_content">إظهار المحتوى مشكوك فيه</string>
<string name="color_theme">مخطط الألوان</string>
<string name="show_in_grid_view">العرض في الشبكة</string>
<string name="theme_name_miku">ميكو</string>
<string name="theme_name_mion">ميون</string>
<string name="nothing_here">لا يوجد شيئ هنا</string>
<string name="sync_auth_hint">يمكنك تسجيل الدخول إلى حساب موجود أصلا أو إنشاء حساب جديد</string>
<string name="paused">متوقف مؤقتاً</string>
<string name="downloads_wifi_only">التحميل عبر شبكة الوايفاي فقط</string>
<string name="suggestions_notifications_summary">إظهار الإشعارات أحيانًا بالمانغا المقترحة</string>
<string name="mirror_switching_summary">اللغة العربية</string>
<string name="suggestions_enable_prompt"></string>
</resources>

View File

@@ -22,7 +22,7 @@
<string name="history_is_empty">Гісторыя пустая</string>
<string name="read">Чытаць</string>
<string name="you_have_not_favourites_yet">Дадайце цікавую для вас мангу ў абранае, каб не страціць яе</string>
<string name="add_to_favourites">Дадаць ў абраныя</string>
<string name="add_to_favourites">Дадаць у абраныя</string>
<string name="add_new_category">Стварыць катэгорыю</string>
<string name="add">Дадаць</string>
<string name="save">Захаваць</string>
@@ -45,7 +45,7 @@
<string name="theme">Тэма</string>
<string name="light">Светлая</string>
<string name="dark">Цёмная</string>
<string name="follow_system">Як ў сістэме</string>
<string name="follow_system">Як у сістэме</string>
<string name="pages">Старонкi</string>
<string name="clear">Ачысціць</string>
<string name="remove">Выдаліць</string>
@@ -78,7 +78,7 @@
<string name="external_storage">Знешняе сховішча</string>
<string name="domain">Дамен</string>
<string name="app_update_available">Даступна абнаўленне праграмы</string>
<string name="open_in_browser">Адкрыць ў браўзеры</string>
<string name="open_in_browser">Адкрыць у браўзеры</string>
<string name="notifications">Паведамленні</string>
<string name="enabled_d_of_d">Ўключана %1$d з %2$d</string>
<string name="new_chapters">Новыя раздзелы</string>
@@ -125,7 +125,7 @@
<string name="right_to_left">Справа налева</string>
<string name="create_category">Стварыць катэгорыю</string>
<string name="scale_mode">Маштабаванне</string>
<string name="zoom_mode_fit_center">Ўмясціць ў экран</string>
<string name="zoom_mode_fit_center">Умясціць у экран</string>
<string name="zoom_mode_fit_height">Падагнаць па вышыні</string>
<string name="zoom_mode_fit_width">Падагнаць па шырыні</string>
<string name="zoom_mode_keep_start">Зыходны памер</string>
@@ -137,7 +137,7 @@
<string name="data_restored">Данныя адноўлены</string>
<string name="preparing_">Падрыхтоўка…</string>
<string name="file_not_found">Файл не знойдзены</string>
<string name="data_restored_success">Ўсе данныя паспяхова адноўлены</string>
<string name="data_restored_success">Усе данныя паспяхова адноўлены</string>
<string name="data_restored_with_errors">Данныя адноўлены, але ўзніклі некаторыя памылкі</string>
<string name="backup_information">Вы можаце стварыць рэзервовую копію абранага і гісторыі і потым аднавіць іх</string>
<string name="just_now">Толькі што</string>
@@ -153,7 +153,7 @@
<string name="clear_cookies">Ачысціць кукi</string>
<string name="cookies_cleared">Ўсе кукi выдалены</string>
<string name="clear_feed">Ачысціць стужку</string>
<string name="text_clear_updates_feed_prompt">Ўся гісторыя абнаўленняў будзе ачышчана і яе нельга будзе вярнуць. Вы ўпэўненыя?</string>
<string name="text_clear_updates_feed_prompt">Уся гісторыя абнаўленняў будзе ачышчана і яе нельга будзе вярнуць. Вы ўпэўненыя?</string>
<string name="check_for_new_chapters">Праверка новых глаў</string>
<string name="reverse">Ў адваротным парадку</string>
<string name="sign_in">Ўвайсці</string>
@@ -163,14 +163,14 @@
<string name="protect_application_subtitle">Калі ласка, увядзіце пароль, які спатрэбіцца пры запуску праграмы</string>
<string name="confirm">Пацвердзіць</string>
<string name="password_length_hint">Пароль павінен змяшчаць не менш за 4 сімвалы</string>
<string name="text_clear_search_history_prompt">Вы сапраўды хочаце выдаліць ўсе апошнія пошукавыя запыты?</string>
<string name="text_clear_search_history_prompt">Вы сапраўды хочаце выдаліць усе апошнія пошукавыя запыты?</string>
<string name="read_more">Падрабязна</string>
<string name="tracker_warning">Некаторыя вытворцы могуць змяняць паводзіны сістэмы, што можа парушаць выкананне фонавых задач.</string>
<string name="backup_saved">Рэзервовая копія паспяхова захавана</string>
<string name="welcome">Вітаю</string>
<string name="text_local_holder_secondary">Захавайце што-небудзь з інтэрнэт-каталога або імпартуйце гэта з файла.</string>
<string name="text_local_holder_primary">Спачатку захавайце што-небудзь</string>
<string name="text_history_holder_secondary">Знайдзіце, што пачытаць, ў раздзеле «Агляд»</string>
<string name="text_history_holder_secondary">Знайдзіце, што пачытаць, у раздзеле «Агляд»</string>
<string name="text_history_holder_primary">Тут будзе паказана манга, якую вы чытаеце</string>
<string name="text_search_holder_secondary">Паспрабуйце перафармуляваць запыт.</string>
<string name="text_empty_holder_primary">Неяк тут пуста…</string>
@@ -178,7 +178,7 @@
<string name="queued">Ў чарзе</string>
<string name="about_app_translation_summary">Дапамагчы з перакладам праграмы</string>
<string name="about_app_translation">Пераклад</string>
<string name="text_clear_cookies_prompt">Вы выйдзеце з усіх крыніц, ў якіх вы аўтарызаваны</string>
<string name="text_clear_cookies_prompt">Вы выйдзеце з усіх крыніц, у якіх вы аўтарызаваны</string>
<string name="auth_not_supported_by">Аўтарызацыя на %s не падтрымліваецца</string>
<string name="auth_complete">Аўтарызацыя выканана</string>
<string name="genres">Жанры</string>
@@ -196,7 +196,7 @@
<string name="enabled">Ўключаны</string>
<string name="exclude_nsfw_from_suggestions">Ня прапаноўваць NSFW мангу</string>
<string name="text_suggestion_holder">Пачніце чытаць мангу, і вы атрымаеце персаналізаваныя прапановы</string>
<string name="suggestions_info">Ўсе даныя аналізуюцца толькі лакальна на гэтай прыладзе і нікуды не адпраўляюцца.</string>
<string name="suggestions_info">Усе даныя аналізуюцца толькі лакальна на гэтай прыладзе і нікуды не адпраўляюцца.</string>
<string name="suggestions_summary">Прапануеце мангу, заснаваную на вашых перавагах</string>
<string name="suggestions_enable">Ўключыць прапановы</string>
<string name="suggestions">Прапанова</string>
@@ -211,10 +211,10 @@
<string name="various_languages">Розныя мовы</string>
<string name="search_chapters">Знайсці главу</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="chapters_empty">Ў гэтай манге няма раздзелаў</string>
<string name="chapters_empty">У гэтай манге няма раздзелаў</string>
<string name="hide">Схаваць</string>
<string name="appearance">Знешні выгляд</string>
<string name="disable_all">Выключыць ўсё</string>
<string name="disable_all">Адключыць усе</string>
<string name="use_fingerprint">Выкарыстоўваць адбітак пальца, калі даступна</string>
<string name="appwidget_shelf_description">Манга з абраных</string>
<string name="appwidget_recent_description">Манга, якую вы нядаўна чыталі</string>
@@ -235,7 +235,7 @@
<string name="new_sources_text">Даступныя новыя крыніцы мангі</string>
<string name="download_slowdown">Запавольванне спампоўкі</string>
<string name="suggestions_excluded_genres">Выключыць жанры</string>
<string name="suggestions_excluded_genres_summary">Укажыце жанры, якія вы не хочаце бачыць ў рэкамендацыях</string>
<string name="suggestions_excluded_genres_summary">Укажыце жанры, якія вы не хочаце бачыць у рэкамендацыях</string>
<string name="text_delete_local_manga_batch">Выдаліць выбраныя элементы з прылады назаўжды\?</string>
<string name="removal_completed">Выдаленне завершана</string>
<string name="download_slowdown_summary">Дапамагае пазбегнуць блакіроўкі па IP-адрасе</string>
@@ -263,20 +263,20 @@
<string name="logout">Выйсці</string>
<string name="show_reading_indicators_summary">Паказваць працэнт прачытанага ў гісторыі і абраных</string>
<string name="data_deletion">Выдаленне даных</string>
<string name="show_all">Паказаць ўсе</string>
<string name="show_all">Паказаць усе</string>
<string name="exclude_nsfw_from_history_summary">Манга, пазначаная як NSFW, не будзе дададзеная ў гісторыю і ваш прагрэс не будзе захаваны</string>
<string name="clear_cookies_summary">Можа дапамагчы з некаторымі праблемам. Ўсе аўтарызацыі будуць ануляваныя</string>
<string name="clear_cookies_summary">Можа дапамагчы з некаторымі праблемам. Усе аўтарызацыі будуць ануляваныя</string>
<string name="not_found_404">Змесціва не знойдзена ці выдалена</string>
<string name="status_re_reading">Перачытваю</string>
<string name="select_range">Выберыце дыяпазон</string>
<string name="nothing_here">Тут нічога няма</string>
<string name="services">Службы</string>
<string name="theme_name_kanade">Канадзе</string>
<string name="clear_all_history">Ачысціць ўсю гісторыю</string>
<string name="clear_all_history">Ачысціць усю гісторыю</string>
<string name="history_cleared">Гісторыя ачышчана</string>
<string name="incognito_mode">Рэжым інкогніта</string>
<string name="categories_delete_confirm">Вы ўпэўнены, што хочаце выдаліць выбраныя абраныя катэгорыі?
\nЎся манга ў ім будзе страчана, і гэта нельга будзе адрабіць.</string>
\nУся манга ў ім будзе страчана, і гэта нельга будзе адрабіць.</string>
<string name="no_bookmarks_summary">Вы можаце стварыць закладку падчас чытання мангі</string>
<string name="saved_manga">Захаваная манга</string>
<string name="theme_name_mamimi">Мамімі</string>
@@ -288,7 +288,7 @@
<string name="mark_as_current">Пазначыць як бягучы</string>
<string name="error_no_space_left">На прыладзе не засталося месца</string>
<string name="network_unavailable">Сетка недаступная</string>
<string name="network_unavailable_hint">Каб чытаць мангу онлайн, ўключыце Wi-Fi або мабільную сетку</string>
<string name="network_unavailable_hint">Каб чытаць мангу онлайн, уключыце Wi-Fi або мабільную сетку</string>
<string name="webtoon_zoom">Маштабаванне ў рэжыме манхвы</string>
<string name="theme_name_dynamic">Дынамічны</string>
<string name="color_theme">Каляровая гама</string>
@@ -304,7 +304,7 @@
<string name="history_shortcuts">Паказаць апошнія ярлыкі мангі</string>
<string name="history_shortcuts_summary">Зрабіце нядаўнюю мангу даступнай, доўга націскаючы на значок праграмы</string>
<string name="reader_control_ltr_summary">Націск на правы край або націск правай клавішы заўсёды перамыкае на наступную старонку.</string>
<string name="reader_control_ltr">Эрганамічны упраўленне чытаннем</string>
<string name="reader_control_ltr">Эрганамічнае кіраванне рэжымам чытання</string>
<string name="color_correction">Карэкцыя колеру</string>
<string name="brightness">Яркасць</string>
<string name="contrast">Кантраст</string>
@@ -321,7 +321,7 @@
<string name="feed">Стужка</string>
<string name="reader_slider">Паказаць паўзунок пераключэння старонак</string>
<string name="source_disabled">Крыніца адключана</string>
<string name="show_in_grid_view">Паказаць ў выглядзе сеткі</string>
<string name="show_in_grid_view">Паказаць у выглядзе сеткі</string>
<string name="theme_name_miku">Міку</string>
<string name="theme_name_asuka">Аска</string>
<string name="theme_name_mion">Міён</string>
@@ -330,7 +330,7 @@
<string name="no_chapters">Няма раздзелаў</string>
<string name="automatic_scroll">Аўтаматычная пракрутка</string>
<string name="reader_info_pattern">Разд. %1$d/%2$d Стар. %3$d/%4$d</string>
<string name="reader_info_bar">Паказаць інфармацыйную панэль ў праграме чытання</string>
<string name="reader_info_bar">Паказаць інфармацыйную панэль у праграме чытання</string>
<string name="comics_archive">Архіў коміксаў</string>
<string name="folder_with_images">Тэчка з малюнкамі</string>
<string name="importing_manga">Імпарт мангі</string>
@@ -339,7 +339,7 @@
<string name="no_bookmarks_yet">Закладак пакуль няма</string>
<string name="bookmarks_removed">Закладкі выдалены</string>
<string name="no_manga_sources">Няма крыніц мангі</string>
<string name="no_manga_sources_text">Каб чытаць мангу онлайн, ўключыце крыніцы мангі</string>
<string name="no_manga_sources_text">Каб чытаць мангу онлайн, уключыце крыніцы мангі</string>
<string name="random">Выпадковы</string>
<string name="reorder">Змяніць парадак</string>
<string name="empty">Пуста</string>
@@ -373,7 +373,7 @@
<string name="ignore_ssl_errors">Ігнараваць памылкі SSL</string>
<string name="resume">Аднавіць</string>
<string name="paused">Прыпынена</string>
<string name="cancel_all">Адмяніць ўсё</string>
<string name="cancel_all">Адмяніць усе</string>
<string name="downloads_wifi_only">Спампаваць толькі праз Wi-Fi</string>
<string name="sync_host_description">Вы можаце выкарыстоўваць уласны сервер сінхранізацыі або сервер па змаўчанні. Не змяняйце гэта, калі вы не ўпэўненыя, што робіце.</string>
<string name="pause">Паўза</string>
@@ -382,7 +382,7 @@
<string name="suggestions_notifications_summary">Часам паказваць апавяшчэнні з прапанаванай мангай</string>
<string name="more">Больш</string>
<string name="enable">Ўключыць</string>
<string name="cancel_all_downloads_confirm">Ўсе актыўныя спампоўкі будуць адменены, часткова спампаваныя даныя будуць страчаны</string>
<string name="cancel_all_downloads_confirm">Усе актыўныя спампоўкі будуць адменены, часткова спампаваныя даныя будуць страчаны</string>
<string name="suggestions_enable_prompt">Хочаце атрымліваць персаналізаваныя прапановы мангі\?</string>
<string name="suggestion_manga">Прапанова: %s</string>
<string name="no_thanks">Не, дзякуй</string>
@@ -407,13 +407,13 @@
<string name="images_procy_description">Выкарыстоўвайце службу wsrv.nl, каб паменшыць выкарыстанне трафіку і паскорыць загрузку малюнкаў, калі гэта магчыма</string>
<string name="password">Пароль</string>
<string name="invert_colors">Інвертаваць колеры</string>
<string name="show_pages_numbers_summary">Паказаць нумары старонак ў ніжнім куце</string>
<string name="show_pages_numbers_summary">Паказаць нумары старонак у ніжнім куце</string>
<string name="network">Сетка</string>
<string name="data_and_privacy">Дадзеныя і канфідэнцыяльнасць</string>
<string name="webtoon_zoom_summary">Дазволіць жэсты маштабавання ў рэжыме манхвы</string>
<string name="restore_summary">Аднавіць раней створаную рэзервовую копію</string>
<string name="reader_info_bar_summary">Паказаць бягучы час і ход чытання ў верхняй частцы экрана</string>
<string name="clear_source_cookies_summary">Выдаліць файлы cookie толькі для вызначанага дамена. Ў большасці выпадкаў гэта робіць аўтарызацыю несапраўднай</string>
<string name="clear_source_cookies_summary">Выдаліць файлы cookie толькі для вызначанага дамена. У большасці выпадкаў гэта робіць аўтарызацыю несапраўднай</string>
<string name="download_option_whole_manga">Манга цалкам</string>
<string name="local_manga_directories">Лакальныя каталогі мангі</string>
<string name="download_option_all_chapters">Ўсе раздзелы з перакладам %s</string>
@@ -459,10 +459,10 @@
<string name="main_screen_sections">Раздзелы галоўнага экрана</string>
<string name="to_top">Ўверх</string>
<string name="zoom_in">Павялічыць</string>
<string name="reader_zoom_buttons_summary">Ці паказваць кнопкі кіравання маштабаваннем ў правым ніжнім куце</string>
<string name="reader_zoom_buttons_summary">Ці паказваць кнопкі кіравання маштабаваннем у правым ніжнім куце</string>
<string name="reader_zoom_buttons">Паказаць кнопкі маштабавання</string>
<string name="zoom_out">Зменшыць</string>
<string name="keep_screen_on">Трымаць экран ўключаным</string>
<string name="keep_screen_on">Трымаць экран уключаным</string>
<string name="keep_screen_on_summary">Ня выключаць экран падчас чытання мангі</string>
<string name="state_abandoned">Кінута</string>
<string name="categories">Катэгорыі</string>
@@ -480,7 +480,7 @@
<string name="frequency_once_per_week">Раз на тыдзень</string>
<string name="periodic_backups">Перыядычнае рэзервовае капіраванне</string>
<string name="frequency_twice_per_month">Два разы на месяц</string>
<string name="frequency_once_per_month">Адзін раз ў месяц</string>
<string name="frequency_once_per_month">Адзін раз у месяц</string>
<string name="last_successful_backup">Апошняе паспяховае рэзервовае капіраванне: %s</string>
<string name="backups_output_directory">Вывадны каталог рэзервовых копій</string>
<string name="speed_value">x%.1f</string>
@@ -496,7 +496,7 @@
<string name="manual">Ўручную</string>
<string name="source_enabled">Крыніца ўключана</string>
<string name="disable_nsfw_summary">Адключыць крыніцы NSFW і схавайць мангу для дарослых са спісу, калі гэта магчыма</string>
<string name="no_manga_sources_catalog_text">Ў гэтым раздзеле няма даступных крыніц, ці ўсе яны маглі быць ўжо дададзены.
<string name="no_manga_sources_catalog_text">У гэтым раздзеле няма даступных крыніц, ці ўсе яны маглі быць ужо дададзены.
\nСачыце за абнаўленнямі</string>
<string name="available_d">Даступна: %1$d</string>
<string name="content_type_other">Іншае</string>
@@ -517,18 +517,18 @@
<string name="skip">Прапусціць</string>
<string name="color_correction_apply_text">Гэтыя налады могуць прымяняцца глабальна або толькі да бягучай мангі. Пры глабальным прымяненні індывідуальныя налады не будуць перавызначаны.</string>
<string name="grayscale">Адценні шэрага</string>
<string name="disable_battery_optimization_summary_downloads">Можа дапамагчы з пачаткам загрузкі, калі у вас ўзнікаюць з ёй праблемы</string>
<string name="disable_battery_optimization_summary_downloads">Можа дапамагчы з пачаткам загрузкі, калі у вас узнікаюць з ёй праблемы</string>
<string name="welcome_text">Выберыце, якія крыніцы змесціва вы хочаце ўключыць. Гэта таксама можна наладзіць пазней ў наладах</string>
<string name="restore">Аднавіць</string>
<string name="backup_date_">Дата стварэння рэзервовай копіі: %s</string>
<string name="sync_auth">Ўвайдзіце, каб сінхранізаваць ўліковы запіс</string>
<string name="sync_auth">Увайсці ў акаўнт сінхранізацыі</string>
<string name="by_name_reverse">Імя (зваротнае)</string>
<string name="content_rating">Рэйтынг кантэнту</string>
<string name="genres_exclude">Выключыць жанры</string>
<string name="rating_safe">Бяспечны</string>
<string name="rating_suggestive">З падказкамі</string>
<string name="rating_adult">Дарослы</string>
<string name="default_tab">Ўкладка па змаўчанні</string>
<string name="default_tab">Укладка па змаўчанні</string>
<string name="state_upcoming">Чакаецца</string>
<string name="volume_">Том %d</string>
<string name="volume_unknown">Невядомы том</string>
@@ -554,7 +554,7 @@
\nУвага: бягучы ход чытання будзе страчаны.</string>
<string name="reader_actions_summary">Налада дзеянняў для сэнсарных абласцей экрана</string>
<string name="remaining_time_pattern">%1$s %2$s</string>
<string name="email_password_enter_hint">Каб працягнуць, ўвядзіце адрас электроннай пошты і пароль</string>
<string name="email_password_enter_hint">Каб працягнуць, увядзіце адрас электроннай пошты і пароль</string>
<string name="config_reset_confirm">Скінуць налады да значэнняў па змаўчанні? Гэта дзеянне нельга адмяніць.</string>
<string name="use_two_pages_landscape">Выкарыстоўвайце двухстаронкавы макет у альбомнай арыентацыі (бэта)</string>
<string name="default_webtoon_zoom_out">Аддаленне ў рэжыме манхвы</string>
@@ -589,7 +589,7 @@
<string name="single_cbz_file">Адзін файл CBZ</string>
<string name="multiple_cbz_files">Некалькі файлаў CBZ</string>
<string name="alternatives">Альтэрнатывы</string>
<string name="migrate_confirmation">Манга «%1$s» з «%2$s» будзе заменена на «%3$s» з «%4$s» ў вашай гісторыі і ў абраных (калі ёсць)</string>
<string name="migrate_confirmation">Манга «%1$s» з «%2$s» будзе заменена на «%3$s» з «%4$s» у вашай гісторыі і ў абраных (калі ёсць)</string>
<string name="migration_completed">Перанос завершаны</string>
<string name="manga_migration">Перанос мангі</string>
<string name="migrate">Перанесці</string>
@@ -609,7 +609,7 @@
<string name="enable_source">Ўключыць крыніцу</string>
<string name="unsupported_source">Гэтая крыніца мангі не падтрымліваецца</string>
<string name="show_pages_thumbs">Паказаць мініяцюры старонак</string>
<string name="show_pages_thumbs_summary">Ўключыце ўкладку «Старонкі» на экране звестак</string>
<string name="show_pages_thumbs_summary">Уключыце ўкладку «Старонкі» на экране звестак</string>
<string name="error_no_data_received">Ніякія дадзеныя не былі атрыманы з сервера</string>
<string name="unsupported_backup_message">Абярыце правільны файл рэзервовай копіі Kotatsu</string>
<string name="last_used">Апошні раз выкарыстоўваўся</string>
@@ -642,4 +642,6 @@
<string name="disable">Адкл.</string>
<string name="sources_disabled">Крыніцы адключаны</string>
<string name="_new">Новае</string>
<string name="all_languages">Усе мовы</string>
<string name="screenshots_block_incognito">Блакіраваць у рэжыме інкогніта</string>
</resources>

View File

@@ -11,7 +11,7 @@
<string name="domain">ডোমেইন</string>
<string name="app_update_available">অ্যাপের নতুন ভার্সন পাওয়া গেছে</string>
<string name="open_in_browser">ব্রাউজারে খুলুন</string>
<string name="error_occurred">কিছু একটা গন্ডগোল হয়েছে</string>
<string name="error_occurred">কিছু একটা সমস্যা হয়েছে</string>
<string name="details">খুঁটিনাটি</string>
<string name="chapters">পর্ব সমূহ</string>
<string name="list">তালিকা</string>
@@ -102,7 +102,7 @@
<string name="manga_save_location">ডাউনলোডের জন্য ফোল্ডার</string>
<string name="suggestions_notifications_summary">কখনও কখনও প্রস্তাবিত মাঙ্গা সহ বিজ্ঞপ্তিগুলি দেখান৷</string>
<string name="updates_feed_cleared">সাফ করা হয়েছে</string>
<string name="list_mode">তালিকা মোড</string>
<string name="list_mode">তালিকার ধরন</string>
<string name="download_complete">ডাউনলোড করা হয়েছে</string>
<string name="update">হালনাগাদ</string>
<string name="feed_will_update_soon">ফিড আপডেট শীঘ্রই শুরু হবে</string>
@@ -151,4 +151,4 @@
<string name="suggestion_manga">পরামর্শ: %s</string>
<string name="text_empty_holder_primary">এখানে খালি…</string>
<string name="done">সম্পন্ন</string>
</resources>
</resources>

View File

@@ -632,4 +632,16 @@
<string name="pin_navigation_ui">Upevnit uživatelské rozhraní pro navigaci</string>
<string name="fix">Upevnit</string>
<string name="pin_navigation_ui_summary">Neskrývat navigační panel a zobrazení vyhledávání při posouvání</string>
<string name="disable_connectivity_check">Vypnout kontrolu připojení</string>
<string name="ignore_ssl_errors_summary">Můžeš vypnout SSL certifikáty pokud se potýkáš s problémy při připojení k internetovým zdrojům. Toto může ovlivnit tvoji bezpečnost. Restart aplikace je požadován po změnění tohoto nastavení.</string>
<string name="sources_disabled">Vypnout zdroje</string>
<string name="disable">Vypnout</string>
<string name="_new">Nový l</string>
<string name="disable_connectivity_check_summary">Přeskoč kontrolu připojení pokud s tím máš problémy (např. zapnout offline režim i když jsi připojený k internetu)</string>
<string name="disable_nsfw_notifications_summary">Nezobrazovat notifikace o nových NSFW manga kapitolách</string>
<string name="all_languages">Všechny jazyky</string>
<string name="screenshots_block_incognito">Zablokovat když je privátní režim</string>
<string name="disable_nsfw_notifications">Vypnout NSFW oznámení</string>
<string name="tracker_debug_info">Log kontroly nových kapitol</string>
<string name="tracker_debug_info_summary">Debug informace o kontrole nových kapitol na pozadí</string>
</resources>

View File

@@ -609,4 +609,7 @@
<string name="enable_source">Quelle aktivieren</string>
<string name="unsupported_source">Diese Manga-Quelle wird nicht unterstützt</string>
<string name="show_pages_thumbs">Seitenvorschau anzeigen</string>
<string name="unsupported_backup_message">Bitte wähle eine richtige Kotatsu-Backup-Datei aus</string>
<string name="show_pages_thumbs_summary">Registerkarte \"Seiten\" auf dem Detailbildschirm aktivieren</string>
<string name="error_no_data_received">Keine Daten vom Server erhalten</string>
</resources>

View File

@@ -644,4 +644,6 @@
<string name="_new">Nuevos</string>
<string name="all_languages">Todos los idiomas</string>
<string name="screenshots_block_incognito">Bloquear en modo incógnito</string>
<string name="image_server">Servidor de imágenes preferido</string>
<string name="crop_pages">Páginas de recortes</string>
</resources>

View File

@@ -642,4 +642,6 @@
<string name="tracker_debug_info_summary">Debug na impormasyon tungkol sa mga pagsusuri sa background para sa mga bagong kabanata</string>
<string name="disable_nsfw_notifications">Di paganahin ang mga abisong NSFW</string>
<string name="_new">Mga bago</string>
<string name="screenshots_block_incognito">Harangan pag naka-incognito mode</string>
<string name="all_languages">Lahat ng wika</string>
</resources>

View File

@@ -644,4 +644,6 @@
<string name="_new">नया</string>
<string name="all_languages">सभी भाषाएं</string>
<string name="screenshots_block_incognito">गुप्त मोड में ब्लॉक करें</string>
<string name="image_server">पसंदीदा छवि सर्वर</string>
<string name="crop_pages">पृष्ठ काटें</string>
</resources>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="items">
<item quantity="one">%1$d stavka</item>
<item quantity="few">%1$d stavke</item>
<item quantity="other">%1$d stavkih</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d novo poglavlje</item>
<item quantity="few">%1$d nova poglavlja</item>
<item quantity="other">%1$d novih poglavlja</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d poglavlje</item>
<item quantity="few">%1$d poglavlja</item>
<item quantity="other">%1$d poglavlja</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">prije %1$d minute</item>
<item quantity="few">prije %1$d minuta</item>
<item quantity="other">prije %1$d minuta</item>
</plurals>
<plurals name="hours_ago">
<item quantity="one">prije %1$d sat</item>
<item quantity="few">prije %1$d sata</item>
<item quantity="other">prije %1$d sati</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">prije %1$d dan</item>
<item quantity="few">prije %1$d dana</item>
<item quantity="other">prije %1$d dana</item>
</plurals>
<plurals name="months_ago">
<item quantity="one">prije %1$d mjesec</item>
<item quantity="few">prije %1$d mjeseca</item>
<item quantity="other">prije %1$d mjeseci</item>
</plurals>
<plurals name="hours">
<item quantity="one">%1$d sat</item>
<item quantity="few">%1$d sata</item>
<item quantity="other">%1$d sati</item>
</plurals>
<plurals name="minutes">
<item quantity="one">%1$d minuta</item>
<item quantity="few">%1$d minute</item>
<item quantity="other">%1$d minuta</item>
</plurals>
</resources>

View File

@@ -0,0 +1,642 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="missing_storage_permission">Nema dopuštenja za pristup mangi na vanjskoj pohrani</string>
<string name="show_pages_thumbs_summary">Omogućite karticu \"Stranice\" na zaslonu s detaljima</string>
<string name="error_no_data_received">Nikakvi podaci nisu primljeni s poslužitelja</string>
<string name="unsupported_backup_message">Odaberite odgovarajuću datoteku sigurnosne kopije Kotatsu</string>
<string name="hours_short">%d h</string>
<string name="minutes_short">%d m</string>
<string name="hours_minutes_short">%1$d h %2$d m</string>
<string name="fix">Popravi</string>
<string name="show_updated">Prikaži ažurirano</string>
<string name="webtoon_gaps_summary">Prikaži okomite razmake između stranica u načinu webtoon</string>
<string name="more_frequently">Češće</string>
<string name="pin_navigation_ui_summary">Nemoj skrivati navigacijsku traku i prikaz pretraživanja prilikom pomicanja</string>
<string name="suggested_queries">Predloženi upiti</string>
<string name="last_used">Zadnje korišteno</string>
<string name="webtoon_gaps">Praznine u webtoon modu</string>
<string name="blocked_by_server_message">Poslužitelj vas je blokirao. Pokušajte koristiti drugu mrežnu vezu (VPN, proxy itd.)</string>
<string name="less_frequently">Rjeđe</string>
<string name="frequency_of_check">Učestalost provjere</string>
<string name="recent_queries">Nedavni upiti</string>
<string name="disable">Onemogući</string>
<string name="pin_navigation_ui">Zakači navigacijsko sučelje</string>
<string name="search_suggestions">Prijedlozi za pretraživanje</string>
<string name="authors">Autori</string>
<string name="sources_disabled">Izvori onemogućeni</string>
<string name="local_storage">Lokalna pohrana</string>
<string name="favourites">Favoriti</string>
<string name="history">Povijest</string>
<string name="error_occurred">Dogodila se greška</string>
<string name="details">Detalji</string>
<string name="chapters">Poglavlja</string>
<string name="list">Popis</string>
<string name="detailed_list">Detaljan popis</string>
<string name="grid">Mreža</string>
<string name="settings">Postavke</string>
<string name="remote_sources">Manga izvori</string>
<string name="loading_">Učitavanje…</string>
<string name="computing_">Računanje…</string>
<string name="chapter_d_of_d">Poglavlje %1$d od %2$d</string>
<string name="close">Zatvori</string>
<string name="try_again">Pokušajte ponovno</string>
<string name="clear_history">Obriši povijest</string>
<string name="nothing_found">Ništa nije pronađeno</string>
<string name="read">Čitaj</string>
<string name="you_have_not_favourites_yet">Još nema favorita</string>
<string name="add_to_favourites">Označite ovo kao favorit</string>
<string name="add_new_category">Nova kategorija</string>
<string name="add">Dodaj</string>
<string name="save">Sačuvaj</string>
<string name="share">Podijeli</string>
<string name="create_shortcut">Napravi prečicu…</string>
<string name="share_s">Podijeli %s</string>
<string name="search">Pretraži</string>
<string name="search_manga">Pretraži mangu</string>
<string name="manga_downloading_">Preuzimanje…</string>
<string name="network_error">Pogreška mreže</string>
<string name="list_mode">Modus popisa</string>
<string name="theme">Tema</string>
<string name="light">Svijetla</string>
<string name="dark">Tamna</string>
<string name="clear">Očisti</string>
<string name="remove">Ukloni</string>
<string name="_s_deleted_from_local_storage">\"%s\" izbrisano iz lokalne pohrane</string>
<string name="page_saved">Spremljeno</string>
<string name="share_image">Dijeli sliku</string>
<string name="_import">Uvoz</string>
<string name="delete">Izbriši</string>
<string name="operation_not_supported">Ova operacija nije podržana</string>
<string name="text_file_not_supported">Odaberite ZIP ili CBZ datoteku.</string>
<string name="no_description">Bez opisa</string>
<string name="clear_pages_cache">Očisti predmemoriju stranice</string>
<string name="read_mode">Način čitanja</string>
<string name="grid_size">Veličina mreže</string>
<string name="search_on_s">Traži na %s</string>
<string name="delete_manga">Izbriši mangu</string>
<string name="text_delete_local_manga">Trajno izbrisati \"%s\" s uređaja?</string>
<string name="reader_settings">Postavke čitača</string>
<string name="switch_pages">Promijenite stranice</string>
<string name="_continue">Nastaviti</string>
<string name="error">Greška</string>
<string name="clear_thumbs_cache">Očisti predmemoriju sličica</string>
<string name="search_history_cleared">Očišćeno</string>
<string name="internal_storage">Interna pohrana</string>
<string name="external_storage">Vanjska pohrana</string>
<string name="domain">Domena</string>
<string name="app_update_available">Dostupna je nova verzija aplikacije</string>
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Omogućeno je %1$d od %2$d</string>
<string name="new_chapters">Nova poglavlja</string>
<string name="download">Preuzimi</string>
<string name="notifications_settings">Postavke obavijesti</string>
<string name="notification_sound">Zvuk obavijesti</string>
<string name="light_indicator">LED indikator</string>
<string name="vibration">Vibracija</string>
<string name="favourites_categories">Omiljene kategorije</string>
<string name="text_search_holder_secondary">Pokušajte preformulirati upit.</string>
<string name="text_history_holder_primary">Ono što pročitate bit će prikazano ovdje</string>
<string name="text_history_holder_secondary">Pronađite što čitati u odjeljku «Istražite»</string>
<string name="text_local_holder_primary">Prvo spremite nešto</string>
<string name="text_local_holder_secondary">Spremite nešto iz online kataloga ili uvezite iz datoteke.</string>
<string name="manga_shelf">Polica</string>
<string name="recent_manga">Nedavno</string>
<string name="pages_animation">Animacija stranice</string>
<string name="manga_save_location">Mapa preuzimanja</string>
<string name="not_available">Nije dostupno</string>
<string name="cannot_find_available_storage">Nema dostupnog prostora za pohranu</string>
<string name="other_storage">Ostala pohrana</string>
<string name="all_favourites">Svi favoriti</string>
<string name="favourites_category_empty">Prazna kategorija</string>
<string name="text_feed_holder">Ovdje su prikazana nova poglavlja onoga što čitate</string>
<string name="search_results">Rezultati pretraživanja</string>
<string name="new_version_s">Nova verzija: %s</string>
<string name="dont_check">Ne provjeravaj</string>
<string name="enter_password">Upišite lozinku</string>
<string name="wrong_password">Pogrešna lozinka</string>
<string name="protect_application">Zaštitite aplikaciju</string>
<string name="protect_application_summary">Traži lozinku prilikom pokretanja Kotatsua</string>
<string name="repeat_password">Ponovite lozinku</string>
<string name="passwords_mismatch">Nepodudarne lozinke</string>
<string name="about">O aplikaciji</string>
<string name="app_version">Verzija %s</string>
<string name="check_for_updates">Provjerite ima li ažuriranja</string>
<string name="no_update_available">Nema dostupnih ažuriranja</string>
<string name="right_to_left">S desna na lijevo</string>
<string name="create_category">Nova kategorija</string>
<string name="scale_mode">Način skaliranja</string>
<string name="zoom_mode_fit_width">Prilagodi širini</string>
<string name="zoom_mode_fit_height">Prilagodi visini</string>
<string name="zoom_mode_fit_center">Prilagodi sredini</string>
<string name="zoom_mode_keep_start">Zadrži na početku</string>
<string name="black_dark_theme">Crno</string>
<string name="black_dark_theme_summary">Koristi manje energije na AMOLED zaslonima</string>
<string name="backup_restore">Sigurnosno kopiranje i vraćanje</string>
<string name="create_backup">Stvorite sigurnosnu kopiju podataka</string>
<string name="group">Grupa</string>
<string name="today">Danas</string>
<string name="tap_to_try_again">Dodirnite za ponovni pokušaj</string>
<string name="reader_mode_hint">Odabrana konfiguracija bit će zapamćena za ovu mangu</string>
<string name="silent">Tiho</string>
<string name="captcha_required">Potrebna CAPTCHA</string>
<string name="captcha_solve">Riješi</string>
<string name="clear_cookies">Obriši kolačiće</string>
<string name="cookies_cleared">Svi kolačići su uklonjeni</string>
<string name="clear_feed">Očisti novosti</string>
<string name="text_clear_updates_feed_prompt">Trajno izbrisati svu povijest ažuriranja?</string>
<string name="check_for_new_chapters">Provjerite ima li novih poglavlja</string>
<string name="reverse">Unazad</string>
<string name="chapters_grid_view">Mrežni prikaz</string>
<string name="sign_in">Prijaviti se</string>
<string name="auth_required">Prijavite se da vidite ovaj sadržaj</string>
<string name="default_s">Zadano: %s</string>
<string name="next">Sljedeće</string>
<string name="protect_application_subtitle">Unesite lozinku za pokretanje aplikacije</string>
<string name="confirm">Potvrdi</string>
<string name="password_length_hint">Lozinka mora imati 4 znaka ili više</string>
<string name="tracker_warning">Neki uređaji imaju drugačije ponašanje sustava, što može prekinuti pozadinske zadatke.</string>
<string name="read_more">Čitaj više</string>
<string name="queued">U redu čekanja</string>
<string name="chapter_is_missing">Poglavlje nedostaje</string>
<string name="about_app_translation_summary">Prevedi ovu aplikaciju</string>
<string name="about_app_translation">Prijevod</string>
<string name="auth_complete">Ovlašteni</string>
<string name="auth_not_supported_by">Prijava na %s nije podržana</string>
<string name="text_clear_cookies_prompt">Bit ćete odjavljeni sa svih izvora</string>
<string name="genres">Žanrovi</string>
<string name="state_finished">Završeno</string>
<string name="state_ongoing">U tijeku</string>
<string name="system_default">Zadano</string>
<string name="exclude_nsfw_from_history">Isključi NSFW mangu iz povijesti</string>
<string name="show_pages_numbers">Numerirane stranice</string>
<string name="screenshots_policy">Pravila snimanja zaslona</string>
<string name="suggestions_info">Svi se podaci analiziraju samo lokalno na ovom uređaju i nikada se ne šalju nigdje.</string>
<string name="text_suggestion_holder">Počnite čitati mangu i dobit ćete personalizirane prijedloge</string>
<string name="exclude_nsfw_from_suggestions">Nemojte predlagati NSFW mangu</string>
<string name="reset_filter">Resetiraj filter</string>
<string name="onboard_text">Odaberite jezike na kojima želite čitati mangu. Kasnije ga možete promijeniti u postavkama.</string>
<string name="never">Nikada</string>
<string name="only_using_wifi">Samo na Wi-Fi</string>
<string name="always">Uvijek</string>
<string name="logged_in_as">Prijavljeni kao %s</string>
<string name="nsfw">18+</string>
<string name="various_languages">Razni jezici</string>
<string name="search_chapters">Pronađi poglavlje</string>
<string name="chapters_will_removed_background">Poglavlja će biti uklonjena u pozadini</string>
<string name="canceled">Otkazano</string>
<string name="account_already_exists">Račun već postoji</string>
<string name="back">Nazad</string>
<string name="sync">Sinkronizacija</string>
<string name="sync_title">Sinkronizirajte vaše podatke</string>
<string name="email_enter_hint">Unesite vašu e-poštu za nastavak</string>
<string name="hide">Sakrij</string>
<string name="new_sources_text">Dostupni su novi izvori mange</string>
<string name="check_new_chapters_title">Provjeriti ima li novih poglavlja i obavijestiti o tome</string>
<string name="show_notification_new_chapters_on">Primit ćete obavijesti o ažuriranjima mange koju čitate</string>
<string name="show_notification_new_chapters_off">Nećete primati obavijesti, ali će nova poglavlja biti istaknuta na popisima</string>
<string name="notifications_enable">Omogući obavijesti</string>
<string name="name">Ime</string>
<string name="edit">Uredi</string>
<string name="edit_category">Uredi kategoriju</string>
<string name="tracking">Pratim</string>
<string name="bookmark_add">Dodaj zabilješku</string>
<string name="bookmark_remove">Ukloni zabilješku</string>
<string name="bookmark_added">Zabilješka dodana</string>
<string name="removed_from_history">Uklonjeno iz povijesti</string>
<string name="detect_reader_mode">Automatsko otkrivanje načina čitanja</string>
<string name="disable_battery_optimization_summary">Pomaže pri provjerama ažuriranja u pozadini</string>
<string name="crash_text">Nešto je pošlo po zlu. Pošaljite izvješće o pogrešci razvojnim programerima kako biste nam pomogli da je popravimo.</string>
<string name="send">Pošalji</string>
<string name="status_planned">Planirani</string>
<string name="status_reading">Čitam</string>
<string name="status_re_reading">Ponovo čitam</string>
<string name="status_completed">Dovršeno</string>
<string name="status_on_hold">Na čekanju</string>
<string name="report">Prijavi</string>
<string name="show_reading_indicators">Prikaži indikatore napretka tokom čitanja</string>
<string name="data_deletion">Brisanje podataka</string>
<string name="show_reading_indicators_summary">Prikaži postotak pročitanih u povijesti i favoritima</string>
<string name="exclude_nsfw_from_history_summary">Manga označena kao NSFW nikada neće biti dodana u povijest i vaš napredak neće biti spremljen</string>
<string name="clear_cookies_summary">Može pomoći u slučaju nekih problema. Sva će ovlaštenja biti poništena</string>
<string name="show_all">Prikaži sve</string>
<string name="invalid_domain_message">Nevažeća domena</string>
<string name="select_range">Odaberite raspon</string>
<string name="clear_all_history">Izbriši svu povijest</string>
<string name="last_2_hours">Zadnja 2 sata</string>
<string name="history_cleared">Povijest izbrisana</string>
<string name="manage">Upravljaj</string>
<string name="no_bookmarks_yet">Još nema zabilješki</string>
<string name="bookmarks_removed">Zabilješka izbrisana</string>
<string name="no_manga_sources">Nema izvora mange</string>
<string name="no_manga_sources_text">Omogućite izvore mange za čitanje mange na mreži</string>
<string name="random">Nasumično</string>
<string name="categories_delete_confirm">Jeste li sigurni da želite izbrisati odabrane omiljene kategorije?
\nSve mange u njoj bit će izgubljene i to se ne može poništiti.</string>
<string name="reader_info_pattern">Poglavlje %1$d/%2$d Stranica %3$d/%4$d</string>
<string name="reader_info_bar">Prikaži informacijsku traku u čitaču</string>
<string name="comics_archive">Arhiva stripova</string>
<string name="folder_with_images">Mapa sa slikama</string>
<string name="importing_manga">Uvoz mange</string>
<string name="import_completed">Uvoz dovršen</string>
<string name="import_completed_hint">Možete izbrisati izvornu datoteku iz pohrane radi uštede prostora</string>
<string name="import_will_start_soon">Uvoz će uskoro početi</string>
<string name="feed">Novosti</string>
<string name="manga_error_description_pattern">Detalji pogreške:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Pokušajte &lt;a href=%2$s&gt;otvoriti mangu u web-pregledniku&lt;/a&gt; kako biste bili sigurni da je dostupna na izvoru&lt;br&gt;2. Provjerite koristite li &lt;a href=kotatsu://about&gt;najnoviju verziju Kotatsu&lt;/a&gt;&lt;br&gt;3. Ako je dostupno, pošaljite izvješće o pogrešci programerima.</string>
<string name="history_shortcuts">Prikaži nedavne manga prečice</string>
<string name="history_shortcuts_summary">Učinite nedavnu mangu dostupnom dugim pritiskom na ikonu aplikacije</string>
<string name="reader_control_ltr_summary">Dodirom desnog ruba ili pritiskom desne tipke uvijek prelazi na sljedeću stranicu.</string>
<string name="reader_control_ltr">Ergonomska kontrola čitača</string>
<string name="color_correction">Korekcija boja</string>
<string name="brightness">Svjetlina</string>
<string name="contrast">Kontrast</string>
<string name="reset">Resetiraj</string>
<string name="text_unsaved_changes_prompt">Spremi ili odbaci nespremljene promjene?</string>
<string name="discard">Odbaci</string>
<string name="error_no_space_left">Nema više prostora na uređaju</string>
<string name="reader_slider">Prikaži klizač za promjenu stranice</string>
<string name="webtoon_zoom">Webtoon zumiranje</string>
<string name="network_unavailable">Mreža nije dostupna</string>
<string name="network_unavailable_hint">Uključite Wi-Fi ili mobilnu mrežu da biste čitali mangu online</string>
<string name="server_error">Pogreška na strani poslužitelja (%1$d). Molimo pokušajte ponovo kasnije</string>
<string name="clear_new_chapters_counters">Također očistite informacije o novim poglavljima</string>
<string name="compact">Kompaktan</string>
<string name="source_disabled">Izvor onemogućen</string>
<string name="prefetch_content">Predučitavanje sadržaja</string>
<string name="mark_as_current">Označi kao trenutno</string>
<string name="language">Jezik</string>
<string name="share_logs">Podijelite zapise</string>
<string name="enable_logging">Omogući bilježenje</string>
<string name="enable_logging_summary">Snimite neke radnje u svrhu otklanjanja pogrešaka. Nemojte ga uključivati ako niste sigurni što radite</string>
<string name="show_suspicious_content">Prikaži sumnjiv sadržaj</string>
<string name="theme_name_dynamic">Dinamičan</string>
<string name="color_theme">Shema boja</string>
<string name="show_in_grid_view">Prikaži u mrežnom prikazu</string>
<string name="theme_name_miku">Miku</string>
<string name="theme_name_asuka">Asuka</string>
<string name="theme_name_mion">Mion</string>
<string name="theme_name_rikka">Rikka</string>
<string name="theme_name_sakura">Sakura</string>
<string name="theme_name_mamimi">Mamimi</string>
<string name="services">Usluge</string>
<string name="allow_unstable_updates">Dopusti nestabilna ažuriranja</string>
<string name="allow_unstable_updates_summary">Primajte obavijesti o nestabilnim verzijama</string>
<string name="download_started">Preuzimanje je počelo</string>
<string name="got_it">Shvaćam</string>
<string name="speed">Brzina</string>
<string name="server_address">Adresa poslužitelja</string>
<string name="sync_host_description">Možete koristiti poslužitelj za sinkronizaciju s vlastitim hostom ili zadanim. Nemojte ovo mijenjati ako niste sigurni što radite.</string>
<string name="ignore_ssl_errors">Ignorirajte SSL greške</string>
<string name="mirror_switching">Odaberite posluživač automatski</string>
<string name="mirror_switching_summary">Automatski mijenjaj domene za izvore mange u slučaju grešaka ako su poslužitelji dostupni</string>
<string name="pause">Pauza</string>
<string name="resume">Nastavi</string>
<string name="paused">Pauzirano</string>
<string name="remove_completed">Ukloni dovršene</string>
<string name="cancel_all">Otkaži sve</string>
<string name="downloads_wifi_only">Preuzimanje samo putem Wi-Fi mreže</string>
<string name="downloads_wifi_only_summary">Zaustavite preuzimanje kada se prebacite na mobilnu mrežu</string>
<string name="suggestion_manga">Prijedlog: %s</string>
<string name="suggestions_notifications_summary">Ponekad prikaži obavijesti s predloženom mangom</string>
<string name="more">Više</string>
<string name="enable">Omogući</string>
<string name="no_thanks">Ne hvala</string>
<string name="cancel_all_downloads_confirm">Sva aktivna preuzimanja bit će otkazana, djelomično preuzeti podaci bit će izgubljeni</string>
<string name="remove_completed_downloads_confirm">Vaša povijest preuzimanja bit će trajno izbrisana</string>
<string name="downloads_resumed">Preuzimanja su nastavljena</string>
<string name="downloads_paused">Preuzimanja su pauzirana</string>
<string name="downloads_removed">Preuzimanja su uklonjena</string>
<string name="suggestions_enable_prompt">Želite li primati personalizirane prijedloge za mange?</string>
<string name="web_view_unavailable">WebView nije dostupan: provjerite je li instaliran WebView provider</string>
<string name="type">Tip</string>
<string name="port">Port</string>
<string name="proxy">Proxy</string>
<string name="invalid_value_message">Nevažeća vrijednost</string>
<string name="email_password_enter_hint">Unesite vašu e-poštu i lozinku za nastavak</string>
<string name="invert_colors">Invertiraj boje</string>
<string name="username">Korisničko ime</string>
<string name="password">Lozinka</string>
<string name="authorization_optional">Autorizacija (nije obavezno)</string>
<string name="invalid_port_number">Nevažeći broj port-a</string>
<string name="network">Mreža</string>
<string name="data_and_privacy">Podaci i privatnost</string>
<string name="restore_summary">Vratite prethodno stvorenu sigurnosnu kopiju</string>
<string name="reader_info_bar_summary">Prikažite trenutno vrijeme i napredak čitanja na vrhu zaslona</string>
<string name="show_pages_numbers_summary">Prikaži brojeve stranica u donjem kutu</string>
<string name="clear_source_cookies_summary">Brisanje kolačića samo za određenu domenu. U većini slučajeva poništit će autorizaciju</string>
<string name="download_option_whole_manga">Cijela manga</string>
<string name="download_option_first_n_chapters">Prvi %s</string>
<string name="description">Opis</string>
<string name="tracker_wifi_only_summary">Ne provjeravajte nova poglavlja pomoću mrežnih veza s ograničenim protokom</string>
<string name="search_hint">Unesite naslov mange, žanr ili naziv izvora</string>
<string name="progress">Napredak</string>
<string name="order_added">Dodano</string>
<string name="show">Prikaži</string>
<string name="captcha_required_summary">%s zahtijeva da se captcha riješi kako bi ispravno radio</string>
<string name="to_top">Na vrh</string>
<string name="zoom_out">Umanji</string>
<string name="zoom_in">Povećaj</string>
<string name="reader_zoom_buttons">Prikaži gumbe za zumiranje</string>
<string name="reader_zoom_buttons_summary">Treba li prikazati gumbe za kontrolu zumiranja u donjem desnom kutu</string>
<string name="keep_screen_on">Držite zaslon uključenim</string>
<string name="keep_screen_on_summary">Ne isključujte ekran dok čitate mangu</string>
<string name="state_abandoned">Izbačeno</string>
<string name="enhanced_colors_summary">Smanjuje trake, ali može utjecati na performanse</string>
<string name="enhanced_colors">32-bitni način rada u boji</string>
<string name="suggest_new_sources">Predloži nove izvore nakon ažuriranja aplikacije</string>
<string name="suggest_new_sources_summary">Upit za omogućavanje novododanih izvora nakon ažuriranja aplikacije</string>
<string name="list_options">Popis opcija</string>
<string name="by_relevance">Relevantnost</string>
<string name="categories">Kategorije</string>
<string name="online_variant">Online varijanta</string>
<string name="periodic_backups">Periodične sigurnosne kopije</string>
<string name="backup_frequency">Učestalost stvaranja sigurnosne kopije</string>
<string name="frequency_every_day">Svaki dan</string>
<string name="content_type_manga">Manga</string>
<string name="content_type_hentai">Hentai</string>
<string name="content_type_comics">Stripovi</string>
<string name="content_type_other">Ostalo</string>
<string name="sources_catalog">Katalog izvora</string>
<string name="source_enabled">Izvor omogućen</string>
<string name="no_manga_sources_catalog_text">U ovom odjeljku nema dostupnih izvora ili su svi možda već dodani.
\nBudite u toku</string>
<string name="no_manga_sources_found">Vašim upitom nisu pronađeni dostupni izvori mangi</string>
<string name="catalog">Katalog</string>
<string name="disable_nsfw_summary">Onemogućite NSFW izvore i sakrijte mangu za odrasle s popisa ako je moguće</string>
<string name="state_paused">Pauzirano</string>
<string name="reader_optimize">Smanjite potrošnju memorije (beta)</string>
<string name="reader_optimize_summary">Smanjite kvalitetu stranica izvan zaslona kako biste koristili manje memorije</string>
<string name="state">Stanje</string>
<string name="error_multiple_genres_not_supported">Ovaj izvor mange ne podržava filtriranje prema više žanrova</string>
<string name="apply">Primjeni</string>
<string name="error_filter_locale_genre_not_supported">Ovaj izvor ne podržava filtriranje po žanrovima i lokalitetima</string>
<string name="error_filter_states_genre_not_supported">Ovaj izvor ne podržava filtriranje po žanrovima i stanjima</string>
<string name="genres_search_hint">Počnite upisivati naziv žanra</string>
<string name="backup_date_">Datum sigurnosne kopije: %s</string>
<string name="state_upcoming">Nadolazeće</string>
<string name="by_name_reverse">Ime obrnuto</string>
<string name="content_rating">Ocjena sadržaja</string>
<string name="rating_safe">Sigurno</string>
<string name="rating_suggestive">Sugestivno</string>
<string name="rating_adult">Za odrasle</string>
<string name="default_tab">Zadana kartica</string>
<string name="mark_as_completed">Označi kao dovršeno</string>
<string name="mark_as_completed_prompt">Označiti odabranu mangu kao potpuno pročitanu?
\n
\nUpozorenje: trenutni napredak čitanja bit će izgubljen.</string>
<string name="category_hidden_done">Ova je kategorija bila skrivena s glavnog zaslona i dostupna je kroz Meni → Upravljanje kategorijama</string>
<string name="volume_">Svezak %d</string>
<string name="volume_unknown">Nepoznati svezak</string>
<string name="incognito_mode_hint">Vaš napredak u čitanju neće biti spremljen</string>
<string name="vertical">Okomito</string>
<string name="last_read">Zadnje pročitano</string>
<string name="show_menu">Prikaži meni</string>
<string name="toggle_ui">Prikaži/sakrij korisničko sučelje</string>
<string name="prev_chapter">Prethodno poglavlje</string>
<string name="next_chapter">Sljedeće poglavlje</string>
<string name="next_page">Sljedeća stranica</string>
<string name="default_webtoon_zoom_out">Zadani webtoon smanji</string>
<string name="fullscreen_mode">Cijeli zaslon</string>
<string name="reader_fullscreen_summary">Sakrij status sustava i navigacijske trake</string>
<string name="reading_time_estimation">Prikaži procijenjeno vrijeme čitanja</string>
<string name="reading_time_estimation_summary">Vrijednost procjene vremena može biti netočna</string>
<string name="suggestions_unavailable_text">Značajka prijedloga je onemogućena</string>
<string name="check_for_new_chapters_disabled">Provjera novih poglavlja je onemogućena</string>
<string name="show_labels_in_navbar">Prikaži oznake u navigacijskoj traci</string>
<string name="pages_saving">Spremanje stranica</string>
<string name="ask_for_dest_dir_every_time">Svaki put pitajte za odredišni direktorij</string>
<string name="default_page_save_dir">Zadani direktorij za spremanje stranice</string>
<string name="remove_from_history">Ukloni iz povijesti</string>
<string name="location">Lokacija</string>
<string name="preferred_download_format">Preferirani format preuzimanja</string>
<string name="automatic">Automatski</string>
<string name="single_cbz_file">Jedna CBZ datoteka</string>
<string name="multiple_cbz_files">Više CBZ datoteka</string>
<string name="reading_stats">Statistika čitanja</string>
<string name="other_manga">Druge mange</string>
<string name="less_than_minute">Manje od minute</string>
<string name="statistics">Statistika</string>
<string name="stats_cleared">Statistika obrisana</string>
<string name="clear_stats">Obriši statistiku</string>
<string name="delete_read_chapters_summary">Izbrišite poglavlja koja ste već pročitali iz lokalne pohrane kako biste oslobodili prostor</string>
<string name="delete_read_chapters_prompt">Ovo će trajno izbrisati sva poglavlja označena kao pročitana iz vaše lokalne pohrane. Kasnije ga možete ponovo preuzeti, ali uvezena poglavlja mogu biti zauvijek izgubljena</string>
<string name="delete_read_chapters_auto">Automatski izbrišite pročitana poglavlja</string>
<string name="runs_on_app_start">Pokreće se kada se aplikacija pokrene</string>
<string name="split_by_translations">Podjeli po prijevodima</string>
<string name="split_by_translations_summary">Pokažite poglavlja s različitim prijevodima odvojeno, umjesto na jednom popisu</string>
<string name="order_oldest">Najstariji</string>
<string name="long_ago_read">Davno pročitano</string>
<string name="unread">Nepročitano</string>
<string name="enable_source">Omogući izvor</string>
<string name="unsupported_source">Ovaj izvor mange nije podržan</string>
<string name="show_pages_thumbs">Prikaži sličice stranica</string>
<string name="disable_connectivity_check">Onemogući provjeru povezivanja</string>
<string name="ignore_ssl_errors_summary">Možete onemogućiti provjeru SSL certifikata u slučaju da se suočite s problemima povezanim sa SSL-om prilikom pristupa mrežnim resursima. To može utjecati na vašu sigurnost. Nakon promjene ove postavke potrebno je ponovno pokretanje aplikacije.</string>
<string name="disable_connectivity_check_summary">Preskočite provjeru povezivanja u slučaju da imate problema s njom (npr. odlazak u izvanmrežni način rada iako je mreža povezana)</string>
<string name="disable_nsfw_notifications">Onemogući NSFW obavijesti</string>
<string name="disable_nsfw_notifications_summary">Ne prikazuj obavijesti o ažuriranjima NSFW mange</string>
<string name="tracker_debug_info">Provjera zapisnika novih poglavlja</string>
<string name="tracker_debug_info_summary">Informacije o otklanjanju pogrešaka o pozadinskim provjerama za nova poglavlja</string>
<string name="_new">Novo</string>
<string name="all_languages">Svi jezici</string>
<string name="screenshots_block_incognito">Blokiraj u anonimnom načinu rada</string>
<string name="processing_">Obrada…</string>
<string name="download_complete">Preuzeto</string>
<string name="by_name">Ime</string>
<string name="newest">Najnoviji</string>
<string name="sort_order">Redoslijed sortiranja</string>
<string name="filter">Filter</string>
<string name="follow_system">Slijedite sustav</string>
<string name="pages">Stranice</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">Standard</string>
<string name="webtoon">Webtoon</string>
<string name="remove_category">Ukloni</string>
<string name="done">Gotovo</string>
<string name="read_later">Pročitaj kasnije</string>
<string name="size_s">Veličina: %s</string>
<string name="feed_will_update_soon">Ažuriranje sažetka sadržaja počet će uskoro</string>
<string name="track_sources">Potražite ažuriranja</string>
<string name="clear_search_history">Obriši povijest pretraživanja</string>
<string name="open_in_browser">Otvorite u web pregledniku</string>
<string name="text_empty_holder_primary">Ovdje je nekako prazno…</string>
<string name="updates">Ažuriranja</string>
<string name="downloads">Preuzimanja</string>
<string name="popular">Popularno</string>
<string name="updated">Ažurirano</string>
<string name="by_rating">Ocjena</string>
<string name="save_page">Spremi stranicu</string>
<string name="notifications">Obavijesti</string>
<string name="update">Ažuriraj</string>
<string name="history_is_empty">Još nema povijesti</string>
<string name="clear_updates_feed">Očisti sažetak ažuriranja</string>
<string name="updates_feed_cleared">Očišćeno</string>
<string name="rotate_screen">Zakreni zaslon</string>
<string name="preparing_">Priprema…</string>
<string name="restore_backup">Vrati iz sigurnosne kopije</string>
<string name="file_not_found">Datoteka nije pronađena</string>
<string name="data_restored_success">Svi podaci su vraćeni</string>
<string name="data_restored_with_errors">Podaci su vraćeni, ali ima grešaka</string>
<string name="backup_information">Možete stvoriti sigurnosnu kopiju svoje povijesti i favorita i vratiti je</string>
<string name="yesterday">Jučer</string>
<string name="long_ago">Davno</string>
<string name="data_restored">Vraćeno</string>
<string name="just_now">Upravo sada</string>
<string name="screenshots_allow">Dopusti</string>
<string name="screenshots_block_all">Uvijek blokiraj</string>
<string name="suggestions">Prijedlozi</string>
<string name="chapters_empty">Nema poglavlja u ovoj mangi</string>
<string name="appearance">Izgled</string>
<string name="suggestions_updating">Ažuriranje prijedloga</string>
<string name="suggestions_excluded_genres_summary">Navedite žanrove koje ne želite vidjeti u prijedlozima</string>
<string name="download_slowdown_summary">Pomaže u izbjegavanju blokiranja vaše IP adrese</string>
<string name="local_manga_processing">Obrada spremljene mange</string>
<string name="bookmarks">Zabilješke</string>
<string name="bookmark_removed">Zabilješka uklonjena</string>
<string name="dns_over_https">DNS preko HTTPS-a</string>
<string name="disable_battery_optimization">Onemogući optimizaciju baterije</string>
<string name="status_dropped">Izbačeno</string>
<string name="appwidget_shelf_description">Manga iz vaših favorita</string>
<string name="no_bookmarks_summary">Možete stvoriti zabilješku dok čitate mangu</string>
<string name="empty">Prazno</string>
<string name="exit_confirmation_summary">Dvaput pritisnite Natrag za izlaz iz aplikacije</string>
<string name="exit_confirmation">Potvrda izlaza</string>
<string name="storage_usage">Korištenje pohrane</string>
<string name="text_clear_search_history_prompt">Želite li trajno ukloniti sve nedavne upite za pretraživanje?</string>
<string name="welcome">Dobrodošli</string>
<string name="backup_saved">Sigurnosna kopija spremljena</string>
<string name="enabled">Omogućeno</string>
<string name="preload_pages">Prethodno učitavanje stranica</string>
<string name="empty_favourite_categories">Nema omiljenih kategorija</string>
<string name="undo">Poništi</string>
<string name="disable_all">Onemogući sve</string>
<string name="use_fingerprint">Koristite otisak prsta ako je dostupan</string>
<string name="appwidget_recent_description">Vaša nedavno pročitana manga</string>
<string name="explore">Istraži</string>
<string name="confirm_exit">Ponovno pritisnite Natrag za izlaz</string>
<string name="saved_manga">Spremljena manga</string>
<string name="pages_cache">Predmemorija stranica</string>
<string name="other_cache">Drugi cache</string>
<string name="available">Dostupno</string>
<string name="disabled">Onemogućeno</string>
<string name="default_mode">Zadani način rada</string>
<string name="detect_reader_mode_summary">Automatski otkrij je li manga webtoon</string>
<string name="removed_from_favourites">Uklonjeno iz favorita</string>
<string name="not_found_404">Sadržaj nije pronađen ili uklonjen</string>
<string name="no_chapters">Nema poglavlja</string>
<string name="options">Opcije</string>
<string name="download_slowdown">Usporavanje preuzimanja</string>
<string name="logout">Odjavite se</string>
<string name="reorder">Rasporedi</string>
<string name="incognito_mode">Anonimni način rada</string>
<string name="suggestions_excluded_genres">Ispostavite žanrove</string>
<string name="text_delete_local_manga_batch">Trajno izbrisati odabrane stavke s uređaja?</string>
<string name="removal_completed">Uklanjanje dovršeno</string>
<string name="automatic_scroll">Automatsko pomicanje</string>
<string name="screenshots_block_nsfw">Blokiraj na NSFW-u</string>
<string name="suggestions_enable">Omogući prijedloge</string>
<string name="suggestions_summary">Predložiti mangu na temelju vaših preferencija</string>
<string name="theme_name_kanade">Kanade</string>
<string name="find_similar">Pronađite slično</string>
<string name="scrobbling_empty_hint">Za praćenje napretka čitanja odaberite Meni → Prati na zaslonu s detaljima mange.</string>
<string name="sources_reorder_tip">Dodirnite i držite stavku da biste im promijenili redoslijed</string>
<string name="settings_apply_restart_required">Ponovno pokrenite aplikaciju kako biste primijenili ove promjene</string>
<string name="folder_with_images_import_description">Možete odabrati direktorij s arhivama ili slikama. Svaka arhiva (ili poddirektorij) bit će prepoznata kao poglavlje.</string>
<string name="sync_settings">Postavke sinkronizacije</string>
<string name="nothing_here">Ovdje nema ničega</string>
<string name="user_agent">Zaglavlje UserAgent-a</string>
<string name="comics_archive_import_description">Možete odabrati jednu ili više .cbz ili .zip datoteka, svaka će datoteka biti prepoznata kao zasebna manga.</string>
<string name="show_on_shelf">Prikaži na polici</string>
<string name="sync_auth_hint">Možete se prijaviti na postojeći račun ili stvoriti novi</string>
<string name="text_downloads_list_holder">Nemate preuzimanja</string>
<string name="downloads_cancelled">Preuzimanja su otkazana</string>
<string name="clear_network_cache">Očisti mrežnu predmemoriju</string>
<string name="address">Adresa</string>
<string name="images_proxy_title">Proxy za optimizaciju slika</string>
<string name="webtoon_zoom_summary">Dopusti gestu zumiranja u načinu webtoona</string>
<string name="download_option_all_chapters">Sva poglavlja s prijevodom %s</string>
<string name="this_month">Ovaj mjesec</string>
<string name="color_light">Svjetla</string>
<string name="color_white">Bijela</string>
<string name="background">Pozadina</string>
<string name="data_not_restored_text">Provjerite jeste li odabrali ispravnu datoteku sigurnosne kopije</string>
<string name="manage_categories">Upravljanje kategorijama</string>
<string name="suggestions_wifi_only_summary">Nemojte ažurirati prijedloge pomoću mrežnih veza s ograničenim protokom</string>
<string name="languages">Jezici</string>
<string name="directories">Direktorij</string>
<string name="main_screen_sections">Odjeljci glavnog zaslona</string>
<string name="items_limit_exceeded">Nije moguće dodati više stavki</string>
<string name="moved_to_top">Premješteno na vrh</string>
<string name="frequency_every_2_days">Svaka 2 dana</string>
<string name="manual">Ručno</string>
<string name="error_search_not_supported">Ovaj izvor mange ne podržava pretraživanje</string>
<string name="skip">Preskoči</string>
<string name="grayscale">Sivi tonovi</string>
<string name="globally">Globalno</string>
<string name="this_manga">Ova manga</string>
<string name="color_correction_apply_text">Ove postavke mogu se primijeniti globalno ili samo na trenutnu mangu. Ako se primjenjuju globalno, pojedinačne postavke neće biti zamjenjene.</string>
<string name="welcome_text">Odaberite koje izvore sadržaja želite omogućiti. To se također može konfigurirati kasnije u postavkama</string>
<string name="sync_auth">Prijavite se za sinkronizaciju računa</string>
<string name="prev_page">Prethodna stranica</string>
<string name="reader_actions">Radnje čitatelja</string>
<string name="reader_actions_summary">Konfigurirajte radnje za dodirna područja zaslona</string>
<string name="switch_pages_volume_buttons_summary">Koristite tipke za glasnoću za prebacivanje stranica</string>
<string name="tap_action">Radnja pri dodiru</string>
<string name="long_tap_action">Radnja dugog dodira</string>
<string name="use_two_pages_landscape">Koristi izgled dvije stranice u pejzažnoj orijentaciji (beta)</string>
<string name="download_option_next_unread_n_chapters">Sljedeći nepročitani %s</string>
<string name="download_option_all_unread">Sva nepročitana poglavlja</string>
<string name="download_option_all_unread_b">Sva nepročitana poglavlja (%s)</string>
<string name="download_option_manual_selection">Odaberite poglavlja ručno</string>
<string name="pick_custom_directory">Odaberite prilagođeni direktorij</string>
<string name="no_access_to_file">Nemate pristup ovoj datoteci ili direktoriju</string>
<string name="local_manga_directories">Lokalni direktoriji mange</string>
<string name="voice_search">Glasovno pretraživanje</string>
<string name="related_manga">Povezana manga</string>
<string name="color_dark">Tamna</string>
<string name="color_black">Crna</string>
<string name="data_not_restored">Podaci nisu vraćeni</string>
<string name="on_device">Na uređaju</string>
<string name="frequency_once_per_week">Jednom tjedno</string>
<string name="speed_value">x%.1f</string>
<string name="manage_sources">Upravljanje izvorima</string>
<string name="error_multiple_states_not_supported">Ovaj izvor mange ne podržava filtriranje prema višestrukim stanjima</string>
<string name="downloads_settings_info">Možete omogućiti usporavanje preuzimanja za svaki izvor mange zasebno u postavkama izvora ako imate problema s blokiranjem na strani poslužitelja</string>
<string name="restore">Vrati</string>
<string name="switch_pages_volume_buttons">Omogući tipke za glasnoću</string>
<string name="none">Nijedan</string>
<string name="config_reset_confirm">Vratiti postavke na zadane vrijednosti? Ova se radnja ne može poništiti.</string>
<string name="clear_stats_confirm">Želite li stvarno izbrisati sve statistike čitanja? Ova se radnja ne može poništiti.</string>
<string name="month">Mjesec</string>
<string name="pages_read_s">Pročitane stranice: %s</string>
<string name="no_chapters_deleted">Nijedno poglavlje nije izbrisano</string>
<string name="downloaded">Preuzeto</string>
<string name="images_procy_description">Koristite uslugu wsrv.nl kako biste smanjili promet i ubrzali učitavanje slika ako je moguće</string>
<string name="unknown">Nepoznato</string>
<string name="too_many_requests_message">Previše zahtjeva. Pokušajte ponovno kasnije</string>
<string name="manga_list">Popis mangi</string>
<string name="error_corrupted_file">Vraćeni su nevažeći podaci ili je datoteka oštećena</string>
<string name="frequency_twice_per_month">Dva puta mjesečno</string>
<string name="frequency_once_per_month">Jednom mjesečno</string>
<string name="available_d">Dostupno: %1$d</string>
<string name="genres_exclude">Isključi žanrove</string>
<string name="week">Tjedan</string>
<string name="day">Dan</string>
<string name="alternatives">Alternative</string>
<string name="periodic_backups_enable">Omogućite povremene sigurnosne kopije</string>
<string name="disable_battery_optimization_summary_downloads">Moglo bi vam pomoći pri započinjanju preuzimanja ako imate problema s njim</string>
<string name="all_time">Cijelo vrijeme</string>
<string name="manga_migration">Migracija mange</string>
<string name="migration_completed">Migracija dovršena</string>
<string name="in_progress">U toku</string>
<string name="disable_nsfw">Onemogući NSFW</string>
<string name="related_manga_summary">Prikaži popis povezanih mangi. U nekim slučajevima može biti netočan ili nedostajati</string>
<string name="advanced">Napredno</string>
<string name="backups_output_directory">Izlazni direktorij sigurnosne kopije</string>
<string name="three_months">Tri mjeseca</string>
<string name="empty_stats_text">Nema statistike za odabrano razdoblje</string>
<string name="last_successful_backup">Zadnja uspješna sigurnosna kopija: %s</string>
<string name="migrate">Migrirati</string>
<string name="chapters_deleted_pattern">Uklonjeno %1$s, izbrisano %2$s</string>
<string name="lock_screen_rotation">Zaključavanje zakretanja zaslona</string>
<string name="migrate_confirmation">Manga \"%1$s\" iz \"%2$s\" bit će zamijenjena s \"%3$s\" iz \"%4$s\" u vašoj povijesti i favoritima (ako postoje)</string>
<string name="delete_read_chapters">Brisanje pročitanih poglavlja</string>
</resources>

View File

@@ -581,4 +581,66 @@
<string name="reader_fullscreen_summary">Sembunyikan status sistem dan bilah navigasi</string>
<string name="automatic">Otomatis</string>
<string name="default_webtoon_zoom_out">Zoom webtoon default</string>
<string name="last_used">Terakhir digunakan</string>
<string name="_new">Terbaru</string>
<string name="hours_short">%d j</string>
<string name="minutes_short">%d m</string>
<string name="hours_minutes_short">%1$d j %2$d m</string>
<string name="default_page_save_dir">Direktori penyimpanan halaman default</string>
<string name="clear_stats">Bersihkan statistik</string>
<string name="delete_read_chapters_prompt">Hal ini akan menghapus secara permanen semua bab yang ditandai sebagai telah dibaca dari penyimpanan lokal Anda. Anda dapat mengunduh ulang nanti, tetapi bab yang diimpor mungkin akan hilang selamanya</string>
<string name="reading_stats">Statistik membaca</string>
<string name="other_manga">Manga lainnya</string>
<string name="show_pages_thumbs">Menampilkan gambar mini halaman</string>
<string name="show_updated">Tampilkan yang diperbarui</string>
<string name="ignore_ssl_errors_summary">Anda dapat menonaktifkan verifikasi sertifikat SSL jika Anda menghadapi masalah terkait SSL saat mengakses sumber daya jaringan. Hal ini dapat memengaruhi keamanan Anda. Diperlukan pengaktifan ulang aplikasi setelah mengubah pengaturan ini.</string>
<string name="multiple_cbz_files">Beberapa file CBZ</string>
<string name="pages_read_s">Halaman yang dibaca: %s</string>
<string name="migrate_confirmation">Manga \"%1$s\" dari \"%2$s\" akan diganti dengan \"%3$s\" dari \"%4$s\" di riwayat dan favorit Anda (jika ada)</string>
<string name="unread">Belum dibaca</string>
<string name="show_pages_thumbs_summary">Aktifkan tab \"Halaman\" pada layar detail</string>
<string name="unsupported_backup_message">Pilih file cadangan Kotatsu yang tepat</string>
<string name="fix">Memperbaiki</string>
<string name="missing_storage_permission">Tidak ada izin untuk mengakses manga di penyimpanan eksternal</string>
<string name="disable_nsfw_notifications_summary">Jangan tampilkan pemberitahuan tentang pembaruan manga NSFW</string>
<string name="search_suggestions">Saran pencarian</string>
<string name="recent_queries">Pertanyaan terbaru</string>
<string name="authors">Penulis</string>
<string name="single_cbz_file">File CBZ tunggal</string>
<string name="empty_stats_text">Tidak ada statistik untuk periode yang dipilih</string>
<string name="preferred_download_format">Format unduhan yang disukai</string>
<string name="migration_completed">Migrasi selesai</string>
<string name="delete_read_chapters_summary">Menghapus bab yang telah Anda baca dari penyimpanan lokal untuk mengosongkan ruang</string>
<string name="migrate">Migrasi</string>
<string name="manga_migration">Manga migration</string>
<string name="delete_read_chapters_auto">Menghapus bab yang sudah dibaca secara otomatis</string>
<string name="runs_on_app_start">Berjalan saat aplikasi dimulai</string>
<string name="delete_read_chapters">Menghapus bab yang sudah dibaca</string>
<string name="no_chapters_deleted">Tidak ada bab yang telah dihapus</string>
<string name="split_by_translations">Dibagi berdasarkan terjemahan</string>
<string name="split_by_translations_summary">Menampilkan bab dengan terjemahan yang berbeda secara terpisah, bukan dalam satu daftar</string>
<string name="order_oldest">Terlama</string>
<string name="long_ago_read">Dibaca lama sekali</string>
<string name="webtoon_gaps">Celah dalam mode webtoon</string>
<string name="more_frequently">Lebih sering</string>
<string name="webtoon_gaps_summary">Menampilkan celah vertikal di antara halaman dalam mode webtoon</string>
<string name="frequency_of_check">Frekuensi pemeriksaan</string>
<string name="pin_navigation_ui">Pin UI navigasi</string>
<string name="tracker_debug_info">Memeriksa log bab baru</string>
<string name="disable_connectivity_check">Menonaktifkan pemeriksaan konektivitas</string>
<string name="disable_connectivity_check_summary">Lewati pemeriksaan konektivitas jika Anda mengalami masalah dengan konektivitas (misalnya, masuk ke mode offline meskipun jaringan tersambung)</string>
<string name="disable_nsfw_notifications">Menonaktifkan notifikasi NSFW</string>
<string name="tracker_debug_info_summary">Informasi debug tentang pemeriksaan latar belakang untuk bab baru</string>
<string name="all_languages">Semua bahasa</string>
<string name="screenshots_block_incognito">Blokir saat mode penyamaran</string>
<string name="error_no_data_received">Tidak ada data yang diterima dari server</string>
<string name="disable">Nonaktifkan</string>
<string name="sources_disabled">Sumber dinonaktifkan</string>
<string name="enable_source">Aktifkan sumber</string>
<string name="unsupported_source">Sumber manga ini tidak didukung</string>
<string name="blocked_by_server_message">Anda diblokir oleh server. Coba gunakan koneksi jaringan yang berbeda (VPN, Proxy, dll.)</string>
<string name="less_frequently">Lebih jarang</string>
<string name="pin_navigation_ui_summary">Jangan sembunyikan bilah navigasi dan tampilan pencarian saat menggulir</string>
<string name="chapters_deleted_pattern">Dihapus, dibersihkan</string>
<string name="suggested_queries">Pertanyaan yang disarankan</string>
</resources>

View File

@@ -593,4 +593,20 @@
<string name="more_frequently">Più frequente</string>
<string name="frequency_of_check">Frequenza di controllo</string>
<string name="new_chapters_pattern">%1$s: %2$d</string>
<string name="_new">Nuovi</string>
<string name="automatic">Automatico</string>
<string name="all_languages">Tutte le lingue</string>
<string name="delete_read_chapters">Cancella capitoli letti</string>
<string name="manga_migration">Migrazione manga</string>
<string name="migration_completed">Migrazione completata</string>
<string name="no_chapters_deleted">Nessun capitolo è stato cancellato</string>
<string name="day">Giorno</string>
<string name="three_months">Tre mesi</string>
<string name="empty_stats_text">Non ci sono statistiche per il periodo selezionato</string>
<string name="ask_for_dest_dir_every_time">Chiedi per la cartella di destinazione ogni volta</string>
<string name="pages_saving">Salva pagine</string>
<string name="preferred_download_format">Formato download preferito</string>
<string name="single_cbz_file">File CBZ singolo</string>
<string name="multiple_cbz_files">Molti file CBZ</string>
<string name="reading_stats">Lettura statistiche</string>
</resources>

View File

@@ -17,8 +17,8 @@
<string name="chapters">Тарау</string>
<string name="detailed_list">Егжей-тегжейлі тізім</string>
<string name="list_mode">Тізім түрі</string>
<string name="remote_sources">Маңга қайнары</string>
<string name="loading_">Жүктеу</string>
<string name="remote_sources">Маңга дереккөзі</string>
<string name="loading_">Жүктеліп жатыр</string>
<string name="computing_">Есептеу…</string>
<string name="favourites">Таңдаулы</string>
<string name="network_error">Желі қатесі</string>
@@ -34,7 +34,7 @@
<string name="create_shortcut">Таңбаша жасау…</string>
<string name="share_s">%s бөлісу</string>
<string name="search">Іздеу</string>
<string name="manga_downloading_">Жүктеу</string>
<string name="manga_downloading_">Жүктеліп жатыр</string>
<string name="processing_">Үдерісте…</string>
<string name="download_complete">Жүктелді</string>
<string name="by_name">Атауы</string>
@@ -107,7 +107,7 @@
<string name="history_is_empty">Әзірге тарих жоқ</string>
<string name="search_manga">Маңга іздеу</string>
<string name="add_to_favourites">Таңдаулыға</string>
<string name="downloads">Жүктелгендер</string>
<string name="downloads">Жүктелген</string>
<string name="operation_not_supported">Қолжетімсіз амал</string>
<string name="delete">Жою</string>
<string name="text_file_not_supported">CBZ не ZIP пішімде таңдаңыз.</string>
@@ -272,7 +272,7 @@
<string name="never">Ешқашан</string>
<string name="disable_battery_optimization">Қуат оңтайлығын өшіру</string>
<string name="status_planned">Жоспарланған</string>
<string name="bookmarks">Бетбелгілер</string>
<string name="bookmarks">Бетбелгі</string>
<string name="show_all">Бәрін көрсету</string>
<string name="empty_favourite_categories">Таңдаулы санатыңыз жоқ</string>
<string name="invalid_domain_message">Қате дәмейін</string>
@@ -303,7 +303,7 @@
<string name="password">Құпиясөз</string>
<string name="download_option_whole_manga">Маңганы толықтай</string>
<string name="settings_apply_restart_required">Өзгерту іске қосылуы үшін қолданбаны өшіріп қосыңыз</string>
<string name="source_disabled">Дереккөз сөніп жатыр</string>
<string name="source_disabled">Дереккөз өшіп жатыр</string>
<string name="backup_frequency">Сақтық көшірмесінің жиілігі</string>
<string name="data_and_privacy">Дерек пен құпиялық</string>
<string name="clear_cookies_summary">Қате болса көмектесе алады. Түгел тіркелгінің күші жойылады</string>
@@ -381,7 +381,7 @@
<string name="downloads_paused">Жүктеп алу тоқтап қалды</string>
<string name="too_many_requests_message">Тым көп сұрату. Біраздан соң қайталап көріңіз</string>
<string name="downloads_wifi_only">Wi-Fi арқылы ғана жүктеу</string>
<string name="cancel_all_downloads_confirm">Белсенді жүктеудің бәрі жойылып, жартылай жүктелгендер жоғалып кетеді</string>
<string name="cancel_all_downloads_confirm">Жүктеліп жатқанның бәрі жойылып, жартылай жүктелгендер жоғалып кетеді</string>
<string name="by_relevance">Өзектілігі</string>
<string name="related_manga">Ұқсас маңга</string>
<string name="discard">Сақтамау</string>
@@ -471,7 +471,7 @@
<string name="exit_confirmation_summary">Шығып кету үшін «Кері» батырмасын екі рет басыңыз</string>
<string name="bookmarks_removed">Бетбелгілер жойылды</string>
<string name="theme_name_mion">Мион</string>
<string name="mirror_switching_summary">Дереккөз дәмейіннің қатесі пайда болып, айнасы қолжетімді болса, өздігінен соған ауыстыру</string>
<string name="mirror_switching_summary">Дереккөз дәмейнінің қатесі пайда болып, айнасы қолжетімді болса, өздігінен соған ауыстыру</string>
<string name="no_bookmarks_yet">Бетбелгі жоқ</string>
<string name="no_thanks">Жоқ, рақмет</string>
<string name="suggest_new_sources_summary">Қолданбаның жаңа нұсқасында пайда болған дереккөзді ұсыну</string>
@@ -514,4 +514,28 @@
<string name="this_manga">Бұл маңга</string>
<string name="error_filter_locale_genre_not_supported">Бұл дереккөзде жанр бойынша және локал файлдар бойынша сүзуге болмайды</string>
<string name="error_multiple_genres_not_supported">Бұл дереккөзде бірнеше жанр бойынша сүзуге болмайды</string>
<string name="genres_exclude">Жанр алып тастау</string>
<string name="rating_safe">Қауіпсіз</string>
<string name="chapters_grid_view">Кесте түрі</string>
<string name="error_filter_states_genre_not_supported">Бұл дереккөзде жанр мен күні бойынша сүзу жоқ</string>
<string name="genres_search_hint">Жанр атауын тере бастаңыз</string>
<string name="disable_battery_optimization_summary_downloads">Жүктеу дұрыс басталмай жүрсе көмектесе алады</string>
<string name="restore">Қалпына келтіру</string>
<string name="backup_date_">Сақтық көшірме күні: %s</string>
<string name="state_upcoming">Жақында</string>
<string name="by_name_reverse">Теріс ат</string>
<string name="content_rating">Контент рейтиңі</string>
<string name="prev_chapter">Алдыңғы тарау</string>
<string name="next_chapter">Келесі тарау</string>
<string name="prev_page">Алдыңғы бет</string>
<string name="switch_pages_volume_buttons">Дыбыс батырмасын қосу</string>
<string name="switch_pages_volume_buttons_summary">Дыбыс батырмасы арқылы парақтау</string>
<string name="tap_action">Басқандағы әрекет</string>
<string name="long_tap_action">Басып тұрғандағы әрекет</string>
<string name="none">Түк</string>
<string name="welcome_text">Қандай дереккөз қосқыңыз келетінін таңдаңыз. Кейін баптап алуға болады</string>
<string name="next_page">Келесі бет</string>
<string name="reader_actions">Оқымадағы әрекет</string>
<string name="sync_auth">Синхрондау тіркелгісіне кіру</string>
<string name="config_reset_confirm">Әдепкі баптауға қайтайық па? Әрекетті қайтаруға болмайды.</string>
</resources>

View File

@@ -3,4 +3,6 @@
<string name="history">ചരിത്രം</string>
<string name="chapters">അദ്ധ്യായങ്ങൾ</string>
<string name="settings">ക്രമീകരണങ്ങൾ</string>
<string name="favourites">പ്രിയപ്പെട്ടവ</string>
<string name="details">വിശദാംശങ്ങൾ</string>
</resources>

View File

@@ -642,4 +642,15 @@
<string name="disable">Откл.</string>
<string name="sources_disabled">Источники отключены</string>
<string name="_new">Новое</string>
<string name="all_languages">Все языки</string>
<string name="screenshots_block_incognito">Блокировать в режиме инкогнито</string>
<string name="source_pinned">Источник закреплён</string>
<string name="sources_unpinned">Источники откреплены</string>
<string name="sources_pinned">Источники закреплены</string>
<string name="recent_sources">Недавние источники</string>
<string name="crop_pages">Обрезать страницы</string>
<string name="pin">Закрепить</string>
<string name="unpin">Открепить</string>
<string name="source_unpinned">Источник откреплён</string>
<string name="image_server">Сервер изображений</string>
</resources>

View File

@@ -243,7 +243,7 @@
<string name="invalid_value_message">Погрешна вредност</string>
<string name="downloads_cancelled">Преузимања су отказана</string>
<string name="webtoon_zoom">Webtoon увећање</string>
<string name="various_languages">Сви језици</string>
<string name="various_languages">Разни језици</string>
<string name="removal_completed">Уклањање је завршено</string>
<string name="theme_name_miku">Мику</string>
<string name="edit">Уреди</string>
@@ -642,4 +642,6 @@
<string name="tracker_debug_info">Провера дневника нових поглавља</string>
<string name="tracker_debug_info_summary">Информације о отклањању грешака о позадинским проверама за нова поглавља</string>
<string name="_new">Нови</string>
<string name="all_languages">Сви језици</string>
<string name="screenshots_block_incognito">Блокирај у режиму без архивирања</string>
</resources>

View File

@@ -642,4 +642,15 @@
<string name="disable">Вимкнути</string>
<string name="sources_disabled">Джерела вимкнено</string>
<string name="_new">Нове</string>
<string name="all_languages">Всі мови</string>
<string name="screenshots_block_incognito">Блокувати в режимі інкогніто</string>
<string name="image_server">Сервер зображень</string>
<string name="unpin">Відкріпити</string>
<string name="source_pinned">Джерело закріплено</string>
<string name="sources_pinned">Джерела закріплені</string>
<string name="recent_sources">Нещодавні джерела</string>
<string name="crop_pages">Обрізати сторінки</string>
<string name="sources_unpinned">Джерела відкріплені</string>
<string name="pin">Закріпити</string>
<string name="source_unpinned">Джерело відкріплено</string>
</resources>

View File

@@ -642,6 +642,6 @@
<string name="tracker_debug_info">漫画更新日志</string>
<string name="tracker_debug_info_summary">记录漫画后台更新时的调试日志</string>
<string name="_new">最新</string>
<string name="screenshots_block_incognito">无痕模式时禁止</string>
<string name="screenshots_block_incognito">开启无痕模式时禁止</string>
<string name="all_languages">所有语言</string>
</resources>

View File

@@ -97,4 +97,8 @@
<item>@string/system_default</item>
<item>@string/more_frequently</item>
</string-array>
<string-array name="reader_crop" translatable="false">
<item>@string/pages</item>
<item>@string/webtoon</item>
</string-array>
</resources>

View File

@@ -68,4 +68,8 @@
<item>1</item>
<item>2</item>
</string-array>
<string-array name="values_reader_crop" translatable="false">
<item>1</item>
<item>2</item>
</string-array>
</resources>

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